// 상담 텍스트 → 견적 항목 자동 추출 (window.EstAI)
// 1시간짜리 상담 녹취/메모를 붙여넣으면 Claude 가 단가 사전에 맞춰 견적 품목을 뽑아준다.
// API 키는 이 브라우저(localStorage)에만 저장되고 Anthropic 외 어디로도 전송되지 않는다.
const aiWon = window.EstCalc.won;
const aiBadge = window.EstEditor.gBadge;
const AI_ORDER = window.GONGJONG_ORDER;

const PROVIDER_LS = 'est:ai_provider';
const PROVIDERS = {
  gemini: { label: 'Gemini', keyLS: 'est:gemini_key', modelLS: 'est:gemini_model', defModel: 'gemini-2.5-flash', keyHint: 'AIza...', keyUrl: 'aistudio.google.com/apikey' },
  claude: { label: 'Claude', keyLS: 'est:anthropic_key', modelLS: 'est:anthropic_model', defModel: 'claude-sonnet-4-6', keyHint: 'sk-ant-...', keyUrl: 'console.anthropic.com' },
};

/* 사전 품목명에 정확히/부분 매칭 → 단가 자동 연결 */
function matchDict(gongjong, name) {
  const dict = window.DICT;
  const exact = dict.find((d) => d.gongjong === gongjong && d.name === name);
  if (exact) return exact;
  const exactName = dict.find((d) => d.name === name);
  if (exactName) return exactName;
  const norm = (s) => String(s).replace(/[\s_()/]/g, '').toLowerCase();
  const n = norm(name);
  // 부분 포함 (가장 많이 쓰인 품목 우선)
  const cands = dict
    .filter((d) => { const dn = norm(d.name); return dn.includes(n) || n.includes(dn); })
    .sort((a, b) => b.count - a.count);
  return cands[0] || null;
}

/* 추출 결과 1건 → 에디터 행으로 정규화 (단가는 사전에서) */
function toRow(it) {
  const m = matchDict(it.gongjong, it.name);
  if (m) {
    return {
      gongjong: m.gongjong, name: m.name, unit: m.unit,
      qty: it.qty || 1,
      mat: m.mat ? m.mat.recent : '', lab: m.lab ? m.lab.recent : '',
      gubun: m.gubun || '', note: it.reason || '',
      _matched: true, _matchName: m.name, _count: m.count,
    };
  }
  return {
    gongjong: AI_ORDER.includes(it.gongjong) ? it.gongjong : '기타공사',
    name: it.name, unit: it.unit || 'EA', qty: it.qty || 1,
    mat: '', lab: '', gubun: '', note: (it.reason || '') + ' · 사전에 없음(단가 직접 입력)',
    _matched: false,
  };
}

const TOOL = {
  name: 'add_estimate_items',
  description: '상담 내용에서 파악한 인테리어 견적 품목들을 구조화해 반환한다.',
  input_schema: {
    type: 'object',
    properties: {
      items: {
        type: 'array',
        description: '상담에서 명확히 드러난 공사 품목만. 추측으로 없는 항목을 만들지 말 것.',
        items: {
          type: 'object',
          properties: {
            gongjong: { type: 'string', enum: window.GONGJONG_ORDER, description: '공종' },
            name: { type: 'string', description: '품목명. 아래 단가사전 목록에 있으면 그 이름을 글자 그대로 사용.' },
            unit: { type: 'string', description: '단위 (EA, PY, M, LOT, 자당, M/D 등). 사전 품목이면 사전 단위.' },
            qty: { type: 'number', description: '수량. 평형·개소·면적 등 상담 근거로 추정, 불명확하면 1.' },
            reason: { type: 'string', description: '상담의 어느 대목에서 나온 항목인지 짧게 (한국어).' },
          },
          required: ['gongjong', 'name', 'unit', 'qty'],
        },
      },
      memo: { type: 'string', description: '견적에 반영하기 애매하거나 담당자 확인이 필요한 사항 (한국어, 없으면 빈 값).' },
    },
    required: ['items'],
  },
};

