// Breakdown Table view — full pivot builder (Phase 1)
// Rows: hierarchical multi-dim (MAKE > MODEL > CAT GROUP 등, 드래그앤드롭 가능)
// Columns: single dim toggle (Channel / Year / Quarter / Month / Wing Type / Make Group / B2B-B2C)
// Values: QTY + KRW (둘 다 동시 표시)
// 디폴트 colDim='channel'로 v3.1 이전 동작 재현.

function BreakdownView({ lang, perms }) {
  const [loading, setLoading] = React.useState(true);
  const [allRows, setAllRows] = React.useState([]);
  const [err, setErr]         = React.useState(null);

  // Controls
  const [period, setPeriod]   = React.useState(String(new Date().getFullYear()));  // 기본=올해. all|연도|custom
  const [dateFrom, setDateFrom] = React.useState('');
  const [dateTo, setDateTo]   = React.useState('');
  // Default to 3 levels — adding sku_raw at root creates ~20K leaves and freezes
  // first paint. User can opt into deeper grouping via the GROUP BY chips.
  const [groupBy, setGroupBy] = React.useState(['make','model','category_group']);
  // v3.2: 열 차원 (Phase 1은 단일). 'channel' 기본 → 이전 v3.1 동작 그대로 재현.
  const [colDim, setColDim] = React.useState('channel');
  // v3.3: 표 폭 확장 토글 — 기본은 다른 카드와 같은 폭, 클릭 시 viewport 끝까지.
  const [wideMode, setWideMode] = React.useState(false);
  // v3.4: 표↔차트 토글. ON: 표 위에 차트 카드 추가. 기본 OFF.
  const [chartMode, setChartMode] = React.useState(false);

  // Chip filters
  const [fChannel, setFChannel]   = React.useState([]);  // []= all
  const [fCountry, setFCountry]   = React.useState([]);
  const [fRegion,  setFRegion]    = React.useState([]);
  const [fMakeGrp, setFMakeGrp]   = React.useState([]);
  const [fSalesCh, setFSalesCh]   = React.useState([]);  // B2B/B2C
  const [fModel,   setFModel]     = React.useState([]);
  const [fCatGrp,  setFCatGrp]    = React.useState([]);
  const [fCat,     setFCat]       = React.useState([]);
  // v3.1: 윙 종류 + 회사 (특히 ROW 시장에서 중요)
  const [fUWT,     setFUWT]       = React.useState([]);  // UNIVERSAL WING TYPE
  const [fCompany, setFCompany]   = React.useState([]);
  // v3.2: MAKE 필터 (Make Group보다 세밀한 단위 — BMW, TOYOTA 등)
  const [fMake,    setFMake]      = React.useState([]);

  // Drag-and-drop reorder state for group chips
  const [dragIdx, setDragIdx] = React.useState(null);
  const [dropIdx, setDropIdx] = React.useState(null);

  // Currency display unit for KRW columns
  const [unit, setUnit] = React.useState('1e8');  // '1' | '1e4' | '1e8' | '1e9'

  // Table state
  const [expanded, setExpanded]   = React.useState({});  // key -> bool
  const [sortKey, setSortKey]     = React.useState('krw_total');
  const [sortAsc, setSortAsc]     = React.useState(false);
  // Note: sortKey/sortAsc are read by both `setSortAsc(a => !a)` and inside
  // buildTree via the deferred-compute effect below. Don't reorder.

  const t = lang === 'en' ? BT_EN : BT_KO;

  React.useEffect(() => {
    (async () => {
      setLoading(true);
      setErr(null);
      try {
        const rows = await SALES_DB.fetchAllSalesData();
        setAllRows(rows);
      } catch (e) {
        setErr(e?.message || String(e));
      } finally {
        setLoading(false);
      }
    })();
  }, []);

  // Derived: filtered rows
  const filtered = React.useMemo(() => {
    // Early-return so first render (before fetch resolves) is O(1).
    // Hooks can't be conditional, but their bodies can.
    if (!allRows || allRows.length === 0) return allRows || [];
    let rows = allRows;

    // Period filter
    if (period !== 'all' && period !== 'custom') {
      rows = rows.filter(r => r.order_date?.startsWith(period));
    } else if (period === 'custom') {
      // Only apply bound when fully formed YYYY-MM-DD (length 10) — avoids
      // wiping all rows during keystroke-by-keystroke partial input.
      if (dateFrom && dateFrom.length === 10) rows = rows.filter(r => r.order_date >= dateFrom);
      if (dateTo   && dateTo.length   === 10) rows = rows.filter(r => r.order_date <= dateTo);
    }

    // Chip filters
    if (fChannel.length)  rows = rows.filter(r => fChannel.includes(r.channel_group));
    if (fCountry.length)  rows = rows.filter(r => fCountry.includes(r.country));
    if (fRegion.length)   rows = rows.filter(r => fRegion.includes(r.region));
    if (fMakeGrp.length)  rows = rows.filter(r => fMakeGrp.includes(r.make_group));
    if (fSalesCh.length)  rows = rows.filter(r => fSalesCh.includes(r.sales_channel));
    if (fModel.length)    rows = rows.filter(r => fModel.includes(r.model));
    if (fCatGrp.length)   rows = rows.filter(r => fCatGrp.includes(r.category_group));
    if (fCat.length)      rows = rows.filter(r => fCat.includes(r.category));
    if (fUWT.length)      rows = rows.filter(r => fUWT.includes(r.universal_wing_type));
    if (fCompany.length)  rows = rows.filter(r => fCompany.includes(r.company));
    if (fMake.length)     rows = rows.filter(r => fMake.includes(r.make));

    return rows;
  }, [allRows, period, dateFrom, dateTo, fChannel, fCountry, fRegion, fMakeGrp, fSalesCh, fModel, fCatGrp, fCat, fUWT, fCompany, fMake]);

  // Derived: hierarchy tree (sorted inside buildTree, so render is cheap)
  const [computedTree, setComputedTree] = React.useState(null);
  const [computing, setComputing] = React.useState(false);
  React.useEffect(() => {
    // Skip heavy work entirely until data has arrived. Without this guard
    // the buildTree call still runs on the empty array (cheap) but the
    // effect+setState round-trip causes an extra render before the loading
    // branch can show.
    if (!filtered || filtered.length === 0) {
      setComputedTree(filtered ? [] : null);
      setComputing(false);
      return;
    }
    setComputing(true);
    // Defer the heavy compute by one tick so the loading state can paint first.
    const id = setTimeout(() => {
      setComputedTree(buildTree(filtered, groupBy, sortKey, sortAsc, colDim));
      setComputing(false);
    }, 0);
    return () => clearTimeout(id);
  }, [filtered, groupBy, sortKey, sortAsc, colDim]);
  const tree = computedTree;

  // v3.2: 열 차원 unique key 추출 (헤더 + 셀 매핑용)
  const colKeys = React.useMemo(() => {
    if (!filtered || filtered.length === 0) return [];
    const set = new Set();
    for (const r of filtered) set.add(getColKey(r, colDim));
    return sortColKeys([...set]);
  }, [filtered, colDim]);

  // v3.3: Cross-filter 옵션 — 각 필터 옵션은 "자기 자신 필터 제외한 모든 다른 필터 적용된 행"에서 추출.
  // BMW 선택 시 MODEL/WING TYPE/COUNTRY 옵션이 BMW에 한정해서 좁혀짐 (BI 표준 동작).
  // 자기 자신은 제외하므로 사용자가 추가 선택을 막지 않음 (BMW 선택했어도 MAKE 옵션엔 다른 메이크 다 보임).
  const options = React.useMemo(() => {
    const empty = { channel:[], country:[], region:[], makeGrp:[], salesCh:[], model:[], catGrp:[], cat:[], uwt:[], company:[], make:[] };
    if (!allRows || allRows.length === 0) return empty;

    // 행 순회 한 번으로 11개 필터 옵션 모두 추출하기 위해, 각 행을 11번 평가 대신
    // 각 필터의 "다른 필터들 통과 여부" 비트마스크를 인라인 계산.
    function buildSet(excludeKey, getter) {
      let rows = allRows;
      if (excludeKey !== 'period') {
        if (period !== 'all' && period !== 'custom') {
          rows = rows.filter(r => r.order_date?.startsWith(period));
        } else if (period === 'custom') {
          if (dateFrom && dateFrom.length === 10) rows = rows.filter(r => r.order_date >= dateFrom);
          if (dateTo   && dateTo.length   === 10) rows = rows.filter(r => r.order_date <= dateTo);
        }
      }
      if (excludeKey !== 'channel' && fChannel.length) rows = rows.filter(r => fChannel.includes(r.channel_group));
      if (excludeKey !== 'country' && fCountry.length) rows = rows.filter(r => fCountry.includes(r.country));
      if (excludeKey !== 'region'  && fRegion.length)  rows = rows.filter(r => fRegion.includes(r.region));
      if (excludeKey !== 'makeGrp' && fMakeGrp.length) rows = rows.filter(r => fMakeGrp.includes(r.make_group));
      if (excludeKey !== 'make'    && fMake.length)    rows = rows.filter(r => fMake.includes(r.make));
      if (excludeKey !== 'salesCh' && fSalesCh.length) rows = rows.filter(r => fSalesCh.includes(r.sales_channel));
      if (excludeKey !== 'model'   && fModel.length)   rows = rows.filter(r => fModel.includes(r.model));
      if (excludeKey !== 'catGrp'  && fCatGrp.length)  rows = rows.filter(r => fCatGrp.includes(r.category_group));
      if (excludeKey !== 'cat'     && fCat.length)     rows = rows.filter(r => fCat.includes(r.category));
      if (excludeKey !== 'uwt'     && fUWT.length)     rows = rows.filter(r => fUWT.includes(r.universal_wing_type));
      if (excludeKey !== 'company' && fCompany.length) rows = rows.filter(r => fCompany.includes(r.company));
      const set = new Set();
      for (const r of rows) {
        const v = getter(r);
        if (v) set.add(v);
      }
      return [...set].sort();
    }
    return {
      channel: buildSet('channel', r => r.channel_group),
      country: buildSet('country', r => r.country),
      region:  buildSet('region',  r => r.region),
      makeGrp: buildSet('makeGrp', r => r.make_group),
      make:    buildSet('make',    r => r.make),
      salesCh: buildSet('salesCh', r => r.sales_channel),
      model:   buildSet('model',   r => r.model),
      catGrp:  buildSet('catGrp',  r => r.category_group),
      cat:     buildSet('cat',     r => r.category),
      uwt:     buildSet('uwt',     r => r.universal_wing_type),
      company: buildSet('company', r => r.company),
    };
  }, [allRows, period, dateFrom, dateTo, fChannel, fCountry, fRegion, fMakeGrp, fMake, fSalesCh, fModel, fCatGrp, fCat, fUWT, fCompany]);

  function toggleExpand(key) {
    setExpanded(prev => ({ ...prev, [key]: !prev[key] }));
  }

  function expandAll() {
    if (!tree) return;
    const all = {};
    (function walk(nodes) {
      for (const n of nodes) {
        all[n.key] = true;
        if (n.children) walk(n.children);
      }
    })(tree);
    setExpanded(all);
  }
  // v3.3: collapseAll은 모든 노드를 명시적 false로 설정. autoOpen(첫 3-depth 자동 펼침)
  // 로직이 explicit collapse를 무시하면 안 되므로 키마다 false 명시 필요.
  function collapseAll() {
    if (!tree) return;
    const all = {};
    (function walk(nodes) {
      for (const n of nodes) {
        all[n.key] = false;
        if (n.children) walk(n.children);
      }
    })(tree);
    setExpanded(all);
  }

  function handleSort(key) {
    if (sortKey === key) setSortAsc(a => !a);
    else { setSortKey(key); setSortAsc(false); }
  }

  function exportCsv() {
    // v3.2: 열 차원이 동적이므로 헤더/셀도 colKeys 기반으로 생성.
    const headers = [
      'Dimension',
      ...colKeys.map(k => `${k} QTY`), 'Total QTY',
      ...colKeys.map(k => `${k} KRW`), 'Total KRW',
    ];
    const rows = flattenForExport(tree);
    const csv = [headers, ...rows.map(r => [
      r.label,
      ...colKeys.map(k => r.agg.qty[k] || 0), r.agg.qty_total,
      ...colKeys.map(k => r.agg.krw[k] || 0), r.agg.krw_total,
    ])].map(r => r.join(',')).join('\n');
    const blob = new Blob(['﻿' + csv], { type: 'text/csv;charset=utf-8;' });
    const url  = URL.createObjectURL(blob);
    const a    = document.createElement('a'); a.href = url; a.download = 'breakdown.csv'; a.click();
    URL.revokeObjectURL(url);
  }

  if (loading) return <BDPlaceholder msg={t.loading}/>;
  if (err)     return <BDPlaceholder msg={'Error: ' + err}/>;

  const DIMS = ['make','model','category_group','category','sku_raw','country','region','universal_wing_type','company'];
  const DIM_LABELS = {
    make:'MAKE', model:'MODEL', category_group:'CAT GROUP', category:'CATEGORY',
    sku_raw:'SKU', country:'COUNTRY', region:'REGION',
    universal_wing_type:'WING TYPE', company:'COMPANY',
  };
  // v3.2: 열 차원 옵션. 'channel'(=channel_group, US/ROW/KR)이 디폴트 — 이전 v3.1 재현.
  // 'channel'은 내부 키 그대로 두고 라벨만 'Market'으로(B2B/B2C와 어감 구분).
  const COL_DIM_OPTIONS = [
    { key: 'channel',             label: lang === 'en' ? 'Market' : '시장' },
    { key: 'year',                label: lang === 'en' ? 'Year' : '연도' },
    { key: 'quarter',             label: lang === 'en' ? 'Quarter' : '분기' },
    { key: 'month',               label: lang === 'en' ? 'Month' : '월' },
    { key: 'universal_wing_type', label: lang === 'en' ? 'Wing Type' : 'Wing Type' },
    { key: 'make_group',          label: 'Make Group' },
    { key: 'sales_channel',       label: 'B2B/B2C' },
  ];
  // Month-to-date 프리셋 — 이번 달 1일~오늘 (현재 날짜 기준 동적, 하드코딩 금지).
  // MTD는 'custom' + 이번 달 범위로 표현 — 기존 custom 필터 로직 그대로 재사용.
  const _now = new Date();
  const _pad = n => String(n).padStart(2, '0');
  const mtdFrom = `${_now.getFullYear()}-${_pad(_now.getMonth() + 1)}-01`;
  const mtdTo   = `${_now.getFullYear()}-${_pad(_now.getMonth() + 1)}-${_pad(_now.getDate())}`;
  const mtdActive = period === 'custom' && dateFrom === mtdFrom && dateTo === mtdTo;
  // 연도 칩 — 데이터 시작연도(2024)부터 올해까지 동적 생성(하드코딩 금지, 최신연도 우선).
  const years = [];
  for (let y = _now.getFullYear(); y >= 2024; y--) years.push(String(y));

  return (
    <div>
      <header style={{ marginBottom:16 }}>
        <h2 style={{ fontSize:22, fontWeight:700, letterSpacing:-.01, margin:0 }}>{t.title}</h2>
      </header>

      {/* Controls bar */}
      <div style={{ ...bdCardStyle, display:'flex', gap:14, flexWrap:'wrap', alignItems:'flex-end', marginBottom:14 }}>
        {/* Period */}
        <BDGroup label={t.period}>
          <BDBtn active={period==='all'} onClick={() => setPeriod('all')}>{t.all}</BDBtn>
          {years.map(y => (
            <BDBtn key={y} active={period===y} onClick={() => setPeriod(y)}>{y}</BDBtn>
          ))}
          {/* '이번 달'(MTD)은 '직접 설정' 바로 옆 — 둘 다 날짜범위 프리셋 */}
          <BDBtn active={mtdActive} title={t.mtdTitle}
            onClick={() => { setDateFrom(mtdFrom); setDateTo(mtdTo); setPeriod('custom'); }}>{t.mtd}</BDBtn>
          <BDBtn active={period==='custom' && !mtdActive} onClick={() => setPeriod('custom')}>{t.custom}</BDBtn>
          {period === 'custom' && (
            <span style={{ display:'flex', gap:4, alignItems:'center' }}>
              <KrDateInput value={dateFrom} onChange={setDateFrom} style={bdDateInputStyle} placeholder="YYYY-MM-DD"/>
              <span style={{ fontSize:12, color:'var(--fg-muted)' }}>~</span>
              <KrDateInput value={dateTo}   onChange={setDateTo}   style={bdDateInputStyle} placeholder="YYYY-MM-DD"/>
            </span>
          )}
        </BDGroup>

        {/* Row count */}
        <div style={{ fontSize:11, color:'var(--fg-muted)', alignSelf:'flex-end' }}>
          {t.rows.replace('{n}', filtered.length.toLocaleString())}
        </div>
        <div style={{ flex:1 }}/>
        <BDGroup label={t.unit}>
          <select value={unit} onChange={e => setUnit(e.target.value)} style={{
            padding:'5px 10px', fontSize:12, background:'var(--surface-2)',
            border:'1px solid var(--border)', borderRadius:6, color:'var(--fg)',
          }}>
            <option value="1">{t.unitWon}</option>
            <option value="1e4">{t.unitMan}</option>
            <option value="1e8">{t.unitEok}</option>
            <option value="1e9">{t.unitBil}</option>
          </select>
        </BDGroup>
        <button
          onClick={() => setChartMode(c => !c)}
          title={lang === 'en' ? 'Toggle chart view' : '차트 보기 토글'}
          style={{
            padding:'6px 12px', fontSize:12, fontWeight:500,
            background: chartMode ? 'var(--accent-soft)' : 'var(--surface-2)',
            color:      chartMode ? 'var(--accent)'      : 'var(--fg-2)',
            border:     `1px solid ${chartMode ? 'var(--accent)' : 'var(--border)'}`,
            borderRadius:8, cursor:'pointer',
          }}>
          📊 {lang === 'en' ? (chartMode ? 'Hide chart' : 'Chart') : (chartMode ? '차트 숨김' : '차트')}
        </button>
        <button
          onClick={() => setWideMode(w => !w)}
          title={lang === 'en' ? 'Toggle wide table' : '표 폭 확장 토글'}
          style={{
            padding:'6px 12px', fontSize:12, fontWeight:500,
            background: wideMode ? 'var(--accent-soft)' : 'var(--surface-2)',
            color:      wideMode ? 'var(--accent)'      : 'var(--fg-2)',
            border:     `1px solid ${wideMode ? 'var(--accent)' : 'var(--border)'}`,
            borderRadius:8, cursor:'pointer',
          }}>
          ⤢ {lang === 'en' ? (wideMode ? 'Narrow' : 'Wide') : (wideMode ? '기본' : '확장')}
        </button>
        <button onClick={exportCsv} style={exportBtnStyle}>⬇ {t.exportCsv}</button>
      </div>

      {/* Grouping chips — pivot-style ordered selection */}
      <div style={{ ...bdCardStyle, marginBottom:14 }}>
        {/* Selected (in order) — with position number + reorder arrows + remove */}
        {groupBy.length > 0 && (
          <div style={{ display:'flex', gap:6, alignItems:'center', flexWrap:'wrap', marginBottom: groupBy.length > 0 ? 10 : 0 }}>
            <span style={{ fontSize:10.5, fontWeight:700, color:'var(--fg-muted)', textTransform:'uppercase', letterSpacing:.06, marginRight:4 }}>
              {lang === 'en' ? 'Order' : '그룹 순서'}
            </span>
            {groupBy.map((d, i) => {
              // v3.1: 화살표 ◀▶ 제거, native HTML5 drag-and-drop 도입.
              // chip 전체가 draggable, ⋮⋮ 그립 핸들로 시각 신호.
              const isDragging   = dragIdx === i;
              const isDropTarget = dropIdx === i && dragIdx !== null && dragIdx !== i;
              return (
                <div key={d}
                  draggable
                  onDragStart={(e) => {
                    setDragIdx(i);
                    e.dataTransfer.effectAllowed = 'move';
                    // Firefox 호환: setData 호출 필요
                    try { e.dataTransfer.setData('text/plain', String(i)); } catch (err) {}
                  }}
                  onDragEnter={(e) => {
                    e.preventDefault();
                    if (dragIdx !== null && dragIdx !== i) setDropIdx(i);
                  }}
                  onDragOver={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; }}
                  onDrop={(e) => {
                    e.preventDefault();
                    if (dragIdx === null || dragIdx === i) {
                      setDragIdx(null); setDropIdx(null); return;
                    }
                    setGroupBy(prev => {
                      const next = [...prev];
                      const [moved] = next.splice(dragIdx, 1);
                      next.splice(i, 0, moved);
                      return next;
                    });
                    setDragIdx(null); setDropIdx(null);
                  }}
                  onDragEnd={() => { setDragIdx(null); setDropIdx(null); }}
                  style={{
                    display:'inline-flex', alignItems:'center', gap:0,
                    background:'var(--accent-soft)', color:'var(--accent)',
                    border:`1px solid ${isDropTarget ? 'var(--fg)' : 'var(--accent)'}`,
                    borderRadius:6, overflow:'hidden',
                    opacity: isDragging ? 0.4 : 1,
                    cursor:'grab', userSelect:'none',
                    boxShadow: isDropTarget ? '0 0 0 2px var(--accent-soft)' : 'none',
                    transition:'opacity 0.15s, border-color 0.15s, box-shadow 0.15s',
                  }}>
                  <span title={lang === 'en' ? 'Drag to reorder' : '드래그로 순서 변경'}
                    style={{ fontSize:11, padding:'3px 5px', color:'rgba(0,0,0,0.4)', cursor:'grab', lineHeight:1, letterSpacing:-1 }}>⋮⋮</span>
                  <span style={{ fontSize:10, fontWeight:700, padding:'3px 6px', background:'rgba(0,0,0,0.06)' }}>{i+1}</span>
                  <span style={{ fontSize:11.5, fontWeight:500, padding:'3px 8px' }}>{DIM_LABELS[d]}</span>
                  <button
                    draggable="false"
                    onMouseDown={(e) => e.stopPropagation()}
                    onClick={() => setGroupBy(prev => prev.filter(x => x !== d))}
                    title={lang === 'en' ? 'Remove' : '제거'}
                    style={{ ...chipBtnStyle, color:'var(--fg-muted)', cursor:'pointer' }}>✕</button>
                </div>
              );
            })}
            <div style={{ flex:1 }}/>
            {groupBy.length >= 3 && (
              <>
                <button onClick={expandAll} style={{
                  padding:'3px 10px', fontSize:11, fontWeight:500, borderRadius:6, cursor:'pointer',
                  background:'var(--surface-2)', color:'var(--fg-2)', border:'1px solid var(--border)',
                }}>▾ {lang === 'en' ? 'Expand all' : '전체 펼치기'}</button>
                <button onClick={collapseAll} style={{
                  padding:'3px 10px', fontSize:11, fontWeight:500, borderRadius:6, cursor:'pointer',
                  background:'var(--surface-2)', color:'var(--fg-2)', border:'1px solid var(--border)',
                }}>▸ {lang === 'en' ? 'Collapse all' : '전체 접기'}</button>
              </>
            )}
          </div>
        )}
        {/* Available — click to append in order */}
        <div style={{ display:'flex', gap:6, alignItems:'center', flexWrap:'wrap' }}>
          <span style={{ fontSize:10.5, fontWeight:700, color:'var(--fg-muted)', textTransform:'uppercase', letterSpacing:.06, marginRight:4 }}>
            {lang === 'en' ? 'Add' : '추가'}
          </span>
          {DIMS.filter(d => !groupBy.includes(d)).map(d => (
            <button key={d} onClick={() => setGroupBy(prev => [...prev, d])} style={{
              padding:'3px 10px', fontSize:11.5, fontWeight:500, borderRadius:6, cursor:'pointer',
              background:'var(--surface-2)', color:'var(--fg-2)',
              border:'1px solid var(--border)',
            }}>+ {DIM_LABELS[d]}</button>
          ))}
          {DIMS.every(d => groupBy.includes(d)) && (
            <span style={{ fontSize:11, color:'var(--fg-subtle)', fontStyle:'italic' }}>
              {lang === 'en' ? 'all dimensions in use' : '모든 차원 선택됨'}
            </span>
          )}
        </div>
      </div>

      {/* v3.2: 열 차원 토글 — 단일 차원 (Phase 1) */}
      <div style={{ ...bdCardStyle, marginBottom:14 }}>
        <div style={{ display:'flex', gap:6, alignItems:'center', flexWrap:'wrap' }}>
          <span style={{ fontSize:10.5, fontWeight:700, color:'var(--fg-muted)', textTransform:'uppercase', letterSpacing:.06, marginRight:4 }}>
            {lang === 'en' ? 'Columns' : '열'}
          </span>
          {COL_DIM_OPTIONS.map(({ key, label }) => (
            <button key={key} onClick={() => setColDim(key)} style={{
              padding:'4px 10px', fontSize:11.5, fontWeight:500, borderRadius:6, cursor:'pointer',
              background: colDim === key ? 'var(--accent-soft)' : 'var(--surface-2)',
              color:      colDim === key ? 'var(--accent)'      : 'var(--fg-2)',
              border:     `1px solid ${colDim === key ? 'var(--accent)' : 'var(--border)'}`,
            }}>{label}</button>
          ))}
          <span style={{ flex:1 }}/>
          <span style={{ fontSize:10.5, color:'var(--fg-subtle)', fontStyle:'italic' }}>
            {lang === 'en'
              ? `${colKeys.length} column${colKeys.length === 1 ? '' : 's'}`
              : `컬럼 ${colKeys.length}개`}
          </span>
        </div>
      </div>

      {/* Chip filters */}
      <div style={{ ...bdCardStyle, display:'flex', gap:12, flexWrap:'wrap', marginBottom:14, alignItems:'center' }}>
        <span style={{ fontSize:10.5, fontWeight:700, color:'var(--fg-muted)', textTransform:'uppercase', letterSpacing:.06 }}>{t.filter}</span>
        <MultiChip label={lang === 'en' ? 'Market' : '시장'} values={options.channel} selected={fChannel} onChange={setFChannel}/>
        <MultiChip label={t.country}   values={options.country} selected={fCountry} onChange={setFCountry}/>
        <MultiChip label={t.region}    values={options.region}  selected={fRegion}  onChange={setFRegion}/>
        <MultiChip label="Make Group"  values={options.makeGrp} selected={fMakeGrp} onChange={setFMakeGrp}/>
        <MultiChip label="Make"        values={options.make}    selected={fMake}    onChange={setFMake}/>
        <MultiChip label="B2B/B2C"     values={options.salesCh} selected={fSalesCh} onChange={setFSalesCh}/>
        <MultiChip label="Model"       values={options.model}   selected={fModel}   onChange={setFModel}/>
        <MultiChip label="Cat Group"   values={options.catGrp}  selected={fCatGrp}  onChange={setFCatGrp}/>
        <MultiChip label="Category"    values={options.cat}     selected={fCat}     onChange={setFCat}/>
        <MultiChip label="Wing Type"   values={options.uwt}     selected={fUWT}     onChange={setFUWT}/>
        <MultiChip label="Company"     values={options.company} selected={fCompany} onChange={setFCompany}/>
        {(fChannel.length + fCountry.length + fRegion.length + fMakeGrp.length + fSalesCh.length + fModel.length + fCatGrp.length + fCat.length + fUWT.length + fCompany.length + fMake.length > 0) && (
          <button onClick={() => { setFChannel([]); setFCountry([]); setFRegion([]); setFMakeGrp([]); setFSalesCh([]); setFModel([]); setFCatGrp([]); setFCat([]); setFUWT([]); setFCompany([]); setFMake([]); }}
            style={{ fontSize:11, color:'var(--fg-muted)', background:'none', border:'none', cursor:'pointer', textDecoration:'underline' }}>
            {t.clearFilters}
          </button>
        )}
      </div>

      {/* v3.4: 차트 카드 — chartMode ON 시 표 위에 표시 */}
      {chartMode && (
        <BdChartView tree={tree} colKeys={colKeys} colDim={colDim} lang={lang} unit={unit} t={t}/>
      )}

      {/* Table — v3.3: 자체 스크롤 컨테이너로 만들어 thead sticky 작동.
          wideMode 토글 시 viewport 풀폭에 가까이 확장하되 양 옆 16px 여백 남김.
          이유: viewport 가장자리에 닿으면 macOS trackpad swipe navigation(뒤로가기)이
          트리거됨. 또 overscrollBehaviorX:contain으로 표 끝에서 페이지로 흐르는 것도 차단. */}
      <div style={{
        ...bdCardStyle, padding:0, position:'relative',
        overflow:'auto', maxHeight:'76vh',
        overscrollBehaviorX: 'contain',
        // 50vw 트릭 + 양 옆 16px 여백
        marginLeft:  wideMode ? 'calc(-50vw + 50% + 16px)' : 0,
        marginRight: wideMode ? 'calc(-50vw + 50% + 16px)' : 0,
        transition: 'margin 0.2s ease',
      }}>
        {computing && (
          <div style={{
            position:'absolute', top:0, left:0, right:0, padding:'6px 12px',
            fontSize:11, color:'var(--fg-muted)', background:'var(--surface-2)',
            borderBottom:'1px solid var(--border)', zIndex:10,
          }}>
            {lang === 'en' ? 'Computing…' : '계산 중…'}
          </div>
        )}
        <DrillTable tree={tree} groupBy={groupBy} colKeys={colKeys} expanded={expanded} onExpand={toggleExpand}
          sortKey={sortKey} sortAsc={sortAsc} onSort={handleSort} lang={lang} t={t} unit={unit}/>
      </div>
    </div>
  );
}

