// data-import.jsx — Excel/CSV 上傳 + AI 解析工作台
// Loaded after SheetJS (XLSX) and React/Babel.

const DASHBOARD_TARGETS = [
  { key: 'daily_revenue', label: '💰 每日營業額', fields: ['日期', '客戶', '品項', '金額', '單號'] },
  { key: 'drivers', label: '🚛 業務司機績效', fields: ['司機', '車牌', '送貨金額', '單數', '客戶數'] },
  { key: 'sales', label: '📈 業務開發業績', fields: ['業務', '客戶', '類型', '金額', '日期'] },
  { key: 'unpaid', label: '💵 未收款客戶', fields: ['客戶', '對帳月', '金額', '帳齡', '狀態'] },
  { key: 'reconciliation', label: '📒 對帳單寄送', fields: ['客戶', '寄送日', '金額', '狀態'] },
  { key: 'purchase', label: '📦 採購預計到貨', fields: ['到貨日', '廠商', '品項', '金額', '狀態'] },
  { key: 'packaging', label: '✅ 包裝部門進度', fields: ['商品', '數量', '完成時間', '排程'] },
  { key: 'shopee', label: '🛒 蝦皮出貨', fields: ['商品', '件數', '日期', '調貨來源'] },
  { key: 'stock_alert', label: '🚨 庫存警示', fields: ['品項', '現有量', '安全水位', '下次到貨'] },
  { key: 'price_changes', label: '📊 漲跌價', fields: ['品項', '原價', '新價', '變動'] },
  { key: 'customer_needs', label: '📌 客戶特殊需求', fields: ['客戶', '需求', '頻率', '負責司機'] },
  { key: 'complaints', label: '📋 客訴與退貨', fields: ['日期', '客戶', '品項', '原因', '處理', '狀態'] },
  { key: 'eggs', label: '🐔 養雞場進蛋', fields: ['日期', '雞場', '盒數', '負責司機'] },
  { key: 'attendance', label: '👥 員工出勤', fields: ['姓名', '部門', '狀態', '時段'] },
  { key: 'announcements', label: '📢 公司公告', fields: ['標題', '等級', '內容', '日期'] },
];

