/* screens-hiring-override.jsx — /planner/hiring and /planner/override-import */

// ============================================================
// Hiring
// ============================================================
function HiringScreen({ warehouseId, addToast }) {
  const [tab, setTab] = React.useState('ALL');
  const [q, setQ] = React.useState('');
  const [selected, setSelected] = React.useState(new Set());
  const [pdfOpen, setPdfOpen] = React.useState(false);
  const [bulkAction, setBulkAction] = React.useState(null);
  const { isMobile } = useViewport();

  let rows = WFP.HIRING.filter(h => h.warehouseId === warehouseId);
  if (tab !== 'ALL') rows = rows.filter(r => r.status === tab);
  if (q) rows = rows.filter(r => (r.id + r.role + r.shiftCode).toLowerCase().includes(q.toLowerCase()));

  function toggle(id) {
    const next = new Set(selected);
    if (next.has(id)) next.delete(id); else next.add(id);
    setSelected(next);
  }
  function toggleAll() {
    if (selected.size === rows.length) setSelected(new Set());
    else setSelected(new Set(rows.map(r => r.id)));
  }

  const counts = ['ALL','DRAFT','APPROVED','SENT','FULFILLED','CANCELLED'].reduce((m, s) => {
    m[s] = s === 'ALL' ? WFP.HIRING.filter(h => h.warehouseId === warehouseId).length
                       : WFP.HIRING.filter(h => h.warehouseId === warehouseId && h.status === s).length;
    return m;
  }, {});

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
      <PageHeader
        breadcrumb={['Planner','Hiring']}
        title="Hiring requests"
        subtitle="Auto-generated from shift plans. Ops manager approves; HR receives a daily PDF batch."
        actions={
          <>
            {!isMobile && <Button variant="outline" icon="excel">Export</Button>}
            <Button variant="outline" icon="pdf" onClick={() => setPdfOpen(true)}>{isMobile ? 'PDF' : 'Preview HR PDF'}</Button>
            <Button variant="primary" icon="check" disabled={selected.size === 0} onClick={() => setBulkAction('approve')}>
              Approve {selected.size > 0 ? '· ' + selected.size : ''}
            </Button>
          </>
        }
      />
      <div style={{ flex: 1, overflowY: 'auto', padding: isMobile ? 14 : 20 }}>
        <Card padding={0}>
          <div style={{ padding: '12px 14px', borderBottom: '1px solid var(--bx-border-muted)', display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
            <div style={{ overflowX: 'auto', maxWidth: '100%' }}>
              <Tabs value={tab} onChange={setTab} dense tabs={[
                { id: 'ALL',       label: 'All',        count: counts.ALL },
                { id: 'DRAFT',     label: 'Draft',      count: counts.DRAFT },
                { id: 'APPROVED',  label: 'Approved',   count: counts.APPROVED },
                { id: 'SENT',      label: 'Sent',       count: counts.SENT },
                { id: 'FULFILLED', label: 'Fulfilled',  count: counts.FULFILLED },
                { id: 'CANCELLED', label: 'Cancelled',  count: counts.CANCELLED },
              ]}/>
            </div>
            <div style={{ flex: 1, minWidth: 100 }}/>
            <div style={{ width: isMobile ? '100%' : 240 }}>
              <Input icon="search" size="sm" placeholder="Search ID, role, shift…" value={q} onChange={e => setQ(e.target.value)}/>
            </div>
          </div>

          <Table columns={[
            { header: <input type="checkbox" checked={selected.size === rows.length && rows.length > 0} onChange={toggleAll} style={{ accentColor: 'var(--bx-primary)' }}/>,
              width: 32, render: r => <input type="checkbox" checked={selected.has(r.id)} onChange={() => toggle(r.id)} style={{ accentColor: 'var(--bx-primary)' }}/> },
            { header: 'Request', mono: true, render: r => (
              <div>
                <div style={{ fontWeight: 600, color: 'var(--bx-text-dark)' }}>{r.id}</div>
                <div style={{ fontSize: 11, color: 'var(--bx-text-muted)' }}>{fmt.date(r.targetDate)} · {r.shiftCode}</div>
              </div>
            )},
            { header: 'Target date', priority: 2, mono: true, render: r => fmt.date(r.targetDate) },
            { header: 'Shift', priority: 2, mono: true, render: r => r.shiftCode },
            { header: 'Role', render: r => <span style={{ textTransform: 'capitalize' }}>{r.role.toLowerCase()} · {r.level}</span> },
            { header: 'Type', priority: 2, render: r => <Badge kind={r.staffType === 'OFFICIAL' ? 'APPROVED' : 'WARNING'} size="sm">{r.staffType}</Badge> },
            { header: 'Count', mono: true, align: 'right', render: r => <b>{r.count}</b> },
            { header: 'Status', render: r => <Badge kind={r.status}>{r.status}</Badge> },
            { header: 'Approver', priority: 3, render: r => r.approvedBy ? <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}><Avatar name={r.approvedBy} size={20}/><span style={{ fontSize: 11.5, color: 'var(--bx-text-muted)' }}>{r.approvedBy.split('@')[0]}</span></span> : <span style={{ color: 'var(--bx-text-muted)' }}>—</span> },
            { header: 'Created', priority: 3, mono: true, render: r => fmt.dateTime(r.createdAt) },
            { header: '', width: 80, priority: 2, render: r => (
              <div style={{ display: 'flex', gap: 4, justifyContent: 'flex-end' }}>
                {r.status === 'DRAFT' && <IconBtn name="check" title="Approve"/>}
                {(r.status === 'APPROVED' || r.status === 'SENT') && <IconBtn name="pdf" title="View PDF"/>}
                <IconBtn name="ellipsis" title="More"/>
              </div>
            )},
          ]} rows={rows} getRowKey={r => r.id} emptyText="No hiring requests"/>
        </Card>

        {/* Detail panel for selected */}
        {selected.size > 0 && (
          <div style={{
            position: 'sticky', bottom: 0, marginTop: 16,
            background: '#fff', border: '1px solid var(--bx-border-muted)',
            borderRadius: 'var(--bx-radius-lg)', padding: '12px 16px',
            boxShadow: 'var(--bx-shadow-pop)', display: 'flex', alignItems: 'center', gap: 12,
          }}>
            <Badge kind="APPROVED">{selected.size} selected</Badge>
            <span style={{ fontSize: 12.5, color: 'var(--bx-text-muted)' }}>
              {Array.from(selected).reduce((sum, id) => sum + (WFP.HIRING.find(h => h.id === id)?.count || 0), 0)} total people
            </span>
            <span style={{ flex: 1 }}/>
            <Button variant="outline" size="sm" icon="x" onClick={() => setSelected(new Set())}>Clear</Button>
            <Button variant="outline" size="sm" icon="trash">Cancel</Button>
            <Button variant="primary" size="sm" icon="check" onClick={() => setBulkAction('approve')}>Approve {selected.size}</Button>
          </div>
        )}
      </div>

      <PdfPreviewDialog open={pdfOpen} onClose={() => setPdfOpen(false)}/>
      <Dialog open={bulkAction === 'approve'} onClose={() => setBulkAction(null)}
        title={'Approve ' + selected.size + ' hiring request' + (selected.size === 1 ? '' : 's') + '?'}
        subtitle="They will move to APPROVED, then auto-batch into a PDF for HR vendor at the next 17:00 cutoff."
        footer={
          <>
            <Button variant="outline" onClick={() => setBulkAction(null)}>Cancel</Button>
            <Button variant="primary" icon="check" onClick={() => {
              setBulkAction(null);
              addToast({ kind: 'FULFILLED', title: 'Approved', body: selected.size + ' hiring requests approved and queued.' });
              setSelected(new Set());
            }}>Approve {selected.size}</Button>
          </>
        }>
        <p style={{ margin: 0, fontSize: 13, color: 'var(--bx-text-body)' }}>
          Approver: <b>linh.tran@boxme.tech</b> · timestamp will be recorded as {fmt.dateTime(new Date().toISOString())}.
        </p>
      </Dialog>
    </div>
  );
}