// --- Pivot helpers (v3.2) ------------------------------------------------
// 열 차원에서 행마다 colKey를 추출. 시계열(year/quarter/month)은 order_date 파싱.
function getColKey(row, dim) {
  if (dim === 'channel') return row.channel_group || '(blank)';  // alias
  if (dim === 'year')    return row.order_date ? row.order_date.slice(0, 4) : '(blank)';
  if (dim === 'month')   return row.order_date ? row.order_date.slice(0, 7) : '(blank)';
  if (dim === 'quarter') {
    const d = row.order_date;
    if (!d) return '(blank)';
    const y = d.slice(2, 4);  // 24, 25, 26
    const m = parseInt(d.slice(5, 7), 10);
    const q = Math.ceil(m / 3);
    return y + 'Q' + q;       // '24Q1' 식 — 알파벳 정렬이 곧 시간순
  }
  return row[dim] || '(blank)';  // universal_wing_type, make_group, sales_channel
}

// 시계열은 시간순(알파벳도 동일), 카테고리컬은 알파벳.
function sortColKeys(keys) {
  return [...keys].sort();
}

// 정렬값 추출 — sortKey 형식: 'label' | 'qty_total' | 'krw_total' | 'qty:<colKey>' | 'krw:<colKey>'
function getNodeSortValue(node, sortKey) {
  if (sortKey === 'label')      return node.label;
  if (sortKey === 'qty_total')  return node.agg.qty_total;
  if (sortKey === 'krw_total')  return node.agg.krw_total;
  if (sortKey.startsWith('qty:')) return node.agg.qty[sortKey.slice(4)] || 0;
  if (sortKey.startsWith('krw:')) return node.agg.krw[sortKey.slice(4)] || 0;
  return 0;
}

