/* screens-planner.jsx — /planner/forecast-input and /planner/shift-plan (hero) */

// ============================================================
// Forecast review — per-customer submissions
// One row per submission (NOT per SKU). Compact UI.
// Lifecycle: NEW → AM_REVIEWED → PLANNER_ACCEPTED | PLANNER_REJECTED
// Cut-off: PAYDAY/MID_MONTH/MINI_SPIKE = 3 days; DOUBLE_DAY/MEGA_SALE = 7 days.
// Late submissions are accepted but deprioritized.
// ============================================================

const STATE_META = {
  NEW:               { label: 'Mới',          kind: 'WARNING',  short: 'Mới' },
  AM_REVIEWED:       { label: 'AM đã xem',    kind: 'INFO',     short: 'AM đã xem' },
  PLANNER_ACCEPTED:  { label: 'Đã duyệt',     kind: 'APPROVED', short: 'Đã duyệt' },
  PLANNER_REJECTED:  { label: 'Từ chối',      kind: 'CRITICAL', short: 'Từ chối' },
};
const STATE_FLOW = ['NEW', 'AM_REVIEWED', 'PLANNER_ACCEPTED'];

function cutoffDaysFor(dayType) {
  if (dayType === 'MEGA_SALE' || dayType === 'DOUBLE_DAY') return 7;
  if (dayType === 'PAYDAY' || dayType === 'MID_MONTH' || dayType === 'MINI_SPIKE') return 3;
  return 1;
}
function leadDays(submittedAt, targetDate) {
  const a = new Date(submittedAt + (submittedAt.endsWith('Z') ? '' : '')).getTime();
  const b = new Date(targetDate + 'T00:00:00Z').getTime();
  return Math.floor((b - a) / 86400000);
}

function ForecastInputScreen({ warehouseId, navigate }) {
  const [statusTab, setStatusTab] = React.useState('PENDING');
  const [tierFilter, setTierFilter] = React.useState('all');
  const [eventFilter, setEventFilter] = React.useState('all');
  const [expandedId, setExpandedId] = React.useState(null);
  const [overrides, setOverrides] = React.useState({}); // id -> { state, decisionNote }
  const [decision, setDecision] = React.useState(null);  // {row, target: state}
  const { isMobile, vw } = useViewport();
  const compact     = vw < 1200;  // drop vs P85 column
  const stackedRow  = vw < 940;   // switch to card-style row (tablet + mobile)
  const tinyHeader  = vw < 520;   // very narrow phone

  const wh = WFP.WAREHOUSES.find(w => w.id === warehouseId) || WFP.WAREHOUSES[0];

  // Apply state overrides
  const baseRows = WFP.SUBMITS.filter(r => r.warehouseId === warehouseId).map(r => {
    const o = overrides[r.id];
    if (!o) return r;
    return { ...r, state: o.state ?? r.state, stateLog: o.stateLog ?? r.stateLog };
  });

  // Filter
  let rows = baseRows;
  if (statusTab === 'PENDING')  rows = rows.filter(r => r.state === 'NEW' || r.state === 'AM_REVIEWED');
  if (statusTab === 'ACCEPTED') rows = rows.filter(r => r.state === 'PLANNER_ACCEPTED');
  if (statusTab === 'REJECTED') rows = rows.filter(r => r.state === 'PLANNER_REJECTED');
  if (tierFilter !== 'all')  rows = rows.filter(r => r.customerTier === tierFilter);
  if (eventFilter !== 'all') rows = rows.filter(r => r.dayType === eventFilter);
  rows.sort((a, b) => {
    // Sort: late vs on-time? Actually sort by target date asc, then tier (KEY first)
    const ld = a.targetDate.localeCompare(b.targetDate);
    if (ld !== 0) return ld;
    const tierOrder = { KEY: 0, MAJOR: 1, STANDARD: 2 };
    return (tierOrder[a.customerTier] - tierOrder[b.customerTier]);
  });

  const pendingRows = baseRows.filter(r => r.state === 'NEW' || r.state === 'AM_REVIEWED');
  const counts = {
    PENDING:  pendingRows.length,
    ACCEPTED: baseRows.filter(r => r.state === 'PLANNER_ACCEPTED').length,
    REJECTED: baseRows.filter(r => r.state === 'PLANNER_REJECTED').length,
    ALL:      baseRows.length,
  };

  // KPIs
  const keyPending = pendingRows.filter(r => r.customerTier === 'KEY').length;
  const lateCount = pendingRows.filter(r => leadDays(r.submittedAt, r.targetDate) < cutoffDaysFor(r.dayType)).length;
  const alertCount = pendingRows.filter(r => Math.abs(r.qty - r.histP85) / r.histP85 > 0.30).length;

  function advance(row, target, note) {
    const newLog = [...(row.stateLog || []), { state: target, at: new Date().toISOString(), by: 'linh.tran@boxme.tech', note }];
    setOverrides(prev => ({ ...prev, [row.id]: { state: target, stateLog: newLog } }));
  }

  function handleDecide(target, note) {
    if (!decision) return;
    advance(decision.row, target, note);
    setDecision(null);
  }

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
      <PageHeader
        breadcrumb={['Lập kế hoạch','Duyệt yêu cầu']}
        title="Duyệt yêu cầu"
        subtitle={wh.code + ' · ' + counts.PENDING + ' yêu cầu chờ · ' + keyPending + ' từ khách KEY · ' + lateCount + ' trễ hạn'}
        actions={
          <>
            <Button variant="outline" size="sm" icon="upload" onClick={() => navigate('/planner/override-import')}>{stackedRow ? 'Gửi' : 'Gửi mới'}</Button>
            <Button variant="primary" size="sm" icon="plan" iconRight={stackedRow ? null : 'arrowRight'} onClick={() => navigate('/planner/shift-plan')}>
              {stackedRow ? 'Lập KH' : 'Tiếp tục lập kế hoạch'}
            </Button>
          </>
        }
      />
      <div style={{ flex: 1, overflowY: 'auto', overflowX: 'hidden', padding: isMobile ? 12 : 16, minWidth: 0 }}>
        {/* Compact KPI strip — 4 tiles */}
        <div style={{
          display: 'grid',
          gridTemplateColumns: vw < 560 ? 'repeat(2, 1fr)' : 'repeat(4, 1fr)',
          gap: 10, marginBottom: 12,
        }}>
          <CompactKpi label="Chờ duyệt"        value={counts.PENDING}  hint={'trên tổng ' + counts.ALL}/>
          <CompactKpi label="Khách KEY chờ"    value={keyPending}      hint=">2000 đơn/ngày · ưu tiên" tone={keyPending > 0 ? 'warn' : 'ok'}/>
          <CompactKpi label="Trễ hạn"          value={lateCount}       hint="sau cut-off · giảm ưu tiên" tone={lateCount > 0 ? 'warn' : 'ok'}/>
          <CompactKpi label="Cảnh báo Δ"       value={alertCount}      hint="vượt 30% so P85" tone={alertCount > 0 ? 'warn' : 'ok'}/>
        </div>

        {/* Filter bar */}
        <Card padding={0}>
          <div style={{
            padding: stackedRow ? '8px 10px' : '8px 12px',
            borderBottom: '1px solid var(--bx-border-muted)',
            display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap',
          }}>
            <div style={{ overflowX: 'auto', maxWidth: '100%', flex: stackedRow ? '1 1 100%' : '0 0 auto', margin: stackedRow ? '0 -2px' : 0 }}>
              <Tabs value={statusTab} onChange={setStatusTab} dense tabs={[
                { id: 'PENDING',  label: 'Chờ duyệt', count: counts.PENDING },
                { id: 'ACCEPTED', label: 'Đã duyệt',  count: counts.ACCEPTED },
                { id: 'REJECTED', label: 'Từ chối',   count: counts.REJECTED },
                { id: 'ALL',      label: 'Tất cả',    count: counts.ALL },
              ]}/>
            </div>
            <div style={{ flex: stackedRow ? 'unset' : 1 }}/>
            <div style={{
              display: 'flex', alignItems: 'center', gap: 6,
              flexWrap: 'nowrap', minWidth: 0,
              width: stackedRow ? '100%' : 'auto',
            }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 6, flex: '1 1 50%', minWidth: 0 }}>
                <span style={{ fontSize: 10, color: 'var(--bx-text-muted)', textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 700, flexShrink: 0, whiteSpace: 'nowrap' }}>Tier</span>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <Select size="sm" value={tierFilter} onChange={e => setTierFilter(e.target.value)} options={[
                    { value: 'all',      label: 'Tất cả' },
                    { value: 'KEY',      label: 'KEY · >2000' },
                    { value: 'MAJOR',    label: 'MAJOR · 500–2k' },
                    { value: 'STANDARD', label: 'STANDARD · <500' },
                  ]}/>
                </div>
              </div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 6, flex: '1 1 50%', minWidth: 0 }}>
                <span style={{ fontSize: 10, color: 'var(--bx-text-muted)', textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 700, flexShrink: 0, whiteSpace: 'nowrap' }}>Sự kiện</span>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <Select size="sm" value={eventFilter} onChange={e => setEventFilter(e.target.value)} options={[
                    { value: 'all',        label: 'Tất cả' },
                    { value: 'MEGA_SALE',  label: 'Mega Sale' },
                    { value: 'DOUBLE_DAY', label: 'Double Day' },
                    { value: 'MID_MONTH',  label: 'Mid-Month' },
                    { value: 'PAYDAY',     label: 'Payday' },
                    { value: 'MINI_SPIKE', label: 'Mini Spike' },
                    { value: 'NORMAL',     label: 'Bình thường' },
                  ]}/>
                </div>
              </div>
            </div>
          </div>

          {!stackedRow && <SubmitHeader compact={compact}/>}

          {rows.length === 0 ? (
            <Empty icon="check" title="Không có yêu cầu nào" subtitle="Không có yêu cầu khớp bộ lọc hiện tại."/>
          ) : rows.map(r => (
            <SubmitRow key={r.id} row={r}
              expanded={expandedId === r.id}
              compact={compact}
              stacked={stackedRow}
              onToggle={() => setExpandedId(expandedId === r.id ? null : r.id)}
              onMarkReviewed={() => advance(r, 'AM_REVIEWED')}
              onAccept={() => setDecision({ row: r, target: 'PLANNER_ACCEPTED' })}
              onReject={() => setDecision({ row: r, target: 'PLANNER_REJECTED' })}
            />
          ))}
        </Card>

        {/* Compact rule explainer */}
        <div style={{
          marginTop: 12, padding: '8px 12px', background: 'var(--bx-surface-2)', borderRadius: 8,
          fontSize: 11.5, color: 'var(--bx-text-muted)', display: 'flex', flexWrap: 'wrap', gap: 14, lineHeight: 1.5,
        }}>
          <span><b style={{ color: 'var(--bx-text-body)' }}>Cut-off:</b> Payday / Mid-Month / Mini Spike = trước 3 ngày · Double Day / Mega Sale = trước 7 ngày.</span>
          <span><b style={{ color: 'var(--bx-text-body)' }}>Bị từ chối:</b> hệ thống sẽ dùng số dự báo cho ngày đó.</span>
          <span><b style={{ color: 'var(--bx-text-body)' }}>So sánh:</b> P85 từ 90 ngày cùng loại sự kiện · vượt ±30% sẽ có cảnh báo cam.</span>
        </div>
      </div>

      <DecisionDialog d={decision} onClose={() => setDecision(null)} onConfirm={handleDecide}/>
    </div>
  );
}

