/* token-io.jsx — Token import / export.
   • Export Pixso / DTCG-style JSON (nested by token path, with a $extensions
     block that round-trips our exact source/layer/status).
   • Export Excel (.xlsx via SheetJS when available, CSV fallback otherwise).
   • Import Pixso / DTCG / Tokens-Studio JSON → merge into the token set. */
const { useState: useStateIO } = React;

const IO_KIND_TYPE = { color: 'color', spacing: 'dimension', radius: 'dimension', type: 'dimension', shadow: 'shadow' };
const IO_CAT_OF_KIND = { color: 'Color', spacing: 'Spacing', radius: 'Radius', type: 'Typography', shadow: 'Shadow' };

function ioDtcgType(kind) { return IO_KIND_TYPE[kind] || 'other'; }
function ioDtcgValue(tk, ctx) {
  const s = tk.source;
  if (s.type === 'ref') return '{' + s.ref + '}';
  if (s.type === 'modeRef') return '{' + s.light + '}';
  if (s.type === 'value') return s.value;
  const r = resolve(tk.id, ctx); // themeAccent → concrete
  return r.value;
}

/* ---------- EXPORT: JSON ---------- */
function ioBuildTree(tokens, ctx) {
  const root = {};
  tokens.forEach(tk => {
    const parts = tk.id.split('.');
    let node = root;
    for (let i = 0; i < parts.length - 1; i++) { node[parts[i]] = node[parts[i]] || {}; node = node[parts[i]]; }
    const leaf = { $type: ioDtcgType(tk.kind), $value: ioDtcgValue(tk, ctx) };
    if (tk.description) leaf.$description = tk.description;
    leaf.$extensions = { 'design.sicarrier': { layer: tk.layer, kind: tk.kind, category: tk.category, status: tk.status, source: tk.source, tags: tk.tags || [] } };
    node[parts[parts.length - 1]] = leaf;
  });
  return root;
}
function ioExportJSON(tokens, ctx) {
  const tree = ioBuildTree(tokens, ctx);
  ioDownload('sicarrier-tokens.json', JSON.stringify(tree, null, 2), 'application/json;charset=utf-8');
}