// --- Tree builder --------------------------------------------------------
// v3.2: 열 차원(colDim) 동적 — agg.qty/krw가 단일 키 합산이 아닌 colKey별 객체.
// `sortKey`/`sortAsc` are baked in here so the tree's children arrays are
// pre-sorted at every level. The render path then becomes a linear walk.
function buildTree(rows, groupDims, sortKey, sortAsc, colDim) {
  function aggregate(rowList) {
    const agg = { qty: {}, krw: {}, qty_total: 0, krw_total: 0 };
    for (const r of rowList) {
      const qty = r.qty || 0;
      const krw = r.sales_amount_krw || 0;
      const colKey = getColKey(r, colDim);
      agg.qty[colKey] = (agg.qty[colKey] || 0) + qty;
      agg.krw[colKey] = (agg.krw[colKey] || 0) + krw;
      agg.qty_total += qty;
      agg.krw_total += krw;
    }
    return agg;
  }

  function sortNodes(nodes) {
    if (!nodes) return nodes;
    nodes.sort((a, b) => {
      let av = getNodeSortValue(a, sortKey);
      let bv = getNodeSortValue(b, sortKey);
      if (typeof av === 'string') av = av.toLowerCase();
      if (typeof bv === 'string') bv = bv.toLowerCase();
      const cmp = av < bv ? -1 : av > bv ? 1 : 0;
      return sortAsc ? cmp : -cmp;
    });
    return nodes;
  }

  function recurse(rowList, dims, depth, prefix) {
    if (dims.length === 0) return null;
    const [dim, ...restDims] = dims;
    const grouped = new Map();
    for (const r of rowList) {
      const key = String(r[dim] || '(blank)');
      if (!grouped.has(key)) grouped.set(key, []);
      grouped.get(key).push(r);
    }
    const nodes = [];
    for (const [key, children] of grouped.entries()) {
      const nodeKey = prefix + '|' + key;
      const agg = aggregate(children);
      const sub = restDims.length > 0 ? recurse(children, restDims, depth + 1, nodeKey) : null;
      nodes.push({ key: nodeKey, label: key, dim, depth, agg, children: sub, rowCount: children.length });
    }
    return sortNodes(nodes);
  }

  return recurse(rows, groupDims, 0, 'root');
}