// ---------- Compact KPI tile ----------
function CompactKpi({ label, value, hint, tone }) {
  const tc = tone === 'warn' ? 'var(--bx-warning-fg)' : tone === 'down' ? 'var(--bx-error)' : tone === 'up' ? 'var(--bx-success)' : 'var(--bx-text-heading)';
  const bg = tone === 'warn' ? 'var(--bx-warning-bg)' : '#fff';
  return (
    <div style={{ background: bg, border: '1px solid var(--bx-border-muted)', borderRadius: 10, padding: '10px 12px' }}>
      <div style={{ fontSize: 10.5, color: 'var(--bx-text-muted)', textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 700 }}>{label}</div>
      <div style={{ fontFamily: 'var(--bx-font-mono)', fontSize: 22, fontWeight: 700, color: tc, lineHeight: 1.1, marginTop: 4 }}>{value}</div>
      <div style={{ fontSize: 10.5, color: 'var(--bx-text-muted)', marginTop: 2 }}>{hint}</div>
    </div>
  );
}

// ---------- Compact tier + event badges ----------
function TierBadge({ tier, size }) {
  const m = {
    KEY:      { label: 'KEY',      bg: '#0b43ea', color: '#fff' },
    MAJOR:    { label: 'MAJOR',    bg: '#eef2ff', color: '#0b43ea' },
    STANDARD: { label: 'STANDARD', bg: 'var(--bx-surface-2)', color: 'var(--bx-text-muted)' },
  }[tier] || { label: tier, bg: '#eee', color: '#666' };
  return (
    <span style={{
      display: 'inline-block', padding: '1px 6px',
      borderRadius: 4, fontSize: size === 'sm' ? 9.5 : 10.5, fontWeight: 700,
      letterSpacing: '0.04em', color: m.color, background: m.bg, lineHeight: 1.4,
    }}>{m.label}</span>
  );
}
function EventBadge({ type, size }) {
  const m = {
    MEGA_SALE:  { label: 'Mega Sale',   color: '#fff',     bg: '#ff6467' },
    DOUBLE_DAY: { label: 'Double Day',  color: '#fff',     bg: '#a855f7' },
    MID_MONTH:  { label: 'Mid-Month',   color: '#fff',     bg: '#0b43ea' },
    PAYDAY:     { label: 'Payday',      color: '#fff',     bg: '#15803d' },
    MINI_SPIKE: { label: 'Mini Spike',  color: '#92400e',  bg: '#fef3c7' },
    NORMAL:     { label: 'Bình thường', color: 'var(--bx-text-muted)', bg: 'var(--bx-surface-2)' },
  }[type] || { label: type, color: '#666', bg: '#eee' };
  return (
    <span style={{
      display: 'inline-block', padding: '1px 7px',
      borderRadius: 9999, fontSize: size === 'sm' ? 10 : 11, fontWeight: 600,
      color: m.color, background: m.bg, lineHeight: 1.5, whiteSpace: 'nowrap',
    }}>{m.label}</span>
  );
}

// ---------- Header (grid, flexible) ----------
// Columns: chevron | customer | date+event | qty | [vs P85] | submitted | state | actions
function submitGridCols(compact) {
  return compact
    ? '22px minmax(160px, 1.6fr) minmax(96px, 0.9fr) minmax(72px, 0.6fr) minmax(108px, 1fr) minmax(92px, 0.7fr) minmax(196px, 1.3fr)'
    : '22px minmax(170px, 1.6fr) minmax(96px, 0.9fr) minmax(72px, 0.6fr) minmax(100px, 0.9fr) minmax(108px, 1fr) minmax(92px, 0.7fr) minmax(196px, 1.3fr)';
}
const HEAD_CELL = {
  padding: '8px 10px', fontSize: 10, fontWeight: 700, color: 'var(--bx-text-muted)',
  textTransform: 'uppercase', letterSpacing: '0.05em', whiteSpace: 'nowrap',
  overflow: 'hidden', textOverflow: 'ellipsis',
};
function SubmitHeader({ compact }) {
  return (
    <div style={{ display: 'grid', gridTemplateColumns: submitGridCols(compact),
      background: '#fafafa', borderBottom: '1px solid var(--bx-border-muted)' }}>
      <div/>
      <div style={HEAD_CELL}>Khách hàng</div>
      <div style={HEAD_CELL}>Ngày · Sự kiện</div>
      <div style={{ ...HEAD_CELL, textAlign: 'right' }}>Số gửi</div>
      {!compact && <div style={{ ...HEAD_CELL, textAlign: 'right' }}>vs P85</div>}
      <div style={HEAD_CELL}>Gửi lúc</div>
      <div style={HEAD_CELL}>Trạng thái</div>
      <div style={{ ...HEAD_CELL, textAlign: 'right' }}>Hành động</div>
    </div>
  );
}

