/* category-confidence-panel.jsx — 카테고리 신뢰도 지도 (Task 017, ROADMAP II-M1)
 *
 *  560 leaf 카테고리 각각의 표본 상태(브랜드 실측 n·conjugate 사후 σ·confidence)를
 *  한눈에 보여주는 읽기 전용 패널. 새 수치를 계산하지 않고 기존
 *  window.BrandProfiles.categoryPriors()(brand-profiles.js) 산출만 소비한다(무중단 가산).
 *
 *  ★거짓정밀 금지(DP-4): categoryPriors()는 브랜드 meta.categoryLabel 단위로 집계되며
 *  560 leaf와 1:1 매핑이 존재하지 않는다. 이 패널은 leaf 경로와 categoryLabel의 토큰
 *  중첩(브랜드-표본 매칭에 쓰이는 BrandProfiles.reference()와 동일 원리)으로 "근사 매칭"만
 *  하고, 매칭 실패(표본 브랜드 0곳)인 leaf는 절대 수치를 만들어내지 않고 "표본 없음"으로만 표기한다.
 */
(function () {
  function Badge({ tone, children }) {
    var c = { amber: ['#3a2a12', '#f0b46a'], cyan: ['#11303a', '#5fc7e0'],
              plant: ['#13301f', '#5fcf94'], mute: ['#23262d', '#9aa0aa'],
              red: ['#3a1b1b', '#e08a8a'] }[tone || 'mute'];
    return (
      <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4, padding: '2px 7px',
        borderRadius: 4, fontSize: 10.5, fontFamily: 'var(--font-mono)', fontWeight: 600,
        background: c[0], color: c[1], whiteSpace: 'nowrap' }}>{children}</span>
    );
  }

  function flattenLeaves(nodes, out) {
    out = out || [];
    for (const n of (nodes || [])) {
      if (n.leaf) out.push(n);
      else if (n.children) flattenLeaves(n.children, out);
    }
    return out;
  }

  function tokenize(s) {
    return (s || '').toLowerCase().split(/[\s,·/>]+/).map(t => t.trim()).filter(t => t.length >= 2);
  }

  // leaf path ↔ categoryPriors() 키(categoryLabel) 근사 매칭 — BrandProfiles.reference()와 같은
  // 토큰중첩 원리를 재사용(신규 알고리즘 발명 금지). 매칭 실패 시 null(=표본 없음, 날조 금지).
  function matchCategoryForLeaf(leaf, priorKeys) {
    const leafToks = tokenize(leaf.path);
    let best = null, bestScore = 0;
    for (const catLabel of priorKeys) {
      const catToks = tokenize(catLabel);
      let score = 0;
      for (const t of leafToks) for (const c of catToks) {
        if (t === c || t.includes(c) || c.includes(t)) { score++; break; }
      }
      if (score > bestScore) { bestScore = score; best = catLabel; }
    }
    return bestScore > 0 ? best : null;
  }

  function fmtDim(dim, kind) {
    if (!dim || dim.mu == null) return { text: '—', tone: 'mute' };
    let text;
    if (kind === 'aov') text = Math.round(dim.mu).toLocaleString() + '원';
    else if (kind === 'skew') text = (dim.mu * 100).toFixed(1) + '%';
    else text = dim.mu.toFixed(1) + '%';
    const sigmaText = dim.sigma == null ? ' (σ 미상 · n=1)' : ' (σ±' + (kind === 'aov' ? Math.round(dim.sigma).toLocaleString() : dim.sigma.toFixed(kind === 'skew' ? 3 : 1)) + ')';
    const tone = dim.confidence >= 70 ? 'plant' : dim.confidence >= 50 ? 'cyan' : 'amber';
    return { text: text + sigmaText, tone, confidence: dim.confidence };
  }

  const DIM_LABELS = [
    ['femaleSkew', '여성비중', 'skew'],
    ['aov', '객단가(AOV)', 'aov'],
    ['repurchasePct', '재구매율', 'pct'],
    ['mobilePct', '모바일비중', 'pct'],
  ];

  function CategoryConfidencePanel() {
    const [q, setQ] = React.useState('');
    const [onlyCovered, setOnlyCovered] = React.useState(false);

    const priors = React.useMemo(() => (window.BrandProfiles ? window.BrandProfiles.categoryPriors() : {}), []);
    const priorKeys = React.useMemo(() => Object.keys(priors), [priors]);
    const leaves = React.useMemo(() => flattenLeaves(window.TAXONOMY), []);

    const rows = React.useMemo(() => leaves.map(leaf => {
      const catLabel = matchCategoryForLeaf(leaf, priorKeys);
      const p = catLabel ? priors[catLabel] : null;
      return { leaf, catLabel, p };
    }), [leaves, priorKeys, priors]);

    const covered = rows.filter(r => r.p);
    const uncoveredCount = rows.length - covered.length;
    const avgConfidence = covered.length
      ? Math.round(covered.reduce((s, r) => s + (r.p.dims.mobilePct?.confidence ?? r.p.dims.femaleSkew?.confidence ?? 0), 0) / covered.length)
      : null;
    const uniqueCoveredCats = new Set(covered.map(r => r.catLabel)).size;

    const filtered = React.useMemo(() => {
      let list = rows;
      if (onlyCovered) list = list.filter(r => r.p);
      if (q.trim()) {
        const needle = q.trim().toLowerCase();
        list = list.filter(r => r.leaf.path.toLowerCase().includes(needle));
      }
      return list.slice().sort((a, b) => {
        const an = a.p ? a.p.sampleN : -1, bn = b.p ? b.p.sampleN : -1;
        if (an !== bn) return bn - an;
        return a.leaf.path.localeCompare(b.leaf.path, 'ko');
      });
    }, [rows, q, onlyCovered]);

    return (
      <div style={{ padding: '18px 20px', maxWidth: 1180, margin: '0 auto' }}>
        <div style={{ display: 'flex', alignItems: 'flex-start', gap: 14, flexWrap: 'wrap',
          padding: '14px 16px', background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 8 }}>
          <div style={{ flex: '1 1 420px' }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
              <strong style={{ fontFamily: 'var(--font-display)', fontSize: 16 }}>카테고리 신뢰도 지도</strong>
              <Badge tone="mute">읽기 전용 · 재계산 없음</Badge>
              <Badge tone={uniqueCoveredCats > 0 ? 'plant' : 'red'}>실측 카테고리 {uniqueCoveredCats}종</Badge>
              <Badge tone="cyan">전체 leaf {rows.length}종</Badge>
            </div>
            <p style={{ margin: '8px 0 0', fontSize: 12, lineHeight: 1.55, color: 'var(--ink-300)' }}>
              브랜드 실측(tier-A) 표본이 있는 카테고리일수록 conjugate 사후결합으로 σ가 좁혀지고 confidence가 오릅니다.
              대부분의 leaf는 아직 표본 브랜드가 0곳(순수 시뮬레이션 tier-D)입니다 — 이 지도가 다음에 어느
              카테고리를 실측으로 채워야 광고 추천 정밀도가 오르는지 알려주는 나침반입니다.
            </p>
            <p style={{ margin: '6px 0 0', fontSize: 10.5, color: 'var(--ink-500)', fontFamily: 'var(--font-mono)' }}>
              ⚠ leaf ↔ categoryLabel 매칭은 텍스트 토큰중첩 근사치(1:1 공식 매핑 아님) — 거짓정밀 방지를 위해
              매칭 실패 leaf는 절대 수치를 만들지 않고 "표본 없음"으로만 표기합니다.
            </p>
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 4, minWidth: 160 }}>
            <span style={{ fontSize: 10, color: 'var(--ink-400)', fontFamily: 'var(--font-mono)' }}>커버리지</span>
            <strong style={{ fontSize: 20, fontFamily: 'var(--font-display)' }}>
              {rows.length ? Math.round((covered.length / rows.length) * 100) : 0}%
            </strong>
            <span style={{ fontSize: 10.5, color: 'var(--ink-400)' }}>
              {covered.length}/{rows.length}leaf 매칭 · 미커버 {uncoveredCount}
            </span>
            {avgConfidence != null && (
              <span style={{ fontSize: 10.5, color: 'var(--ink-400)' }}>매칭분 평균 confidence {avgConfidence}%</span>
            )}
          </div>
        </div>

        <div style={{ display: 'flex', alignItems: 'center', gap: 10, margin: '14px 0 8px', flexWrap: 'wrap' }}>
          <input type="text" placeholder="카테고리 경로 검색…" value={q} onChange={e => setQ(e.target.value)}
                 style={{ flex: '1 1 220px', padding: '6px 10px', fontSize: 12, background: 'var(--surface)',
                          border: '1px solid var(--border)', borderRadius: 6, color: 'var(--ink-100)' }} />
          <label style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 11.5, color: 'var(--ink-300)', cursor: 'pointer' }}>
            <input type="checkbox" checked={onlyCovered} onChange={e => setOnlyCovered(e.target.checked)} />
            실측 있는 카테고리만
          </label>
          <span style={{ fontSize: 10.5, color: 'var(--ink-500)', fontFamily: 'var(--font-mono)' }}>{filtered.length}건 표시</span>
        </div>

        <div style={{ maxHeight: 560, overflowY: 'auto', border: '1px solid var(--border)', borderRadius: 8 }}>
          <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 11.5 }}>
            <thead style={{ position: 'sticky', top: 0, background: 'var(--surface)', zIndex: 1 }}>
              <tr>
                <th style={th}>카테고리 경로</th>
                <th style={th}>매칭 categoryLabel</th>
                <th style={th}>n(브랜드)</th>
                <th style={th}>실주문건수</th>
                {DIM_LABELS.map(([key, label]) => <th key={key} style={th}>{label}</th>)}
                <th style={th}>method</th>
              </tr>
            </thead>
            <tbody>
              {filtered.map((r, i) => (
                <tr key={r.leaf.path} style={{ background: i % 2 ? 'transparent' : 'rgba(255,255,255,0.015)' }}>
                  <td style={td} title={r.leaf.path}>{r.leaf.path}</td>
                  <td style={td}>
                    {r.catLabel
                      ? <span style={{ color: 'var(--ink-200)' }}>{r.catLabel}</span>
                      : <Badge tone="mute">표본 없음</Badge>}
                  </td>
                  <td style={td}>{r.p ? r.p.sampleN : '—'}</td>
                  <td style={td}>{r.p ? r.p.sampleSize.toLocaleString() : '—'}</td>
                  {DIM_LABELS.map(([key, , kind]) => {
                    const d = r.p ? fmtDim(r.p.dims[key], kind) : { text: '—', tone: 'mute' };
                    return (
                      <td key={key} style={td}>
                        <span style={{ color: d.tone === 'plant' ? 'var(--plant)' : d.tone === 'cyan' ? 'var(--cyan)' : d.tone === 'amber' ? 'var(--signal)' : 'var(--ink-500)',
                                       fontFamily: 'var(--font-mono)', fontSize: 10.5 }}>{d.text}</span>
                      </td>
                    );
                  })}
                  <td style={td}><span style={{ fontSize: 10, color: 'var(--ink-500)' }}>{r.p ? r.p.method : '—'}</span></td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
    );
  }

  const th = { textAlign: 'left', padding: '7px 10px', fontFamily: 'var(--font-mono)', fontSize: 10,
               color: 'var(--ink-400)', borderBottom: '1px solid var(--border)', whiteSpace: 'nowrap' };
  const td = { padding: '6px 10px', borderBottom: '1px solid var(--border)', color: 'var(--ink-200)',
               maxWidth: 220, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' };

  Object.assign(window, { CategoryConfidencePanel });
})();