// ---------- PDF preview Dialog ----------
function PdfPreviewDialog({ open, onClose }) {
  const { vw } = useViewport();
  const stacked = vw < 820;
  return (
    <Dialog open={open} onClose={onClose} width={820}
      title="HR vendor PDF · preview"
      subtitle="Daily batch · all APPROVED requests · sent automatically at 17:00 ICT"
      footer={
        <>
          <Button variant="outline" icon="print">Print preview</Button>
          <Button variant="outline" onClick={onClose}>Close</Button>
          <Button variant="primary" icon="download">Download PDF</Button>
        </>
      }>
      <div style={{ display: 'flex', gap: 14, alignItems: 'stretch', flexDirection: stacked ? 'column' : 'row' }}>
        {/* Page chrome / faux PDF */}
        <div style={{ flex: 1, background: 'var(--bx-surface-2)', padding: 24, borderRadius: 8 }}>
          <div style={{
            background: '#fff', width: '100%', minHeight: 540,
            boxShadow: 'var(--bx-shadow-card)', borderRadius: 4,
            padding: '28px 32px', display: 'flex', flexDirection: 'column', gap: 14,
            fontFamily: 'var(--bx-font-sans)',
          }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', borderBottom: '2px solid var(--bx-text-heading)', paddingBottom: 10 }}>
              <div>
                <div style={{ fontSize: 16, fontWeight: 700, color: 'var(--bx-text-heading)' }}>Hiring Request · Daily Batch</div>
                <div style={{ fontSize: 11, color: 'var(--bx-text-muted)', marginTop: 2 }}>Boxme Asia · WH VN-HN-01 · {fmt.date('2026-05-19')}</div>
              </div>
              <div style={{ textAlign: 'right' }}>
                <div style={{ fontSize: 10, color: 'var(--bx-text-muted)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>Document #</div>
                <div style={{ fontFamily: 'var(--bx-font-mono)', fontSize: 12, fontWeight: 600 }}>HR-2026-0518-A</div>
              </div>
            </div>

            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14, fontSize: 11.5 }}>
              <div>
                <div style={{ color: 'var(--bx-text-muted)', fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 600 }}>From</div>
                <div style={{ marginTop: 2 }}>Mark Tran · Ops Manager</div>
                <div style={{ color: 'var(--bx-text-muted)' }}>mark@boxme.tech</div>
              </div>
              <div>
                <div style={{ color: 'var(--bx-text-muted)', fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 600 }}>To · Vendor</div>
                <div style={{ marginTop: 2 }}>HanoiTemps Co. Ltd.</div>
                <div style={{ color: 'var(--bx-text-muted)' }}>fulfillment@hanoitemps.vn</div>
              </div>
            </div>

            <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 11.5, marginTop: 4 }}>
              <thead>
                <tr style={{ background: 'var(--bx-surface-2)' }}>
                  <th style={pdfTh}>Shift</th>
                  <th style={pdfTh}>Role</th>
                  <th style={pdfTh}>Level</th>
                  <th style={{ ...pdfTh, textAlign: 'right' }}>Count</th>
                  <th style={pdfTh}>Notes</th>
                </tr>
              </thead>
              <tbody>
                {WFP.HIRING.filter(h => h.status === 'APPROVED' && h.targetDate === '2026-05-19').map(h => (
                  <tr key={h.id}>
                    <td style={pdfTd}>{h.shiftCode}</td>
                    <td style={pdfTd}>{h.role.toLowerCase()}</td>
                    <td style={pdfTd}>{h.level}</td>
                    <td style={{ ...pdfTd, textAlign: 'right', fontWeight: 600 }}>{h.count}</td>
                    <td style={{ ...pdfTd, color: 'var(--bx-text-muted)' }}>{h.notes || '—'}</td>
                  </tr>
                ))}
                <tr>
                  <td colSpan={3} style={{ ...pdfTd, fontWeight: 700, borderTop: '2px solid var(--bx-text-heading)' }}>Total</td>
                  <td style={{ ...pdfTd, textAlign: 'right', fontWeight: 700, borderTop: '2px solid var(--bx-text-heading)' }}>3</td>
                  <td style={{ ...pdfTd, borderTop: '2px solid var(--bx-text-heading)' }}></td>
                </tr>
              </tbody>
            </table>

            <div style={{ fontSize: 11, color: 'var(--bx-text-body)', marginTop: 14, lineHeight: 1.55 }}>
              <b>Reporting:</b> all temp staff to report to Gate B (VN-HN-01) by shift start. Lead on duty: Hung Nguyen (+84 9 1234 5678).
              Please confirm fulfillment by 14:00 ICT.
            </div>

            <div style={{ marginTop: 'auto', display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', paddingTop: 18, fontSize: 11, color: 'var(--bx-text-muted)' }}>
              <div>
                <div style={{ height: 32, borderBottom: '1px solid var(--bx-text-muted)', width: 180, marginBottom: 4 }}></div>
                Ops Manager signature
              </div>
              <div style={{ fontFamily: 'var(--bx-font-mono)' }}>page 1 / 1</div>
            </div>
          </div>
        </div>

        {/* Side */}
        <div style={{ width: stacked ? '100%' : 220, display: 'flex', flexDirection: 'column', gap: 10 }}>
          <div style={{ fontSize: 11, color: 'var(--bx-text-muted)', textTransform: 'uppercase', letterSpacing: '0.06em', fontWeight: 700 }}>Send to</div>
          <Field label="Vendor">
            <Select value="hanoitemps" onChange={() => {}} options={[
              { value: 'hanoitemps', label: 'HanoiTemps Co.' },
              { value: 'qhr',        label: 'QHR Staffing' },
            ]}/>
          </Field>
          <Field label="Delivery">
            <Select value="email" onChange={() => {}} options={[{value:'email',label:'Email + portal'},{value:'portal',label:'Portal only'}]}/>
          </Field>
          <Field label="Send at" hint="Auto-send unless changed">
            <Input type="time" value="17:00" onChange={() => {}}/>
          </Field>
          <div style={{ borderTop: '1px solid var(--bx-border-muted)', paddingTop: 10 }}>
            <div style={{ fontSize: 11, color: 'var(--bx-text-muted)', marginBottom: 4 }}>Document hash (will be in audit log)</div>
            <div style={{ fontFamily: 'var(--bx-font-mono)', fontSize: 11 }}>sha256:a0f1…d802</div>
          </div>
        </div>
      </div>
    </Dialog>
  );
}
const pdfTh = { textAlign: 'left', padding: '6px 8px', fontSize: 10, color: 'var(--bx-text-muted)', textTransform: 'uppercase', letterSpacing: '0.04em', fontWeight: 700, borderBottom: '1px solid var(--bx-border)' };
const pdfTd = { padding: '8px 8px', borderBottom: '1px solid var(--bx-border-muted)' };


// ============================================================
// Override import — 3-step wizard
// ============================================================
function OverrideImportScreen({ addToast, navigate }) {
  const [step, setStep] = React.useState(1);
  const [file, setFile] = React.useState(null);
  const [parsing, setParsing] = React.useState(false);
  const [committing, setCommitting] = React.useState(false);
  const [committed, setCommitted] = React.useState(false);
  const [filterStatus, setFilterStatus] = React.useState('all');
  const [reason, setReason] = React.useState('L’Oréal surge confirmed by KAM');
  const [method, setMethod] = React.useState('file'); // 'file' | 'api' | 'form'
  const { isMobile, vw } = useViewport();
  const stacked = vw < 980;

  const preview = WFP.OVERRIDE_PREVIEW;
  const rows = filterStatus === 'all' ? preview.rows : preview.rows.filter(r => r.status === filterStatus);
  const totals = {
    rows: preview.rows.length,
    newC: preview.rows.filter(r => r.status === 'new').length,
    upd:  preview.rows.filter(r => r.status === 'update').length,
    same: preview.rows.filter(r => r.status === 'unchanged').length,
    delta: preview.rows.reduce((s, r) => s + r.delta, 0),
    errors: preview.errors.length,
  };

  function startParse(name) {
    setFile({ name, size: 32_412 });
    setParsing(true);
    setTimeout(() => { setParsing(false); setStep(2); }, 900);
  }
  function commit() {
    setCommitting(true);
    setTimeout(() => {
      setCommitting(false); setCommitted(true); setStep(3);
      addToast({ kind: 'FULFILLED', title: 'Đã chốt điều chỉnh', body: (totals.newC + totals.upd) + ' dòng đã được áp dụng. Dự báo đã cập nhật.' });
    }, 900);
  }

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
      <PageHeader
        breadcrumb={['Lập kế hoạch','Gửi điều chỉnh dự báo']}
        title="Gửi điều chỉnh dự báo"
        subtitle="Có thể nhập bằng tệp, API hoặc nhập tay. Sau khi gửi, các dòng sẽ vào hàng chờ duyệt của bộ phận vận hành."
        actions={
          <Button variant="outline" icon="eye" onClick={() => navigate('/planner/forecast-input')}>{isMobile ? 'Hàng chờ' : 'Mở hàng chờ duyệt'}</Button>
        }
      />
      <div style={{ flex: 1, overflowY: 'auto', padding: isMobile ? 14 : 20 }}>
        <Stepper step={step}/>

        {step === 1 && (
          <>
            <div style={{ display: 'flex', gap: 8, marginTop: 16, flexWrap: 'wrap' }}>
              <MethodChip active={method === 'file'} onClick={() => setMethod('file')} icon="upload" label="Tải tệp" sub="CSV / XLSX · hàng loạt"/>
              <MethodChip active={method === 'api'}  onClick={() => setMethod('api')}  icon="link"   label="API"     sub="Gửi JSON hàng loạt"/>
              <MethodChip active={method === 'form'} onClick={() => setMethod('form')} icon="layers" label="Nhập tay" sub="Theo khách hàng × ngày"/>
            </div>

            {method === 'file' && (
              <div style={{ display: 'grid', gridTemplateColumns: stacked ? '1fr' : '2fr 1fr', gap: isMobile ? 12 : 16, marginTop: 16 }}>
            <Card title="1 · Tải tệp điều chỉnh" subtitle="CSV hoặc XLSX · tối đa 5 MB · dòng tiêu đề: warehouseCode, customerCode, date, qty, reason">
              <div onClick={() => !parsing && startParse('forecast-overrides-2026-05-19.xlsx')} style={{
                border: '2px dashed ' + (parsing ? 'var(--bx-primary)' : 'var(--bx-border)'),
                borderRadius: 12, padding: '48px 20px', textAlign: 'center',
                background: parsing ? '#eef2ff' : 'var(--bx-surface-2)', cursor: parsing ? 'wait' : 'pointer',
                transition: 'background 150ms',
              }}>
                <div style={{ width: 52, height: 52, margin: '0 auto', borderRadius: 9999, background: '#fff', color: 'var(--bx-primary)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                  <Icon name={parsing ? 'spark' : 'upload'} size={20}/>
                </div>
                <div style={{ fontSize: 15, fontWeight: 600, color: 'var(--bx-text-heading)', marginTop: 12 }}>
                  {parsing ? 'Đang đọc và kiểm tra…' : 'Kéo thả tệp hoặc bấm để chọn'}
                </div>
                <div style={{ fontSize: 12.5, color: 'var(--bx-text-muted)', marginTop: 4 }}>
                  {parsing ? 'Đang đọc các dòng và so sánh với dự báo hiện tại…' : 'Định dạng: .csv, .xlsx'}
                </div>
                {!parsing && <Button variant="outline" icon="upload" style={{ marginTop: 16 }}>Chọn tệp</Button>}
              </div>
              {parsing && <div style={{ marginTop: 12 }}><Skeleton height={6}/></div>}
            </Card>

            <Card title="Định dạng tệp" subtitle="Các cột bắt buộc · phân biệt chữ hoa, thường">
              <table style={{ width: '100%', fontSize: 12, borderCollapse: 'collapse' }}>
                <thead>
                  <tr style={{ background: 'var(--bx-surface-2)' }}>
                    <th style={{ ...pdfTh, textTransform: 'none', letterSpacing: 0 }}>Cột</th>
                    <th style={{ ...pdfTh, textTransform: 'none', letterSpacing: 0 }}>Kiểu</th>
                    <th style={{ ...pdfTh, textTransform: 'none', letterSpacing: 0 }}>Ví dụ</th>
                  </tr>
                </thead>
                <tbody style={{ fontFamily: 'var(--bx-font-mono)' }}>
                  <tr><td style={pdfTd}>warehouseCode</td><td style={pdfTd}>chuỗi</td><td style={pdfTd}>VN-HN-01</td></tr>
                  <tr><td style={pdfTd}>customerCode</td> <td style={pdfTd}>chuỗi</td><td style={pdfTd}>LZD</td></tr>
                  <tr><td style={pdfTd}>date</td>          <td style={pdfTd}>YYYY-MM-DD</td><td style={pdfTd}>2026-05-19</td></tr>
                  <tr><td style={pdfTd}>qty</td>           <td style={pdfTd}>số nguyên ≥ 0</td><td style={pdfTd}>1180</td></tr>
                  <tr><td style={pdfTd}>reason</td>        <td style={pdfTd}>tuỳ chọn</td><td style={pdfTd}>Sale giữa tháng</td></tr>
                </tbody>
              </table>
              <div style={{ marginTop: 12, padding: 10, background: 'var(--bx-surface-2)', borderRadius: 8, fontSize: 11.5, color: 'var(--bx-text-body)' }}>
                <Icon name="info" size={11} style={{ color: 'var(--bx-primary)', marginRight: 6 }}/>
                Mỗi dòng là một yêu cầu điều chỉnh tổng số đơn của một khách hàng trong một ngày. Sale 4 ngày cho 3 khách hàng = 12 dòng.
              </div>
              <Button variant="ghost-primary" size="sm" icon="download" style={{ marginTop: 12 }}>Tải mẫu</Button>
            </Card>
          </div>
          )}

          {method === 'api' && <ApiMethodCard/>}
          {method === 'form' && <QuickEntryMatrix warehouseId="wh-vn-hn-01" addToast={addToast} navigate={navigate}/>}
          </>
        )}

        {step === 2 && (
          <div style={{ marginTop: 16 }}>
            {/* Summary */}
            <div style={{
              display: 'grid',
              gridTemplateColumns: vw < 560 ? 'repeat(2, 1fr)' : vw < 900 ? 'repeat(3, 1fr)' : 'repeat(5, 1fr)',
              gap: isMobile ? 8 : 12, marginBottom: isMobile ? 12 : 16,
            }}>
              <Metric icon="diff"   label="Tổng số dòng" value={totals.rows}/>
              <Metric icon="plus"   label="Mới"          value={totals.newC}/>
              <Metric icon="edit"   label="Cập nhật"     value={totals.upd}/>
              <Metric icon="check"  label="Không đổi"    value={totals.same}/>
              <Metric icon="warning" label="Lỗi đọc tệp" value={totals.errors} delta={fmt.signed(totals.delta) + ' đơn ròng'} deltaTone={totals.delta >= 0 ? 'up' : 'down'}/>
            </div>

            <Card title="2 · Xem trước thay đổi" subtitle={'Tệp: ' + (file ? file.name : preview.fileName) + ' · ' + preview.fileHash}
              actions={
                <div style={{ overflowX: 'auto', maxWidth: '100%' }}>
                  <Tabs value={filterStatus} onChange={setFilterStatus} dense tabs={[
                    { id: 'all',       label: 'Tất cả',    count: preview.rows.length },
                    { id: 'new',       label: 'Mới',       count: totals.newC },
                    { id: 'update',    label: 'Cập nhật',  count: totals.upd },
                    { id: 'unchanged', label: 'Không đổi', count: totals.same },
                  ]}/>
                </div>
              }
              padding={0}>
              <Table dense columns={[
                { header: 'Kho',       mono: true, render: r => r.warehouseCode },
                { header: 'Ngày',      mono: true, render: r => fmt.date(r.date) },
                { header: 'Hiện tại',  priority: 2, mono: true, align: 'right', render: r => r.oldQty == null ? <span style={{ color: 'var(--bx-text-muted)' }}>—</span> : fmt.int(r.oldQty) },
                { header: 'Điều chỉnh', mono: true, align: 'right', render: r => fmt.int(r.newQty) },
                { header: 'Δ', mono: true, align: 'right', render: r => (
                  <span style={{ color: r.delta > 0 ? 'var(--bx-success)' : r.delta < 0 ? 'var(--bx-error)' : 'var(--bx-text-muted)', fontWeight: 600 }}>
                    {r.delta === 0 ? '—' : fmt.signed(r.delta)}
                  </span>
                )},
                { header: 'Trạng thái', render: r => <Badge kind={r.status}>{r.status}</Badge> },
              ]} rows={rows} getRowKey={r => r.warehouseId + r.date} emptyText="Không có dòng nào trong bộ lọc này"/>
            </Card>

            {/* Errors */}
            {preview.errors.length > 0 && (
              <Card title={'Lỗi đọc tệp · ' + preview.errors.length} subtitle="Các dòng dưới đây sẽ tự động bị bỏ qua." style={{ marginTop: 16 }} padding={0}>
                <Table dense columns={[
                  { header: 'Dòng', mono: true, align: 'right', width: 60, key: 'line' },
                  { header: 'Cột', mono: true, width: 100, key: 'col' },
                  { header: 'Lý do', wrap: true, render: r => <span style={{ color: 'var(--bx-error)' }}>{r.message}</span> },
                ]} rows={preview.errors} getRowKey={(r, i) => i}/>
              </Card>
            )}

            <Card title="Thông tin xác nhận" subtitle="Cần điền trước khi chốt" style={{ marginTop: 16 }}>
              <div style={{ display: 'grid', gridTemplateColumns: stacked ? '1fr' : '1fr 1fr', gap: 14 }}>
                <Field label="Lý do" required hint="Sẽ ghi vào nhật ký">
                  <Input value={reason} onChange={e => setReason(e.target.value)} placeholder="VD: KAM xác nhận sale giữa tháng"/>
                </Field>
                <Field label="Áp dụng" hint="Sẽ tính lại sau khi chốt">
                  <Select value="affected" onChange={() => {}} options={[
                    { value: 'affected', label: 'Tính lại cho các ngày liên quan' },
                    { value: 'none',     label: 'Chỉ chốt, không tính lại' },
                  ]}/>
                </Field>
                <div style={{ gridColumn: stacked ? 'auto' : '1 / span 2', display: 'flex', justifyContent: 'space-between', alignItems: 'center', paddingTop: 4, flexWrap: 'wrap', gap: 10 }}>
                  <span style={{ fontSize: 12, color: 'var(--bx-text-muted)' }}>
                    {totals.newC + totals.upd} dòng sẽ được áp dụng · {totals.same} bỏ qua (không đổi) · {totals.errors} bị loại (lỗi)
                  </span>
                  <div style={{ display: 'flex', gap: 8 }}>
                    <Button variant="outline" onClick={() => { setStep(1); setFile(null); }}>Quay lại</Button>
                    <Button variant="primary" icon="rocket" disabled={!reason || committing} onClick={commit}>
                      {committing ? 'Đang chốt…' : 'Chốt ' + (totals.newC + totals.upd) + ' dòng'}
                    </Button>
                  </div>
                </div>
              </div>
            </Card>
          </div>
        )}

        {step === 3 && (
          <Card style={{ marginTop: 16 }}>
            <div style={{ display: 'flex', gap: 16, alignItems: 'flex-start', flexWrap: 'wrap' }}>
              <div style={{ width: 48, height: 48, borderRadius: 9999, background: 'var(--bx-success-bg)', color: 'var(--bx-success)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
                <Icon name="check" size={20}/>
              </div>
              <div style={{ flex: 1, minWidth: 240 }}>
                <h2 style={{ fontSize: 18, fontWeight: 600, color: 'var(--bx-text-heading)', margin: 0 }}>Đã gửi · {totals.newC + totals.upd} dòng đang chờ duyệt</h2>
                <div style={{ fontSize: 13, color: 'var(--bx-text-body)', marginTop: 6, lineHeight: 1.55 }}>
                  Các dòng đã vào hàng chờ duyệt dự báo. Quản lý vận hành sẽ thấy chúng được nhóm theo khách hàng và ngày kèm thông tin hỗ trợ quyết định. Engine sẽ tính lại sau mỗi đợt duyệt.
                </div>
                <div style={{ display: 'flex', gap: 8, marginTop: 14, flexWrap: 'wrap' }}>
                  <Button variant="primary" icon="eye" onClick={() => navigate('/planner/forecast-input')}>Mở hàng chờ duyệt</Button>
                  <Button variant="outline" icon="refresh" onClick={() => { setStep(1); setFile(null); setCommitted(false); }}>Gửi đợt khác</Button>
                </div>

                <div style={{ marginTop: 18, padding: 12, background: 'var(--bx-surface-2)', borderRadius: 8, display: 'flex', gap: 24, fontSize: 12, flexWrap: 'wrap' }}>
                  <div><span style={{ color: 'var(--bx-text-muted)' }}>Mã xem trước</span> <b style={{ fontFamily: 'var(--bx-font-mono)' }}>{preview.previewId}</b></div>
                  <div><span style={{ color: 'var(--bx-text-muted)' }}>Mã tệp</span> <b style={{ fontFamily: 'var(--bx-font-mono)' }}>{preview.fileHash}</b></div>
                  <div><span style={{ color: 'var(--bx-text-muted)' }}>Người gửi</span> <b>linh.tran@boxme.tech</b></div>
                  <div><span style={{ color: 'var(--bx-text-muted)' }}>Thời điểm chốt</span> <b style={{ fontFamily: 'var(--bx-font-mono)' }}>{fmt.dateTime(new Date().toISOString())}</b></div>
                </div>
              </div>
            </div>
          </Card>
        )}
      </div>
    </div>
  );
}

function Stepper({ step }) {
  const steps = [
    { idx: 1, label: 'Tải tệp',           icon: 'upload' },
    { idx: 2, label: 'Xem trước thay đổi', icon: 'diff' },
    { idx: 3, label: 'Chốt',              icon: 'check' },
  ];
  const { vw } = useViewport();
  const compact = vw < 560;
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 0,
      background: '#fff', border: '1px solid var(--bx-border-muted)',
      borderRadius: 'var(--bx-radius-lg)', padding: compact ? 10 : 14,
    }}>
      {steps.map((s, i) => {
        const active = s.idx === step;
        const done = s.idx < step;
        return (
          <React.Fragment key={s.idx}>
            <div style={{ display: 'flex', alignItems: 'center', gap: compact ? 6 : 10, flex: '0 0 auto' }}>
              <div style={{
                width: compact ? 26 : 30, height: compact ? 26 : 30, borderRadius: 9999,
                background: done ? 'var(--bx-success)' : active ? 'var(--bx-primary)' : 'var(--bx-surface-2)',
                color: done || active ? '#fff' : 'var(--bx-text-muted)',
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                fontWeight: 700, fontSize: 12, flexShrink: 0,
              }}>{done ? <Icon name="check" size={12}/> : s.idx}</div>
              {!(compact && !active) && (
                <div>
                  {!compact && <div style={{ fontSize: 11, color: 'var(--bx-text-muted)', textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 600 }}>Bước {s.idx}</div>}
                  <div style={{ fontSize: compact ? 12 : 13, fontWeight: 600, color: active || done ? 'var(--bx-text-heading)' : 'var(--bx-text-body)' }}>{s.label}</div>
                </div>
              )}
            </div>
            {i < steps.length - 1 && (
              <div style={{ flex: 1, height: 2, background: done ? 'var(--bx-success)' : 'var(--bx-border-muted)', margin: compact ? '0 8px' : '0 16px', minWidth: 12 }}></div>
            )}
          </React.Fragment>
        );
      })}
    </div>
  );
}

Object.assign(window, { HiringScreen, OverrideImportScreen, PdfPreviewDialog, Stepper, MethodChip });

// ---------- Method chip (file / api / form) ----------
function MethodChip({ active, onClick, icon, label, sub }) {
  return (
    <button onClick={onClick} style={{
      flex: 1, minWidth: 180, padding: '14px 16px',
      display: 'flex', alignItems: 'center', gap: 12, cursor: 'pointer',
      border: '1px solid ' + (active ? 'var(--bx-primary)' : 'var(--bx-border-muted)'),
      background: active ? '#eef2ff' : '#fff', borderRadius: 'var(--bx-radius-lg)',
      fontFamily: 'var(--bx-font-sans)', textAlign: 'left',
      boxShadow: active ? '0 0 0 3px rgba(11,67,234,0.10)' : 'none',
      transition: 'box-shadow 120ms, border-color 120ms',
    }}>
      <div style={{
        width: 32, height: 32, borderRadius: 8,
        background: active ? 'var(--bx-primary)' : 'var(--bx-surface-2)',
        color: active ? '#fff' : 'var(--bx-text-body)',
        display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
      }}><Icon name={icon} size={14}/></div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontWeight: 600, fontSize: 13, color: active ? 'var(--bx-primary)' : 'var(--bx-text-dark)' }}>{label}</div>
        <div style={{ fontSize: 11.5, color: 'var(--bx-text-muted)' }}>{sub}</div>
      </div>
      {active && <Icon name="check" size={13} style={{ color: 'var(--bx-primary)' }}/>}
    </button>
  );
}