// ---------- One submission row ----------
function SubmitRow({ row, expanded, compact, stacked, onToggle, onMarkReviewed, onAccept, onReject }) {
  const dlead = leadDays(row.submittedAt, row.targetDate);
  const dcut = cutoffDaysFor(row.dayType);
  const late = dlead < dcut;
  const deltaP85 = (row.qty - row.histP85) / row.histP85;
  const alert = Math.abs(deltaP85) > 0.30;
  const sm = STATE_META[row.state];
  const final = row.state === 'PLANNER_ACCEPTED' || row.state === 'PLANNER_REJECTED';

  if (stacked) {
    return <SubmitRowMobile row={row} expanded={expanded} onToggle={onToggle}
      onMarkReviewed={onMarkReviewed} onAccept={onAccept} onReject={onReject}
      late={late} alert={alert} deltaP85={deltaP85} sm={sm} final={final} dlead={dlead}/>;
  }

  const cellBase = { padding: '8px 10px', display: 'flex', alignItems: 'center', minWidth: 0 };

  return (
    <div style={{ borderBottom: '1px solid var(--bx-border-muted)' }}>
      <div style={{
        display: 'grid', gridTemplateColumns: submitGridCols(compact),
        background: expanded ? 'var(--bx-surface-2)' : '#fff',
        borderLeft: alert ? '3px solid var(--bx-warning-fg)' : late ? '3px solid #f59e0b' : '3px solid transparent',
      }}>
        <button onClick={onToggle} style={{
          border: 'none', background: 'transparent', cursor: 'pointer', padding: 0,
          display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--bx-text-muted)',
        }}>
          <Icon name={expanded ? 'chevron-down' : 'chevron-right'} size={10}/>
        </button>

        {/* Customer */}
        <div style={cellBase}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0, width: '100%' }}>
            <Avatar name={row.customerName} size={22}/>
            <div style={{ minWidth: 0, flex: 1 }}>
              <div style={{ fontSize: 12.5, fontWeight: 600, color: 'var(--bx-text-dark)',
                display: 'flex', alignItems: 'center', gap: 5, minWidth: 0 }}>
                <span style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', minWidth: 0 }}>{row.customerName}</span>
                <TierBadge tier={row.customerTier} size="sm"/>
              </div>
              <div style={{ fontSize: 10.5, color: 'var(--bx-text-muted)', fontFamily: 'var(--bx-font-mono)',
                whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                ~{fmt.int(row.customerAvgDaily)} đơn / ngày
              </div>
            </div>
          </div>
        </div>

        {/* Date + event */}
        <div style={cellBase}>
          <div style={{ minWidth: 0 }}>
            <div style={{ fontSize: 13, fontFamily: 'var(--bx-font-mono)', fontWeight: 700, color: 'var(--bx-text-dark)' }}>{fmt.shortDate(row.targetDate)}</div>
            <div style={{ marginTop: 2 }}><EventBadge type={row.dayType} size="sm"/></div>
          </div>
        </div>

        {/* Qty */}
        <div style={{ ...cellBase, justifyContent: 'flex-end' }}>
          <span style={{ fontFamily: 'var(--bx-font-mono)', fontSize: 14, fontWeight: 700, color: 'var(--bx-text-heading)' }}>{fmt.int(row.qty)}</span>
        </div>

        {/* vs P85 */}
        {!compact && (
          <div style={{ ...cellBase, justifyContent: 'flex-end' }}>
            <div style={{ textAlign: 'right' }}>
              <div style={{ fontSize: 10.5, color: 'var(--bx-text-muted)', fontFamily: 'var(--bx-font-mono)' }}>P85 {fmt.int(row.histP85)}</div>
              <DeltaBadge pct={deltaP85} alert={alert}/>
            </div>
          </div>
        )}

        {/* Submitted */}
        <div style={cellBase}>
          <div style={{ minWidth: 0 }}>
            <div style={{ fontSize: 11.5, fontFamily: 'var(--bx-font-mono)', color: 'var(--bx-text-body)' }}>{fmt.shortDate(row.submittedAt.slice(0,10))}</div>
            <div style={{ marginTop: 2, display: 'flex', alignItems: 'center', gap: 5, flexWrap: 'wrap' }}>
              {late ? (
                <span style={{ display: 'inline-flex', alignItems: 'center', gap: 3, padding: '1px 6px', borderRadius: 4, fontSize: 10, fontWeight: 700, background: '#fff7ed', color: '#c2410c', whiteSpace: 'nowrap' }}>
                  <Icon name="warning" size={9}/> Trễ
                </span>
              ) : (
                <span style={{ display: 'inline-block', padding: '1px 6px', borderRadius: 4, fontSize: 10, fontWeight: 700, background: '#f0fdf4', color: '#15803d', whiteSpace: 'nowrap' }}>
                  Đúng hạn
                </span>
              )}
              <span style={{ fontSize: 10.5, color: 'var(--bx-text-muted)', whiteSpace: 'nowrap' }}>
                {dlead >= 0 ? dlead + 'd' : Math.abs(dlead) + 'd sau'}
              </span>
            </div>
          </div>
        </div>

        {/* State */}
        <div style={cellBase}>
          <Badge kind={sm.kind} size="sm">{sm.short}</Badge>
        </div>

        {/* Actions */}
        <div style={{ padding: '8px 10px 8px 6px', display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 4, minWidth: 0, flexWrap: 'nowrap' }}>
          {row.state === 'NEW' && (
            <Button size="sm" variant="outline" icon="eye" onClick={onMarkReviewed}>AM xem</Button>
          )}
          {row.state === 'AM_REVIEWED' && (
            <>
              <Button size="sm" variant="outline" icon="x" onClick={onReject}>Từ chối</Button>
              <Button size="sm" variant="primary" icon="check" onClick={onAccept}>Duyệt</Button>
            </>
          )}
          {final && (
            <span style={{ fontSize: 10.5, color: 'var(--bx-text-muted)' }}>—</span>
          )}
        </div>
      </div>
      {expanded && <SubmitDetail row={row} late={late} dlead={dlead} dcut={dcut} alert={alert} deltaP85={deltaP85}/>}
    </div>
  );
}

