// 체크리스트 항목 → 단가사전 품목 매핑 설정 (견적사용). window.EstCheckMap
// 저장: /chkmap (chkmap.json). 견적서 만들기 시 이 매핑이 자동매칭보다 우선.
const CM_CAT_G = { admin: '공통공사', window: '샷시공사', aircon: '에어컨공사', demo: '철거공사', plumb: '설비공사', carpentry: '목공사', electric: '전기공사', floor: '바닥공사', tile: '타일공사', wall: '도배공사', film: '필름공사', balcony: '기타공사', middoor: '기타공사', bath: '욕실공사', furniture: '가구공사' };
const cmNorm = (s) => String(s || '').replace(/[\s_()/]/g, '').toLowerCase();
function cmAuto(label, catId) {   // app.jsx matchCL 과 동일 로직 (자동 매칭 미리보기)
  const g = CM_CAT_G[catId]; const n = cmNorm(label);
  const inc = (d) => { const dn = cmNorm(d.name); return dn.includes(n) || n.includes(dn); };
  let hit = window.DICT.filter((d) => (!g || d.gongjong === g) && inc(d)).sort((a, b) => (b.count || 0) - (a.count || 0));
  if (hit[0]) return hit[0];
  hit = window.DICT.filter(inc).sort((a, b) => (b.count || 0) - (a.count || 0));
  return hit[0] || null;
}
const cmWon = window.EstCalc.won;