// ---------- API method card ----------
function ApiMethodCard() {
  const sample = `POST /api/v2/forecast/overrides/batch
Authorization: Bearer <ops_token>
Content-Type: application/json

{
  "warehouseCode": "VN-HN-01",
  "submittedBy": "unilever-vn (api)",
  "reason": "Sale giữa tháng",
  "rows": [
    { "customerCode": "SPE", "date": "2026-05-19", "qty": 1480 },
    { "customerCode": "SPE", "date": "2026-05-20", "qty": 1620 }
  ]
}`;
  const resp = `201 Created
{ "batchId": "api-call-7f12", "accepted": 2, "rejected": 0,
  "reviewUrl": "/planner/forecast-input?batch=api-call-7f12" }`;
  return (
    <div style={{ display: 'grid', gridTemplateColumns: '1.4fr 1fr', gap: 16, marginTop: 16 }}>
      <Card title="Gửi JSON hàng loạt" subtitle="Mỗi dòng là một yêu cầu điều chỉnh tổng đơn. Có thể gửi nhiều dòng trong một lượt.">
        <CodeBlock code={sample}/>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 12 }}>
          <Button variant="outline" size="sm" icon="copy">Sao chép curl</Button>
          <Button variant="ghost-primary" size="sm" icon="external">Tài liệu OpenAPI</Button>
        </div>
      </Card>
      <Card title="Phản hồi" subtitle="Dòng ở trạng thái CHỜ DUYỆT cho đến khi vận hành quyết định.">
        <CodeBlock code={resp}/>
        <div style={{ marginTop: 12, padding: 10, background: 'var(--bx-surface-2)', borderRadius: 8, fontSize: 12, color: 'var(--bx-text-body)', display: 'flex', gap: 8 }}>
          <Icon name="info" size={12} style={{ color: 'var(--bx-primary)', marginTop: 2 }}/>
          <div>Gửi lại cùng (kho, khách hàng, ngày) trong 24h sẽ cập nhật dòng hiện có thay vì tạo trùng.</div>
        </div>
      </Card>
      <Card title="Xác thực" style={{ gridColumn: '1 / span 2' }}
        subtitle="OAuth2 client-credentials. Cấp riêng cho từng đối tác tích hợp.">
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 14, fontSize: 12.5 }}>
          <KV label="Endpoint" mono value={<span>POST <span style={{ fontWeight: 600 }}>/api/v2/forecast/overrides/batch</span></span>}/>
          <KV label="Token endpoint" mono value="/oauth/token"/>
          <KV label="Giới hạn lượt gọi" mono value="600 lượt / 5 phút"/>
          <KV label="Quyền" value={<><Badge kind="INFO" size="sm">forecast.override.write</Badge></>}/>
          <KV label="Số dòng tối đa / lượt" mono value="500"/>
          <KV label="Dung lượng tối đa" mono value="2 MB"/>
        </div>
      </Card>
    </div>
  );
}