// ---------- Stacked / mobile row (also used for tablet) ----------
function SubmitRowMobile({ row, expanded, onToggle, onMarkReviewed, onAccept, onReject, late, alert, deltaP85, sm, final, dlead }) {
  return (
    <div style={{ borderBottom: '1px solid var(--bx-border-muted)',
      borderLeft: alert ? '3px solid var(--bx-warning-fg)' : late ? '3px solid #f59e0b' : '3px solid transparent',
      background: expanded ? 'var(--bx-surface-2)' : '#fff' }}>
      <button onClick={onToggle} style={{
        width: '100%', textAlign: 'left', background: 'transparent', border: 'none', cursor: 'pointer',
        padding: '12px 14px', display: 'grid',
        gridTemplateColumns: 'auto 1fr auto', gap: 10, alignItems: 'center',
      }}>
        <Icon name={expanded ? 'chevron-down' : 'chevron-right'} size={11} style={{ color: 'var(--bx-text-muted)' }}/>
        <div style={{ minWidth: 0 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
            <span style={{ fontSize: 13, fontWeight: 600, color: 'var(--bx-text-dark)', minWidth: 0,
              overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: '100%' }}>{row.customerName}</span>
            <TierBadge tier={row.customerTier} size="sm"/>
            <EventBadge type={row.dayType} size="sm"/>
          </div>
          <div style={{ marginTop: 5, fontSize: 11.5, color: 'var(--bx-text-muted)', display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 8, rowGap: 4 }}>
            <span style={{ fontFamily: 'var(--bx-font-mono)', color: 'var(--bx-text-body)', fontWeight: 600 }}>{fmt.shortDate(row.targetDate)}</span>
            <span style={{ fontFamily: 'var(--bx-font-mono)' }}>
              <b style={{ color: 'var(--bx-text-dark)' }}>{fmt.int(row.qty)}</b> đơn
            </span>
            <span style={{ fontFamily: 'var(--bx-font-mono)' }}>P85 {fmt.int(row.histP85)}</span>
            <DeltaBadge pct={deltaP85} alert={alert}/>
            {late && (
              <span style={{ display: 'inline-flex', alignItems: 'center', gap: 3, padding: '1px 6px', borderRadius: 4, fontSize: 10, fontWeight: 700, background: '#fff7ed', color: '#c2410c', whiteSpace: 'nowrap' }}>
                <Icon name="warning" size={9}/> Trễ {Math.max(0, cutoffDaysFor(row.dayType) - dlead)}d
              </span>
            )}
          </div>
        </div>
        <Badge kind={sm.kind} size="sm">{sm.short}</Badge>
      </button>
      {expanded && (
        <div style={{ padding: '0 14px 14px' }}>
          <SubmitDetail row={row} late={late} dlead={leadDays(row.submittedAt, row.targetDate)} dcut={cutoffDaysFor(row.dayType)} alert={alert} deltaP85={deltaP85} mobile/>
          {!final && (
            <div style={{ display: 'flex', gap: 6, marginTop: 12 }}>
              {row.state === 'NEW' && <Button variant="outline" icon="eye" onClick={onMarkReviewed} style={{ flex: 1 }}>AM đã xem</Button>}
              {row.state === 'AM_REVIEWED' && (
                <>
                  <Button variant="outline" icon="x" onClick={onReject} style={{ flex: 1 }}>Từ chối</Button>
                  <Button variant="primary" icon="check" onClick={onAccept} style={{ flex: 1 }}>Duyệt</Button>
                </>
              )}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// ---------- Δ vs P85 badge ----------
function DeltaBadge({ pct, alert }) {
  const sign = pct > 0 ? '+' : '';
  const text = sign + (pct * 100).toFixed(0) + '%';
  if (alert) {
    return (
      <span style={{
        display: 'inline-flex', alignItems: 'center', gap: 3,
        padding: '2px 6px', borderRadius: 9999,
        background: 'var(--bx-warning-bg)', color: 'var(--bx-warning-fg)',
        fontSize: 10.5, fontWeight: 700, fontFamily: 'var(--bx-font-mono)',
      }}>
        <Icon name="warning" size={9}/>{text}
      </span>
    );
  }
  return (
    <span style={{ fontFamily: 'var(--bx-font-mono)', fontSize: 11.5, fontWeight: 600,
      color: Math.abs(pct) < 0.10 ? 'var(--bx-text-muted)' : pct > 0 ? 'var(--bx-success)' : 'var(--bx-error)' }}>{text}</span>
  );
}

// ---------- Expanded detail panel (simple) ----------
function SubmitDetail({ row, late, dlead, dcut, alert, deltaP85, mobile }) {
  return (
    <div style={{ padding: mobile ? '4px 0 0' : '12px 16px 14px 36px',
      borderTop: mobile ? 'none' : '1px dashed var(--bx-border-muted)' }}>
      {/* State pipeline */}
      <StatePipeline state={row.state} log={row.stateLog}/>

      {/* Info grid */}
      <div style={{ display: 'grid',
        gridTemplateColumns: mobile ? '1fr' : 'repeat(2, 1fr)',
        gap: 10, marginTop: 12 }}>
        <DetailBlock label="Thông tin gửi">
          <KV2 label="Nguồn">{sourceLabel(row.source)}</KV2>
          <KV2 label="Người gửi"><span style={{ fontFamily: 'var(--bx-font-mono)', fontSize: 11.5 }}>{row.submittedBy}</span></KV2>
          <KV2 label="Lúc"><span style={{ fontFamily: 'var(--bx-font-mono)', fontSize: 11.5 }}>{fmt.dateTime(row.submittedAt)}</span></KV2>
          <KV2 label="Lý do"><span style={{ fontSize: 11.5, fontStyle: 'italic' }}>{row.reason || '—'}</span></KV2>
        </DetailBlock>
        <DetailBlock label="Đúng hạn">
          <KV2 label="Cut-off">{dcut} ngày trước (loại {row.dayType.replace('_',' ').toLowerCase()})</KV2>
          <KV2 label="Cách target">{dlead >= 0 ? dlead + ' ngày' : Math.abs(dlead) + ' ngày sau'}</KV2>
          <KV2 label="Kết luận">{late ? (
            <span style={{ color: '#c2410c', fontWeight: 600 }}>Trễ hạn · giảm ưu tiên</span>
          ) : (
            <span style={{ color: 'var(--bx-success)', fontWeight: 600 }}>Đúng hạn</span>
          )}</KV2>
        </DetailBlock>

        <DetailBlock label="Cùng kỳ 90 ngày">
          <div style={{ display: 'flex', gap: 16, fontFamily: 'var(--bx-font-mono)' }}>
            <div>
              <div style={{ fontSize: 10, color: 'var(--bx-text-muted)', textTransform: 'uppercase', letterSpacing: '0.04em', fontWeight: 700 }}>TB</div>
              <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--bx-text-dark)' }}>{fmt.int(row.histAvg)}</div>
            </div>
            <div>
              <div style={{ fontSize: 10, color: 'var(--bx-text-muted)', textTransform: 'uppercase', letterSpacing: '0.04em', fontWeight: 700 }}>P85</div>
              <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--bx-text-dark)' }}>{fmt.int(row.histP85)}</div>
            </div>
            <div>
              <div style={{ fontSize: 10, color: 'var(--bx-text-muted)', textTransform: 'uppercase', letterSpacing: '0.04em', fontWeight: 700 }}>Δ</div>
              <DeltaBadge pct={deltaP85} alert={alert}/>
            </div>
            <div>
              <div style={{ fontSize: 10, color: 'var(--bx-text-muted)', textTransform: 'uppercase', letterSpacing: '0.04em', fontWeight: 700 }}>Mẫu</div>
              <div style={{ fontSize: 12, color: 'var(--bx-text-muted)' }}>{row.histSamples}</div>
            </div>
          </div>
        </DetailBlock>
        <DetailBlock label="Nếu từ chối">
          <div style={{ fontSize: 11.5, color: 'var(--bx-text-body)' }}>
            Sẽ dùng dự báo hệ thống: <b style={{ fontFamily: 'var(--bx-font-mono)' }}>{fmt.int(row.forecastQty)}</b> đơn.
            KAM cần gửi lại nếu muốn ghi đè.
          </div>
        </DetailBlock>
      </div>
    </div>
  );
}

function StatePipeline({ state, log }) {
  if (state === 'PLANNER_REJECTED') {
    // Rejected variant: show NEW → AM → Rejected
    const steps = ['NEW', 'AM_REVIEWED', 'PLANNER_REJECTED'];
    return <PipelineDots steps={steps} current={state} log={log}/>;
  }
  return <PipelineDots steps={STATE_FLOW} current={state} log={log}/>;
}
function PipelineDots({ steps, current, log }) {
  const curIdx = steps.indexOf(current);
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 0, flexWrap: 'wrap' }}>
      {steps.map((s, i) => {
        const isCurrent = s === current;
        const isDone = i < curIdx;
        const isFuture = i > curIdx;
        const isReject = s === 'PLANNER_REJECTED';
        const entry = log?.find(e => e.state === s);
        const bg = isReject && isCurrent ? '#fef2f2' : isCurrent ? (s === 'PLANNER_ACCEPTED' ? '#f0fdf4' : '#eef2ff') : isDone ? '#f0fdf4' : '#fafafa';
        const fg = isReject && isCurrent ? '#b91c1c' : isCurrent ? (s === 'PLANNER_ACCEPTED' ? '#15803d' : '#0b43ea') : isDone ? '#15803d' : 'var(--bx-text-muted)';
        return (
          <React.Fragment key={s}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 6,
              padding: '4px 10px', borderRadius: 9999, background: bg, color: fg,
              border: '1px solid ' + (isCurrent ? fg : 'transparent'),
            }}>
              <Icon name={isReject && (isDone || isCurrent) ? 'x' : isDone ? 'check' : isCurrent ? 'spark' : 'info'} size={10}/>
              <span style={{ fontSize: 11, fontWeight: 700 }}>{STATE_META[s].label}</span>
              {entry && (
                <span style={{ fontSize: 10, opacity: 0.7, fontFamily: 'var(--bx-font-mono)' }}>
                  · {entry.at.slice(5,10)} {entry.at.slice(11,16)}
                </span>
              )}
            </div>
            {i < steps.length - 1 && (
              <div style={{ width: 16, height: 1.5, background: i < curIdx ? '#86efac' : 'var(--bx-border-muted)' }}/>
            )}
          </React.Fragment>
        );
      })}
    </div>
  );
}

function DetailBlock({ label, children }) {
  return (
    <div style={{ background: '#fff', border: '1px solid var(--bx-border-muted)', borderRadius: 8, padding: 10 }}>
      <div style={{ fontSize: 10, color: 'var(--bx-text-muted)', textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 700, marginBottom: 6 }}>{label}</div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>{children}</div>
    </div>
  );
}
function KV2({ label, children }) {
  return (
    <div style={{ display: 'flex', alignItems: 'baseline', gap: 10, fontSize: 11.5 }}>
      <div style={{ flex: '0 0 70px', color: 'var(--bx-text-muted)' }}>{label}</div>
      <div style={{ flex: 1, minWidth: 0, color: 'var(--bx-text-dark)' }}>{children}</div>
    </div>
  );
}
function sourceLabel(s) {
  const m = { file: 'Tệp', api: 'API', form: 'Nhập tay', import: 'Tệp' };
  return m[s] || s;
}

// ---------- Decision dialog ----------
function DecisionDialog({ d, onClose, onConfirm }) {
  const [note, setNote] = React.useState('');
  React.useEffect(() => { setNote(''); }, [d]);
  if (!d) return null;
  const approve = d.target === 'PLANNER_ACCEPTED';
  const row = d.row;
  return (
    <Dialog open={true} onClose={onClose} destructive={!approve} width={520}
      title={approve ? 'Duyệt yêu cầu của ' + row.customerName + '?' : 'Từ chối yêu cầu của ' + row.customerName + '?'}
      subtitle={fmt.date(row.targetDate) + ' · ' + fmt.int(row.qty) + ' đơn'}
      footer={
        <>
          <Button variant="outline" onClick={onClose}>Hủy</Button>
          <Button variant={approve ? 'primary' : 'danger'} icon={approve ? 'check' : 'x'}
            disabled={!approve && !note}
            onClick={() => onConfirm(d.target, note)}>
            {approve ? 'Duyệt' : 'Từ chối · dùng dự báo'}
          </Button>
        </>
      }>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
        {!approve && (
          <div style={{ padding: 10, background: 'var(--bx-warning-bg)', color: 'var(--bx-warning-fg)', borderRadius: 8, fontSize: 12, display: 'flex', gap: 8 }}>
            <Icon name="warning" size={13} style={{ marginTop: 2 }}/>
            <div>Hệ thống sẽ dùng số dự báo <b style={{ fontFamily: 'var(--bx-font-mono)' }}>{fmt.int(row.forecastQty)}</b> cho ngày này. KAM cần gửi lại nếu muốn ghi đè.</div>
          </div>
        )}
        <Field label="Ghi chú" required={!approve} hint={approve ? 'Tùy chọn · sẽ ghi vào nhật ký.' : 'Bắt buộc khi từ chối.'}>
          <Input value={note} onChange={e => setNote(e.target.value)}
            placeholder={approve ? 'VD: KAM xác nhận' : 'VD: Không liên hệ được KAM, dùng dự báo'}/>
        </Field>
      </div>
    </Dialog>
  );
}

// ============================================================
// Shift plan — the hero page
// ============================================================
function ShiftPlanScreen({ warehouseId, navigate, addToast }) {
  const plan = WFP.SHIFT_PLAN;
  const [tab, setTab] = React.useState('layer1');
  const [warningOpen, setWarningOpen] = React.useState(false);
  const [promoteOpen, setPromoteOpen] = React.useState(false);
  const [reRunning, setReRunning] = React.useState(false);
  const { isMobile, isTablet, vw } = useViewport();
  const stacked = vw < 1024;
  // start the rail open on desktop only
  React.useEffect(() => { setWarningOpen(!stacked); }, [stacked]);

  const tabs = [
    { id: 'layer1', idx: 1, label: 'Decomposition' },
    { id: 'layer2', idx: 2, label: 'Hours needed' },
    { id: 'layer3', idx: 3, label: 'Headcount' },
    { id: 'layer4', idx: 4, label: 'Shift allocation' },
    { id: 'layer5', idx: 5, label: 'Final plan', count: '· ' + plan.layer5.gap.total + ' gap' },
  ];

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0 }}>
      <PageHeader
        breadcrumb={['Planner','Shift plan', fmt.date(plan.date)]}
        title={'Shift plan · ' + fmt.date(plan.date)}
        subtitle={'Plan ' + plan.id + ' · day type ' + plan.dayType + ' · forecast ' + fmt.int(plan.forecastQty) + ' orders'}
        status={<Badge kind={plan.status}>{plan.status}</Badge>}
        actions={
          <>
            {!isMobile && <Button variant="outline" icon="refresh" onClick={() => { setReRunning(true); setTimeout(() => { setReRunning(false); addToast({ kind: 'INFO', title: 'Engine re-ran', body: '5 layers regenerated in 1.2s.' }); }, 1100); }}>
              {reRunning ? 'Re-running…' : 'Re-run engine'}
            </Button>}
            {!isTablet && !isMobile && <Button variant="outline" icon="diff">Compare to last</Button>}
            {stacked && <Button variant="outline" icon="warning" onClick={() => setWarningOpen(true)}>
              {plan.warnings.length} warning{plan.warnings.length === 1 ? '' : 's'}
            </Button>}
            <Button variant="primary" icon="rocket" onClick={() => setPromoteOpen(true)}>{isMobile ? 'Promote' : 'Promote plan'}</Button>
          </>
        }
      >
        <div style={{ marginTop: 4, overflowX: 'auto', marginLeft: -8, marginRight: -8, paddingLeft: 8, paddingRight: 8 }}>
          <div style={{ minWidth: 'fit-content' }}>
            <SectionTabs value={tab} onChange={setTab} tabs={tabs}/>
          </div>
        </div>
      </PageHeader>

      <div style={{ flex: 1, display: 'flex', minHeight: 0 }}>
        <div style={{ flex: 1, overflowY: 'auto', padding: isMobile ? 14 : 20, position: 'relative' }}>
          {reRunning && <ReRunOverlay/>}
          {tab === 'layer1' && <Layer1Panel plan={plan}/>}
          {tab === 'layer2' && <Layer2Panel plan={plan}/>}
          {tab === 'layer3' && <Layer3Panel plan={plan}/>}
          {tab === 'layer4' && <Layer4Panel plan={plan}/>}
          {tab === 'layer5' && <Layer5Panel plan={plan} navigate={navigate}/>}
        </div>
        {!stacked && <WarningsRail plan={plan} open={warningOpen} setOpen={setWarningOpen}/>}
        {stacked && <WarningsSheet plan={plan} open={warningOpen} onClose={() => setWarningOpen(false)}/>}
      </div>

      <Dialog open={promoteOpen} onClose={() => setPromoteOpen(false)}
        title="Promote plan to active?"
        subtitle={'This will lock the plan, send hiring requests, and freeze the config snapshot used.'}
        footer={
          <>
            <Button variant="outline" onClick={() => setPromoteOpen(false)}>Cancel</Button>
            <Button variant="primary" icon="rocket" onClick={() => {
              setPromoteOpen(false);
              addToast({ kind: 'FULFILLED', title: 'Plan promoted', body: '3 hiring requests sent to ops manager for approval.' });
              navigate('/planner/hiring');
            }}>Promote and continue to hiring</Button>
          </>
        }>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
          <PromoteStat label="Forecast" value={fmt.int(plan.forecastQty) + ' orders'} mono/>
          <PromoteStat label="Headcount" value={plan.layer5.assigned.total + ' / ' + plan.layer5.required.total + ' (gap ' + plan.layer5.gap.total + ')'} mono/>
          <PromoteStat label="OT projected" value={fmt.hrs(plan.layer5.otHours) + ' / ' + fmt.hrs(plan.layer5.otCapHours) + ' cap'} mono/>
          <PromoteStat label="Hiring requests"  value={plan.layer5.hiringRequests.length + ' will be created in DRAFT'} mono/>
          <PromoteStat label="Warnings"  value={
            <span style={{ display: 'inline-flex', gap: 6 }}>
              <Badge kind="CRITICAL" size="sm">1 critical</Badge>
              <Badge kind="WARNING"  size="sm">1 warning</Badge>
            </span>
          }/>
        </div>
        <div style={{ marginTop: 14, padding: 10, background: '#fef2f2', borderRadius: 8, fontSize: 12, color: '#b91c1c', display: 'flex', gap: 8 }}>
          <Icon name="warning" size={13} style={{ marginTop: 2 }}/>
          <div>1 CRITICAL warning is unresolved (<b>OT_NEAR_CAP</b>). You can still promote, but acknowledge in the dialog before sending.</div>
        </div>
        <label style={{ display: 'flex', gap: 8, alignItems: 'center', marginTop: 10, fontSize: 12.5, color: 'var(--bx-text-dark)', cursor: 'pointer' }}>
          <input type="checkbox" style={{ accentColor: 'var(--bx-primary)' }}/>
          I acknowledge unresolved warnings and want to proceed.
        </label>
      </Dialog>
    </div>
  );
}