// Gemini 는 함수선언 스키마 타입이 대문자(OBJECT/STRING/...)
const GEMINI_TOOL = {
  name: 'add_estimate_items',
  description: TOOL.description,
  parameters: {
    type: 'OBJECT',
    properties: {
      items: {
        type: 'ARRAY',
        description: TOOL.input_schema.properties.items.description,
        items: {
          type: 'OBJECT',
          properties: {
            gongjong: { type: 'STRING', enum: window.GONGJONG_ORDER, description: '공종' },
            name: { type: 'STRING', description: '품목명. 단가사전 목록에 있으면 글자 그대로.' },
            unit: { type: 'STRING', description: '단위 (EA, PY, M, LOT, 자당, M/D 등).' },
            qty: { type: 'NUMBER', description: '수량. 단서 없으면 1.' },
            reason: { type: 'STRING', description: '상담 근거 (한국어).' },
          },
          required: ['gongjong', 'name', 'unit', 'qty'],
        },
      },
      memo: { type: 'STRING', description: '담당자 확인 필요 사항 (한국어, 없으면 빈 값).' },
    },
    required: ['items'],
  },
};

function buildPrompt(text) {
  const dictList = window.DICT
    .map((d) => `- [${d.gongjong}] ${d.name} (${d.unit})`)
    .join('\n');
  return `당신은 아정당인테리어의 견적 담당자를 돕는 보조입니다. 아래 고객 상담 내용을 읽고, 견적서에 들어갈 공사 품목을 뽑아 add_estimate_items 도구로 반환하세요.

규칙:
- 상담에서 **명확히 언급되었거나 강하게 시사된** 공사만 항목으로 만드세요. 근거 없는 항목을 지어내지 마세요.
- 가능하면 아래 "단가 사전 품목 목록"의 **품목명을 글자 그대로** 쓰세요(그래야 과거 시공단가가 자동 연결됩니다). 사전에 없으면 자연스러운 새 품목명으로 적되 단위를 함께 주세요.
- gongjong(공종)은 반드시 사전의 공종 중 하나로 분류하세요.
- 수량은 평형·개소·면적 등 상담 단서로 추정하고, 단서가 없으면 1로 두세요. 가격/단가는 절대 추정하지 마세요(시스템이 사전에서 채웁니다).
- 욕실 2개·방 3개처럼 개소가 여러 개면 수량에 반영하세요.

[단가 사전 품목 목록]
${dictList}

[고객 상담 내용]
${text}`;
}

async function callAnthropic(text, key, model) {
  const res = await fetch('https://api.anthropic.com/v1/messages', {
    method: 'POST',
    headers: {
      'content-type': 'application/json',
      'x-api-key': key,
      'anthropic-version': '2023-06-01',
      'anthropic-dangerous-direct-browser-access': 'true',
    },
    body: JSON.stringify({
      model, max_tokens: 4096,
      tools: [TOOL],
      tool_choice: { type: 'tool', name: 'add_estimate_items' },
      messages: [{ role: 'user', content: buildPrompt(text) }],
    }),
  });
  const data = await res.json();
  if (!res.ok) throw mkErr(data?.error?.message || `HTTP ${res.status}`, res.status);
  const block = (data.content || []).find((b) => b.type === 'tool_use');
  if (!block) throw new Error('견적 항목을 받지 못했습니다. 상담 내용을 더 구체적으로 넣어보세요.');
  return block.input; // { items, memo }
}