function CodeBlock({ code }) {
  return (
    <pre style={{
      margin: 0, padding: 12, background: '#0b1020', color: '#e8eefc',
      borderRadius: 8, fontSize: 11.5, fontFamily: 'var(--bx-font-mono)',
      whiteSpace: 'pre-wrap', wordBreak: 'break-word', lineHeight: 1.5,
      overflow: 'auto', maxHeight: 360,
    }}>{code}</pre>
  );
}

// ---------- Single-row form (kept for backward compat) ----------
function SingleFormMethod({ warehouseId, addToast, navigate }) {
  return <QuickEntryMatrix warehouseId={warehouseId} addToast={addToast} navigate={navigate}/>;
}

// ---------- Customer baseline helper ----------
function customerBaseline(customerId) {
  return WFP.BSIN_CATALOG.filter(c => c.customerId === customerId)
    .reduce((s, c) => s + c.baseline, 0);
}

// ---------- Quick entry matrix (Customer rows × Date columns) ----------
function QuickEntryMatrix({ warehouseId, addToast, navigate }) {
  const { vw, isMobile } = useViewport();
  const [reason, setReason] = React.useState('Sale giữa tháng · đợt 4 ngày');
  const [dates, setDates] = React.useState(['2026-05-19', '2026-05-20', '2026-05-21', '2026-05-22']);
  // rows: customerId -> { qty: { date: value } }
  const [rows, setRows] = React.useState([
    { customerId: 'cus-001', qty: { '2026-05-19': 2600, '2026-05-20': 2900 } },
    { customerId: 'cus-002', qty: { '2026-05-19': 1480 } },
  ]);
  const [submitted, setSubmitted] = React.useState(false);

  function addCustomer(id) {
    if (rows.find(r => r.customerId === id)) return;
    setRows(prev => [...prev, { customerId: id, qty: {} }]);
  }
  function removeCustomer(id) { setRows(prev => prev.filter(r => r.customerId !== id)); }
  function setQty(id, date, value) {
    setRows(prev => prev.map(r => r.customerId === id ? { ...r, qty: { ...r.qty, [date]: value } } : r));
  }
  function addDate(d) {
    if (dates.includes(d)) return;
    setDates([...dates, d].sort());
  }
  function removeDate(d) {
    if (dates.length <= 1) return;
    setDates(dates.filter(x => x !== d));
    setRows(prev => prev.map(r => { const q = { ...r.qty }; delete q[d]; return { ...r, qty: q }; }));
  }

  // Totals
  let totalRows = 0, totalOverride = 0, totalDelta = 0;
  rows.forEach(r => {
    const base = customerBaseline(r.customerId);
    dates.forEach(d => {
      const v = parseInt(r.qty[d], 10);
      if (!isNaN(v)) {
        totalRows++;
        totalOverride += v;
        totalDelta += v - base;
      }
    });
  });

  function fillMultiplier(mult) {
    setRows(prev => prev.map(r => {
      const base = customerBaseline(r.customerId);
      const q = { ...r.qty };
      dates.forEach(d => { if (!q[d]) q[d] = Math.round(base * mult); });
      return { ...r, qty: q };
    }));
  }
  function clearAll() { setRows(prev => prev.map(r => ({ ...r, qty: {} }))); }

  function submit() {
    setSubmitted(true);
    addToast({ kind: 'FULFILLED', title: 'Đã gửi chờ duyệt', body: totalRows + ' dòng điều chỉnh đã vào hàng chờ.' });
    setTimeout(() => navigate('/planner/forecast-input'), 700);
  }

  const valid = totalRows > 0 && reason.length >= 4 && !submitted;
  const availableCustomers = WFP.CUSTOMERS.filter(c => !rows.find(r => r.customerId === c.id));

  return (
    <div style={{ marginTop: 16, display: 'flex', flexDirection: 'column', gap: 14 }}>
      {/* Context bar */}
      <Card title="Nhập tay · bảng khách hàng × ngày" subtitle="Chọn kho, chọn dải ngày, thêm khách hàng và điền tổng số đơn. Tab sang phải →, Enter xuống dưới ↓.">
        <div style={{ display: 'grid', gridTemplateColumns: vw < 900 ? '1fr' : '1fr 2fr', gap: 14 }}>
          <Field label="Kho" required>
            <Select value={warehouseId} onChange={() => {}}
              options={WFP.WAREHOUSES.map(w => ({ value: w.id, label: w.code + ' · ' + w.name }))}/>
          </Field>
          <Field label="Lý do · áp dụng cho mọi dòng" required hint="Sẽ ghi vào nhật ký cho từng dòng.">
            <Input value={reason} onChange={e => setReason(e.target.value)} placeholder="VD: Sale giữa tháng · đợt 4 ngày"/>
          </Field>
        </div>
      </Card>

      {/* Date columns */}
      <Card title="Cột ngày" subtitle="Mỗi cột là một ngày. Sale thường kéo dài 2–4 ngày." padding={12}
        actions={<DateAdder onAdd={addDate} existing={dates}/>}>
        <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
          {dates.map(d => (
            <div key={d} style={{
              display: 'inline-flex', alignItems: 'center', gap: 6,
              padding: '6px 4px 6px 10px', borderRadius: 9999,
              background: '#eef2ff', color: 'var(--bx-primary)',
              fontSize: 12, fontWeight: 600,
            }}>
              <span style={{ fontFamily: 'var(--bx-font-mono)' }}>{fmt.shortDate(d)}</span>
              <span style={{ fontSize: 10, fontWeight: 500, opacity: 0.7 }}>{vnWeekday(d)}</span>
              <IconBtn name="x" size={20} onClick={() => removeDate(d)} title="Xóa ngày" style={{ marginLeft: 2 }}/>
            </div>
          ))}
        </div>
      </Card>

      {/* Matrix */}
      <Card title={'Khách hàng · ' + rows.length + ' dòng'} subtitle="Mỗi dòng là tổng số đơn của một khách hàng trong ngày đó."
        actions={
          <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
            <Button size="sm" variant="outline" icon="sliders" onClick={() => fillMultiplier(2)}>Tự điền 2×</Button>
            <Button size="sm" variant="outline" icon="sliders" onClick={() => fillMultiplier(3)}>Tự điền 3×</Button>
            <Button size="sm" variant="ghost" icon="trash" onClick={clearAll}>Xóa số</Button>
          </div>
        }
        padding={0}>
        {/* Customer picker chips */}
        <div style={{ padding: 14, borderBottom: '1px solid var(--bx-border-muted)', display: 'flex', flexWrap: 'wrap', gap: 6, alignItems: 'center' }}>
          <span style={{ fontSize: 11, color: 'var(--bx-text-muted)', textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 700, marginRight: 4 }}>Thêm khách hàng</span>
          {availableCustomers.length === 0 ? (
            <span style={{ fontSize: 12, color: 'var(--bx-text-muted)' }}>Đã thêm tất cả khách hàng.</span>
          ) : availableCustomers.map(c => (
            <button key={c.id} onClick={() => addCustomer(c.id)} style={{
              display: 'inline-flex', alignItems: 'center', gap: 6,
              padding: '6px 10px', borderRadius: 9999,
              background: '#fff', border: '1px dashed var(--bx-border)',
              fontFamily: 'var(--bx-font-sans)', fontSize: 12, color: 'var(--bx-text-dark)',
              cursor: 'pointer',
            }}>
              <Icon name="plus" size={10} style={{ color: 'var(--bx-primary)' }}/>
              <span style={{ fontFamily: 'var(--bx-font-mono)', fontWeight: 600 }}>{c.code}</span>
              <span style={{ color: 'var(--bx-text-muted)' }}>{c.name}</span>
            </button>
          ))}
        </div>

        {rows.length === 0 ? (
          <Empty icon="layers" title="Chưa có khách hàng" subtitle="Bấm một khách hàng ở trên để thêm dòng."/>
        ) : (
          <div style={{ overflowX: 'auto' }}>
            <table style={{ width: '100%', borderCollapse: 'separate', borderSpacing: 0, fontSize: 13, minWidth: 700 }}>
              <thead>
                <tr>
                  <th style={mthStyle({ left: true })}>Khách hàng</th>
                  {dates.map(d => (
                    <th key={d} style={mthStyle()}>
                      <div style={{ fontFamily: 'var(--bx-font-mono)' }}>{fmt.shortDate(d)}</div>
                      <div style={{ fontSize: 10, fontWeight: 500, color: 'var(--bx-text-muted)' }}>{vnWeekday(d)}</div>
                    </th>
                  ))}
                  <th style={{ ...mthStyle({ right: true }), textAlign: 'right' }}>Tổng dòng</th>
                  <th style={{ ...mthStyle(), width: 36 }}></th>
                </tr>
              </thead>
              <tbody>
                {rows.map((r) => {
                  const cust = WFP.CUSTOMERS.find(c => c.id === r.customerId);
                  const base = customerBaseline(r.customerId);
                  const rowTotal = dates.reduce((s, d) => s + (parseInt(r.qty[d], 10) || 0), 0);
                  return (
                    <tr key={r.customerId}>
                      <td style={mtdStyle({ left: true })}>
                        <div style={{ fontFamily: 'var(--bx-font-mono)', fontSize: 12, fontWeight: 600, color: 'var(--bx-text-dark)' }}>{cust.code}</div>
                        <div style={{ fontSize: 11.5, color: 'var(--bx-text-muted)', marginTop: 2 }}>{cust.name}</div>
                        <div style={{ fontSize: 10.5, color: 'var(--bx-text-muted)', marginTop: 2 }}>nền <span style={{ fontFamily: 'var(--bx-font-mono)', fontWeight: 600 }}>{fmt.int(base)}</span> đơn / ngày</div>
                      </td>
                      {dates.map(d => {
                        const v = r.qty[d];
                        const num = parseInt(v, 10);
                        const hasVal = !isNaN(num);
                        const delta = hasVal ? num - base : null;
                        return (
                          <td key={d} style={mtdStyle()}>
                            <input
                              type="text" inputMode="numeric" pattern="[0-9]*"
                              value={v ?? ''}
                              onChange={e => setQty(r.customerId, d, e.target.value.replace(/[^0-9]/g, ''))}
                              placeholder={base.toString()}
                              style={{
                                width: '100%', height: 34, padding: '0 8px',
                                border: '1px solid ' + (hasVal ? 'var(--bx-primary)' : 'var(--bx-border-muted)'),
                                borderRadius: 8, outline: 'none',
                                fontFamily: 'var(--bx-font-mono)', fontSize: 13, fontWeight: 600,
                                textAlign: 'right', color: 'var(--bx-text-dark)',
                                background: hasVal ? '#eef2ff' : '#fff',
                              }}
                              onFocus={e => { e.target.style.boxShadow = '0 0 0 3px rgba(11,67,234,0.18)'; e.target.style.borderColor = 'var(--bx-primary)'; }}
                              onBlur={e => { e.target.style.boxShadow = 'none'; if (!hasVal) e.target.style.borderColor = 'var(--bx-border-muted)'; }}
                            />
                            {hasVal && (
                              <div style={{
                                fontSize: 10, color: delta > 0 ? 'var(--bx-success)' : delta < 0 ? 'var(--bx-error)' : 'var(--bx-text-muted)',
                                fontFamily: 'var(--bx-font-mono)', textAlign: 'right', marginTop: 2, fontWeight: 600,
                              }}>{delta > 0 ? '+' : ''}{delta} · ×{(num / base).toFixed(1)}</div>
                            )}
                          </td>
                        );
                      })}
                      <td style={{ ...mtdStyle({ right: true }), textAlign: 'right' }}>
                        <span style={{ fontFamily: 'var(--bx-font-mono)', fontWeight: 700, fontSize: 13 }}>{rowTotal > 0 ? fmt.int(rowTotal) : <span style={{ color: 'var(--bx-text-muted)' }}>—</span>}</span>
                      </td>
                      <td style={mtdStyle()}>
                        <IconBtn name="x" onClick={() => removeCustomer(r.customerId)} title="Xóa khách hàng"/>
                      </td>
                    </tr>
                  );
                })}
                {/* Totals row */}
                <tr>
                  <td style={{ ...mtdStyle({ left: true }), background: 'var(--bx-surface-2)', fontWeight: 700, fontSize: 12, color: 'var(--bx-text-dark)' }}>
                    Tổng cột
                  </td>
                  {dates.map(d => {
                    const colTotal = rows.reduce((s, r) => s + (parseInt(r.qty[d], 10) || 0), 0);
                    return (
                      <td key={d} style={{ ...mtdStyle(), background: 'var(--bx-surface-2)', textAlign: 'right' }}>
                        <span style={{ fontFamily: 'var(--bx-font-mono)', fontWeight: 700, fontSize: 13, color: 'var(--bx-text-dark)' }}>{colTotal > 0 ? fmt.int(colTotal) : <span style={{ color: 'var(--bx-text-muted)' }}>—</span>}</span>
                      </td>
                    );
                  })}
                  <td style={{ ...mtdStyle({ right: true }), background: 'var(--bx-surface-2)', textAlign: 'right' }}>
                    <span style={{ fontFamily: 'var(--bx-font-mono)', fontWeight: 700, fontSize: 14, color: 'var(--bx-primary)' }}>{fmt.int(totalOverride)}</span>
                  </td>
                  <td style={{ ...mtdStyle(), background: 'var(--bx-surface-2)' }}></td>
                </tr>
              </tbody>
            </table>
          </div>
        )}
      </Card>

      {/* Submit footer */}
      <Card>
        <div style={{ display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap' }}>
          <div style={{ display: 'flex', gap: 18, alignItems: 'center', flexWrap: 'wrap' }}>
            <Stat label="Số dòng" value={totalRows} sub="ô đã nhập"/>
            <Stat label="Tổng điều chỉnh" value={fmt.int(totalOverride)} sub="đơn"/>
            <Stat label="Chênh so với nền" value={fmt.signed(totalDelta)} sub="đơn" tone={totalDelta > 0 ? 'up' : totalDelta < 0 ? 'down' : 'flat'}/>
          </div>
          <div style={{ flex: 1 }}/>
          <div style={{ display: 'flex', gap: 8 }}>
            <Button variant="outline" onClick={() => setRows([])}>Làm lại</Button>
            <Button variant="primary" icon="check" disabled={!valid} onClick={submit}>
              {submitted ? 'Đã gửi' : 'Gửi ' + totalRows + ' dòng chờ duyệt'}
            </Button>
          </div>
        </div>
        {totalRows === 0 && (
          <div style={{ marginTop: 10, fontSize: 12, color: 'var(--bx-text-muted)' }}>
            <Icon name="info" size={11} style={{ marginRight: 4 }}/>
            Thêm ít nhất một khách hàng và nhập số đơn để có thể gửi.
          </div>
        )}
      </Card>
    </div>
  );
}

function vnWeekday(d) {
  const m = ['CN','T2','T3','T4','T5','T6','T7'];
  return m[new Date(d).getDay()];
}

function Stat({ label, value, sub, tone }) {
  const c = tone === 'down' ? 'var(--bx-error)' : tone === 'up' ? 'var(--bx-success)' : tone === 'flat' ? 'var(--bx-text-muted)' : 'var(--bx-text-dark)';
  return (
    <div>
      <div style={{ fontSize: 10.5, color: 'var(--bx-text-muted)', textTransform: 'uppercase', letterSpacing: '0.04em', fontWeight: 600 }}>{label}</div>
      <div style={{ fontFamily: 'var(--bx-font-mono)', fontSize: 18, fontWeight: 700, color: c }}>{value}</div>
      {sub && <div style={{ fontSize: 10.5, color: 'var(--bx-text-muted)' }}>{sub}</div>}
    </div>
  );
}

const mthStyle = (opt = {}) => ({
  background: 'var(--bx-surface-2)',
  textAlign: opt.left ? 'left' : 'center',
  padding: '8px 10px', fontSize: 11, fontWeight: 600,
  color: 'var(--bx-text-muted)', letterSpacing: '0.04em', textTransform: 'uppercase',
  borderBottom: '1px solid var(--bx-border-muted)',
  position: opt.left ? 'sticky' : 'static', left: opt.left ? 0 : undefined,
  minWidth: opt.left ? 220 : 110,
  zIndex: opt.left ? 1 : 0,
});
const mtdStyle = (opt = {}) => ({
  padding: '10px 10px',
  borderBottom: '1px solid var(--bx-border-muted)',
  textAlign: opt.left ? 'left' : 'center',
  background: opt.left ? '#fff' : 'transparent',
  position: opt.left ? 'sticky' : 'static', left: opt.left ? 0 : undefined,
  zIndex: opt.left ? 1 : 0,
});

// ---------- SKU autocomplete ----------
function SkuAutocomplete({ customerId, exclude, onPick }) {
  const [q, setQ] = React.useState('');
  const [open, setOpen] = React.useState(false);
  const [highlight, setHighlight] = React.useState(0);
  const ref = React.useRef(null);
  React.useEffect(() => {
    function onDoc(e) { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }
    document.addEventListener('mousedown', onDoc); return () => document.removeEventListener('mousedown', onDoc);
  }, []);
  const matches = WFP.BSIN_CATALOG
    .filter(c => c.customerId === customerId)
    .filter(c => !exclude.includes(c.bsin))
    .filter(c => !q.trim() || (c.bsin + ' ' + c.name).toLowerCase().includes(q.trim().toLowerCase()))
    .slice(0, 10);

  function commit(idx) {
    const m = matches[idx];
    if (!m) return;
    onPick(m); setQ(''); setHighlight(0); setOpen(true); // keep open for fast batch-adding
  }
  function onKey(e) {
    if (!open) return;
    if (e.key === 'ArrowDown') { e.preventDefault(); setHighlight(h => Math.min(matches.length - 1, h + 1)); }
    else if (e.key === 'ArrowUp') { e.preventDefault(); setHighlight(h => Math.max(0, h - 1)); }
    else if (e.key === 'Enter') { e.preventDefault(); commit(highlight); }
    else if (e.key === 'Escape') { setOpen(false); }
  }

  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <Input icon="search" value={q} onChange={e => { setQ(e.target.value); setHighlight(0); setOpen(true); }}
        placeholder="Search SKUs by BSIN or product name (e.g. “lipstick”, “BSIN-LZ”)…"
        onKeyDown={onKey}
        iconRight="chevron-down"
        style={{ width: '100%' }}/>
      <button type="button" onClick={() => setOpen(o => !o)} style={{
        position: 'absolute', top: 0, right: 0, width: 36, height: '100%',
        border: 'none', background: 'transparent', cursor: 'pointer',
      }} tabIndex={-1} aria-hidden="true"/>
      {open && matches.length > 0 && (
        <div style={{
          position: 'absolute', top: 'calc(100% + 4px)', left: 0, right: 0,
          background: '#fff', border: '1px solid var(--bx-border-muted)',
          borderRadius: 10, boxShadow: 'var(--bx-shadow-pop)', zIndex: 50,
          padding: 4, maxHeight: 320, overflowY: 'auto',
        }}>
          <div style={{ fontSize: 10, color: 'var(--bx-text-muted)', padding: '6px 10px 2px', textTransform: 'uppercase', letterSpacing: '0.06em', fontWeight: 700 }}>
            BSIN catalog · {matches.length} match{matches.length === 1 ? '' : 'es'}
          </div>
          {matches.map((m, i) => (
            <button key={m.bsin} onMouseDown={(e) => { e.preventDefault(); commit(i); }}
              onMouseEnter={() => setHighlight(i)} style={{
                width: '100%', display: 'flex', alignItems: 'center', gap: 10,
                padding: '8px 10px', borderRadius: 6,
                border: 'none', cursor: 'pointer', textAlign: 'left',
                background: i === highlight ? '#eef2ff' : 'transparent',
                fontFamily: 'var(--bx-font-sans)',
              }}>
              <Icon name="cube" size={12} style={{ color: 'var(--bx-text-muted)' }}/>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                  <span style={{ fontFamily: 'var(--bx-font-mono)', fontSize: 12, fontWeight: 600, color: i === highlight ? 'var(--bx-primary)' : 'var(--bx-text-dark)' }}>{m.bsin}</span>
                  <span style={{ fontSize: 12.5, color: 'var(--bx-text-dark)' }}>{m.name}</span>
                </div>
                <div style={{ fontSize: 10.5, color: 'var(--bx-text-muted)' }}>baseline {m.baseline} / day</div>
              </div>
              <span style={{ fontSize: 10, color: 'var(--bx-text-muted)' }}>{i === highlight ? '↵ enter' : ''}</span>
            </button>
          ))}
        </div>
      )}
      {open && matches.length === 0 && (
        <div style={{
          position: 'absolute', top: 'calc(100% + 4px)', left: 0, right: 0,
          background: '#fff', border: '1px solid var(--bx-border-muted)',
          borderRadius: 10, boxShadow: 'var(--bx-shadow-pop)', zIndex: 50,
          padding: '16px 14px', fontSize: 12, color: 'var(--bx-text-muted)', textAlign: 'center',
        }}>
          {q ? 'No matching SKUs for this customer.' : 'Type to search the BSIN catalog.'}
        </div>
      )}
    </div>
  );
}

// ---------- Date adder ----------
function DateAdder({ onAdd, existing }) {
  const [open, setOpen] = React.useState(false);
  const [v, setV] = React.useState('2026-05-23');
  const ref = React.useRef(null);
  React.useEffect(() => {
    function onDoc(e) { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }
    document.addEventListener('mousedown', onDoc); return () => document.removeEventListener('mousedown', onDoc);
  }, []);
  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <Button size="sm" variant="outline" icon="plus" onClick={() => setOpen(o => !o)}>Thêm ngày</Button>
      {open && (
        <div style={{
          position: 'absolute', top: 'calc(100% + 6px)', right: 0,
          background: '#fff', border: '1px solid var(--bx-border-muted)',
          borderRadius: 10, boxShadow: 'var(--bx-shadow-pop)', zIndex: 50,
          padding: 12, width: 240,
        }}>
          <Field label="Ngày">
            <Input type="date" value={v} onChange={e => setV(e.target.value)}/>
          </Field>
          <div style={{ display: 'flex', gap: 6, marginTop: 10, justifyContent: 'flex-end' }}>
            <Button size="sm" variant="ghost" onClick={() => setOpen(false)}>Hủy</Button>
            <Button size="sm" variant="primary" disabled={existing.includes(v)} onClick={() => { onAdd(v); setOpen(false); }}>Thêm</Button>
          </div>
        </div>
      )}
    </div>
  );
}

Object.assign(window, { MethodChip, ApiMethodCard, CodeBlock, SingleFormMethod, QuickEntryMatrix, SkuAutocomplete });