function ReRunOverlay() {
  return (
    <div style={{
      position: 'absolute', inset: 0, background: 'rgba(255,255,255,0.7)', zIndex: 10,
      display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column', gap: 12,
      backdropFilter: 'blur(2px)',
    }}>
      <div style={{ width: 36, height: 36, borderRadius: 9999, border: '3px solid #eef2ff', borderTopColor: 'var(--bx-primary)', animation: 'wfpSpin 0.9s linear infinite' }}/>
      <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--bx-text-heading)' }}>Running engine · 5 layers</div>
      <div style={{ fontSize: 11.5, color: 'var(--bx-text-muted)' }}>Resolving config snapshot…</div>
    </div>
  );
}

function PromoteStat({ label, value, mono }) {
  return (
    <div style={{ display: 'flex', justifyContent: 'space-between', padding: '8px 10px', background: 'var(--bx-surface-2)', borderRadius: 6, fontSize: 12.5 }}>
      <span style={{ color: 'var(--bx-text-muted)' }}>{label}</span>
      <span style={{ fontFamily: mono ? 'var(--bx-font-mono)' : 'inherit', fontVariantNumeric: 'tabular-nums', fontWeight: 600, color: 'var(--bx-text-dark)' }}>{value}</span>
    </div>
  );
}

// ---------- Layer 1 ----------
function Layer1Panel({ plan }) {
  const L1 = plan.layer1;
  const colors = ['#0b43ea','#4f7dff','#a4b5ff','#dde4ff','#c5d0ff','#8da7ff'];
  const { isMobile, vw } = useViewport();
  const stacked = vw < 900;
  return (
    <div style={{ display: 'grid', gap: 16 }}>
      <LayerHeader idx={1} title="Forecast decomposition"
        explain="Total forecast is split by complexity mix and customer mix. UPH multipliers in Layer 2 use the complexity proportions."/>
      <div style={{ display: 'grid', gridTemplateColumns: stacked ? '1fr' : '1fr 1fr', gap: 16 }}>
        <Card title="Complexity mix" subtitle="Pct of volume by complexity level">
          <div style={{ display: 'flex', height: 24, borderRadius: 6, overflow: 'hidden', marginBottom: 14 }}>
            {L1.complexityMix.map((c, i) => (
              <div key={c.level} style={{
                flex: c.pctVolume, background: colors[i], display: 'flex',
                alignItems: 'center', justifyContent: 'center', color: '#fff',
                fontSize: 11, fontWeight: 700,
              }}>{(c.pctVolume * 100).toFixed(0)}%</div>
            ))}
          </div>
          <Table dense columns={[
            { header: 'Level', render: r => (
              <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
                <span style={{ width: 8, height: 8, borderRadius: 2, background: colors[L1.complexityMix.indexOf(r)] }}></span>
                <span style={{ fontWeight: 600 }}>{r.level}</span>
              </span>
            )},
            { header: '% volume', mono: true, align: 'right', render: r => (r.pctVolume * 100).toFixed(0) + '%' },
            { header: 'Qty',     mono: true, align: 'right', render: r => fmt.int(r.qty) },
            { header: 'UPH ×',  mono: true, align: 'right', render: r => '×' + r.uphMultiplier.toFixed(2) },
          ]} rows={L1.complexityMix} getRowKey={r => r.level}/>
        </Card>

        <Card title="Customer mix" subtitle="Top sellers contributing to tomorrow's volume">
          <Table dense columns={[
            { header: 'Customer', render: r => <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
              <Avatar name={r.name} size={22}/>
              <span style={{ fontWeight: 500 }}>{r.name}</span>
            </span>},
            { header: 'Qty', mono: true, align: 'right', render: r => fmt.int(r.qty) },
            { header: 'Share', render: r => {
              const pct = r.qty / plan.forecastQty;
              return (
                <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                  <div style={{ flex: 1, height: 6, background: 'var(--bx-surface-2)', borderRadius: 9999, overflow: 'hidden' }}>
                    <div style={{ width: (pct * 100) + '%', height: '100%', background: 'var(--bx-primary)' }}></div>
                  </div>
                  <span style={{ fontFamily: 'var(--bx-font-mono)', fontSize: 11.5, color: 'var(--bx-text-muted)', minWidth: 36, textAlign: 'right' }}>{(pct * 100).toFixed(0)}%</span>
                </div>
              );
            }},
          ]} rows={L1.customerMix} getRowKey={r => r.customerId}/>
        </Card>
      </div>
    </div>
  );
}