async function callGemini(text, key, model) {
  const url = `https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(model)}:generateContent?key=${encodeURIComponent(key)}`;
  const res = await fetch(url, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({
      contents: [{ role: 'user', parts: [{ text: buildPrompt(text) }] }],
      tools: [{ functionDeclarations: [GEMINI_TOOL] }],
      toolConfig: { functionCallingConfig: { mode: 'ANY', allowedFunctionNames: ['add_estimate_items'] } },
    }),
  });
  const data = await res.json();
  if (!res.ok) throw mkErr(data?.error?.message || `HTTP ${res.status}`, res.status);
  const parts = data?.candidates?.[0]?.content?.parts || [];
  const fc = parts.find((p) => p.functionCall);
  if (!fc) throw new Error('견적 항목을 받지 못했습니다. 상담 내용을 더 구체적으로 넣어보세요.');
  return fc.functionCall.args; // { items, memo }
}

// 재시도 가능한 오류(서버 혼잡/과부하) 표시
function mkErr(msg, status) {
  const e = new Error(msg);
  e.retryable = [429, 500, 502, 503, 529].includes(status) || /high demand|overload|unavailable|exhausted|try again|busy/i.test(msg);
  return e;
}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

// 혼잡 시 자동 재시도 (점점 더 길게 대기)
async function callLLMRetry(provider, text, key, model, onStatus) {
  const delays = [2000, 5000, 10000];
  for (let attempt = 0; ; attempt++) {
    try {
      return await (provider === 'claude' ? callAnthropic(text, key, model) : callGemini(text, key, model));
    } catch (e) {
      if (!e.retryable || attempt >= delays.length) throw e;
      onStatus && onStatus(`AI 서버가 혼잡합니다 — ${Math.round(delays[attempt] / 1000)}초 후 자동 재시도 (${attempt + 1}/${delays.length})`);
      await sleep(delays[attempt]);
    }
  }
}