// ── AI 提供商選擇器（内嵌小元件）────────────────────────────────────────────
function AIProviderPicker({ onChanged }) {
  const claude = window.claude;
  const [currentId, setCurrentId] = React.useState(claude?.getProvider ? claude.getProvider() : '');
  const [open, setOpen] = React.useState(false);
  const wrapRef = React.useRef(null);

  // 點外側關閉
  React.useEffect(() => {
    const handler = (e) => { if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', handler);
    return () => document.removeEventListener('mousedown', handler);
  }, []);

  if (!claude || !claude.providers || !claude.providers.length) return null;

  const providers = claude.providers;
  const current = providers.find(p => p.id === currentId);

  async function select(id) {
    setOpen(false);
    if (claude.setProvider) {
      await claude.setProvider(id);
      const newId = claude.getProvider();
      setCurrentId(newId);
      onChanged && onChanged(newId);
    }
  }

  return (
    <div ref={wrapRef} style={{ position: 'relative', marginBottom: 10 }}>
      {/* 目前選擇列 */}
      <div style={{
        display: 'flex', alignItems: 'center', gap: 8,
        background: '#F5F0E8', borderRadius: 9, padding: '7px 12px',
        fontSize: 12.5, cursor: 'default',
      }}>
        <span style={{ color: '#9a7a5a', fontSize: 11.5 }}>🤖 AI 模型</span>
        <span style={{ fontWeight: 700, color: '#261810' }}>
          {current ? current.name : '未設定'}
        </span>
        {current && (
          <span style={{
            fontSize: 10, fontWeight: 700, color: '#fff',
            background: current.badgeColor, padding: '2px 7px', borderRadius: 4,
          }}>{current.badge}</span>
        )}
        {current && (
          <span style={{ fontSize: 10.5, color: '#9a7a5a' }}>{current.sub}</span>
        )}
        <span style={{ flex: 1 }} />
        <button
          onClick={() => setOpen(v => !v)}
          style={{
            border: '1.5px solid #D97A3B', borderRadius: 6,
            background: open ? '#D97A3B' : 'transparent',
            color: open ? '#fff' : '#D97A3B',
            padding: '3px 11px', fontSize: 11.5, fontWeight: 700,
            cursor: 'pointer', transition: 'all 120ms',
          }}
        >切換 {open ? '▴' : '▾'}</button>
      </div>

      {/* 下拉清單 */}
      {open && (
        <div style={{
          position: 'absolute', top: 'calc(100% + 4px)', right: 0, zIndex: 9999,
          background: '#fff', borderRadius: 12, padding: 6,
          boxShadow: '0 8px 28px rgba(0,0,0,0.18)',
          minWidth: 260, border: '1px solid #ede8df',
        }}>
          {providers.map(p => {
            const isActive = p.id === currentId;
            // 判斷此提供商是否已有儲存的 Key
            const savedKey = localStorage.getItem('mile_ai_key_' + p.id) || '';
            const hasKey   = p.id === 'ollama' || !!savedKey;
            return (
              <div
                key={p.id}
                onClick={() => select(p.id)}
                style={{
                  display: 'flex', alignItems: 'center', gap: 9,
                  padding: '9px 11px', borderRadius: 8, cursor: 'pointer',
                  background: isActive ? '#FFF6EE' : 'transparent',
                  transition: 'background 100ms',
                }}
                onMouseEnter={e => { if (!isActive) e.currentTarget.style.background = '#f7f3ec'; }}
                onMouseLeave={e => { if (!isActive) e.currentTarget.style.background = 'transparent'; }}
              >
                <span style={{ flex: 1, fontSize: 13, fontWeight: isActive ? 700 : 400, color: '#261810' }}>
                  {p.name}
                </span>
                <span style={{
                  fontSize: 10, fontWeight: 700, color: '#fff',
                  background: p.badgeColor, padding: '2px 7px',
                  borderRadius: 4, whiteSpace: 'nowrap',
                }}>{p.badge}</span>
                {/* 已儲存 Key 的綠色標記 */}
                {hasKey && !isActive && (
                  <span style={{ fontSize: 10, color: '#1a7f37', background: '#dafbe1', padding: '2px 6px', borderRadius: 4, fontWeight: 700 }}>
                    已設定
                  </span>
                )}
                {isActive && <span style={{ color: '#D97A3B', fontSize: 14 }}>✓</span>}
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

// ── DataImportModal ───────────────────────────────────────────────────────────
function DataImportModal({ open, onClose }) {
  const [stage, setStage] = React.useState('upload'); // upload → preview → ai → apply
  const [file, setFile] = React.useState(null);
  const [sheets, setSheets] = React.useState([]);
  const [activeSheet, setActiveSheet] = React.useState(0);
  const [headers, setHeaders] = React.useState([]);
  const [rows, setRows] = React.useState([]);
  const [aiStatus, setAiStatus] = React.useState('idle'); // idle | loading | done | error
  const [aiResult, setAiResult] = React.useState(null);
  const [aiError, setAiError] = React.useState('');
  const [editedResult, setEditedResult] = React.useState(null); // 人工覆核用（深拷貝 aiResult，可修改）
  const [, forceUpdate] = React.useReducer(x => x + 1, 0); // 切換 AI 後觸發重繪
  const [history, setHistory] = React.useState(() => {
    try { return JSON.parse(localStorage.getItem('mile_import_history') || '[]'); }
    catch (e) { return []; }
  });
  const fileInputRef = React.useRef(null);

  // Reset when closed
  React.useEffect(() => {
    if (!open) {
      setTimeout(() => {
        setStage('upload');
        setFile(null);
        setSheets([]);
        setAiResult(null);
        setEditedResult(null);
        setAiError('');
        setAiStatus('idle');
      }, 300);
    }
  }, [open]);

  // 每次 AI 解析完成，將結果深拷貝到 editedResult 供人工修改
  React.useEffect(() => {
    if (aiResult) setEditedResult(JSON.parse(JSON.stringify(aiResult)));
  }, [aiResult]);

  function handleFile(f) {
    if (!f) return;
    setFile(f);
    const reader = new FileReader();
    reader.onload = (e) => {
      try {
        const data = new Uint8Array(e.target.result);
        const wb = XLSX.read(data, { type: 'array' });
        const sheetData = wb.SheetNames.map((name) => {
          const ws = wb.Sheets[name];
          const rows = XLSX.utils.sheet_to_json(ws, { header: 1, defval: '', raw: false });
          return { name, rows };
        });
        setSheets(sheetData);
        setActiveSheet(0);
        loadSheetData(sheetData[0]);
        setStage('preview');
      } catch (err) {
        alert('讀取檔案失敗：' + err.message);
      }
    };
    reader.readAsArrayBuffer(f);
  }

  function loadSheetData(sheet) {
    const rows = sheet.rows || [];
    // First non-empty row as headers
    let headerIdx = 0;
    while (headerIdx < rows.length && rows[headerIdx].every((c) => !String(c).trim())) headerIdx++;
    const hdrs = (rows[headerIdx] || []).map((h) => String(h).trim());
    const data = rows.slice(headerIdx + 1).filter((r) => r.some((c) => String(c).trim()));
    setHeaders(hdrs);
    setRows(data);
  }

  function switchSheet(i) {
    setActiveSheet(i);
    loadSheetData(sheets[i]);
    setAiResult(null);
    setAiStatus('idle');
  }

  // 從 AI 回應中強健地擷取 JSON（應對前後有多餘文字的情況）
  function extractJSON(text) {
    const s = text.trim();
    // 1. 直接解析
    try { return JSON.parse(s); } catch {}
    // 2. 去掉 code fence
    const fenced = s.replace(/^```(json)?\s*/i, '').replace(/\s*```\s*$/, '').trim();
    try { return JSON.parse(fenced); } catch {}
    // 3. 找第一個 { 到最後一個 }
    const a = s.indexOf('{'), b = s.lastIndexOf('}');
    if (a >= 0 && b > a) {
      try { return JSON.parse(s.slice(a, b + 1)); } catch {}
    }
    throw new Error('AI 回應無法解析為 JSON，原始內容：\n' + s.slice(0, 300));
  }

  async function analyzeWithAI() {
    setAiStatus('loading');
    setAiError('');
    const sampleRows = rows.slice(0, 5);
    const targetList = DASHBOARD_TARGETS.map((t) => `- ${t.key}: ${t.label}（欄位：${t.fields.join('、')}）`).join('\n');
    const prompt = `你是米樂家戰情室的資料解析助手。使用者上傳了一個 Excel/CSV 檔案，請判斷這份資料屬於哪個戰情室區塊，並把欄位對應到目標欄位。

【戰情室可用區塊】
${targetList}

【使用者上傳的資料】
工作表名稱：${sheets[activeSheet].name}
欄位（共 ${headers.length} 個）：${headers.join(' | ')}

前 5 列範例：
${sampleRows.map((r, i) => `第${i+1}列: ${r.slice(0, headers.length).join(' | ')}`).join('\n')}

【請回傳 JSON】格式必須完全符合：
{
  "target_key": "從上方 key 選一個最匹配的",
  "target_label": "對應的中文 label",
  "confidence": 0.0 到 1.0 之間的數字,
  "reasoning": "簡短說明判斷理由（30 字內）",
  "field_mapping": [
    {"source_column": "原始欄位名", "target_field": "對應的目標欄位（或 null 表示忽略）", "sample_value": "從第一筆資料抓的值"}
  ],
  "row_count": ${rows.length},
  "summary": "資料摘要：例如 '本日 23 筆未收款，合計 $384K，最大筆 $72K'（從資料內容歸納）",
  "warnings": ["如果有資料品質問題，列在這裡，例如：'第 3 列金額欄位空白'"]
}

只回傳 JSON，不要 markdown 標記，不要任何其他文字。`;

    try {
      // 若使用代理型提供商（Groq/OpenRouter/Mistral/OpenAI/Anthropic），
      // 先確認 serve.js 是否在運行
      const providerId = window.claude?.getProvider?.() || '';
      const directProviders = ['gemini', 'ollama'];
      if (!directProviders.includes(providerId)) {
        try {
          await fetch('/api/ai', { method: 'OPTIONS' });
        } catch {
          throw new Error('⚠️ 本地伺服器未啟動！\n請先在命令提示字元執行：\n\nnode "D:\\米樂家\\米樂家戰情室\\serve.js"');
        }
      }

      const response = await window.claude.complete(prompt);
      const parsed = extractJSON(response);
      setAiResult(parsed);
      setAiStatus('done');
    } catch (err) {
      setAiError(err.message);
      setAiStatus('error');
    }
  }

  function clearHistory() {
    if (!confirm('確定要清除所有匯入紀錄與已匯入資料嗎？戰情室會還原為範例資料。')) return;
    setHistory([]);
    localStorage.removeItem('mile_import_history');
    // Also clear keyed data so dashboard reverts to placeholders
    Object.keys(localStorage).filter((k) => k.startsWith('mile_data_') || k.startsWith('mile_history_')).forEach((k) => localStorage.removeItem(k));
    window.dispatchEvent(new CustomEvent('mileDataImported', { detail: { cleared: true } }));
  }

  // ── 雲端同步（非同步，失敗不影響本地操作）────────────────────────────────
  async function syncToCloud(record) {
    const url = (window.MILE_CONFIG || {}).sheetsUrl || '';
    if (!url) return;
    try {
      // 使用 text/plain 避免 CORS preflight，Apps Script 端仍可解析 JSON
      const res = await fetch(url, {
        method:  'POST',
        headers: { 'Content-Type': 'text/plain' },
        body:    JSON.stringify({ key: record.target_key, record }),
      });
      if (!res.ok) throw new Error('HTTP ' + res.status);
      console.info('[米樂家] 雲端同步成功：', record.target_key);
    } catch (e) {
      console.warn('[米樂家] 雲端同步失敗（本地已儲存，不影響使用）：', e.message);
    }
  }

  function applyToDashboard() {
    if (!editedResult) return;
    const now = new Date();
    const record = {
      id: Date.now(),
      timestamp: now.toISOString(),
      timestampDisplay: now.toLocaleString('zh-TW', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }),
      filename: file.name,
      sheet: sheets[activeSheet].name,
      target: editedResult.target_label,
      target_key: editedResult.target_key,
      confidence: editedResult.confidence,
      row_count: editedResult.row_count,
      summary: editedResult.summary,
      headers: headers,
      rows: rows,                              // ★ 完整資料列（不只 sample）
      field_mapping: editedResult.field_mapping,
      warnings: editedResult.warnings || [],
    };
    // 1. 本地儲存（即時，永遠先做）
    localStorage.setItem('mile_data_' + editedResult.target_key, JSON.stringify(record));
    // 2. 雲端同步（非同步背景執行，失敗只印 warning）
    syncToCloud(record);

    // === 累積歷史快照（給趨勢圖用）===
    try {
      const histKey = 'mile_history_' + editedResult.target_key;
      const hist = JSON.parse(localStorage.getItem(histKey) || '[]');
      const today = now.toISOString().slice(0, 10);
      // Compute summary for this snapshot
      const summary = computeSnapshot(record);
      const snapshot = { date: today, ...summary };
      // Replace today's entry if exists
      const existingIdx = hist.findIndex((h) => h.date === today);
      if (existingIdx >= 0) hist[existingIdx] = snapshot;
      else hist.push(snapshot);
      // Keep last 90 days
      const recent = hist.slice(-90);
      localStorage.setItem(histKey, JSON.stringify(recent));
    } catch (e) {
      console.warn('History snapshot 失敗:', e);
    }
    // Save history (給匯入工作台的紀錄列表用)
    const newHistory = [{
      id: record.id,
      timestamp: record.timestamp,
      filename: record.filename,
      sheet: record.sheet,
      target: record.target,
      target_key: record.target_key,
      row_count: record.row_count,
      summary: record.summary,
    }, ...history].slice(0, 20);
    setHistory(newHistory);
    localStorage.setItem('mile_import_history', JSON.stringify(newHistory));
    // Broadcast to deck — injector listens and re-renders
    window.dispatchEvent(new CustomEvent('mileDataImported', { detail: record }));
    setStage('applied');
  }

  if (!open) return null;

  return (
    <div className="di-backdrop" onClick={onClose}>
      <div className="di-modal" onClick={(e) => e.stopPropagation()}>
        <div className="di-head">
          <div>
            <div className="di-title">📥 資料匯入工作台</div>
            <div className="di-sub">上傳 Excel / CSV → AI 自動解析欄位 → 套用到戰情室</div>
          </div>
          <button className="di-close" onClick={onClose}>✕</button>
        </div>

        <div className="di-steps">
          <div className={`di-step ${stage==='upload' ? 'active' : ['preview','ai','applied'].includes(stage) ? 'done' : ''}`}>① 上傳</div>
          <div className={`di-step ${stage==='preview' ? 'active' : ['ai','applied'].includes(stage) ? 'done' : ''}`}>② 預覽</div>
          <div className={`di-step ${stage==='ai' ? 'active' : stage==='applied' ? 'done' : ''}`}>③ AI 解析</div>
          <div className={`di-step ${stage==='applied' ? 'active' : ''}`}>④ 套用</div>
        </div>

        <div className="di-body">
          {stage === 'upload' && (
            <div className="di-upload">
              <div
                className="di-dropzone"
                onClick={() => fileInputRef.current?.click()}
                onDragOver={(e) => { e.preventDefault(); e.currentTarget.classList.add('dragging'); }}
                onDragLeave={(e) => e.currentTarget.classList.remove('dragging')}
                onDrop={(e) => {
                  e.preventDefault();
                  e.currentTarget.classList.remove('dragging');
                  if (e.dataTransfer.files[0]) handleFile(e.dataTransfer.files[0]);
                }}
              >
                <div className="di-dropzone-icon">📊</div>
                <div className="di-dropzone-title">點擊或拖曳檔案到此</div>
                <div className="di-dropzone-sub">支援 .xlsx · .xls · .csv</div>
                <input
                  ref={fileInputRef}
                  type="file"
                  accept=".xlsx,.xls,.csv"
                  style={{ display: 'none' }}
                  onChange={(e) => handleFile(e.target.files[0])}
                />
              </div>
              <div className="di-tips">
                <div className="di-tips-title">💡 上傳建議</div>
                <ul>
                  <li>第一列為欄位名稱（例：客戶、金額、日期）</li>
                  <li>數字欄不要混雜文字（例：金額直接寫 48200，不要加 $ 或 ,）</li>
                  <li>日期統一格式（建議 YYYY-MM-DD 或 5/19）</li>
                  <li>多個工作表時，AI 會逐表分析</li>
                </ul>
              </div>
              {history.length > 0 && (
                <div className="di-history">
                  <div className="di-history-head">
                    <span>📚 最近匯入（{history.length} 筆）</span>
                    <button onClick={clearHistory}>清除紀錄</button>
                  </div>
                  <ul>
                    {history.slice(0, 5).map((h) => (
                      <li key={h.id}>
                        <span className="di-h-target">{h.target}</span>
                        <span className="di-h-file">{h.filename} · {h.row_count} 筆</span>
                        <span className="di-h-time">{new Date(h.timestamp).toLocaleString('zh-TW', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })}</span>
                      </li>
                    ))}
                  </ul>
                </div>
              )}
            </div>
          )}

          {stage === 'preview' && (
            <div className="di-preview">
              <div className="di-file-info">
                <div><b>📄 {file.name}</b> · {(file.size/1024).toFixed(1)} KB</div>
                <div>{sheets.length} 個工作表 · 當前 <b>{sheets[activeSheet]?.name}</b> · {rows.length} 列資料 · {headers.length} 個欄位</div>
              </div>

              {sheets.length > 1 && (
                <div className="di-sheets">
                  {sheets.map((s, i) => (
                    <button
                      key={i}
                      className={`di-sheet ${i === activeSheet ? 'active' : ''}`}
                      onClick={() => switchSheet(i)}
                    >{s.name} <span className="di-sheet-rows">{(s.rows.length - 1)}</span></button>
                  ))}
                </div>
              )}

              <div className="di-table-wrap">
                <table className="di-table">
                  <thead>
                    <tr>
                      <th className="di-rownum">#</th>
                      {headers.map((h, i) => <th key={i}>{h || `欄位${i+1}`}</th>)}
                    </tr>
                  </thead>
                  <tbody>
                    {rows.slice(0, 12).map((r, i) => (
                      <tr key={i}>
                        <td className="di-rownum">{i+1}</td>
                        {headers.map((_, j) => <td key={j}>{String(r[j] ?? '')}</td>)}
                      </tr>
                    ))}
                  </tbody>
                </table>
                {rows.length > 12 && <div className="di-more">…還有 {rows.length - 12} 列未顯示</div>}
              </div>

              <AIProviderPicker onChanged={forceUpdate} />
              <div className="di-actions">
                <button className="di-btn di-btn-ghost" onClick={() => setStage('upload')}>← 重新上傳</button>
                <button className="di-btn di-btn-primary" onClick={() => { setStage('ai'); analyzeWithAI(); }}>
                  🤖 AI 解析欄位 →
                </button>
              </div>
            </div>
          )}

          {stage === 'ai' && (
            <div className="di-ai">
              {aiStatus === 'loading' && (
                <div className="di-loading">
                  <div className="di-spinner"></div>
                  <div>AI 解析中…</div>
                  <div className="di-loading-sub">判斷資料類型 · 比對欄位 · 產生摘要</div>
                </div>
              )}

              {aiStatus === 'error' && (
                <div className="di-error">
                  <div className="di-error-title">❌ 解析失敗</div>
                  <div className="di-error-msg" style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word', maxHeight: 140, overflowY: 'auto' }}>
                    {aiError}
                  </div>
                  {/* 常見錯誤提示 */}
                  {/401|403|invalid.*key|api.key/i.test(aiError) && (
                    <div style={{ fontSize: 11.5, color: '#9a3412', background: '#fff7ed', borderRadius: 7, padding: '7px 11px', marginTop: 6 }}>
                      💡 API Key 錯誤或已失效，請切換提供商重新輸入 Key
                    </div>
                  )}
                  {(/本地伺服器未啟動|ECONNREFUSED/.test(aiError) || aiError === 'Failed to fetch') && (
                    <div style={{ fontSize: 11.5, color: '#1e40af', background: '#eff6ff', borderRadius: 7, padding: '7px 11px', marginTop: 6 }}>
                      💡 伺服器未啟動，請在命令提示字元執行：<code style={{ fontSize: 11 }}>node serve.js</code>
                      <br/>（Gemini 和 Ollama 不需要伺服器，可直接切換使用）
                    </div>
                  )}
                  {/json|parse|syntax/i.test(aiError) && (
                    <div style={{ fontSize: 11.5, color: '#166534', background: '#f0fdf4', borderRadius: 7, padding: '7px 11px', marginTop: 6 }}>
                      💡 AI 回傳格式不符，切換至 Google Gemini 或 Groq 通常更穩定
                    </div>
                  )}
                  <AIProviderPicker onChanged={(id) => { forceUpdate(); setAiStatus('idle'); setAiError(''); }} />
                  <div className="di-actions" style={{ marginTop: 0 }}>
                    <button className="di-btn di-btn-ghost" onClick={() => setStage('preview')}>← 返回預覽</button>
                    <button className="di-btn di-btn-primary" onClick={analyzeWithAI}>🔄 重試</button>
                  </div>
                </div>
              )}

              {aiStatus === 'done' && aiResult && editedResult && (
                <div className="di-result">

                  {/* ── 人工覆核提示列 ── */}
                  <div style={{
                    display:'flex', alignItems:'center', gap:8,
                    background:'#eff6ff', borderRadius:8,
                    padding:'7px 13px', marginBottom:14,
                    border:'1px solid #bfdbfe',
                  }}>
                    <span style={{ fontSize:13, fontWeight:800, color:'#1d4ed8' }}>✏️ 人工覆核</span>
                    <span style={{ fontSize:11.5, color:'#3b82f6', flex:1 }}>
                      以下結果可直接修改，確認後再點「套用到戰情室」
                    </span>
                    <div className="di-conf" style={{ margin:0 }}>
                      <div className="di-conf-num" style={{ fontSize:18 }}>
                        {Math.round((aiResult.confidence||0)*100)}<span style={{ fontSize:11 }}>%</span>
                      </div>
                      <div className="di-conf-label">信心度</div>
                    </div>
                  </div>

                  {/* ── 目標區塊（下拉可改） ── */}
                  <div style={{ marginBottom:14 }}>
                    <div className="di-result-label" style={{ marginBottom:5 }}>
                      目標區塊
                      <span style={{ fontSize:10.5, color:'#9a7a5a', fontWeight:400, marginLeft:6 }}>
                        AI 建議：{aiResult.target_label} — {aiResult.reasoning}
                      </span>
                    </div>
                    <select
                      value={editedResult.target_key || ''}
                      onChange={(e) => {
                        const t = DASHBOARD_TARGETS.find(x => x.key === e.target.value);
                        if (!t) return;
                        setEditedResult(prev => ({
                          ...prev,
                          target_key: t.key,
                          target_label: t.label,
                          field_mapping: (prev.field_mapping || []).map(m => ({
                            ...m,
                            target_field: t.fields.includes(m.target_field) ? m.target_field : null,
                          })),
                        }));
                      }}
                      style={{
                        display:'block', width:'100%',
                        fontSize:15, fontWeight:800, color:'#261810',
                        border:'2px solid #D97A3B', borderRadius:9,
                        padding:'7px 12px', background:'#FFF6EE',
                        cursor:'pointer', outline:'none',
                      }}
                    >
                      {DASHBOARD_TARGETS.map(t => (
                        <option key={t.key} value={t.key}>{t.label}</option>
                      ))}
                    </select>
                  </div>

                  {/* ── 資料摘要（textarea 可改） ── */}
                  <div className="di-summary" style={{ marginBottom:14 }}>
                    <div className="di-summary-label">📊 資料摘要（可編輯）</div>
                    <textarea
                      value={editedResult.summary || ''}
                      onChange={(e) => setEditedResult(prev => ({ ...prev, summary: e.target.value }))}
                      rows={2}
                      style={{
                        width:'100%', boxSizing:'border-box',
                        border:'1.5px solid #e5e5e5', borderRadius:8,
                        padding:'8px 12px', fontSize:12.5,
                        fontFamily:'inherit', resize:'vertical',
                        color:'#261810', outline:'none',
                        transition:'border-color 120ms',
                        lineHeight:1.55,
                      }}
                      onFocus={e => e.target.style.borderColor='#D97A3B'}
                      onBlur={e => e.target.style.borderColor='#e5e5e5'}
                    />
                  </div>

                  {/* ── 欄位對應（每列下拉可改） ── */}
                  <div className="di-mapping">
                    <div className="di-mapping-label">🔗 欄位對應（可調整）</div>
                    <table className="di-table">
                      <thead>
                        <tr>
                          <th>原始欄位</th>
                          <th>→ 對應目標欄位</th>
                          <th>範例值</th>
                        </tr>
                      </thead>
                      <tbody>
                        {(editedResult.field_mapping || []).map((m, i) => {
                          const curTarget = DASHBOARD_TARGETS.find(t => t.key === editedResult.target_key);
                          const tFields = curTarget ? curTarget.fields : [];
                          return (
                            <tr key={i}>
                              <td>{m.source_column}</td>
                              <td>
                                <select
                                  value={m.target_field || ''}
                                  onChange={(e) => {
                                    const val = e.target.value || null;
                                    setEditedResult(prev => ({
                                      ...prev,
                                      field_mapping: prev.field_mapping.map((x, j) =>
                                        j === i ? { ...x, target_field: val } : x
                                      ),
                                    }));
                                  }}
                                  style={{
                                    fontSize:12, border:'1px solid #ddd', borderRadius:5,
                                    padding:'4px 7px', width:'100%',
                                    background: m.target_field ? '#f0fdf4' : '#fff7ed',
                                    color: m.target_field ? '#166534' : '#92400e',
                                    cursor:'pointer', outline:'none',
                                  }}
                                >
                                  <option value="">略過</option>
                                  {tFields.map(f => (
                                    <option key={f} value={f}>{f}</option>
                                  ))}
                                </select>
                              </td>
                              <td className="di-sample">{String(m.sample_value ?? '')}</td>
                            </tr>
                          );
                        })}
                      </tbody>
                    </table>
                  </div>

                  {(aiResult.warnings || []).length > 0 && (
                    <div className="di-warnings">
                      <div className="di-warnings-label">⚠️ 注意</div>
                      <ul>{aiResult.warnings.map((w, i) => <li key={i}>{w}</li>)}</ul>
                    </div>
                  )}

                  <AIProviderPicker onChanged={(id) => { forceUpdate(); analyzeWithAI(); }} />
                  <div className="di-actions">
                    <button className="di-btn di-btn-ghost" onClick={() => setStage('preview')}>← 返回預覽</button>
                    <button className="di-btn di-btn-ghost" onClick={analyzeWithAI}>🔄 重新解析</button>
                    <button className="di-btn di-btn-primary" onClick={applyToDashboard}>✓ 套用到戰情室 →</button>
                  </div>
                </div>
              )}
            </div>
          )}

          {stage === 'applied' && (
            <div className="di-applied">
              <div className="di-applied-icon">✅</div>
              <div className="di-applied-title">已存入戰情室後台</div>
              <div className="di-applied-sub">
                <b>{editedResult?.target_label}</b> · {editedResult?.row_count} 筆資料<br/>
                來源：{file.name} / {sheets[activeSheet].name}
              </div>
              <div className="di-applied-note">
                💡 本次匯入已記錄。資料目前儲存在瀏覽器後台，串接 ERP 後可即時更新對應戰情室頁面。
              </div>
              <div className="di-actions">
                <button className="di-btn di-btn-ghost" onClick={onClose}>關閉</button>
                <button className="di-btn di-btn-primary" onClick={() => setStage('upload')}>📥 繼續匯入下一份</button>
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// Compute a snapshot summary for trend tracking
function computeSnapshot(record) {
  const out = { count: record.row_count, total: 0 };
  try {
    const amountField = (record.field_mapping || []).find(
      (m) => /金額|amount/i.test(m.target_field || m.source_column || '')
    );
    if (amountField && amountField.source_column) {
      const idx = record.headers.indexOf(amountField.source_column);
      if (idx >= 0) {
        out.total = record.rows.reduce((s, r) => {
          const n = parseFloat(String(r[idx] || '').replace(/[$,\s]/g, ''));
          return s + (isNaN(n) ? 0 : n);
        }, 0);
      }
    }
  } catch (e) {}
  return out;
}

// Mount on its own root
window.__DataImportModal = DataImportModal;