// ---------- Layer 2 ----------
function Layer2Panel({ plan }) {
  const L2 = plan.layer2;
  const data = L2.byRole.map(r => ({
    label: r.role.toLowerCase(),
    L1: r.L1, L2: r.L2, L3: r.L3,
  }));
  const { isMobile, vw } = useViewport();
  return (
    <div style={{ display: 'grid', gap: 16 }}>
      <LayerHeader idx={2} title="Hours needed"
        explain="qty ÷ UPH(role,level,staffType) = hours. Sum by role, then break down by level. UPH table is the resolved config snapshot."/>
      <div style={{
        display: 'grid',
        gridTemplateColumns: vw < 560 ? 'repeat(2, 1fr)' : 'repeat(4, 1fr)',
        gap: 12,
      }}>
        <Metric label="Total hours"  value={fmt.hrs(L2.totals.totalHours)} sublabel="across pick/pack/check"/>
        <Metric label="Pick hours"   value={fmt.hrs(L2.totals.pickHours)}  sublabel={(L2.totals.pickHours / L2.totals.totalHours * 100).toFixed(0) + '% of total'}/>
        <Metric label="Pack hours"   value={fmt.hrs(L2.totals.packHours)}  sublabel={(L2.totals.packHours / L2.totals.totalHours * 100).toFixed(0) + '% of total'}/>
        <Metric label="Check hours"  value={fmt.hrs(L2.totals.checkHours)} sublabel={(L2.totals.checkHours / L2.totals.totalHours * 100).toFixed(0) + '% of total'}/>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: vw < 1100 ? '1fr' : '2fr 1fr', gap: 16 }}>
        <Card title="Hours by role · stacked by level" subtitle="From qty × complexity × (1/UPH) summed across customers.">
          <ChartLegend items={[
            { label: 'L1', color: '#a4b5ff' },
            { label: 'L2', color: '#4f7dff' },
            { label: 'L3', color: '#0b43ea' },
          ]} style={{ marginBottom: 8 }}/>
          <StackedBar data={data} stacks={[
            { key: 'L1', color: '#a4b5ff', label: 'L1' },
            { key: 'L2', color: '#4f7dff', label: 'L2' },
            { key: 'L3', color: '#0b43ea', label: 'L3' },
          ]} height={240}/>
        </Card>

        <Card title="UPH table snapshot" subtitle="Resolved config v107 · units / hr">
          <Table dense columns={[
            { header: 'Role', render: r => <span style={{ fontWeight: 600 }}>{r.role}</span> },
            { header: 'L1', mono: true, align: 'right' },
            { header: 'L2', mono: true, align: 'right' },
            { header: 'L3', mono: true, align: 'right' },
            { header: 'Temp · L1', mono: true, align: 'right', key: 'temp_L1' },
          ]} rows={L2.uphTableSnapshot} getRowKey={r => r.role}/>
          <a href="#" onClick={e => e.preventDefault()} style={{ fontSize: 12, marginTop: 10, display: 'inline-block' }}>
            Open in config registry <Icon name="external" size={10}/>
          </a>
        </Card>
      </div>

      <Card title="Hours · detail" padding={0}>
        <Table dense columns={[
          { header: 'Role',    render: r => <span style={{ fontWeight: 600 }}>{r.role}</span> },
          { header: 'L1 (h)',  mono: true, align: 'right', render: r => r.L1.toFixed(1) },
          { header: 'L2 (h)',  mono: true, align: 'right', render: r => r.L2.toFixed(1) },
          { header: 'L3 (h)',  mono: true, align: 'right', render: r => r.L3.toFixed(1) },
          { header: 'Total',   mono: true, align: 'right', render: r => <b>{(r.L1 + r.L2 + r.L3).toFixed(1)}</b> },
          { header: 'Share',   render: r => {
            const tot = r.L1 + r.L2 + r.L3;
            return (
              <div style={{ display: 'flex', gap: 4, alignItems: 'center' }}>
                <div style={{ flex: 1, height: 6, background: 'var(--bx-surface-2)', borderRadius: 9999, overflow: 'hidden' }}>
                  <div style={{ width: (tot / L2.totals.totalHours * 100) + '%', height: '100%', background: 'var(--bx-primary)' }}></div>
                </div>
              </div>
            );
          }},
        ]} rows={L2.byRole} getRowKey={r => r.role}/>
      </Card>
    </div>
  );
}