/* ---------- EXPORT: rows for Excel / CSV ---------- */
function ioRows(tokens) {
  const th = THEMES[0];
  return tokens.map(tk => {
    const light = resolve(tk.id, { theme: th, mode: 'light' });
    const dark = resolve(tk.id, { theme: th, mode: 'dark' });
    const s = tk.source || {};
    return {
      ID: tk.id, Layer: tk.layer, Category: tk.category, Kind: tk.kind,
      'Source type': s.type || '',
      Reference: s.type === 'ref' ? s.ref
        : s.type === 'modeRef' ? `light:${s.light} / dark:${s.dark}`
          : s.type === 'themeAccent' ? `themeAccent.${s.shade}` : '',
      'Raw value': s.type === 'value' ? String(s.value) : '',
      'Resolved (light)': light.value != null ? String(light.value) : '',
      'Resolved (dark)': dark.value != null ? String(dark.value) : '',
      Description: tk.description || '',
      Status: tk.status || '',
      Tags: (tk.tags || []).join(' '),
    };
  });
}
function ioExportExcel(tokens) {
  const rows = ioRows(tokens);
  if (typeof XLSX !== 'undefined') {
    const ws = XLSX.utils.json_to_sheet(rows);
    ws['!cols'] = [{ wch: 22 }, { wch: 11 }, { wch: 12 }, { wch: 9 }, { wch: 11 }, { wch: 26 }, { wch: 12 }, { wch: 16 }, { wch: 16 }, { wch: 40 }, { wch: 11 }, { wch: 20 }];
    const wb = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(wb, ws, 'Tokens');
    XLSX.writeFile(wb, 'sicarrier-tokens.xlsx');
    return 'xlsx';
  }
  // CSV fallback
  const cols = Object.keys(rows[0] || { ID: '' });
  const esc = v => { v = String(v == null ? '' : v); return /[",\n]/.test(v) ? '"' + v.replace(/"/g, '""') + '"' : v; };
  const csv = [cols.join(',')].concat(rows.map(r => cols.map(c => esc(r[c])).join(','))).join('\n');
  ioDownload('sicarrier-tokens.csv', '﻿' + csv, 'text/csv;charset=utf-8');
  return 'csv';
}

function ioDownload(name, content, mime) {
  const blob = new Blob([content], { type: mime });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url; a.download = name; document.body.appendChild(a); a.click();
  setTimeout(() => { URL.revokeObjectURL(url); a.remove(); }, 0);
}

/* ---------- IMPORT: parse nested JSON → token objects ---------- */
function ioKindFromType(t, ext) {
  if (ext && ext.kind) return ext.kind;
  switch (t) {
    case 'color': return 'color';
    case 'shadow': case 'boxShadow': return 'shadow';
    case 'borderRadius': return 'radius';
    case 'fontSizes': case 'fontSize': return 'type';
    case 'dimension': case 'spacing': case 'sizing': return 'spacing';
    default: return 'color';
  }
}
function ioLeafToToken(path, node, errors) {
  const id = path.join('.');
  const ext = node.$extensions && (node.$extensions['design.sicarrier'] || node.$extensions.sicarrier);
  const rawVal = '$value' in node ? node.$value : node.value;
  let source;
  if (ext && ext.source) {
    source = ext.source;
  } else if (typeof rawVal === 'string' && rawVal.trim().startsWith('{') && rawVal.trim().endsWith('}')) {
    source = { type: 'ref', ref: rawVal.trim().slice(1, -1) };
  } else {
    source = { type: 'value', value: rawVal };
  }
  const typeStr = node.$type || node.type;
  const kind = (ext && ext.kind) || ioKindFromType(typeStr, ext);
  const category = (ext && ext.category) || IO_CAT_OF_KIND[kind] || 'Color';
  const layer = (ext && ext.layer) || (source.type === 'value' ? 'primitive' : 'semantic');
  return {
    id, layer, category, kind, source,
    description: node.$description || node.description || '',
    tags: (ext && ext.tags) || Array.from(new Set([id.split('.')[0], category.toLowerCase()])),
    status: (ext && ext.status) || 'new',
  };
}
function ioParseJSON(text) {
  let data;
  try { data = JSON.parse(text); } catch (e) { return { tokens: [], errors: ['Invalid JSON: ' + e.message] }; }
  const tokens = [], errors = [];
  (function walk(node, path) {
    if (node == null || typeof node !== 'object' || Array.isArray(node)) return;
    const isLeaf = ('$value' in node) || (('value' in node) && (typeof node.value !== 'object' || node.value === null));
    if (isLeaf) {
      try { tokens.push(ioLeafToToken(path, node, errors)); }
      catch (e) { errors.push('Skipped ' + path.join('.') + ': ' + e.message); }
      return;
    }
    for (const k of Object.keys(node)) { if (k[0] === '$') continue; walk(node[k], path.concat(k)); }
  })(data, []);
  if (!tokens.length && !errors.length) errors.push('No tokens found in file.');
  return { tokens, errors };
}

/* ---------- IMPORT: parse Excel/CSV rows (the ioRows shape) → token objects ---------- */
function ioRowToToken(r) {
  const id = (r.ID || r.id || '').toString().trim();
  if (!id) return null;
  const kind = (r.Kind || r.kind || 'color').toString().toLowerCase();
  const category = r.Category || IO_CAT_OF_KIND[kind] || 'Color';
  const st = (r['Source type'] || r['Source Type'] || '').toString().toLowerCase();
  const ref = (r.Reference || '').toString();
  let source;
  if (st === 'ref') source = { type: 'ref', ref: ref.trim() };
  else if (st === 'moderef') { const m = ref.match(/light:\s*([^/]+?)\s*\/\s*dark:\s*(.+)/i); source = m ? { type: 'modeRef', light: m[1].trim(), dark: m[2].trim() } : { type: 'value', value: r['Raw value'] }; }
  else if (st === 'themeaccent') { const m = ref.match(/themeAccent\.(\w+)/); source = { type: 'themeAccent', shade: m ? m[1] : '500' }; }
  else { let v = r['Raw value']; if (v != null && v !== '' && kind !== 'color' && !isNaN(Number(v))) v = Number(v); source = { type: 'value', value: v }; }
  const layer = r.Layer || (source.type === 'value' ? 'primitive' : 'semantic');
  return { id, layer, category, kind, source,
    description: r.Description || '',
    tags: String(r.Tags || '').split(/\s+/).filter(Boolean),
    status: r.Status || 'new' };
}
function ioParseExcel(arrayBuffer) {
  if (typeof XLSX === 'undefined') return { tokens: [], errors: ['Excel parser (SheetJS) is not loaded.'] };
  let wb;
  try { wb = XLSX.read(arrayBuffer, { type: 'array' }); } catch (e) { return { tokens: [], errors: ['Invalid spreadsheet: ' + e.message] }; }
  const ws = wb.Sheets[wb.SheetNames[0]];
  if (!ws) return { tokens: [], errors: ['No sheet found in the workbook.'] };
  const rows = XLSX.utils.sheet_to_json(ws, { defval: '' });
  const tokens = [], errors = [];
  rows.forEach((r, i) => { try { const t = ioRowToToken(r); if (t) tokens.push(t); else errors.push('Row ' + (i + 2) + ': missing ID — skipped.'); } catch (e) { errors.push('Row ' + (i + 2) + ': ' + e.message); } });
  if (!tokens.length && !errors.length) errors.push('No token rows found in the spreadsheet.');
  return { tokens, errors };
}

/* ---------- UI: import / export control ---------- */
function ImportExportMenu({ tokens, ctx, onImport }) {
  const [open, setOpen] = useStateIO(false);
  const [impOpen, setImpOpen] = useStateIO(false);
  const [toast, setToast] = useStateIO(null); // { kind:'ok'|'err', msg }
  const jsonRef = React.useRef(null);
  const xlsxRef = React.useRef(null);

  const flash = (kind, msg) => { setToast({ kind, msg }); setTimeout(() => setToast(null), 4200); };

  const doExportJSON = () => { setOpen(false); ioExportJSON(tokens, ctx); flash('ok', tr('Exported tokens as Pixso JSON.', '已导出 Pixso JSON 文件。')); };
  const doExportExcel = () => {
    setOpen(false);
    const fmt = ioExportExcel(tokens);
    flash('ok', fmt === 'xlsx' ? tr('Exported tokens as Excel (.xlsx).', '已导出 Excel（.xlsx）文件。')
      : tr('SheetJS unavailable — exported CSV instead.', '未加载 SheetJS，已改为导出 CSV 文件。'));
  };
  const finishImport = (parsed, errors) => {
    if (!parsed.length) { flash('err', (errors[0] || tr('Import failed.', '导入失败。'))); return; }
    const res = onImport(parsed);
    const n = parsed.length;
    flash('ok', tr(`Imported ${n} token${n === 1 ? '' : 's'} · ${res.added} new, ${res.updated} updated.`,
      `已导入 ${n} 个 token · 新增 ${res.added}，更新 ${res.updated}。`));
  };
  const onPickJSON = (e) => {
    const f = e.target.files && e.target.files[0]; e.target.value = ''; if (!f) return;
    const reader = new FileReader();
    reader.onload = () => { const { tokens: parsed, errors } = ioParseJSON(String(reader.result)); finishImport(parsed, errors); };
    reader.readAsText(f);
  };
  const onPickXLSX = (e) => {
    const f = e.target.files && e.target.files[0]; e.target.value = ''; if (!f) return;
    const reader = new FileReader();
    reader.onload = () => { const { tokens: parsed, errors } = ioParseExcel(reader.result); finishImport(parsed, errors); };
    reader.readAsArrayBuffer(f);
  };

  return (
    <>
      <input ref={jsonRef} type="file" accept=".json,application/json" className="hidden" onChange={onPickJSON} />
      <input ref={xlsxRef} type="file" accept=".xlsx,.xls,.csv" className="hidden" onChange={onPickXLSX} />
      <div className="relative">
        <Button variant="secondary" icon="upload" iconRight="chevronD" disabled={!canEdit()} onClick={() => setImpOpen(o => !o)}>
          {tr('Import', '导入')}
        </Button>
        {impOpen && (
          <>
            <div className="fixed inset-0 z-10" onClick={() => setImpOpen(false)} />
            <div className="absolute left-0 top-10 z-20 w-56 bg-panel border border-line rounded-[12px] py-1.5 shadow-[0_8px_28px_rgba(0,0,0,0.16)]" style={{ animation: 'fadeIn 120ms ease' }}>
              <div className="px-3 py-1 text-[10.5px] uppercase tracking-wide text-ink3">{tr('Import format', '导入格式')}</div>
              <button onClick={() => { setImpOpen(false); jsonRef.current && jsonRef.current.click(); }} className="w-full flex items-center gap-2.5 px-3 h-9 text-[13px] text-ink hover:bg-hover transition">
                <Icon name="hash" size={15} className="text-ink2" />
                <span className="flex-1 text-left">{tr('Pixso JSON', 'Pixso JSON')}</span>
                <span className="text-[11px] text-ink3 font-mono">.json</span>
              </button>
              <button onClick={() => { setImpOpen(false); xlsxRef.current && xlsxRef.current.click(); }} className="w-full flex items-center gap-2.5 px-3 h-9 text-[13px] text-ink hover:bg-hover transition">
                <Icon name="table" size={15} className="text-ink2" />
                <span className="flex-1 text-left">{tr('Excel spreadsheet', 'Excel 表格')}</span>
                <span className="text-[11px] text-ink3 font-mono">.xlsx</span>
              </button>
            </div>
          </>
        )}
      </div>
      <div className="relative">
        <Button variant="secondary" icon="download" iconRight="chevronD" onClick={() => setOpen(o => !o)}>
          {tr('Export', '导出')}
        </Button>
        {open && (
          <>
            <div className="fixed inset-0 z-10" onClick={() => setOpen(false)} />
            <div className="absolute right-0 top-10 z-20 w-56 bg-panel border border-line rounded-[12px] py-1.5 shadow-[0_8px_28px_rgba(0,0,0,0.16)]" style={{ animation: 'fadeIn 120ms ease' }}>
              <div className="px-3 py-1 text-[10.5px] uppercase tracking-wide text-ink3">{tr('Export format', '导出格式')}</div>
              <button onClick={doExportJSON} className="w-full flex items-center gap-2.5 px-3 h-9 text-[13px] text-ink hover:bg-hover transition">
                <Icon name="hash" size={15} className="text-ink2" />
                <span className="flex-1 text-left">{tr('Pixso JSON', 'Pixso JSON')}</span>
                <span className="text-[11px] text-ink3 font-mono">.json</span>
              </button>
              <button onClick={doExportExcel} className="w-full flex items-center gap-2.5 px-3 h-9 text-[13px] text-ink hover:bg-hover transition">
                <Icon name="table" size={15} className="text-ink2" />
                <span className="flex-1 text-left">{tr('Excel spreadsheet', 'Excel 表格')}</span>
                <span className="text-[11px] text-ink3 font-mono">.xlsx</span>
              </button>
            </div>
          </>
        )}
      </div>
      {toast && (
        <div className={cx('fixed bottom-5 left-1/2 -translate-x-1/2 z-50 flex items-center gap-2 h-10 px-4 rounded-[12px] text-[13px] shadow-[0_8px_28px_rgba(0,0,0,0.20)] border',
          toast.kind === 'ok' ? 'bg-panel border-line text-ink' : 'bg-panel border-[color:var(--danger)]/40 text-danger')}
          style={{ animation: 'fadeIn 140ms ease' }}>
          <Icon name={toast.kind === 'ok' ? 'check' : 'warningTri'} size={15} className={toast.kind === 'ok' ? 'text-success' : 'text-danger'} />
          {toast.msg}
        </div>
      )}
    </>
  );
}

Object.assign(window, { ImportExportMenu, ioParseJSON, ioParseExcel, ioExportJSON, ioExportExcel, ioRows });