function CheckMapEditor() {
  const CD = window.CHECKLIST_DATA;
  const [map, setMap] = useState(() => ({ ...(window.CHK_MAP || {}) }));
  const [cat, setCat] = useState(CD[0].id);
  const [editId, setEditId] = useState('');
  const [q, setQ] = useState('');
  const [dirty, setDirty] = useState(false);
  const [saving, setSaving] = useState(false);
  const [msg, setMsg] = useState('');
  const [expCand, setExpCand] = useState({});   // 추가 후보 펼침 (item id → bool)
  const [showAll, setShowAll] = useState(false); // 전체 현황: 미지정만 보기(false) / 전체(true)
  const [ovOpen, setOvOpen] = useState(false);   // 전체 매핑 현황 펼침

  const getList = (id) => { const v = map[id]; return Array.isArray(v) ? v : (v ? [v] : []); };
  const norm = (v) => (Array.isArray(v) ? v : (v ? [v] : []));
  const addCand = (itemId, d) => { setMap((m) => { const list = norm(m[itemId]); if (list.some((x) => x.gongjong === d.gongjong && x.name === d.name)) return m; return { ...m, [itemId]: [...list, { gongjong: d.gongjong, name: d.name }] }; }); setDirty(true); setMsg(''); };
  const rmCand = (itemId, i) => { setMap((m) => { const list = norm(m[itemId]).filter((_, j) => j !== i); const n = { ...m }; if (list.length) n[itemId] = list; else delete n[itemId]; return n; }); setDirty(true); setMsg(''); };
  const mkDefault = (itemId, i) => { setMap((m) => { const list = norm(m[itemId]).slice(); const x = list.splice(i, 1)[0]; list.unshift(x); return { ...m, [itemId]: list }; }); setDirty(true); setMsg(''); };
  // 단가사전 세트(setName) 목록
  const SETS = useMemo(() => { const o = {}; window.DICT.forEach((d) => { const k = (d.setName || '').trim(); if (k) { if (!o[k]) o[k] = 0; o[k]++; } }); return o; }, []);
  const setNames = Object.keys(SETS);
  const addSetCand = (itemId, name) => { setMap((m) => { const list = norm(m[itemId]); if (list.some((x) => x.set === name)) return m; return { ...m, [itemId]: [...list, { set: name }] }; }); setDirty(true); setMsg(''); };
  const save = async () => {
    setSaving(true); setMsg('');
    try {
      const r = await fetch('/chkmap', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(map) });
      if (!r.ok) throw new Error('HTTP ' + r.status);
      window.CHK_MAP = map; setDirty(false); setMsg('저장됨 — "견적서 만들기"에 즉시 반영');
    } catch (e) { setMsg('저장 실패: ' + (e.message || '') + ' — 시작.bat(서버)로 실행했는지 확인'); } finally { setSaving(false); }
  };
  useEffect(() => { window.CHKMAP_SAVE = save; });   // 상단바 "매핑 저장"이 호출
  const search = (catId) => {
    const g = CM_CAT_G[catId]; const n = cmNorm(q);
    if (!n) return [];
    const inc = (d) => cmNorm(d.gongjong + d.name).includes(n) || cmNorm(d.name).includes(n);
    let list = window.DICT.filter((d) => (!g || d.gongjong === g) && inc(d)).sort((a, b) => (b.count || 0) - (a.count || 0));
    if (list.length < 4) list = list.concat(window.DICT.filter((d) => g && d.gongjong !== g && inc(d)).sort((a, b) => (b.count || 0) - (a.count || 0)));
    return list.slice(0, 10);
  };

  const curCat = CD.find((c) => c.id === cat) || CD[0];
  const mappedCount = curCat.items.filter((it) => map[it.id]).length;

  return (
    <div style={{ maxWidth: 980, margin: '0 auto', padding: '20px 24px 80px' }}>
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 10, marginBottom: 4 }}>
        <h2 style={{ margin: 0, fontSize: 20, fontWeight: 800 }}>체크 매핑</h2>
        <span style={{ fontSize: 12.5, color: 'var(--ink-3)' }}>체크리스트 항목에 들어갈 단가 사전 품목을 지정합니다. 지정하지 않으면 자동으로 매칭돼요.{dirty ? ' · 저장 안 한 변경 있음' : ''}</span>
      </div>
      {msg && <div style={{ fontSize: 12, marginBottom: 8, color: msg.startsWith('저장됨') ? 'var(--good)' : 'var(--danger)', fontWeight: 600 }}>{msg}</div>}

      {/* 카테고리 선택 — 한 줄 스크롤, 미지정 있는 곳만 강조 */}
      <div className="no-sb" style={{ display: 'flex', gap: 6, overflowX: 'auto', padding: '10px 0 16px' }}>
        {CD.map((c) => {
          const mc = c.items.filter((it) => map[it.id]).length;
          const on = cat === c.id;
          const miss = c.items.length - mc;
          return (
            <button key={c.id} onClick={() => { setCat(c.id); setEditId(''); }} style={{
              flex: 'none', display: 'inline-flex', alignItems: 'center', gap: 6, padding: '7px 13px', borderRadius: 20, fontSize: 12.5, fontWeight: 700, cursor: 'pointer', whiteSpace: 'nowrap',
              border: '1px solid ' + (on ? 'var(--accent)' : (miss ? 'var(--warn)' : 'var(--line)')),
              background: on ? 'var(--accent)' : 'var(--surface)',
              color: on ? '#fff' : (miss ? 'var(--warn)' : 'var(--ink-3)'),
            }}>
              {c.name}
              <span style={{ fontSize: 11, fontWeight: 800 }}>{miss ? mc + '/' + c.items.length : '✓'}</span>
            </button>
          );
        })}
      </div>

      <div style={{ border: '1px solid var(--line)', borderRadius: 12, overflow: 'hidden', background: 'var(--surface)' }}>
        <div style={{ padding: '10px 16px', background: 'var(--surface-2)', borderBottom: '1px solid var(--line)', fontWeight: 800, fontSize: 14 }}>
          {curCat.name} <span style={{ fontWeight: 600, color: 'var(--ink-3)', fontSize: 12 }}>· 지정 {mappedCount}/{curCat.items.length}</span>
        </div>
        {curCat.items.map((it) => {
          const list = getList(it.id);
          const auto = list.length ? null : cmAuto(it.label, cat);
          const def = list[0];
          const dd = def && !def.set ? window.DICT.find((d) => d.gongjong === def.gongjong && d.name === def.name) : null;
          const meta = dd ? [dd.mat ? '자재 ' + cmWon(dd.mat.recent) : '', dd.lab ? '노무 ' + cmWon(dd.lab.recent) : ''].filter(Boolean).join(' · ') : '';
          return (
            <div key={it.id}>
              <div className="map-row">
                <span className="mlabel">{it.label}</span>
                <span className={'map-arrow' + (list.length ? '' : ' muted')}>→</span>
                <div style={{ minWidth: 0 }}>
                  {list.length === 0 ? (
                    <span className="autocard">
                      <span className="ac-tag">⚠ 자동매칭</span>
                      <span className="ac-name">{auto ? auto.name : '매칭 없음'}</span>
                      <span className="ac-hint">직접 지정 권장</span>
                    </span>
                  ) : (
                    <div style={{ display: 'flex', flexDirection: 'column', gap: 6, alignItems: 'flex-start' }}>
                      <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
                        <span className="linkcard" title={def.set ? '패키지 — 체크 시 패키지 전체가 견적에 들어감' : ''}>
                          {def.set
                            ? <><span style={{ fontSize: 11, fontWeight: 800, color: 'var(--accent-ink)', background: 'var(--accent-soft)', borderRadius: 5, padding: '1px 6px' }}>📦</span><span className="lk-name">{def.set}</span><span className="lk-meta">{(SETS[def.set] || 0)}품목 패키지</span></>
                            : <><span style={window.EstEditor.gBadge(def.gongjong)}>{def.gongjong.replace('공사', '')}</span><span className="lk-name">{def.name}</span>{meta && <span className="lk-meta mono">{meta}</span>}</>}
                        </span>
                        {list.length > 1 && <button className="morecand" onClick={() => setExpCand((e) => ({ ...e, [it.id]: !e[it.id] }))}>{expCand[it.id] ? '후보 접기 ▴' : '＋' + (list.length - 1) + ' 후보 ▾'}</button>}
                      </div>
                      {list.length > 1 && expCand[it.id] && (
                        <div style={{ display: 'flex', flexDirection: 'column', gap: 5, paddingLeft: 4 }}>
                          <div style={{ fontSize: 11, color: 'var(--ink-3)', fontWeight: 600 }}>대안 후보 — '기본으로' 누르면 견적에 들어갈 품목이 바뀝니다</div>
                          {list.slice(1).map((c, i0) => {
                            const i = i0 + 1;
                            const cd = c.set ? null : window.DICT.find((x) => x.gongjong === c.gongjong && x.name === c.name);
                            const cmeta = cd ? [cd.mat ? '자재 ' + cmWon(cd.mat.recent) : '', cd.lab ? '노무 ' + cmWon(cd.lab.recent) : ''].filter(Boolean).join(' · ') : (c.set ? (SETS[c.set] || 0) + '품목 패키지' : '');
                            return (
                              <div key={c.set ? 'set:' + c.set : c.gongjong + c.name} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '6px 10px', border: '1px solid var(--line)', borderRadius: 8, background: 'var(--surface)', maxWidth: 580 }}>
                                {c.set ? <span style={{ fontSize: 11, fontWeight: 800, color: 'var(--accent-ink)', background: 'var(--accent-soft)', borderRadius: 5, padding: '1px 6px' }}>📦</span> : <span style={window.EstEditor.gBadge(c.gongjong)}>{c.gongjong.replace('공사', '')}</span>}
                                <span style={{ flex: 1, fontSize: 12.5, fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{c.set ? c.set : c.name}</span>
                                {cmeta && <span className="mono" style={{ fontSize: 11, color: 'var(--ink-3)', whiteSpace: 'nowrap' }}>{cmeta}</span>}
                                <button onClick={() => mkDefault(it.id, i)} title="이 품목을 기본(견적 반영)으로" style={{ fontSize: 11.5, fontWeight: 700, border: '1px solid var(--accent-line)', background: 'var(--accent-soft)', color: 'var(--accent-ink)', borderRadius: 6, padding: '3px 9px', cursor: 'pointer', whiteSpace: 'nowrap' }}>기본으로</button>
                                <button onClick={() => rmCand(it.id, i)} title="후보에서 제거" style={{ border: 'none', background: 'transparent', color: 'var(--ink-3)', fontSize: 13, cursor: 'pointer', padding: '0 2px' }}>✕</button>
                              </div>
                            );
                          })}
                        </div>
                      )}
                    </div>
                  )}
                </div>
                <button className={'mlink-btn' + (list.length ? '' : ' ghost')} onClick={() => { setEditId(editId === it.id ? '' : it.id); setQ(''); }}>{list.length ? '연결 편집' : '＋ 연결'}</button>
              </div>
              {editId === it.id && (
                <div style={{ margin: '0 16px 12px', background: 'var(--surface-2)', border: '1px solid var(--line)', borderRadius: 9, padding: '10px 12px' }}>
                  <input value={q} onChange={(e) => setQ(e.target.value)} autoFocus placeholder={`단가사전에서 '${it.label}'에 연결할 품목 검색`}
                    style={{ width: '100%', padding: '8px 10px', border: '1px solid var(--accent)', borderRadius: 7, outline: 'none', fontSize: 13.5 }} />
                  <div style={{ marginTop: 8, display: 'flex', flexDirection: 'column', gap: 2, maxHeight: 240, overflow: 'auto' }}>
                    {setNames.filter((nm) => !q.trim() || nm.toLowerCase().includes(q.trim().toLowerCase())).slice(0, 6).map((nm) => (
                      <button key={'set:' + nm} onClick={() => addSetCand(it.id, nm)} style={{ display: 'flex', alignItems: 'center', gap: 8, textAlign: 'left', border: 'none', background: 'transparent', padding: '7px 8px', borderRadius: 6, cursor: 'pointer' }}
                        onMouseEnter={(e) => e.currentTarget.style.background = 'var(--surface)'} onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
                        <span style={{ fontSize: 11, fontWeight: 800, color: 'var(--accent-ink)', background: 'var(--accent-soft)', borderRadius: 6, padding: '2px 7px' }}>📦 패키지</span>
                        <span style={{ flex: 1, fontSize: 13, fontWeight: 700 }}>{nm}</span>
                        <span style={{ fontSize: 11.5, color: 'var(--ink-3)' }}>{SETS[nm]}품목 패키지</span>
                      </button>
                    ))}
                    {search(cat).map((d) => (
                      <button key={d.id} onClick={() => addCand(it.id, d)} style={{ display: 'flex', alignItems: 'center', gap: 8, textAlign: 'left', border: 'none', background: 'transparent', padding: '7px 8px', borderRadius: 6, cursor: 'pointer' }}
                        onMouseEnter={(e) => e.currentTarget.style.background = 'var(--surface)'} onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
                        <span style={window.EstEditor.gBadge(d.gongjong)}>{d.gongjong.replace('공사', '')}</span>
                        <span style={{ flex: 1, fontSize: 13, fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{d.name}</span>
                        <span className="mono" style={{ fontSize: 11.5, color: 'var(--ink-3)' }}>{d.mat ? '자재 ' + cmWon(d.mat.recent) : ''}{d.lab ? ' 노무 ' + cmWon(d.lab.recent) : ''} · {d.count}회</span>
                      </button>
                    ))}
                    {q.trim() && search(cat).length === 0 && <div style={{ fontSize: 12.5, color: 'var(--ink-3)', padding: '8px' }}>검색 결과 없음</div>}
                  </div>
                </div>
              )}
            </div>
          );
        })}
      </div>

      {/* 전체 매핑 현황 — 접이식 요약 */}
      <div style={{ marginTop: 22 }}>
        {(() => {
          const total = CD.reduce((s, c) => s + c.items.length, 0);
          const mapped = CD.reduce((s, c) => s + c.items.filter((it) => getList(it.id).length).length, 0);
          const miss = total - mapped;
          return (
            <button onClick={() => setOvOpen(!ovOpen)} style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 10, padding: '12px 16px', background: 'var(--surface)', border: '1px solid var(--line)', borderRadius: 12, cursor: 'pointer', textAlign: 'left' }}>
              <span style={{ fontSize: 14, fontWeight: 800 }}>전체 매핑 현황</span>
              <span style={{ fontSize: 12.5, color: 'var(--ink-3)' }}>전체 {total} · 지정 {mapped}{miss ? <b style={{ color: 'var(--warn)' }}> · 미지정 {miss}</b> : <b style={{ color: 'var(--good)' }}> · 모두 지정 ✓</b>}</span>
              <span style={{ flex: 1 }} />
              <span style={{ color: 'var(--ink-3)', fontSize: 13 }}>{ovOpen ? '접기 ▲' : '펼치기 ▼'}</span>
            </button>
          );
        })()}
        {ovOpen && (<>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, margin: '10px 0 8px', flexWrap: 'wrap' }}>
          <span style={{ fontSize: 12, color: 'var(--ink-3)' }}>행을 클릭하면 해당 항목으로 이동</span>
          <span style={{ flex: 1 }} />
          <div style={{ display: 'flex', border: '1px solid var(--line-strong)', borderRadius: 8, overflow: 'hidden' }}>
            <button onClick={() => setShowAll(false)} style={{ padding: '6px 12px', fontSize: 12, fontWeight: 700, border: 'none', cursor: 'pointer', background: !showAll ? 'var(--accent)' : 'var(--surface)', color: !showAll ? '#fff' : 'var(--ink-2)' }}>미지정만</button>
            <button onClick={() => setShowAll(true)} style={{ padding: '6px 12px', fontSize: 12, fontWeight: 700, border: 'none', cursor: 'pointer', background: showAll ? 'var(--accent)' : 'var(--surface)', color: showAll ? '#fff' : 'var(--ink-2)' }}>전체</button>
          </div>
        </div>
        <div style={{ border: '1px solid var(--line)', borderRadius: 12, overflow: 'auto', maxHeight: '52vh', background: 'var(--surface)' }}>
          <table style={{ width: '100%', borderCollapse: 'collapse', minWidth: 760 }}>
            <thead>
              <tr>
                {['카테고리', '체크 항목', '기본 연결 품목', '공종', '자재단가', '노무단가', '후보'].map((h, i) => (
                  <th key={h} style={{ textAlign: i >= 4 ? 'right' : 'left', fontSize: 11, fontWeight: 700, color: 'var(--ink-3)', padding: '8px 10px', borderBottom: '1.5px solid var(--line-strong)', position: 'sticky', top: 0, background: 'var(--surface)', whiteSpace: 'nowrap' }}>{h}</th>
                ))}
              </tr>
            </thead>
            <tbody>
              {CD.map((c) => c.items.filter((it) => showAll || getList(it.id).length === 0).map((it) => {
                const list = getList(it.id);
                const def = list[0];
                const dd = def ? window.DICT.find((d) => d.gongjong === def.gongjong && d.name === def.name) : null;
                const auto = list.length ? null : cmAuto(it.label, c.id);
                const show = dd || auto;
                const tdc = { padding: '6px 10px', fontSize: 12.5, borderBottom: '1px solid var(--line)' };
                const tdr = { ...tdc, textAlign: 'right', fontFamily: 'var(--mono)' };
                return (
                  <tr key={it.id} onClick={() => { setCat(c.id); setEditId(it.id); window.scrollTo({ top: 0, behavior: 'smooth' }); }} style={{ cursor: 'pointer' }}
                    onMouseEnter={(e) => e.currentTarget.style.background = 'var(--surface-2)'} onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
                    <td style={{ ...tdc, color: 'var(--ink-3)' }}>{c.name}</td>
                    <td style={{ ...tdc, fontWeight: 700 }}>{it.label}</td>
                    <td style={{ ...tdc, color: def ? 'var(--ink)' : 'var(--ink-3)' }}>{def ? (def.set ? '📦 ' + def.set + ' (' + (SETS[def.set] || 0) + '품목)' : def.name) : (auto ? '자동: ' + auto.name : '— 미매칭')}</td>
                    <td style={{ ...tdc, color: 'var(--ink-3)' }}>{show ? show.gongjong : ''}</td>
                    <td style={tdr}>{show && show.mat ? cmWon(show.mat.recent) : '-'}</td>
                    <td style={tdr}>{show && show.lab ? cmWon(show.lab.recent) : '-'}</td>
                    <td style={{ ...tdr, color: list.length ? 'var(--accent-ink)' : 'var(--ink-3)' }}>{list.length ? list.length + '개' : '자동'}</td>
                  </tr>
                );
              }))}
              {!showAll && CD.every((c) => c.items.every((it) => getList(it.id).length > 0)) && (
                <tr><td colSpan={7} style={{ padding: '24px', textAlign: 'center', color: 'var(--ink-3)', fontSize: 13 }}>미지정 항목이 없습니다 — 모두 직접 지정됨 ✓ (‘전체 펼쳐보기’로 전체 확인)</td></tr>
              )}
            </tbody>
          </table>
        </div>
        </>)}
      </div>
    </div>
  );
}

window.EstCheckMap = { CheckMapEditor };