// ---------- Layer 3 ----------
function Layer3Panel({ plan }) {
  const L3 = plan.layer3;
  const { vw } = useViewport();
  return (
    <div style={{ display: 'grid', gap: 16 }}>
      <LayerHeader idx={3} title="Headcount needed"
        explain="hours ÷ shift length, then apply shrinkage (6.5% official, 14% temp). Result is gross headcount, plus an extra buffer baked in."/>
      <div style={{ display: 'grid', gridTemplateColumns: vw < 560 ? 'repeat(2, 1fr)' : 'repeat(4, 1fr)', gap: 12 }}>
        <Metric label="Gross headcount" value={L3.grossHeadcount} sublabel="from hours / shift length"/>
        <Metric label="Shrinkage buffer" value={'+' + L3.shrinkageAdj} sublabel="6.5% / 14% applied" deltaTone="up"/>
        <Metric label="Net headcount" value={L3.netHeadcount} sublabel="target for Layer 4"/>
        <Metric label="Of which · temp" value={L3.byRole.reduce((s,r) => s + r.temp, 0)} sublabel={Math.round(L3.byRole.reduce((s,r) => s + r.temp, 0) / L3.netHeadcount * 100) + '% of plan'}/>
      </div>

      <Card title="Headcount by role" padding={0}>
        <Table columns={[
          { header: 'Role', render: r => <span style={{ fontWeight: 600 }}>{r.role}</span> },
          { header: 'Official', mono: true, align: 'right' },
          { header: 'Temp',     mono: true, align: 'right' },
          { header: 'Total',    mono: true, align: 'right', render: r => <b>{r.total}</b> },
          { header: 'Composition', render: r => {
            const pctO = (r.official / r.total) * 100;
            return (
              <div style={{ display: 'flex', height: 8, background: 'var(--bx-surface-2)', borderRadius: 9999, overflow: 'hidden', minWidth: 160 }}>
                <div style={{ width: pctO + '%', background: 'var(--bx-primary)' }}></div>
                <div style={{ width: (100 - pctO) + '%', background: '#fde68a' }}></div>
              </div>
            );
          }},
        ]} rows={L3.byRole} getRowKey={r => r.role}/>
      </Card>

      <Card title="Formula trace" subtitle="How Layer 2 hours became Layer 3 headcount">
        <div style={{ display: 'flex', flexDirection: 'column', gap: 6, fontFamily: 'var(--bx-font-mono)', fontSize: 12.5, color: 'var(--bx-text-body)' }}>
          <code>net_headcount = ceil(total_hours / shift_length) × (1 + shrinkage)</code>
          <code>= ceil(<b>251.9h</b> / 8h) × (1 + <b>weighted shrinkage 0.085</b>)</code>
          <code>= 32 × 1.085 → ceil → <b style={{ color: 'var(--bx-primary)' }}>41 people</b></code>
        </div>
        <div style={{ fontSize: 11, color: 'var(--bx-text-muted)', marginTop: 10 }}>
          Shrinkage values resolved from <span style={{ fontFamily: 'var(--bx-font-mono)' }}>engine.shrinkage.official</span> (v4 · 6.5%) and <span style={{ fontFamily: 'var(--bx-font-mono)' }}>engine.shrinkage.temp</span> (v3 · 14%).
        </div>
      </Card>
    </div>
  );
}

// ---------- Layer 4 ----------
function Layer4Panel({ plan }) {
  const L4 = plan.layer4;
  return (
    <div style={{ display: 'grid', gap: 16 }}>
      <LayerHeader idx={4} title="Shift allocation"
        explain="Headcount split across configured shifts (CA_SANG, CA_CHIEU, CA_DEM). Coverage by hour should stay above demand curve."/>

      <Card title="Coverage by hour" subtitle="Stacked headcount per shift across 24h">
        <ChartLegend items={[
          { color: '#0b43ea', label: 'Morning · 08–17' },
          { color: '#4f7dff', label: 'Afternoon · 13–22' },
          { color: '#a4b5ff', label: 'Night · 22–06' },
        ]} style={{ marginBottom: 12 }}/>
        <CoverageChart data={L4.coverageByHour}/>
      </Card>

      <Card title="Shift detail" padding={0}>
        <Table columns={[
          { header: 'Shift', render: r => (
            <div>
              <div style={{ fontFamily: 'var(--bx-font-mono)', fontWeight: 600, color: 'var(--bx-text-dark)' }}>{r.code}</div>
              <div style={{ fontSize: 11, color: 'var(--bx-text-muted)' }}>{r.label} · {r.start}–{r.end}</div>
            </div>
          )},
          { header: 'Pickers',  mono: true, align: 'right', key: 'pickers' },
          { header: 'Packers',  mono: true, align: 'right', key: 'packers' },
          { header: 'Checkers', mono: true, align: 'right', key: 'checkers' },
          { header: 'Leads',    mono: true, align: 'right', key: 'leads' },
          { header: 'Total',    mono: true, align: 'right', render: r => <b>{r.total}</b> },
        ]} rows={L4.shifts} getRowKey={r => r.code}/>
      </Card>
    </div>
  );
}