function flattenForExport(tree) {
  if (!tree) return [];
  const out = [];
  function walk(nodes) {
    for (const n of nodes) {
      out.push({ label: '  '.repeat(n.depth) + n.label, agg: n.agg });
      if (n.children) walk(n.children);
    }
  }
  walk(tree);
  return out;
}

// --- DrillTable ----------------------------------------------------------
// Wrapped in React.memo so it doesn't re-render when the parent re-renders
// for unrelated state (filter dropdowns, hover states, etc). Children inside
// `tree` are already sorted by buildTree, so renderRows is now just a walk.
const DrillTable = React.memo(function DrillTable({ tree, groupBy, colKeys, expanded, onExpand, sortKey, sortAsc, onSort, lang, t, unit }) {
  // tree===null means the deferred-compute hasn't finished yet (first render).
  // tree===[] means there genuinely are no rows. Distinguish so we don't flash
  // a "no rows" message during the initial 1-tick compute.
  if (tree == null) {
    return (
      <div style={{ padding:40, textAlign:'center', color:'var(--fg-muted)', fontSize:13 }}>
        {lang === 'en' ? 'Computing…' : '계산 중…'}
      </div>
    );
  }
  if (tree.length === 0) {
    return (
      <div style={{ padding:40, textAlign:'center', color:'var(--fg-muted)', fontSize:13 }}>
        {lang === 'en' ? 'No rows match the current filters.' : '필터 조건에 맞는 행이 없습니다.'}
      </div>
    );
  }

  const unitSuffix = unit === '1' ? '' : unit === '1e4' ? ' (만)' : unit === '1e8' ? ' (억)' : ' (십억)';
  // v3.2: colKeys 기반 동적 헤더. QTY 그룹 → Total QTY → KRW 그룹 → Total KRW 순서.
  const COL_HEADERS = [
    { key:'label', label: t.dimension, align:'left' },
    ...colKeys.map(k => ({ key:`qty:${k}`, label:`${k} QTY`,                align:'right' })),
    { key:'qty_total', label: (lang === 'en' ? 'Total QTY' : '합계 QTY'),    align:'right' },
    ...colKeys.map(k => ({ key:`krw:${k}`, label:`${k} KRW${unitSuffix}`,    align:'right' })),
    { key:'krw_total', label: (lang === 'en' ? 'Total KRW' : '합계 KRW') + unitSuffix, align:'right' },
  ];

  // Auto-render depth scales with how many group-by chips the user picked,
  // capped at 3 to keep the initial paint snappy. Beyond depth 3 (e.g. when
  // SKU is selected) the user opts in via the per-row chevron OR the
  // "전체 펼치기" button in the controls bar.
  const INITIAL_RENDER_DEPTH = Math.min(groupBy.length, 3);

  const renderRows = (nodes, depth = 0) => {
    const rows = [];
    if (!nodes) return rows;
    for (const node of nodes) {
      const hasChildren = node.children && node.children.length > 0;
      // v3.3: explicit true/false 구분. expanded[key]가 false면 사용자가 명시적으로
      // 접은 것이므로 autoOpen(첫 3-depth 자동 펼침) 무시.
      const explicitState = expanded[node.key];
      const isOpen = explicitState === true;
      const isExplicitlyCollapsed = explicitState === false;
      rows.push(
        <tr key={node.key} style={{ borderBottom:'1px solid var(--divider)' }}
          className="bd-row">
          <td style={{
            padding:'7px 12px', paddingLeft: 12 + depth * 20,
            // v3.3: 첫 컬럼 sticky — 가로 스크롤 시 좌측 고정
            position:'sticky', left:0, zIndex:1,
            background:'var(--bg-solid)',
            borderRight:'1px solid var(--divider)',
          }}>
            <div style={{ display:'flex', alignItems:'center', gap:6 }}>
              {hasChildren ? (
                <button onClick={() => onExpand(node.key)} style={{
                  background:'none', border:'none', cursor:'pointer', padding:'2px 4px',
                  color:'var(--fg-muted)', fontSize:11, lineHeight:1,
                }}>
                  {isOpen ? '▾' : '▸'}
                </button>
              ) : (
                <span style={{ width:20, display:'inline-block' }}/>
              )}
              <span style={{ fontSize:12.5, fontWeight: depth === 0 ? 600 : 400 }}>{node.label}</span>
              <span style={{ fontSize:10, color:'var(--fg-subtle)' }}>
                ({node.rowCount.toLocaleString()})
              </span>
            </div>
          </td>
          {/* v3.2: 동적 셀 — QTY 그룹 → Total QTY → KRW 그룹 → Total KRW */}
          {colKeys.map(k => (
            <td key={`qty:${k}`} style={{
              padding:'7px 12px', textAlign:'right', fontSize:12, color:'var(--fg-2)',
            }}>
              {fmtQty(node.agg.qty[k])}
            </td>
          ))}
          <td style={{
            padding:'7px 12px', textAlign:'right', fontSize:12,
            fontWeight:600, color:'var(--fg)',
          }}>{fmtQty(node.agg.qty_total)}</td>
          {colKeys.map(k => (
            <td key={`krw:${k}`} style={{
              padding:'7px 12px', textAlign:'right', fontSize:12, color:'var(--fg-2)',
            }}>
              {fmtKrwUnit(node.agg.krw[k], unit)}
            </td>
          ))}
          <td style={{
            padding:'7px 12px', textAlign:'right', fontSize:12,
            fontWeight:600, color:'var(--fg)',
          }}>{fmtKrwUnit(node.agg.krw_total, unit)}</td>
        </tr>
      );
      // Render children when:
      //   (a) user has explicitly expanded this node, OR
      //   (b) we're still within the initial auto-render depth AND
      //       user hasn't explicitly collapsed this node (v3.3 fix:
      //       collapseAll이 일부만 작동하던 버그 — autoOpen이 explicit false를 덮어쓰던 문제).
      const autoOpen = !isExplicitlyCollapsed && depth + 1 < INITIAL_RENDER_DEPTH;
      if (hasChildren && (isOpen || autoOpen)) {
        rows.push(...renderRows(node.children, depth + 1));
      }
    }
    return rows;
  };

  return (
    <table style={{ borderCollapse:'collapse', minWidth:'100%' }}>
      <thead>
        <tr>
          {COL_HEADERS.map(h => {
            const isLabel = h.key === 'label';
            return (
              <th key={h.key} onClick={() => onSort(h.key === 'label' ? 'label' : h.key)}
                style={{
                  padding:'8px 12px', fontSize:10.5, fontWeight:700, color:'var(--fg-muted)',
                  textTransform:'uppercase', letterSpacing:.05,
                  textAlign: h.align, cursor:'pointer', userSelect:'none',
                  whiteSpace:'nowrap',
                  // v3.3: sticky 헤더 — 세로 스크롤 시 위에 고정
                  position:'sticky', top:0,
                  background:'var(--surface-2)',
                  borderBottom:'2px solid var(--border)',
                  // 첫 컬럼(구분)은 가로 스크롤 시 좌측 고정 + 좌상단 코너는 z-index 가장 높게
                  ...(isLabel
                    ? { left:0, zIndex:3, minWidth:240, borderRight:'1px solid var(--divider)' }
                    : { zIndex:2, minWidth:110 }),
                }}>
                {h.label}{' '}
                {sortKey === h.key ? (sortAsc ? '↑' : '↓') : ''}
              </th>
            );
          })}
        </tr>
      </thead>
      <tbody>{renderRows(tree)}</tbody>
    </table>
  );
});