/* ---------------- 메인 패널 ---------------- */
function AIPanel({ onAddRows, flash, defaultOpen }) {
  const [open, setOpen] = useState(!!defaultOpen);
  const [text, setText] = useState('');
  const [provider, setProvider] = useState(() => localStorage.getItem(PROVIDER_LS) || 'gemini');
  const P = PROVIDERS[provider];
  const [key, setKey] = useState(() => localStorage.getItem(P.keyLS) || '');
  const [model, setModel] = useState(() => localStorage.getItem(P.modelLS) || P.defModel);
  const [showKey, setShowKey] = useState(false);
  const [loading, setLoading] = useState(false);
  const [err, setErr] = useState('');
  const [rows, setRows] = useState(null);   // 추출 결과(검토용)
  const [memo, setMemo] = useState('');
  const [transcribing, setTranscribing] = useState(false);
  const [sttMsg, setSttMsg] = useState('');

  const onAudio = async (ev) => {
    const file = ev.target.files[0]; ev.target.value = '';
    if (!file) return;
    setErr(''); setTranscribing(true);
    setSttMsg(`'${file.name}' 변환 중… 녹음 길이에 따라 수 분~십수 분 걸립니다. 창을 닫지 마세요.`);
    try {
      const res = await fetch('/transcribe', { method: 'POST', headers: { 'x-filename': encodeURIComponent(file.name) }, body: file });
      const txt = await res.text();
      if (!res.ok) throw new Error(txt.slice(0, 300));
      setText((prev) => (prev ? prev + '\n' : '') + txt.trim());
      setSttMsg(`변환 완료 — ${txt.trim().length.toLocaleString()}자. 아래 내용 확인 후 「AI로 견적 항목 뽑기」 하세요.`);
    } catch (e) {
      setSttMsg('');
      setErr('음성 변환 실패: ' + (e.message || '') + ' — 앱을 시작.bat 로 실행했는지, python·faster-whisper 가 설치돼 있는지 확인하세요.');
    } finally { setTranscribing(false); }
  };

  const saveKey = (v) => { setKey(v); localStorage.setItem(P.keyLS, v); };
  const saveModel = (v) => { setModel(v); localStorage.setItem(P.modelLS, v); };
  const switchProvider = (p) => {
    setProvider(p); localStorage.setItem(PROVIDER_LS, p); setErr('');
    setKey(localStorage.getItem(PROVIDERS[p].keyLS) || '');
    setModel(localStorage.getItem(PROVIDERS[p].modelLS) || PROVIDERS[p].defModel);
  };

  const run = async () => {
    setErr('');
    if (!key.trim()) { setErr(`먼저 ${P.label} API 키를 입력하세요.`); setShowKey(true); return; }
    if (!text.trim()) { setErr('상담 내용을 붙여넣으세요.'); return; }
    setLoading(true); setRows(null); setMemo('');
    try {
      const out = await callLLMRetry(provider, text, key.trim(), model, (m) => setErr(m));
      setErr('');
      const mapped = (out.items || []).map(toRow).map((r) => ({ ...r, _use: true }));
      setRows(mapped);
      setMemo(out.memo || '');
      if (mapped.length === 0) setErr('뽑아낼 항목이 없었습니다. 상담 내용을 더 구체적으로 넣어보세요.');
    } catch (e) {
      setErr((e.message || '호출 실패') + (e.retryable ? ' — 잠시 후 다시 시도하거나 엔진을 Claude로 바꿔보세요.' : ''));
    } finally { setLoading(false); }
  };

  const toggle = (i) => setRows((rs) => rs.map((r, j) => j === i ? { ...r, _use: !r._use } : r));
  const patch = (i, p) => setRows((rs) => rs.map((r, j) => j === i ? { ...r, ...p } : r));
  const addSelected = () => {
    const chosen = rows.filter((r) => r._use)
      .map(({ _use, _matched, _matchName, _count, ...r }) => r);
    if (!chosen.length) { setErr('추가할 항목을 선택하세요.'); return; }
    onAddRows(chosen);
    setRows(null); setText(''); setErr('');
    flash(`${chosen.length}개 항목 추가됨`);
  };

  const useCnt = rows ? rows.filter((r) => r._use).length : 0;

  return (
    <div style={{ background: 'var(--surface)', border: '1.5px solid var(--accent-line)', borderRadius: 12, marginBottom: 12, overflow: 'hidden', boxShadow: open ? 'var(--shadow)' : 'none' }}>
      <button onClick={() => setOpen(!open)} style={{ width: '100%', textAlign: 'left', border: 'none', background: open ? 'var(--accent-soft)' : 'var(--surface)', padding: '12px 16px', display: 'flex', alignItems: 'center', gap: 9 }}>
        <span style={{ width: 22, height: 22, borderRadius: 6, background: 'var(--accent)', color: '#fff', display: 'grid', placeItems: 'center', fontSize: 13, fontWeight: 800 }}>AI</span>
        <span style={{ fontWeight: 800, fontSize: 14, color: 'var(--accent-ink)' }}>AI 상담 보완 — 빠진 항목 제안</span>
        <span style={{ fontSize: 11.5, color: 'var(--ink-3)' }}>녹취·메모 붙여넣기 → 체크 못 한 항목을 찾아 제안</span>
        <span style={{ flex: 1 }} />
        <span style={{ color: 'var(--accent-ink)' }}>{open ? '▲' : '▼'}</span>
      </button>

      {open && (
        <div style={{ padding: '14px 16px' }}>
          {/* AI 엔진 선택 */}
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
            <span style={{ fontSize: 11, fontWeight: 600, color: 'var(--ink-3)' }}>AI 엔진</span>
            <div style={{ display: 'flex', background: 'var(--surface-2)', border: '1px solid var(--line)', borderRadius: 8, padding: 3 }}>
              {Object.keys(PROVIDERS).map((p) => (
                <button key={p} onClick={() => switchProvider(p)}
                  style={{ padding: '5px 14px', border: 'none', borderRadius: 6, fontSize: 12.5, fontWeight: 700, background: provider === p ? 'var(--accent)' : 'transparent', color: provider === p ? '#fff' : 'var(--ink-3)' }}>
                  {PROVIDERS[p].label}
                </button>
              ))}
            </div>
            <a href={`https://${P.keyUrl}`} target="_blank" rel="noreferrer" style={{ fontSize: 11, color: 'var(--accent-ink)' }}>키 발급 → {P.keyUrl}</a>
          </div>

          {/* API 키 */}
          <div style={{ display: 'flex', gap: 8, alignItems: 'flex-end', marginBottom: 10, flexWrap: 'wrap' }}>
            <label style={{ flex: 1, minWidth: 240, display: 'flex', flexDirection: 'column', gap: 4 }}>
              <span style={{ fontSize: 11, fontWeight: 600, color: 'var(--ink-3)' }}>
                {P.label} API 키 {key ? <span style={{ color: 'var(--good)' }}>· 저장됨(이 브라우저에만)</span> : <span style={{ color: 'var(--danger)' }}>· 미설정</span>}
              </span>
              <input type={showKey ? 'text' : 'password'} value={key} onChange={(e) => saveKey(e.target.value)} placeholder={P.keyHint} className="mono"
                style={{ padding: '8px 10px', border: '1px solid var(--line-strong)', borderRadius: 7, outline: 'none', fontSize: 12.5 }} />
            </label>
            <button onClick={() => setShowKey(!showKey)} style={aiChipBtn}>{showKey ? '가리기' : '보기'}</button>
            <label style={{ width: 180, display: 'flex', flexDirection: 'column', gap: 4 }}>
              <span style={{ fontSize: 11, fontWeight: 600, color: 'var(--ink-3)' }}>모델</span>
              <input value={model} onChange={(e) => saveModel(e.target.value)} className="mono"
                style={{ padding: '8px 10px', border: '1px solid var(--line-strong)', borderRadius: 7, outline: 'none', fontSize: 12.5 }} />
            </label>
          </div>

          {/* 음성파일 → 텍스트 (로컬 변환) */}
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 8, flexWrap: 'wrap' }}>
            <label style={{ display: 'inline-flex', alignItems: 'center', gap: 6, padding: '8px 14px', border: '1.5px solid var(--accent)', color: 'var(--accent-ink)', background: 'var(--accent-soft)', borderRadius: 8, fontSize: 13, fontWeight: 700, cursor: transcribing ? 'default' : 'pointer', opacity: transcribing ? 0.6 : 1 }}>
              🎙 상담 녹음파일 올려서 자동 변환
              <input type="file" accept="audio/*,video/*,.wav,.m4a,.mp3,.aac" onChange={onAudio} disabled={transcribing} style={{ display: 'none' }} />
            </label>
            {transcribing && <span style={{ fontSize: 12, color: 'var(--accent-ink)', fontWeight: 600 }}>⏳ 변환 중…</span>}
            {sttMsg && <span style={{ fontSize: 11.5, color: transcribing ? 'var(--accent-ink)' : 'var(--good)' }}>{sttMsg}</span>}
          </div>

          {/* 상담 텍스트 */}
          <textarea value={text} onChange={(e) => setText(e.target.value)} rows={7}
            placeholder="고객 상담 내용을 붙여넣으세요. 예) 44평 아파트 올수리, 욕실 2개 전체 철거 후 타일·도기 교체, 거실 확장, 주방 가구 새로, 도배는 실크로 전체, 바닥 강마루…"
            style={{ width: '100%', padding: '11px 13px', border: '1px solid var(--line-strong)', borderRadius: 9, outline: 'none', fontSize: 13.5, lineHeight: 1.6, resize: 'vertical', fontFamily: 'inherit' }}
            onFocus={(e) => e.target.style.borderColor = 'var(--accent)'} onBlur={(e) => e.target.style.borderColor = 'var(--line-strong)'} />

          <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 10 }}>
            <button onClick={run} disabled={loading}
              style={{ padding: '9px 18px', border: 'none', borderRadius: 8, background: loading ? 'var(--ink-3)' : 'var(--accent)', color: '#fff', fontWeight: 700, fontSize: 13.5 }}>
              {loading ? 'AI 분석 중…' : '✦ AI로 견적 항목 뽑기'}
            </button>
            <span style={{ fontSize: 11.5, color: 'var(--ink-3)' }}>뽑힌 항목은 추가 전에 검토·수정할 수 있어요.</span>
          </div>

          {err && <div style={{ marginTop: 10, padding: '9px 12px', background: 'var(--warn-soft)', borderRadius: 8, fontSize: 12.5, color: 'var(--danger)' }}>{err}</div>}

          {/* 추출 결과 검토 */}
          {rows && rows.length > 0 && (
            <div style={{ marginTop: 14, border: '1px solid var(--line)', borderRadius: 10, overflow: 'hidden' }}>
              <div style={{ padding: '9px 13px', background: 'var(--surface-2)', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'center', gap: 10 }}>
                <b style={{ fontSize: 13 }}>AI 추출 {rows.length}개</b>
                <span style={{ fontSize: 11.5, color: 'var(--good)' }}>사전매칭 {rows.filter((r) => r._matched).length}</span>
                <span style={{ fontSize: 11.5, color: 'var(--warn)' }}>신규 {rows.filter((r) => !r._matched).length}</span>
                <span style={{ flex: 1 }} />
                <button onClick={addSelected} style={{ padding: '6px 14px', border: 'none', borderRadius: 7, background: 'var(--accent)', color: '#fff', fontWeight: 700, fontSize: 12.5 }}>선택 {useCnt}개 견적에 추가 →</button>
              </div>
              <div style={{ maxHeight: 320, overflowY: 'auto' }}>
                {rows.map((r, i) => (
                  <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 9, padding: '7px 13px', borderTop: i ? '1px solid var(--line)' : 'none', opacity: r._use ? 1 : 0.45 }}>
                    <input type="checkbox" checked={r._use} onChange={() => toggle(i)} style={{ accentColor: 'var(--accent)', width: 15, height: 15 }} />
                    <span style={aiBadge(r.gongjong)}>{r.gongjong.replace('공사', '')}</span>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{ fontSize: 13, fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{r.name}</div>
                      <div style={{ fontSize: 11, color: 'var(--ink-3)' }}>
                        {r._matched
                          ? <span className="mono">{r.mat ? '자재 ' + aiWon(r.mat) : ''}{r.mat && r.lab ? ' / ' : ''}{r.lab ? '노무 ' + aiWon(r.lab) : ''} · 사전 {r._count}회</span>
                          : <span style={{ color: 'var(--warn)' }}>사전에 없음 — 단가 직접 입력</span>}
                        {r.note ? <span style={{ color: 'var(--ink-3)' }}>　{r.note}</span> : ''}
                      </div>
                    </div>
                    <input value={r.qty} onChange={(e) => patch(i, { qty: e.target.value })} className="mono"
                      style={{ width: 50, padding: '4px 6px', border: '1px solid var(--line-strong)', borderRadius: 6, textAlign: 'right', fontSize: 12.5, outline: 'none' }} />
                    <span style={{ fontSize: 11, color: 'var(--ink-3)', width: 30 }}>{r.unit}</span>
                  </div>
                ))}
              </div>
              {memo && <div style={{ padding: '9px 13px', borderTop: '1px solid var(--line)', background: 'var(--accent-soft)', fontSize: 12, color: 'var(--accent-ink)' }}>💬 확인 필요: {memo}</div>}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

const aiChipBtn = { padding: '8px 11px', fontSize: 12, border: '1px solid var(--line-strong)', borderRadius: 7, background: 'var(--surface)', color: 'var(--ink-2)', fontWeight: 600 };

window.EstAI = { AIPanel };