// ---------- Layer 5 ----------
function Layer5Panel({ plan, navigate }) {
  const L5 = plan.layer5;
  const { vw } = useViewport();
  return (
    <div style={{ display: 'grid', gap: 16 }}>
      <LayerHeader idx={5} title="Final plan"
        explain="Bind people to shifts. Anything we can't cover internally becomes a HiringRequest in DRAFT, ready to send."/>

      <div style={{ display: 'grid', gridTemplateColumns: vw < 560 ? 'repeat(2, 1fr)' : 'repeat(4, 1fr)', gap: 12 }}>
        <Metric label="Required" value={L5.required.total} sublabel="net headcount"/>
        <Metric label="Assigned" value={L5.assigned.total} sublabel={L5.assigned.official + ' official · ' + L5.assigned.temp + ' temp'}/>
        <Metric label="Gap" value={L5.gap.total} sublabel={L5.gap.temp + ' temp pickers/packers'} deltaTone="down" delta="must-fill"/>
        <Metric label="OT projected" value={fmt.hrs(L5.otHours)} sublabel={'of ' + fmt.hrs(L5.otCapHours) + ' cap'} deltaTone={L5.otHours / L5.otCapHours > 0.85 ? 'down' : 'up'} delta={Math.round(L5.otHours / L5.otCapHours * 100) + '%'}/>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: vw < 1100 ? '1fr' : '2fr 1fr', gap: 16 }}>
        <Card title="Hiring requests · auto-generated" subtitle={L5.hiringRequests.length + ' draft requests for ops manager approval'}
          actions={<Button variant="primary" size="sm" icon="hiring" onClick={() => navigate('/planner/hiring')}>Review hiring</Button>}
          padding={0}>
          <Table dense columns={[
            { header: 'Shift',  render: r => <span style={{ fontFamily: 'var(--bx-font-mono)', fontWeight: 600 }}>{r.shiftCode}</span> },
            { header: 'Role',   render: r => <span>{r.role.toLowerCase() + ' · ' + r.level}</span> },
            { header: 'Type',   render: r => <Badge kind="WARNING" size="sm">{r.staffType}</Badge> },
            { header: 'Count',  mono: true, align: 'right', render: r => <b>{r.count}</b> },
            { header: 'Status', render: () => <Badge kind="DRAFT">DRAFT</Badge> },
          ]} rows={L5.hiringRequests} getRowKey={r => r.id}/>
        </Card>

        <Card title="Cost summary" subtitle="Labor cost projection for tomorrow">
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            <KV label="Total labor"   mono value={fmt.vnd(L5.cost.laborVND)}/>
            <KV label="Per order"     mono value={fmt.vnd(Math.round(L5.cost.laborVND / plan.forecastQty))}/>
            <KV label="Official rate" mono value="42,000đ / hr · L1"/>
            <KV label="Temp rate"     mono value="32,000đ / hr · L1"/>
            <KV label="OT premium"    mono value={fmt.hrs(L5.otHours) + ' × 1.5'}/>
          </div>
          <div style={{ marginTop: 12, paddingTop: 12, borderTop: '1px solid var(--bx-border-muted)' }}>
            <Button variant="outline" size="sm" icon="excel">Export breakdown</Button>
          </div>
        </Card>
      </div>
    </div>
  );
}

// ---------- Layer header strip ----------
function LayerHeader({ idx, title, explain }) {
  const { isMobile } = useViewport();
  return (
    <div style={{ display: 'flex', gap: 14, alignItems: 'flex-start', padding: '14px 16px', background: '#fff', border: '1px solid var(--bx-border-muted)', borderRadius: 'var(--bx-radius-lg)' }}>
      <div style={{ width: 36, height: 36, borderRadius: 9, background: 'var(--bx-primary)', color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 700, fontSize: 14, flexShrink: 0 }}>L{idx}</div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 14, fontWeight: 600, color: 'var(--bx-text-heading)' }}>Layer {idx} · {title}</div>
        <div style={{ fontSize: 12.5, color: 'var(--bx-text-body)', marginTop: 4, lineHeight: 1.5 }}>{explain}</div>
      </div>
      {!isMobile && <Badge kind="INFO">Pure function</Badge>}
    </div>
  );
}

// ---------- Warnings rail ----------
function WarningsRail({ plan, open, setOpen }) {
  const W = plan.warnings;
  if (!open) {
    return (
      <button onClick={() => setOpen(true)} style={{
        width: 36, background: '#fff', borderLeft: '1px solid var(--bx-border-muted)',
        cursor: 'pointer', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8,
        padding: '14px 0', flexShrink: 0,
      }}>
        <Icon name="warning" size={14} style={{ color: 'var(--bx-warning-fg)' }}/>
        <div style={{ writingMode: 'vertical-rl', transform: 'rotate(180deg)', fontSize: 11, fontWeight: 600, color: 'var(--bx-text-muted)', letterSpacing: '0.04em' }}>
          Warnings · {W.length}
        </div>
      </button>
    );
  }
  return (
    <div style={{ width: 320, background: '#fff', borderLeft: '1px solid var(--bx-border-muted)', display: 'flex', flexDirection: 'column', flexShrink: 0 }}>
      <div style={{ padding: '14px 16px', borderBottom: '1px solid var(--bx-border-muted)', display: 'flex', alignItems: 'center', gap: 8 }}>
        <Icon name="warning" size={13} style={{ color: 'var(--bx-warning-fg)' }}/>
        <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--bx-text-heading)' }}>Warnings · {W.length}</div>
        <span style={{ flex: 1 }}/>
        <IconBtn name="chevron-right" onClick={() => setOpen(false)} title="Collapse"/>
      </div>
      <div style={{ flex: 1, overflowY: 'auto', padding: 12, display: 'flex', flexDirection: 'column', gap: 10 }}>
        {W.map((w, i) => <WarningCard key={i} warning={w}/>)}
      </div>
      <div style={{ borderTop: '1px solid var(--bx-border-muted)', padding: 12, background: 'var(--bx-surface-2)' }}>
        <div style={{ fontSize: 11, color: 'var(--bx-text-muted)', marginBottom: 8 }}>Audit trail · {plan.auditTrail.length} entries</div>
        {plan.auditTrail.slice().reverse().slice(0, 3).map((e, i) => (
          <div key={i} style={{ fontSize: 11.5, color: 'var(--bx-text-body)', marginBottom: 6, lineHeight: 1.45 }}>
            <span style={{ fontFamily: 'var(--bx-font-mono)', color: 'var(--bx-text-muted)' }}>{fmt.dateTime(e.ts)}</span>
            <span style={{ margin: '0 4px' }}>·</span>{e.what}
          </div>
        ))}
      </div>
    </div>
  );
}

function WarningCard({ warning }) {
  const c = warning.level === 'CRITICAL' ? { bg: '#fef2f2', fg: '#b91c1c', icon: 'critical', kind: 'CRITICAL' }
          : warning.level === 'WARNING'  ? { bg: '#fefce8', fg: '#92400e', icon: 'warning',  kind: 'WARNING' }
          :                                 { bg: '#eff6ff', fg: '#1e40af', icon: 'info',     kind: 'INFO' };
  return (
    <div style={{ border: '1px solid ' + c.fg + '22', background: c.bg, borderRadius: 8, padding: 12, display: 'flex', gap: 10 }}>
      <div style={{ width: 22, height: 22, borderRadius: 6, background: '#fff', color: c.fg, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
        <Icon name={c.icon} size={11}/>
      </div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          <span style={{ fontFamily: 'var(--bx-font-mono)', fontSize: 11, fontWeight: 700, color: c.fg }}>{warning.code}</span>
          <Badge kind={c.kind} size="sm">{warning.level}</Badge>
        </div>
        <div style={{ fontSize: 12, color: 'var(--bx-text-dark)', marginTop: 6, lineHeight: 1.5 }}>{warning.message}</div>
        <div style={{ marginTop: 8, display: 'flex', gap: 6 }}>
          <Button variant="ghost-primary" size="sm">Open layer</Button>
          <Button variant="ghost" size="sm">Acknowledge</Button>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { ForecastInputScreen, ShiftPlanScreen, LayerHeader, WarningsRail });

// ---------- Warnings as bottom sheet (mobile/tablet) ----------
function WarningsSheet({ plan, open, onClose }) {
  if (!open) return null;
  return (
    <Dialog open={open} onClose={onClose}
      title={'Warnings · ' + plan.warnings.length}
      subtitle="Issues from this engine run"
      footer={<Button variant="primary" onClick={onClose}>Close</Button>}>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
        {plan.warnings.map((w, i) => <WarningCard key={i} warning={w}/>)}
      </div>
      <div style={{ marginTop: 14, paddingTop: 14, borderTop: '1px solid var(--bx-border-muted)' }}>
        <div style={{ fontSize: 11, color: 'var(--bx-text-muted)', textTransform: 'uppercase', letterSpacing: '0.06em', fontWeight: 700, marginBottom: 8 }}>Recent activity</div>
        {plan.auditTrail.slice().reverse().slice(0, 3).map((e, i) => (
          <div key={i} style={{ fontSize: 12, color: 'var(--bx-text-body)', marginBottom: 6, lineHeight: 1.45 }}>
            <span style={{ fontFamily: 'var(--bx-font-mono)', color: 'var(--bx-text-muted)' }}>{fmt.dateTime(e.ts)}</span>
            <span style={{ margin: '0 4px' }}>·</span>{e.what}
          </div>
        ))}
      </div>
    </Dialog>
  );
}
window.WarningsSheet = WarningsSheet;