// --- Multi-chip filter component ------------------------------------------
// Adds a search input when the option list exceeds SEARCH_THRESHOLD items
// (Models / Countries are unusable as raw scrolling lists). Selected items
// always render at the top so the user can see / unselect them even while
// the search filter would otherwise hide them.
function MultiChip({ label, values, selected, onChange }) {
  const [open, setOpen] = React.useState(false);
  const [search, setSearch] = React.useState('');
  const wrapperRef = React.useRef(null);
  const hasSelected = selected.length > 0;
  const SEARCH_THRESHOLD = 12;
  const showSearch = values.length > SEARCH_THRESHOLD;

  const selectedSet = React.useMemo(() => new Set(selected), [selected]);
  const q = search.trim().toLowerCase();
  const filteredUnselected = React.useMemo(() => {
    return values.filter(v => !selectedSet.has(v) && (!q || String(v).toLowerCase().includes(q)));
  }, [values, selectedSet, q]);

  // Outside-click closes the dropdown. Listener is only attached while open
  // so multiple MultiChip instances don't all run handlers simultaneously,
  // and clicking a sibling chip's button closes this one (its mousedown
  // fires outside of our wrapper before its own setOpen runs).
  React.useEffect(() => {
    if (!open) return;
    const onDocMouseDown = (e) => {
      if (wrapperRef.current && !wrapperRef.current.contains(e.target)) {
        setOpen(false);
      }
    };
    document.addEventListener('mousedown', onDocMouseDown);
    return () => document.removeEventListener('mousedown', onDocMouseDown);
  }, [open]);

  return (
    <div ref={wrapperRef} style={{ position:'relative' }}>
      <button onClick={() => setOpen(o => !o)} style={{
        padding:'4px 10px', fontSize:11.5, fontWeight:500, borderRadius:6, cursor:'pointer',
        background: hasSelected ? 'var(--accent-soft)' : 'var(--surface-2)',
        color:      hasSelected ? 'var(--accent)'      : 'var(--fg-2)',
        border:     `1px solid ${hasSelected ? 'var(--accent)' : 'var(--border)'}`,
      }}>
        {label}{hasSelected ? ` (${selected.length})` : ''} ▾
      </button>
      {open && (
        <div style={{
          position:'absolute', top:'100%', left:0, zIndex:200,
          background:'var(--bg-solid)', border:'1px solid var(--border)',
          borderRadius:8, padding:6, marginTop:2, minWidth:180,
          boxShadow:'var(--shadow-3)', maxHeight:280, overflowY:'auto',
        }}>
          {showSearch && (
            <input
              type="text"
              value={search}
              onChange={e => setSearch(e.target.value)}
              placeholder="검색…"
              autoFocus
              style={{
                width:'100%', boxSizing:'border-box', padding:'5px 8px', fontSize:11.5,
                background:'var(--surface-2)', border:'1px solid var(--border)',
                borderRadius:6, color:'var(--fg)', marginBottom:6, outline:'none',
              }}
            />
          )}
          {selected.length > 0 && (
            <div style={{ borderBottom: filteredUnselected.length ? '1px solid var(--divider)' : 'none', paddingBottom:4, marginBottom:4 }}>
              {selected.map(v => (
                <label key={v} style={{ display:'flex', alignItems:'center', gap:6, padding:'4px 8px', cursor:'pointer', borderRadius:4, fontSize:12 }}
                  className="chip-opt">
                  <input type="checkbox" checked readOnly onClick={() => onChange(selected.filter(x => x !== v))}/>
                  {v}
                </label>
              ))}
            </div>
          )}
          {filteredUnselected.map(v => (
            <label key={v} style={{ display:'flex', alignItems:'center', gap:6, padding:'4px 8px', cursor:'pointer', borderRadius:4, fontSize:12 }}
              className="chip-opt">
              <input type="checkbox" checked={false} onChange={() => onChange([...selected, v])}/>
              {v}
            </label>
          ))}
          {showSearch && q && filteredUnselected.length === 0 && (
            <div style={{ padding:'6px 8px', fontSize:11, color:'var(--fg-muted)' }}>
              No matches
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// --- Misc helpers --------------------------------------------------------
function BDGroup({ label, children }) {
  return (
    <div style={{ display:'flex', flexDirection:'column', gap:4 }}>
      <div style={{ fontSize:10, fontWeight:600, color:'var(--fg-muted)', textTransform:'uppercase', letterSpacing:.06 }}>{label}</div>
      <div style={{ display:'flex', gap:4, flexWrap:'wrap', alignItems:'center' }}>{children}</div>
    </div>
  );
}
function BDBtn({ active, onClick, children, title }) {
  return (
    <button onClick={onClick} title={title} style={{
      padding:'5px 10px', fontSize:12, fontWeight:500,
      background: active ? 'var(--accent-soft)' : 'var(--surface-2)',
      color:      active ? 'var(--accent)'      : 'var(--fg-2)',
      border:     `1px solid ${active ? 'var(--accent)' : 'var(--border)'}`,
      borderRadius:6, cursor:'pointer',
    }}>{children}</button>
  );
}
function BDPlaceholder({ msg }) {
  return <div style={{ padding:40, textAlign:'center', color:'var(--fg-muted)', fontSize:14 }}>{msg}</div>;
}

function fmtQty(n) {
  if (!n) return '—';
  return n.toLocaleString();
}
function fmtKrw(n) {
  if (!n) return '—';
  if (Math.abs(n) >= 1e8) return (n / 1e8).toFixed(1).replace(/\.0$/, '') + '억';
  if (Math.abs(n) >= 1e4) return (n / 1e4).toFixed(0) + '만';
  return n.toLocaleString();
}
// Format with explicit unit divisor; suffix is in the column header.
function fmtKrwUnit(n, unit) {
  if (!n) return '—';
  const div = parseFloat(unit) || 1;
  if (div === 1) return Math.round(n).toLocaleString();
  // For 만 keep integers; for 억/십억 show 1 decimal (trim trailing .0)
  if (div === 1e4) return Math.round(n / div).toLocaleString();
  const v = (n / div).toFixed(1).replace(/\.0$/, '');
  return v;
}

const bdCardStyle = {
  background:'var(--bg-solid)', border:'1px solid var(--border)',
  borderRadius:12, padding:14, boxShadow:'var(--shadow-1)',
};
const exportBtnStyle = {
  padding:'6px 12px', fontSize:12, fontWeight:500,
  background:'var(--accent-soft)', color:'var(--accent)',
  border:'1px solid var(--accent)', borderRadius:8, cursor:'pointer',
};
// Mini button inside selected-group chip (◀ ▶ ✕)
const chipBtnStyle = {
  fontSize:10, padding:'3px 5px', cursor:'pointer',
  background:'transparent', border:'none', borderLeft:'1px solid rgba(0,0,0,0.08)',
  color:'inherit', lineHeight:1,
};
const bdDateInputStyle = {
  padding:'4px 8px', fontSize:11.5, background:'var(--surface-2)',
  border:'1px solid var(--border)', borderRadius:6, color:'var(--fg)',
};

const BT_KO = {
  title:'상세 분석', loading:'불러오는 중…',
  period:'기간', all:'전체', custom:'직접 설정',
  mtd:'이번 달', mtdTitle:'이번 달 1일부터 오늘까지',
  groupBy:'그룹핑', filter:'필터', country:'국가', region:'지역', clearFilters:'필터 초기화',
  dimension:'구분', exportCsv:'CSV 내보내기',
  rows:'{n}개 행',
  unit:'단위', unitWon:'원', unitMan:'만원', unitEok:'억원', unitBil:'십억원',
};
const BT_EN = {
  title:'Breakdown', loading:'Loading…',
  period:'Period', all:'All', custom:'Custom',
  mtd:'MTD', mtdTitle:'Month-to-date (1st → today)',
  groupBy:'Group by', filter:'Filter', country:'Country', region:'Region', clearFilters:'Clear filters',
  dimension:'Dimension', exportCsv:'Export CSV',
  rows:'{n} rows',
  unit:'Unit', unitWon:'KRW', unitMan:'10K', unitEok:'100M', unitBil:'1B',
};

// v3.4: 차트 뷰. tree의 root 노드(top-level 그룹)를 시각화.
// 시계열 colDim (year/quarter/month) → 라인 차트 (그룹별 series)
// 카테고리 colDim (channel/make_group/sales_channel/universal_wing_type) → 그룹 막대 차트
// ⚠️ 함수명 Bd* prefix 필수 — view-overview.jsx의 ChartCard와 글로벌 충돌 방지.
function BdChartView({ tree, colKeys, colDim, lang, unit, t }) {
  const canvasRef = React.useRef(null);
  const chartRef  = React.useRef(null);
  const isTimeSeries = ['year','quarter','month'].includes(colDim);

  // 차트별 매출액(krw)/수량(qty) 토글 — 세션 한정(새로고침 시 krw로 리셋).
  // BdChartView에는 차트가 1개(line/bar 모드 전환)뿐이므로 키 1개.
  const [chartMetric, setChartMetric] = React.useState({ main:'krw' });
  const setMetric = (key, val) => setChartMetric(prev => ({ ...prev, [key]: val }));

  // main 차트 useEffect — 자기 metric 키(chartMetric.main)만 deps에 포함.
  React.useEffect(() => {
    if (typeof Chart === 'undefined') return;
    if (chartRef.current) { chartRef.current.destroy(); chartRef.current = null; }
    if (!canvasRef.current || !tree || tree.length === 0 || colKeys.length === 0) return;

    const metric = chartMetric.main;
    const isQty  = metric === 'qty';
    const TOP_N = 8;
    const topNodes = tree.slice(0, TOP_N);
    const others   = tree.slice(TOP_N);
    const div = parseFloat(unit) || 1;
    const palette = ['#007AFF','#34C759','#FF9500','#FF3B30','#AF52DE','#5AC8FA','#FFCC00','#FF2D55'];
    // 수량 모드는 원자료 카운트 그대로, 매출액 모드만 unit 배율 적용.
    const valDiv = isQty ? 1 : div;
    const unitSuffix = isQty ? '' : (unit === '1e8' ? '억' : unit === '1e9' ? '십억' : unit === '1e4' ? '만' : '');
    // 버킷에서 metric별 값 추출 — krw면 unit 배율, qty면 raw count.
    const cell = (agg, k) => isQty ? (agg.qty[k] || 0) : ((agg.krw[k] || 0) / valDiv);

    const commonOptions = {
      responsive: true,
      maintainAspectRatio: false,
      animation: { duration: 200 },
      plugins: {
        legend: { position:'top', labels:{ boxWidth:10, font:{ size:10 } } },
        tooltip: {
          callbacks: {
            label: (ctx) => `${ctx.dataset.label}: ${Number(ctx.parsed.y).toLocaleString()}${unitSuffix}`,
          },
        },
      },
      scales: {
        y: {
          beginAtZero:true,
          ticks:{ font:{ size:10 }, callback: v => Number(v).toLocaleString() + unitSuffix },
        },
        x: { ticks:{ font:{ size:10 } } },
      },
    };

    if (isTimeSeries) {
      const datasets = topNodes.map((n, i) => ({
        label: n.label,
        data: colKeys.map(k => cell(n.agg, k)),
        borderColor: palette[i % palette.length],
        backgroundColor: palette[i % palette.length] + '20',
        tension: 0.3,
        fill: false,
        pointRadius: 2,
      }));
      if (others.length > 0) {
        const otherData = colKeys.map(k => others.reduce((s, n) => s + cell(n.agg, k), 0));
        datasets.push({
          label: lang === 'en' ? 'Other' : '기타',
          data: otherData, borderColor:'#888', borderDash:[4,4], fill:false, pointRadius:1,
        });
      }
      chartRef.current = new Chart(canvasRef.current, {
        type:'line',
        data: { labels: colKeys, datasets },
        options: commonOptions,
      });
    } else {
      // 카테고리형 colDim — 그룹 막대 차트
      // colKeys가 너무 많으면 (예: country 12개) 처음 6개만 + "기타"로 묶음
      const COL_LIMIT = 6;
      const visibleCols = colKeys.length > COL_LIMIT ? colKeys.slice(0, COL_LIMIT) : colKeys;
      const labels = topNodes.map(n => n.label);
      const datasets = visibleCols.map((k, i) => ({
        label: k,
        data: topNodes.map(n => cell(n.agg, k)),
        backgroundColor: palette[i % palette.length],
        borderRadius: 3,
      }));
      if (colKeys.length > COL_LIMIT) {
        const restCols = colKeys.slice(COL_LIMIT);
        datasets.push({
          label: lang === 'en' ? 'Other' : '기타',
          data: topNodes.map(n => restCols.reduce((s, k) => s + cell(n.agg, k), 0)),
          backgroundColor: '#888',
          borderRadius: 3,
        });
      }
      chartRef.current = new Chart(canvasRef.current, {
        type:'bar',
        data: { labels, datasets },
        options: commonOptions,
      });
    }
    return () => { if (chartRef.current) { chartRef.current.destroy(); chartRef.current = null; } };
  }, [tree, colKeys, colDim, unit, lang, isTimeSeries, chartMetric.main]);

  // 데이터 없음 / 그룹 미선택 안내
  if (!tree || tree.length === 0 || colKeys.length === 0) {
    return (
      <div style={{ ...bdCardStyle, marginBottom:14, padding:'24px 14px', textAlign:'center', color:'var(--fg-muted)', fontSize:12 }}>
        {lang === 'en' ? 'Select a group dimension to see the chart.' : '그룹을 선택하면 차트가 표시됩니다.'}
      </div>
    );
  }

  return (
    <div style={{ ...bdCardStyle, marginBottom:14 }}>
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:8 }}>
        <div style={{ fontSize:11, fontWeight:700, color:'var(--fg-muted)', textTransform:'uppercase', letterSpacing:.06 }}>
          {lang === 'en' ? `Top ${Math.min(8, tree.length)} ${isTimeSeries ? 'over time' : 'by ' + colDim}` : `상위 ${Math.min(8, tree.length)}개 ${isTimeSeries ? '시계열' : (colDim + '별')}`}
        </div>
        <MetricToggle value={chartMetric.main} onChange={v => setMetric('main', v)} lang={lang}/>
      </div>
      <div style={{ height:280 }}>
        <canvas ref={canvasRef}/>
      </div>
    </div>
  );
}

window.BreakdownView = BreakdownView;
