// §3.8 Rate Health Dashboard — Completeness, Expiry, Validation

function RecalcOverlay() {
  return (
    <div style={{ position: "absolute", inset: 0, zIndex: 10,
      background: "rgba(255,255,255,0.78)", display: "flex",
      flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 10 }}>
      <div className="ocms-spinner" />
      <div style={{ fontSize: 12, color: "#555", fontWeight: 500 }}>Recalculating…</div>
    </div>
  );
}

function RateHealth({ user, perms, onAddRate, onOpenRate, refreshKey, onValidationData }) {
  const [tab, setTab] = useState("completeness");
  const [completenessData, setCompletenessData] = useState(null);
  const [expiryData, setExpiryData] = useState(null);
  const [validationData, setValidationData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [localRefresh, setLocalRefresh] = useState(0);
  const [selectedTerminal, setSelectedTerminal] = useState(null);
  const triggerRefresh = () => setLocalRefresh(n => n + 1);

  useEffect(() => {
    if (!user || user.role === "unknown") return;
    setLoading(true);
    Promise.all([
      apiFetch("/api/health/completeness"),
      apiFetch("/api/health/expiry?window=90"),
      apiFetch("/api/health/validation"),
    ]).then(([c, e, v]) => {
      setCompletenessData(c || { rows: [], summary: {} });
      setExpiryData(e || { expiring: [], expired: [] });
      const vd = v || { findings: [], rules: [], last_run: null, rates_scanned: 0 };
      setValidationData(vd);
      onValidationData?.(vd);
    }).catch(() => {
      setCompletenessData({ rows: [], summary: {} });
      setExpiryData({ expiring: [], expired: [] });
      setValidationData({ findings: [], rules: [], last_run: null, rates_scanned: 0 });
    }).finally(() => setLoading(false));
  }, [user?.role, refreshKey, localRefresh]);

  const tabs = [
    { id: "completeness", label: "Completeness" },
    { id: "expiry", label: "Expiry Alerts", count: expiryData?.expiring?.length },
    { id: "validation", label: "Quality Validation" },
  ];

  return (
    <div>
      <PageHeader
        title="Rate Health Dashboard"
        sub="Data quality across all terminals · scoped to your access · real-time"
        right={
          <>
            {(perms.edit || perms.admin) && (
              <button className="btn" disabled={loading} onClick={() => { setTab("validation"); triggerRefresh(); }}>
                <Icon name="refresh" size={14} />
                {loading ? "Running…" : "Run validation"}
              </button>
            )}
          </>
        }
      />
      <HealthKPIs completeness={completenessData} expiry={expiryData} validation={validationData} loading={loading} />
      <TerminalCoverageCards data={completenessData} validation={validationData} loading={loading}
        selected={selectedTerminal} onSelect={t => setSelectedTerminal(prev => prev === t ? null : t)} />
      {selectedTerminal && (
        <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 10,
          padding: "6px 12px", background: "rgba(0,158,135,.08)",
          border: "1px solid var(--teal)", borderRadius: 6 }}>
          <span className="tiny" style={{ color: "var(--teal)" }}>
            <Icon name="filter" size={12} /> Filtered by terminal:
            <strong style={{ marginLeft: 4 }}>{selectedTerminal}</strong>
          </span>
          <div className="grow" />
          <button className="btn ghost tiny" onClick={() => setSelectedTerminal(null)}>
            <Icon name="x" size={11} /> Clear
          </button>
        </div>
      )}
      <Tabs tabs={tabs} current={tab} onChange={setTab} />
      {tab === "completeness" && <Completeness data={completenessData} loading={loading} onAddRate={onAddRate} terminalFilter={selectedTerminal} />}
      {tab === "expiry" && <ExpiryAlerts data={expiryData} loading={loading} onAddRate={onAddRate} onRefresh={triggerRefresh} terminalFilter={selectedTerminal} />}
      {tab === "validation" && <Validation data={validationData} loading={loading} onOpenRate={onOpenRate} terminalFilter={selectedTerminal} onRefresh={triggerRefresh} />}
    </div>
  );
}

function HealthKPIs({ completeness, expiry, validation, loading }) {
  const rows = completeness?.rows || [];
  const totalExpected = rows.length;
  const present = rows.filter(c => c.status === "Present").length;
  const missing = rows.filter(c => c.status === "Missing").length;
  const overallPct = totalExpected > 0 ? Math.round((present / totalExpected) * 100) : 0;

  const expiringCount = expiry?.expiring?.length ?? 0;
  const findings = validation?.findings || [];
  const errCount = findings.filter(r => r.severity === "Error").length;
  const warnCount = findings.filter(r => r.severity === "Warning").length;

  const dash = loading ? "—" : null;

  return (
    <div className="grid-4" style={{ marginBottom: 18 }}>
      <div className="kpi">
        <div className="kpi-label">Overall Completeness</div>
        <div className="row gap-12" style={{ alignItems: "center", marginTop: 4 }}>
          <div className="kpi-value">{dash ?? `${overallPct}%`}</div>
          {!loading && <Ring pct={overallPct} size={48} stroke={5} color="var(--teal)" />}
        </div>
        <div className="kpi-delta">{dash ?? `${present} / ${totalExpected} combinations`}</div>
      </div>
      <div className="kpi">
        <div className="kpi-label">Missing Rates</div>
        <div className="kpi-value" style={{ color: missing > 5 ? "var(--red)" : "var(--ink)" }}>{dash ?? missing}</div>
        <div className="kpi-delta down">across terminals · click to add</div>
      </div>
      <div className="kpi">
        <div className="kpi-label">Expiring &lt; 90 days</div>
        <div className="kpi-value" style={{ color: "var(--amber)" }}>{dash ?? expiringCount}</div>
        <div className="kpi-delta">active rates approaching expiry</div>
      </div>
      <div className="kpi">
        <div className="kpi-label">Open Validation Issues</div>
        <div className="kpi-value">{dash ?? findings.length}</div>
        <div className="kpi-delta">
          {loading ? "—" : (
            <>
              <span style={{ color: "var(--red)", fontWeight: 600 }}>{errCount} errors</span>
              <span style={{ margin: "0 4px" }}>·</span>
              <span style={{ color: "var(--amber)" }}>{warnCount} warnings</span>
            </>
          )}
        </div>
      </div>
    </div>
  );
}

// ============== COMPLETENESS PANEL ==============
function Completeness({ data, loading, onAddRate, terminalFilter }) {
  const DEFAULT_FILTERS = {};
  const [colFilters, setColFilters]     = useState(DEFAULT_FILTERS);
  const [openDropdown, setOpenDropdown] = useState(null);
  const [dropdownSearch, setDropdownSearch] = useState("");

  const handleAddRate = async (r) => {
    if (!onAddRate) return;
    const dimFields = {
      terminal_id:        r.terminal_id,
      cost_type_id:       r.cost_type_id,
      containertype:      r.equipment_type_id    || "",
      container_size:     r.container_size  === "All" ? "" : r.container_size,
      container_fullness: r.container_fullness === "All" ? "" : r.container_fullness,
      shipment_type:      r.shipment_type_id     || "",
      brand_company_id:   r.brand_company_id     || "",
    };
    try {
      const rates = await apiFetch(`/api/rates?terminal_id=${r.terminal_id}&cost_type_id=${r.cost_type_id}&limit=10`);
      if (rates && rates.length > 0) {
        const today = Date.now();
        const ref = rates.slice().sort((a, b) => {
          const da = a.effective_date ? Math.abs(new Date(a.effective_date) - today) : Infinity;
          const db = b.effective_date ? Math.abs(new Date(b.effective_date) - today) : Infinity;
          return da - db;
        })[0];
        onAddRate({
          ...dimFields,
          brand_company_id:  dimFields.brand_company_id || ref.brand_company_id || "",
          cost_category:     ref.cost_category    || "",
          effective_date:    ref.effective_date   || "",
          expiry_date:       ref.expiry_date      || "",
          charge_unit_id:    ref.charge_unit_id   || "",
          primary_currency:  ref.primary_currency || "",
          contract_owner:    ref.contract_owner   || "",
          transport_type:    ref.transport_type   || "",
          payment_terms:     ref.payment_terms    || "",
          payable_by:        ref.payable_by       || "",
          sap_vendor_code:   ref.sap_vendor_code  || "",
          slab_unit:         ref.slab_unit        || "",
          slab_type:         ref.slab_type        || "",
          is_invoicerate:    ref.is_invoicerate   ?? "",
          stcy_flag:         ref.stcy_flag        ?? "",
          calculator:        ref.calculator       ?? "",
          contract_ref:      ref.contract_ref     || "",
          route:             ref.route            || "",
          corridor_from:     ref.corridor_from    || "",
          corridor_to:       ref.corridor_to      || "",
        });
      } else {
        onAddRate(dimFields);
      }
    } catch {
      onAddRate(dimFields);
    }
  };

  const allRows = (data?.rows || []).filter(r => r.status === "Missing" && (!terminalFilter || r.terminal === terminalFilter));
  const summary = data?.summary || {};

  // Apply column filters on Missing rows only
  const filtered = allRows.filter(r =>
    Object.entries(colFilters).every(([key, sel]) =>
      !sel.length || sel.includes(String(r[key] ?? ""))
    )
  );

  const activeFilterCount = Object.values(colFilters).filter(v => v.length > 0).length;

  // Close dropdown on outside click
  useEffect(() => {
    const h = e => { if (!e.target.closest("[data-fdrop]")) { setOpenDropdown(null); setDropdownSearch(""); } };
    document.addEventListener("mousedown", h);
    return () => document.removeEventListener("mousedown", h);
  }, []);

  // Column header with filter dropdown — options always from allRows (stable)
  const fDrop = (key, header, labelFn) => {
    const options = [...new Set(allRows.map(r => String(r[key] ?? "")).filter(Boolean))].sort();
    const sel = colFilters[key] || [];
    const active = sel.length > 0;
    const label = v => labelFn ? labelFn(v) : v;
    return (
      <th key={key} style={{ verticalAlign: "top", padding: "2px 4px", whiteSpace: "nowrap" }}>
        <div style={{ marginBottom: 2, fontSize: 11, fontWeight: 600, color: "var(--ink)" }}>{header}</div>
        <div style={{ position: "relative" }} data-fdrop onMouseDown={e => e.stopPropagation()}>
          <button onClick={() => { setOpenDropdown(openDropdown === key ? null : key); setDropdownSearch(""); }}
            style={{ width: "100%", padding: "2px 6px", fontSize: 11, cursor: "pointer", textAlign: "left",
              border: `1px solid ${active ? "var(--teal)" : "var(--border)"}`, borderRadius: 3,
              background: active ? "rgba(0,158,135,.12)" : "var(--paper)",
              color: active ? "var(--teal)" : "var(--ink-mute)" }}>
            {active ? `✓ ${sel.length}/${options.length}` : "▾ All"}
          </button>
          {openDropdown === key && (
            <div style={{ position: "absolute", top: "calc(100% + 2px)", left: 0, zIndex: 400,
              background: "var(--paper)", border: "1px solid var(--border)", borderRadius: 4,
              boxShadow: "0 4px 16px rgba(0,0,0,.18)", minWidth: 180 }}>
              <div style={{ padding: "6px 8px", borderBottom: "1px solid var(--border)" }}>
                <input autoFocus value={dropdownSearch} onChange={e => setDropdownSearch(e.target.value)}
                  placeholder="Search…" style={{ width: "100%", padding: "3px 7px", fontSize: 12,
                    border: "1px solid var(--border)", borderRadius: 3, outline: "none",
                    boxSizing: "border-box", background: "var(--paper)" }} />
              </div>
              {(() => {
                const vis = options.filter(v => !dropdownSearch || label(v).toLowerCase().includes(dropdownSearch.toLowerCase()));
                return (
                  <>
                    <div style={{ padding: "4px 8px", borderBottom: "1px solid var(--border)", display: "flex", gap: 6 }}>
                      <button className="btn ghost" style={{ fontSize: 10, padding: "1px 8px", color: "var(--teal)" }}
                        onClick={() => setColFilters(f => ({ ...f, [key]: vis }))}>All</button>
                      <button className="btn ghost" style={{ fontSize: 10, padding: "1px 8px" }}
                        onClick={() => setColFilters(f => ({ ...f, [key]: [] }))}>Clear</button>
                    </div>
                    <div style={{ maxHeight: 220, overflowY: "auto" }}>
                      {vis.map(v => (
                        <label key={v} style={{ display: "flex", alignItems: "center", gap: 8, padding: "5px 10px", cursor: "pointer", fontSize: 12 }}>
                          <input type="checkbox" checked={sel.includes(v)}
                            onChange={() => setColFilters(f => {
                              const cur = f[key] || [];
                              return { ...f, [key]: cur.includes(v) ? cur.filter(x => x !== v) : [...cur, v] };
                            })} />
                          {label(v)}
                        </label>
                      ))}
                      {vis.length === 0 && <div style={{ padding: "8px 10px", fontSize: 12, color: "var(--ink-mute)" }}>No options</div>}
                    </div>
                  </>
                );
              })()}
            </div>
          )}
        </div>
      </th>
    );
  };

  // Terminal label helper for dropdown
  const terminalLabel = v => { const s = summary[v]; return s ? `${s.terminal_name} (${v})` : v; };

  return (
    <div>
      <Card title={`Expected Coverage Matrix · ${filtered.length} row${filtered.length !== 1 ? "s" : ""}${activeFilterCount ? ` (${activeFilterCount} filter${activeFilterCount > 1 ? "s" : ""} active)` : ""}`}
        sub="click a Missing row to add rate"
        right={
          <div className="row gap-8">
            {activeFilterCount > 0 && (
              <button className="btn ghost tiny" onClick={() => setColFilters(DEFAULT_FILTERS)}>
                <Icon name="x" size={11} /> Clear filters
              </button>
            )}
            <div className="row gap-12 tiny muted">
              <span><span style={{ display: "inline-block", width: 8, height: 8, background: "var(--red)", borderRadius: 50, marginRight: 4 }} />Missing</span>
              <span><span style={{ display: "inline-block", width: 8, height: 8, background: "var(--amber)", borderRadius: 50, marginRight: 4 }} />Expiring</span>
            </div>
          </div>
        }>
        <div className="table-wrap" style={{ minHeight: 320, position: "relative" }}>
          {loading && data && <RecalcOverlay />}
          <table className="table">
            <thead>
              <tr>
                {fDrop("terminal",          "Terminal",      terminalLabel)}
                {fDrop("cost_type_name",    "Cost Type")}
                {fDrop("equipment",         "Equipment")}
                {fDrop("container_size",    "Size")}
                {fDrop("container_fullness","Fullness")}
                {fDrop("shipment_type",     "Shipment Type")}
                {fDrop("brand_company",     "Brand")}
                <th style={{ padding: "2px 4px" }}>Last Updated</th>
                <th style={{ padding: "2px 4px" }} />
              </tr>
            </thead>
            <tbody>
              {filtered.map((r, i) => (
                <tr key={i}
                  onClick={() => handleAddRate(r)}
                  style={{ cursor: "pointer", background: "#fef4f4" }}>
                  <td className="mono tiny">{r.terminal}</td>
                  <td>{r.cost_type_name}</td>
                  <td className="mono tiny">{r.equipment}</td>
                  <td className="mono tiny">{r.container_size}</td>
                  <td className="tiny">{r.container_fullness}</td>
                  <td className="tiny">{r.shipment_type}</td>
                  <td className="tiny">{r.brand_company}</td>
                  <td className="mono tiny muted">{r.last_updated || "—"}</td>
                  <td>
                    <button className="btn tiny" style={{ height: 24, fontSize: 11 }}
                      onClick={e => { e.stopPropagation(); handleAddRate(r); }}>
                      <Icon name="plus" size={10} /> Add
                    </button>
                  </td>
                </tr>
              ))}
              {!loading && filtered.length === 0 && (
                <tr><td colSpan={10} style={{ textAlign: "center", color: "var(--ink-mute)", padding: 24 }}>No rows match the current filters</td></tr>
              )}
            </tbody>
          </table>
        </div>
      </Card>
    </div>
  );
}

// ============== TERMINAL COVERAGE CARDS (always visible above tabs) ==============
function TerminalCoverageCards({ data, validation, loading, selected, onSelect }) {
  const summary = data?.summary || {};

  // Build per-terminal QV counts; also capture terminal_name from findings
  const qvByTerminal = {};
  const qvTerminalName = {};
  (validation?.findings || []).forEach(f => {
    if (!f.terminal) return;
    qvByTerminal[f.terminal] = (qvByTerminal[f.terminal] || 0) + 1;
    if (f.terminal_name && !qvTerminalName[f.terminal]) qvTerminalName[f.terminal] = f.terminal_name;
  });

  // Terminals from coverage matrix
  const summaries = Object.entries(summary).map(([code, s]) => ({
    code, name: s.terminal_name || code,
    present: s.present, missing: s.missing, expiring: s.expiring, total: s.total,
    qv: qvByTerminal[code] || 0,
    pct: s.total > 0 ? Math.round((s.present / s.total) * 100) : 0,
    qvOnly: false,
  })).sort((a, b) => a.pct - b.pct);

  // Terminals that only appear in QV findings (not in coverage matrix)
  const coveredCodes = new Set(Object.keys(summary));
  const qvOnlySummaries = Object.entries(qvByTerminal)
    .filter(([code]) => !coveredCodes.has(code))
    .map(([code, qv]) => ({
      code, name: qvTerminalName[code] || code,
      present: null, missing: null, expiring: null, total: null, qv,
      pct: null, qvOnly: true,
    })).sort((a, b) => b.qv - a.qv);

  const allSummaries = [...summaries, ...qvOnlySummaries];

  if (!loading && allSummaries.length === 0) return null;

  return (
    <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(180px, 1fr))", gap: 12, marginBottom: 18 }}>
      {loading && allSummaries.length === 0
        ? [1,2,3].map(i => <div key={i} className="card" style={{ padding: 14, minHeight: 72, opacity: .4 }} />)
        : allSummaries.map(s => {
          const isSelected = selected === s.code;
          const ringColor = s.pct >= 80 ? "var(--green)" : s.pct >= 50 ? "var(--amber)" : "var(--red)";
          return (
            <div key={s.code} onClick={() => onSelect?.(s.code)}
              className="kpi"
              style={{
                cursor: "pointer", transition: "border-color .15s, box-shadow .15s, background .15s",
                ...(isSelected
                  ? { border: "2px solid var(--teal)", background: "rgba(0,158,135,.06)",
                      boxShadow: "0 0 0 3px rgba(0,158,135,.15)" }
                  : { border: "1px solid var(--line)" }),
              }}>
              <div className="row gap-8" style={{ marginBottom: 8 }}>
                {s.qvOnly
                  ? <div style={{ width: 40, height: 40, borderRadius: "50%", border: "3px solid #b86a1b",
                      display: "flex", alignItems: "center", justifyContent: "center",
                      color: "#b86a1b", fontSize: 13, fontWeight: 700, flexShrink: 0 }}>QV</div>
                  : <Ring pct={s.pct} size={40} stroke={4} color={ringColor} />
                }
                <div style={{ flex: 1 }}>
                  <div className="bold" style={{ fontSize: 12, color: isSelected ? "var(--teal)" : "var(--ink)" }}>{s.name}</div>
                  <div className="mono tiny" style={{ color: isSelected ? "var(--teal)" : "var(--ink-mute)" }}>{s.code}</div>
                </div>
                {isSelected && <span style={{ color: "var(--teal)", fontSize: 14, alignSelf: "flex-start" }}>✓</span>}
              </div>
              <div className="row gap-6 tiny" style={{ flexWrap: "wrap" }}>
                {!s.qvOnly && <span style={{ color: "var(--green)" }}>● {s.present} present</span>}
                {!s.qvOnly && <span style={{ color: "var(--red)" }}>● {s.missing} missing</span>}
                {!s.qvOnly && <span style={{ color: "var(--amber)" }}>● {s.expiring} expiring</span>}
                {s.qv > 0 && <span style={{ color: "#b86a1b" }}>● {s.qv} QV issues</span>}
              </div>
            </div>
          );
        })
      }
    </div>
  );
}

// ============== RENEW MODAL ==============
function RenewModal({ rate, fullRate, loadingRate, loadError, onClose, onModify, onCreateNew, onSetInactive, creating }) {
  const [inactiveClicked, setInactiveClicked] = useState(false);
  const expiryDate = fullRate?.expiry_date || rate.expiry;
  const newEffective = expiryDate
    ? new Date(new Date(expiryDate).getTime() + 86400000).toISOString().slice(0, 10)
    : new Date().toISOString().slice(0, 10);
  const today = new Date().toISOString().slice(0, 10);
  const isFuture = newEffective > today;
  const busy = loadingRate || creating;
  const hasFullRate = !!fullRate?.id;
  const hasSuccessor = !!rate.has_successor;

  const optionStyle = (disabled) => ({
    textAlign: "left", padding: "14px 16px",
    border: `2px solid ${disabled ? "var(--line)" : "var(--border)"}`, borderRadius: 8,
    background: disabled ? "var(--surface)" : "#fff",
    cursor: disabled ? "not-allowed" : "pointer",
    opacity: disabled ? .55 : 1, transition: "border-color .15s",
  });
  const hoverIn  = (e, disabled) => { if (!disabled) e.currentTarget.style.borderColor = "var(--teal)"; };
  const hoverOut = (e, disabled) => { e.currentTarget.style.borderColor = disabled ? "var(--line)" : "var(--border)"; };

  return (
    <div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.45)", zIndex: 1200,
      display: "flex", alignItems: "center", justifyContent: "center" }}
      onClick={onClose}>
      <div style={{ background: "#fff", borderRadius: 10, boxShadow: "0 8px 40px rgba(0,0,0,0.22)",
        width: 480, maxWidth: "95vw", display: "flex", flexDirection: "column", overflow: "hidden" }}
        onClick={e => e.stopPropagation()}>

        {/* Header */}
        <div style={{ padding: "16px 20px", borderBottom: "1px solid var(--line)",
          display: "flex", alignItems: "flex-start", justifyContent: "space-between" }}>
          <div>
            <div style={{ fontWeight: 700, fontSize: 15 }}>Renew Rate · {rate.id}</div>
            <div style={{ fontSize: 12, color: "var(--ink-mute)", marginTop: 3 }}>
              {rate.terminal} · {rate.cost_type} · expired <span style={{ fontFamily: "monospace" }}>{rate.expiry}</span>
            </div>
          </div>
          <button onClick={onClose} style={{ background: "none", border: "none", cursor: "pointer",
            color: "var(--ink-mute)", padding: 4, marginLeft: 12, lineHeight: 1 }}>
            <Icon name="x" size={16} />
          </button>
        </div>

        {/* Body */}
        <div style={{ padding: "20px 20px 16px", display: "flex", flexDirection: "column", gap: 12 }}>
          {loadingRate && (
            <div style={{ textAlign: "center", color: "var(--ink-mute)", padding: "12px 0", fontSize: 13 }}>
              Loading rate details…
            </div>
          )}
          {loadError && (
            <div style={{ background: "#fef4f4", border: "1px solid var(--red)", borderRadius: 6,
              padding: "10px 14px", color: "var(--red)", fontSize: 12 }}>
              {loadError}
            </div>
          )}

          {/* Successor warning banner */}
          {hasSuccessor && (
            <div style={{ background: "#fffbeb", border: "1px solid #f59e0b", borderRadius: 8,
              padding: "12px 14px", display: "flex", gap: 10, alignItems: "flex-start" }}>
              <span style={{ fontSize: 16, lineHeight: 1.2 }}>⚠️</span>
              <div>
                <div style={{ fontWeight: 600, fontSize: 13, color: "#92400e", marginBottom: 3 }}>
                  A successor rate already exists
                </div>
                <div style={{ fontSize: 12, color: "#78350f" }}>
                  This rate has a follow-up rate already created. The recommended action is to set this rate to
                  <strong> Inactive</strong> to clean up the status.
                </div>
              </div>
            </div>
          )}

          {/* Primary action when successor exists: Set Inactive */}
          {hasSuccessor && (
            <button
              onClick={() => {
                if (inactiveClicked || busy || !hasFullRate) return;
                setInactiveClicked(true);
                onSetInactive();
              }}
              disabled={!hasFullRate && !inactiveClicked}
              style={{
                textAlign: "left", padding: "14px 16px",
                border: `2px solid ${inactiveClicked ? "#16a34a" : "#f59e0b"}`,
                borderRadius: 8,
                background: inactiveClicked ? "#f0fdf4" : "#fffbeb",
                cursor: inactiveClicked || !hasFullRate ? "default" : "pointer",
                transition: "border-color .15s, background .15s",
                display: "flex", alignItems: "center", gap: 10,
              }}
              onMouseEnter={e => { if (!inactiveClicked && hasFullRate) e.currentTarget.style.borderColor = "#d97706"; }}
              onMouseLeave={e => { e.currentTarget.style.borderColor = inactiveClicked ? "#16a34a" : "#f59e0b"; }}>
              <div style={{ flex: 1 }}>
                <div style={{ fontWeight: 600, fontSize: 14, marginBottom: 4,
                  color: inactiveClicked ? "#15803d" : "#92400e" }}>
                  {inactiveClicked ? (creating ? "Processing…" : "Set to Inactive") : "Set current rate to Inactive"}
                </div>
                <div style={{ fontSize: 12, color: inactiveClicked ? "#166534" : "#78350f" }}>
                  {inactiveClicked
                    ? "Request sent. The table will refresh automatically."
                    : "Mark this expired rate as Inactive. The successor rate will continue to be effective."}
                </div>
              </div>
              <div style={{ fontSize: 20, lineHeight: 1, flexShrink: 0 }}>
                {inactiveClicked
                  ? <span style={{ color: "#16a34a" }}>✓</span>
                  : creating
                    ? <div className="ocms-spinner" style={{ width: 18, height: 18 }} />
                    : null}
              </div>
            </button>
          )}

          {/* Divider when successor exists */}
          {hasSuccessor && (
            <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
              <div style={{ flex: 1, height: 1, background: "var(--line)" }} />
              <span style={{ fontSize: 11, color: "var(--ink-mute)" }}>or choose another action</span>
              <div style={{ flex: 1, height: 1, background: "var(--line)" }} />
            </div>
          )}

          {/* Option 1: Extend */}
          <button onClick={onModify} disabled={busy || !hasFullRate}
            style={optionStyle(busy || !hasFullRate)}
            onMouseEnter={e => hoverIn(e, busy || !hasFullRate)}
            onMouseLeave={e => hoverOut(e, busy || !hasFullRate)}>
            <div style={{ fontWeight: 600, fontSize: 14, marginBottom: 4 }}>Extend expiry date</div>
            <div style={{ fontSize: 12, color: "var(--ink-mute)" }}>
              Edit the current rate's expiry date in place. No new record is created.
            </div>
          </button>

          {/* Option 2: Create successor */}
          <button onClick={onCreateNew} disabled={busy || !hasFullRate}
            style={optionStyle(busy || !hasFullRate)}
            onMouseEnter={e => hoverIn(e, busy || !hasFullRate)}
            onMouseLeave={e => hoverOut(e, busy || !hasFullRate)}>
            <div style={{ fontWeight: 600, fontSize: 14, marginBottom: 4 }}>
              {creating ? "Processing…" : "Create successor rate"}
            </div>
            <div style={{ fontSize: 12, color: "var(--ink-mute)" }}>
              New rate effective from <strong style={{ fontFamily: "monospace" }}>{newEffective}</strong>.{" "}
              {isFuture
                ? "Status will be set to Schedule (future-dated)."
                : "Current rate will be set to Inactive; new rate will be Active."}
            </div>
          </button>
        </div>

        {/* Footer */}
        <div style={{ padding: "12px 20px 16px", display: "flex", justifyContent: "flex-end",
          borderTop: "1px solid var(--line)" }}>
          <button className="btn ghost" onClick={onClose}>Cancel</button>
        </div>
      </div>
    </div>
  );
}

// ============== BULK RENEW MODAL ==============
function BulkRenewModal({ rates, onClose, onDone }) {
  const [mode, setMode]               = useState(null);      // null | "extend" | "create"
  const [effectiveDate, setEffectiveDate] = useState("");
  const [expiryDate, setExpiryDate]   = useState("");
  const [processing, setProcessing]   = useState(false);
  const [progress, setProgress]       = useState(null);      // null | { done, total, errors[] }

  const today = new Date().toISOString().slice(0, 10);
  const canConfirm = mode && expiryDate && (mode === "extend" || effectiveDate) && !processing;
  const isDone = progress && progress.done >= rates.length;

  const handleConfirm = async () => {
    setProcessing(true);
    const errors = [];
    setProgress({ done: 0, total: rates.length, errors: [] });

    if (mode === "extend") {
      // Batch PATCH — one request for all
      const ids = rates.map(r => parseInt(String(r.id).replace(/^R-/i, ""), 10)).filter(Boolean);
      try {
        await apiFetch("/api/rates/batch", {
          method: "PATCH",
          body: JSON.stringify({ ids, updates: { expiry_date: expiryDate } }),
        });
        setProgress({ done: rates.length, total: rates.length, errors: [] });
      } catch (e) {
        errors.push(...rates.map(r => r.id));
        setProgress({ done: rates.length, total: rates.length, errors });
      }
    } else {
      // Create successor: single batch request — backend handles set-Inactive + INSERT in one transaction
      const ids = rates.map(r => parseInt(String(r.id).replace(/^R-/i, ""), 10)).filter(Boolean);
      try {
        const res = await apiFetch("/api/rates/batch-renew", {
          method: "POST",
          body: JSON.stringify({ ids, effective_date: effectiveDate, expiry_date: expiryDate || null }),
        });
        const failedIds = (res?.errors || []).map(e => e.split(":")[0]);
        setProgress({ done: rates.length, total: rates.length, errors: failedIds });
      } catch (e) {
        setProgress({ done: rates.length, total: rates.length, errors: rates.map(r => r.id) });
      }
    }

    setProcessing(false);
    if (errors.length === 0) {
      onDone();
    }
  };

  const optCard = (id, title, desc) => (
    <button onClick={() => !processing && setMode(id)}
      style={{
        textAlign: "left", padding: "12px 14px", width: "100%",
        border: `2px solid ${mode === id ? "var(--teal)" : "var(--border)"}`,
        borderRadius: 8,
        background: mode === id ? "rgba(0,158,135,.06)" : "#fff",
        cursor: processing ? "default" : "pointer",
        transition: "border-color .15s, background .15s",
        display: "flex", alignItems: "center", gap: 10,
      }}
      onMouseEnter={e => { if (!processing && mode !== id) e.currentTarget.style.borderColor = "var(--teal)"; }}
      onMouseLeave={e => { e.currentTarget.style.borderColor = mode === id ? "var(--teal)" : "var(--border)"; }}>
      <div style={{ flex: 1 }}>
        <div style={{ fontWeight: 600, fontSize: 14, marginBottom: 3,
          color: mode === id ? "var(--teal)" : "var(--ink)" }}>{title}</div>
        <div style={{ fontSize: 12, color: "var(--ink-mute)" }}>{desc}</div>
      </div>
      {mode === id && <span style={{ color: "var(--teal)", fontSize: 18, flexShrink: 0 }}>✓</span>}
    </button>
  );

  return (
    <div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.45)", zIndex: 1200,
      display: "flex", alignItems: "center", justifyContent: "center" }}
      onClick={!processing ? onClose : undefined}>
      <div style={{ background: "#fff", borderRadius: 10, boxShadow: "0 8px 40px rgba(0,0,0,0.22)",
        width: 500, maxWidth: "95vw", display: "flex", flexDirection: "column", overflow: "hidden" }}
        onClick={e => e.stopPropagation()}>

        {/* Header */}
        <div style={{ padding: "16px 20px", borderBottom: "1px solid var(--line)",
          display: "flex", alignItems: "center", justifyContent: "space-between" }}>
          <div>
            <div style={{ fontWeight: 700, fontSize: 15 }}>Bulk Renew</div>
            <div style={{ fontSize: 12, color: "var(--ink-mute)", marginTop: 3 }}>
              {rates.length} rate{rates.length !== 1 ? "s" : ""} selected
            </div>
          </div>
          <button onClick={onClose} disabled={processing}
            style={{ background: "none", border: "none", cursor: processing ? "default" : "pointer",
              color: "var(--ink-mute)", padding: 4, lineHeight: 1 }}>
            <Icon name="x" size={16} />
          </button>
        </div>

        {/* Body */}
        <div style={{ padding: "20px", display: "flex", flexDirection: "column", gap: 12 }}>
          {optCard("extend", "Extend expiry date",
            "Update the expiry date for all selected rates in place. No new records are created.")}
          {optCard("create", "Create successor rates",
            "If effective date ≤ today: current rates → Inactive, new rates → Active. If future: current rates unchanged, new rates → Schedule.")}

          {/* Date fields — expand when mode chosen */}
          {mode && (
            <div style={{ background: "var(--paper)", border: "1px solid var(--border)", borderRadius: 8,
              padding: "14px 16px", display: "flex", flexDirection: "column", gap: 12 }}>
              <div style={{ display: "flex", gap: 12 }}>
                {mode === "create" && (
                  <label style={{ flex: 1, display: "flex", flexDirection: "column", gap: 4 }}>
                    <span style={{ fontSize: 12, fontWeight: 600 }}>Effective Date</span>
                    <input type="date" value={effectiveDate} onChange={e => setEffectiveDate(e.target.value)}
                      disabled={processing}
                      style={{ padding: "7px 10px", border: "1px solid var(--border)", borderRadius: 4,
                        fontSize: 13, fontFamily: "monospace", background: "#fff", width: "100%",
                        boxSizing: "border-box" }} />
                  </label>
                )}
                <label style={{ flex: 1, display: "flex", flexDirection: "column", gap: 4 }}>
                  <span style={{ fontSize: 12, fontWeight: 600 }}>
                    {mode === "extend" ? "New Expiry Date" : "Expiry Date"}
                  </span>
                  <input type="date" value={expiryDate} onChange={e => setExpiryDate(e.target.value)}
                    disabled={processing}
                    style={{ padding: "7px 10px", border: "1px solid var(--border)", borderRadius: 4,
                      fontSize: 13, fontFamily: "monospace", background: "#fff", width: "100%",
                      boxSizing: "border-box" }} />
                </label>
              </div>
              {mode === "create" && effectiveDate && (() => {
                const isFuture = effectiveDate > today;
                return (
                  <div style={{ fontSize: 11, padding: "6px 10px", borderRadius: 4,
                    background: isFuture ? "#fffbeb" : "#f0fdf4",
                    border: `1px solid ${isFuture ? "#f59e0b" : "#16a34a"}`,
                    color: isFuture ? "#92400e" : "#166534" }}>
                    {isFuture
                      ? "⚠ Future date — current rates remain Active; new rates will be set to Schedule."
                      : "✓ Past/today date — current rates will be set to Inactive; new rates will be Active."}
                  </div>
                );
              })()}
            </div>
          )}

          {/* Progress */}
          {progress && (
            <div style={{
              background: isDone && progress.errors.length === 0 ? "#f0fdf4" : "var(--paper)",
              border: `1px solid ${isDone && progress.errors.length === 0 ? "#16a34a" : "var(--border)"}`,
              borderRadius: 6, padding: "10px 14px", fontSize: 12 }}>
              {isDone && progress.errors.length === 0
                ? <span style={{ color: "#16a34a", fontWeight: 600 }}>
                    ✓ All {rates.length} rate{rates.length !== 1 ? "s" : ""} updated successfully
                  </span>
                : <div>
                    <div style={{ color: "var(--ink-mute)", marginBottom: progress.errors.length ? 6 : 0 }}>
                      Processing {progress.done} / {progress.total}…
                    </div>
                    {progress.errors.length > 0 && (
                      <div style={{ color: "var(--red)" }}>
                        Failed: {progress.errors.join(", ")}
                      </div>
                    )}
                  </div>
              }
            </div>
          )}
        </div>

        {/* Footer */}
        <div style={{ padding: "12px 20px 16px", borderTop: "1px solid var(--line)",
          display: "flex", justifyContent: "flex-end", gap: 8 }}>
          {isDone && progress.errors.length === 0
            ? <button className="btn" onClick={onDone}>Close</button>
            : <>
                <button className="btn ghost" onClick={onClose} disabled={processing}>Cancel</button>
                <button className="btn" onClick={handleConfirm} disabled={!canConfirm}
                  style={{ minWidth: 140, display: "flex", alignItems: "center", justifyContent: "center", gap: 6 }}>
                  {processing
                    ? <><div className="ocms-spinner" style={{ width: 14, height: 14, borderWidth: 2 }} /> Processing…</>
                    : `Confirm · ${rates.length} rate${rates.length !== 1 ? "s" : ""}`}
                </button>
              </>
          }
        </div>
      </div>
    </div>
  );
}

// ============== EXPIRY PANEL ==============
function ExpiryAlerts({ data, loading, onAddRate, onRefresh, terminalFilter }) {
  const [window, setWindow] = useState(60);
  const [renewModal, setRenewModal]   = useState(null);  // { rate, fullRate, loadingRate, loadError }
  const [creating, setCreating]       = useState(false);
  const [filters, setFilters]         = useState({});
  const [openDropdown, setOpenDropdown] = useState(null);
  const [dropdownSearch, setDropdownSearch] = useState("");
  const [selected, setSelected]       = useState(new Set());   // Set of rate id strings
  const [bulkModal, setBulkModal]     = useState(false);

  const isExpired = window === "expired";
  const allRows = (isExpired
    ? (data?.expired || [])
    : (data?.expiring || []).filter(a => a.days <= window)
  ).filter(r => !terminalFilter || r.terminal === terminalFilter);

  const handleRenew = (r) => {
    // Show modal immediately, then load full rate in background
    setRenewModal({ rate: r, fullRate: null, loadingRate: true, loadError: null });
    const numericId = String(r.id).replace(/^R-/i, "");
    apiFetch(`/api/rates/${numericId}`)
      .then(fullRate => {
        if (fullRate?.id) {
          setRenewModal(prev => prev ? { ...prev, fullRate, loadingRate: false } : null);
        } else {
          setRenewModal(prev => prev ? { ...prev, loadingRate: false, loadError: "Rate not found" } : null);
        }
      })
      .catch(e => {
        setRenewModal(prev => prev ? { ...prev, loadingRate: false, loadError: e.message || "Failed to load rate details" } : null);
      });
  };

  const handleModify = () => {
    if (!renewModal) return;
    onAddRate(renewModal.fullRate);
    setRenewModal(null);
  };

  const handleSetInactive = async () => {
    if (!renewModal?.fullRate) return;
    const { fullRate } = renewModal;
    setCreating(true);
    try {
      const { status_id, terminal_code, terminal_name, cost_type_code, cost_type_name,
              charge_unit_code, containertype_code, shipment_type_code, iso_code,
              slab_unit_code, slab_type_code, vendor_name, material_name,
              brand_company_code, brand_company_name, created_by, created_at,
              modified_by, modified_at, ...body } = fullRate;
      await apiFetch(`/api/rates/${fullRate.id}`, {
        method: "PUT",
        body: JSON.stringify({ ...body, status: "Inactive" }),
      });
    } catch { /* ignore */ }
    setCreating(false);
    setRenewModal(null);
    onRefresh?.();
  };

  const handleCreateNew = async () => {
    if (!renewModal) return;
    const { fullRate } = renewModal;
    const expiryDate = fullRate.expiry_date;
    const newEffective = expiryDate
      ? new Date(new Date(expiryDate).getTime() + 86400000).toISOString().slice(0, 10)
      : new Date().toISOString().slice(0, 10);
    const today = new Date().toISOString().slice(0, 10);
    const isFuture = newEffective > today;

    if (!isFuture) {
      setCreating(true);
      try {
        // Set old rate → Inactive before opening new rate form
        const { status_id, terminal_code, terminal_name, cost_type_code, cost_type_name,
                charge_unit_code, containertype_code, shipment_type_code, iso_code,
                slab_unit_code, slab_type_code, vendor_name, material_name,
                brand_company_code, brand_company_name, created_by, created_at,
                modified_by, modified_at, ...body } = fullRate;
        await apiFetch(`/api/rates/${fullRate.id}`, {
          method: "PUT",
          body: JSON.stringify({ ...body, status: "Inactive" }),
        });
      } catch { /* continue even if update fails */ }
      setCreating(false);
    }

    const { id, expiry_date, created_at, modified_at, status_id,
            terminal_code, terminal_name, cost_type_code, cost_type_name,
            charge_unit_code, containertype_code, shipment_type_code, iso_code,
            slab_unit_code, slab_type_code, vendor_name, material_name,
            brand_company_code, brand_company_name, created_by, modified_by, ...rest } = fullRate;
    onAddRate({ ...rest, effective_date: newEffective, expiry_date: "", status: isFuture ? "Schedule" : "Active" });
    setRenewModal(null);
  };

  // Reset column filters when switching window
  useEffect(() => { setFilters({}); setSelected(new Set()); }, [window]);

  const applyFilters = (rows, f) =>
    rows.filter(r => Object.entries(f).every(([k, sel]) => !sel.length || sel.includes(String(r[k] ?? ""))));

  const displayRows = applyFilters(allRows, filters);

  useEffect(() => {
    const h = e => { if (!e.target.closest("[data-fdrop]")) { setOpenDropdown(null); setDropdownSearch(""); } };
    document.addEventListener("mousedown", h);
    return () => document.removeEventListener("mousedown", h);
  }, []);

  const fDrop = (key, header, labelFn) => {
    const options = [...new Set(allRows.map(r => String(r[key] ?? "")).filter(Boolean))].sort();
    const sel = filters[key] || [];
    const active = sel.length > 0;
    const dropId = `r-${key}`;
    const label = v => labelFn ? labelFn(v) : v;
    return (
      <th key={dropId} style={{ verticalAlign: "top", padding: "2px 4px", whiteSpace: "nowrap" }}>
        <div style={{ marginBottom: 2, fontSize: 11, fontWeight: 600, color: "var(--ink)" }}>{header}</div>
        <div style={{ position: "relative" }} data-fdrop onMouseDown={e => e.stopPropagation()}>
          <button onClick={() => { setOpenDropdown(openDropdown === dropId ? null : dropId); setDropdownSearch(""); }}
            style={{ width: "100%", padding: "2px 6px", fontSize: 11, cursor: "pointer", textAlign: "left",
              border: `1px solid ${active ? "var(--teal)" : "var(--border)"}`, borderRadius: 3,
              background: active ? "rgba(0,158,135,.12)" : "var(--paper)",
              color: active ? "var(--teal)" : "var(--ink-mute)" }}>
            {active ? `✓ ${sel.length}/${options.length}` : "▾ All"}
          </button>
          {openDropdown === dropId && (
            <div style={{ position: "absolute", top: "calc(100% + 2px)", left: 0, zIndex: 400,
              background: "var(--paper)", border: "1px solid var(--border)", borderRadius: 4,
              boxShadow: "0 4px 16px rgba(0,0,0,.18)", minWidth: 180 }}>
              <div style={{ padding: "6px 8px", borderBottom: "1px solid var(--border)" }}>
                <input autoFocus value={dropdownSearch} onChange={e => setDropdownSearch(e.target.value)}
                  placeholder="Search…" style={{ width: "100%", padding: "3px 6px", fontSize: 11,
                    border: "1px solid var(--border)", borderRadius: 3, background: "var(--paper)", color: "var(--ink)" }} />
              </div>
              <div style={{ display: "flex", gap: 4, padding: "4px 8px", borderBottom: "1px solid var(--border)" }}>
                <button onMouseDown={e => { e.preventDefault(); setFilters(f => ({ ...f, [key]: options })); }}
                  style={{ flex: 1, fontSize: 10, padding: "2px 4px", border: "1px solid var(--border)", borderRadius: 3, cursor: "pointer", background: "var(--paper)", color: "var(--ink-mute)" }}>All</button>
                <button onMouseDown={e => { e.preventDefault(); setFilters(f => ({ ...f, [key]: [] })); }}
                  style={{ flex: 1, fontSize: 10, padding: "2px 4px", border: "1px solid var(--border)", borderRadius: 3, cursor: "pointer", background: "var(--paper)", color: "var(--ink-mute)" }}>Clear</button>
              </div>
              <div style={{ maxHeight: 200, overflowY: "auto" }}>
                {options.filter(v => !dropdownSearch || v.toLowerCase().includes(dropdownSearch.toLowerCase())).map(v => (
                  <label key={v} style={{ display: "flex", alignItems: "center", gap: 6, padding: "4px 10px", cursor: "pointer", fontSize: 11, color: "var(--ink)" }}>
                    <input type="checkbox" checked={sel.includes(v)} onChange={e => {
                      setFilters(f => ({ ...f, [key]: e.target.checked ? [...(f[key]||[]), v] : (f[key]||[]).filter(x => x !== v) }));
                    }} />
                    {label(v)}
                  </label>
                ))}
              </div>
            </div>
          )}
        </div>
      </th>
    );
  };

  const activeFilterCount = Object.values(filters).filter(v => v.length).length;

  return (
    <div>
      {renewModal && (
        <RenewModal
          rate={renewModal.rate}
          fullRate={renewModal.fullRate}
          loadingRate={renewModal.loadingRate}
          loadError={renewModal.loadError}
          onClose={() => setRenewModal(null)}
          onModify={handleModify}
          onCreateNew={handleCreateNew}
          onSetInactive={handleSetInactive}
          creating={creating}
        />
      )}
      <div className="filterbar">
        <span className="tiny muted">Alert window</span>
        <div className="row gap-4" style={{ background: "var(--paper)", padding: 3, borderRadius: 4 }}>
          {[
            { v: 30,        label: "30 days" },
            { v: 60,        label: "60 days" },
            { v: 90,        label: "90 days" },
            { v: "expired", label: "Expired" },
          ].map(({ v, label }) => (
            <button key={v} onClick={() => setWindow(v)}
              style={{ padding: "4px 10px", border: 0, borderRadius: 3,
                background: window === v ? (v === "expired" ? "var(--red)" : "#fff") : "transparent",
                fontWeight: window === v ? 600 : 400,
                color: window === v ? (v === "expired" ? "#fff" : "var(--ink)") : "var(--ink-mute)",
                cursor: "pointer", fontSize: 12 }}>{label}</button>
          ))}
        </div>
        <div className="grow" />
        {!isExpired && (
          <div className="row gap-12 tiny muted">
            <span><span style={{ display: "inline-block", width: 8, height: 8, background: "var(--red)", borderRadius: 50, marginRight: 4 }} />≤ 14 days</span>
            <span><span style={{ display: "inline-block", width: 8, height: 8, background: "var(--amber)", borderRadius: 50, marginRight: 4 }} />15–30 days</span>
            <span><span style={{ display: "inline-block", width: 8, height: 8, background: "#d8a83a", borderRadius: 50, marginRight: 4 }} />31–60 days</span>
          </div>
        )}
        <button className="btn ghost"><Icon name="bell" size={14} /> Notification settings</button>
      </div>

      {/* Bulk action bar — only for non-expired mode */}
      {!isExpired && selected.size > 0 && (
        <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 8,
          padding: "8px 14px", background: "rgba(0,158,135,.08)",
          border: "1px solid var(--teal)", borderRadius: 6 }}>
          <span className="tiny" style={{ color: "var(--teal)", fontWeight: 600 }}>
            {selected.size} rate{selected.size !== 1 ? "s" : ""} selected
          </span>
          <div className="grow" />
          <button className="btn ghost tiny" onClick={() => setSelected(new Set())}>
            <Icon name="x" size={11} /> Clear
          </button>
          <button className="btn tiny" style={{ background: "var(--teal)", color: "#fff", borderColor: "var(--teal)" }}
            onClick={() => setBulkModal(true)}>
            <Icon name="copy" size={12} /> Bulk Renew
          </button>
        </div>
      )}

      {bulkModal && !isExpired && (
        <BulkRenewModal
          rates={displayRows.filter(r => selected.has(String(r.id)))}
          onClose={() => setBulkModal(false)}
          onDone={() => { setBulkModal(false); setSelected(new Set()); onRefresh?.(); }}
        />
      )}

      <Card
        title={isExpired ? "Expired rates" : "Rates approaching expiry"}
        sub={isExpired
          ? `${displayRows.length} rate(s) past expiry · no active successor found`
          : `${displayRows.length} active rate(s) within ${window} days · auto-emailed to Rate Focal at 60/30/7 day intervals`}
        right={activeFilterCount > 0 && (
          <button className="btn ghost tiny" onClick={() => setFilters({})}>
            <Icon name="x" size={11} /> Clear filters
          </button>
        )}>
        <div className="table-wrap" style={{ minHeight: 240, position: "relative" }}>
          {loading && data && <RecalcOverlay />}
          <table className="table">
            <thead>
              <tr>
                {!isExpired && (
                  <th style={{ width: 32, padding: "2px 6px", verticalAlign: "middle" }}>
                    <input type="checkbox"
                      checked={displayRows.length > 0 && displayRows.every(r => selected.has(String(r.id)))}
                      onChange={e => {
                        if (e.target.checked) {
                          setSelected(new Set(displayRows.map(r => String(r.id))));
                        } else {
                          setSelected(new Set());
                        }
                      }}
                      style={{ cursor: "pointer" }}
                    />
                  </th>
                )}
                <th style={{ padding: "2px 4px" }}>Rate ID</th>
                {fDrop("terminal",           "Terminal")}
                {fDrop("cost_type",          "Cost Type")}
                {fDrop("equipment",          "Equipment")}
                {fDrop("container_size",     "Size")}
                {fDrop("container_fullness", "Fullness")}
                {fDrop("shipment_type",      "Shipment")}
                {fDrop("brand",              "Brand")}
                {!isExpired && <th style={{ padding: "2px 4px" }}>Rate</th>}
                {fDrop("expiry", isExpired ? "Expired on" : "Expiry")}
                <th style={{ padding: "2px 4px" }}>{isExpired ? "Overdue" : "Days Left"}</th>
                {!isExpired && fDrop("focal", "Focal")}
                <th style={{ padding: "2px 4px" }} />
              </tr>
            </thead>
            <tbody>
              {displayRows.sort((a, b) => a.days - b.days).map(r => {
                const expired = r.days < 0;
                const isSelected = selected.has(String(r.id));
                const color = expired ? "var(--red)"
                  : r.days <= 14 ? "var(--red)"
                  : r.days <= 30 ? "var(--amber)"
                  : "#d8a83a";
                return (
                  <tr key={r.id} style={{
                    ...(expired ? { background: "#fef4f4" } : {}),
                    ...(isSelected ? { background: "rgba(0,158,135,.06)" } : {}),
                  }}>
                    {!isExpired && (
                      <td style={{ padding: "4px 6px", verticalAlign: "middle" }}>
                        <input type="checkbox" checked={isSelected}
                          onChange={e => {
                            const id = String(r.id);
                            setSelected(prev => {
                              const next = new Set(prev);
                              e.target.checked ? next.add(id) : next.delete(id);
                              return next;
                            });
                          }}
                          style={{ cursor: "pointer" }}
                        />
                      </td>
                    )}
                    <td className="mono tiny bold">{r.id}</td>
                    <td className="mono tiny">{r.terminal}</td>
                    <td>{r.cost_type}</td>
                    <td className="tiny mono">{r.equipment || "—"}</td>
                    <td className="tiny mono">{r.container_size || "—"}</td>
                    <td className="tiny mono">{r.container_fullness || "—"}</td>
                    <td className="tiny mono">{r.shipment_type || "—"}</td>
                    <td className="tiny">{r.brand || "—"}</td>
                    {!isExpired && (
                      <td className="num mono">{r.rate != null ? fmt.num(r.rate) : "—"} {r.ccy}</td>
                    )}
                    <td className="mono tiny">{r.expiry}</td>
                    <td>
                      <div className="row gap-6">
                        <span style={{ width: 8, height: 8, background: color, borderRadius: 50 }} />
                        <span className="mono tiny" style={{ color, fontWeight: 600 }}>
                          {expired ? `${Math.abs(r.days)}d ago` : `${r.days}d`}
                        </span>
                      </div>
                    </td>
                    {!isExpired && <td className="tiny">{r.focal}</td>}
                    <td className="row gap-4">
                      <button className="btn tiny" style={{ height: 24, fontSize: 11 }} onClick={() => handleRenew(r)}>
                        <Icon name="copy" size={10} /> Renew
                      </button>
                      {!isExpired && <button className="btn ghost tiny" style={{ height: 24 }}><Icon name="bell" size={10} /></button>}
                    </td>
                  </tr>
                );
              })}
              {!loading && displayRows.length === 0 && (
                <tr>
                  <td colSpan={isExpired ? 13 : 14} style={{ textAlign: "center", color: "var(--ink-mute)", padding: 24 }}>
                    {isExpired ? "No expired rates found" : `No rates expiring within ${window} days`}
                  </td>
                </tr>
              )}
            </tbody>
          </table>
        </div>
      </Card>
    </div>
  );
}

// ============== VALIDATION PANEL ==============
function Validation({ data, loading, onOpenRate, terminalFilter, onRefresh }) {
  const [severity, setSeverity]         = useState("");
  const [colFilters, setColFilters]     = useState({});
  const [openDropdown, setOpenDropdown] = useState(null);
  const [dropdownSearch, setDropdownSearch] = useState("");
  const [fixing, setFixing]             = useState(new Set()); // Set of target strings being fixed
  const [fixed, setFixed]               = useState(new Set()); // Set of targets successfully fixed

  const handleAutoFix = async (r) => {
    const numId = parseInt(r.target.replace(/^R-/i, ""), 10);
    if (!numId) return;
    const detail = r.detail || "";
    let updates = null;
    if (detail.includes("slab_max = 0"))  updates = { slab_max: "" };   // "" → NULL in batch PATCH
    if (detail.includes("slab_min = 0"))  updates = { slab_min: 1 };
    if (!updates) return;

    setFixing(prev => new Set(prev).add(r.target));
    try {
      await apiFetch("/api/rates/batch", {
        method: "PATCH",
        body: JSON.stringify({ ids: [numId], updates }),
      });
      setFixed(prev => new Set(prev).add(r.target));
      onRefresh?.();
    } catch { /* leave button in error state — user can retry */ }
    setFixing(prev => { const s = new Set(prev); s.delete(r.target); return s; });
  };

  const findings = (data?.findings || []).filter(r => !terminalFilter || r.terminal === terminalFilter);
  const rules = data?.rules || [];
  const errCount = findings.filter(r => r.severity === "Error").length;
  const warnCount = findings.filter(r => r.severity === "Warning").length;

  // severity button filter first, then column filters
  const severityRows = findings.filter(r => !severity || r.severity === severity);
  const rows = severityRows.filter(r =>
    Object.entries(colFilters).every(([k, sel]) => !sel.length || sel.includes(String(r[k] ?? "")))
  );
  const activeFilterCount = Object.values(colFilters).filter(v => v.length > 0).length;

  const lastRunStr = data?.last_run
    ? `${data.last_run} · ${(data.rates_scanned || 0).toLocaleString()} rates scanned`
    : "—";

  useEffect(() => {
    const h = e => { if (!e.target.closest("[data-vfdrop]")) { setOpenDropdown(null); setDropdownSearch(""); } };
    document.addEventListener("mousedown", h);
    return () => document.removeEventListener("mousedown", h);
  }, []);

  // column filter dropdown — options sourced from severityRows (respects severity button)
  const fDrop = (key, header) => {
    const options = [...new Set(severityRows.map(r => String(r[key] ?? "")).filter(Boolean))].sort();
    const sel = colFilters[key] || [];
    const active = sel.length > 0;
    const dropId = `v-${key}`;
    return (
      <th key={key} style={{ verticalAlign: "top", padding: "2px 4px", whiteSpace: "nowrap" }}>
        <div style={{ marginBottom: 2, fontSize: 11, fontWeight: 600, color: "var(--ink)" }}>{header}</div>
        <div style={{ position: "relative" }} data-vfdrop onMouseDown={e => e.stopPropagation()}>
          <button onClick={() => { setOpenDropdown(openDropdown === dropId ? null : dropId); setDropdownSearch(""); }}
            style={{ width: "100%", padding: "2px 6px", fontSize: 11, cursor: "pointer", textAlign: "left",
              border: `1px solid ${active ? "var(--teal)" : "var(--border)"}`, borderRadius: 3,
              background: active ? "rgba(0,158,135,.12)" : "var(--paper)",
              color: active ? "var(--teal)" : "var(--ink-mute)" }}>
            {active ? `✓ ${sel.length}/${options.length}` : "▾ All"}
          </button>
          {openDropdown === dropId && (
            <div style={{ position: "absolute", top: "calc(100% + 2px)", left: 0, zIndex: 400,
              background: "var(--paper)", border: "1px solid var(--border)", borderRadius: 4,
              boxShadow: "0 4px 16px rgba(0,0,0,.18)", minWidth: 160 }}>
              <div style={{ padding: "6px 8px", borderBottom: "1px solid var(--border)" }}>
                <input autoFocus value={dropdownSearch} onChange={e => setDropdownSearch(e.target.value)}
                  placeholder="Search…" style={{ width: "100%", padding: "3px 7px", fontSize: 12,
                    border: "1px solid var(--border)", borderRadius: 3, outline: "none",
                    boxSizing: "border-box", background: "var(--paper)" }} />
              </div>
              <div style={{ padding: "4px 8px", borderBottom: "1px solid var(--border)", display: "flex", gap: 6 }}>
                <button className="btn ghost" style={{ fontSize: 10, padding: "1px 8px", color: "var(--teal)" }}
                  onClick={() => setColFilters(f => ({ ...f, [key]: options }))}>All</button>
                <button className="btn ghost" style={{ fontSize: 10, padding: "1px 8px" }}
                  onClick={() => setColFilters(f => ({ ...f, [key]: [] }))}>Clear</button>
              </div>
              <div style={{ maxHeight: 220, overflowY: "auto" }}>
                {options.filter(v => !dropdownSearch || v.toLowerCase().includes(dropdownSearch.toLowerCase())).map(v => (
                  <label key={v} style={{ display: "flex", alignItems: "center", gap: 8, padding: "5px 10px", cursor: "pointer", fontSize: 12 }}>
                    <input type="checkbox" checked={sel.includes(v)}
                      onChange={() => setColFilters(f => {
                        const cur = f[key] || [];
                        return { ...f, [key]: cur.includes(v) ? cur.filter(x => x !== v) : [...cur, v] };
                      })} />
                    {v}
                  </label>
                ))}
                {options.filter(v => !dropdownSearch || v.toLowerCase().includes(dropdownSearch.toLowerCase())).length === 0 && (
                  <div style={{ padding: "8px 10px", fontSize: 12, color: "var(--ink-mute)" }}>No options</div>
                )}
              </div>
            </div>
          )}
        </div>
      </th>
    );
  };

  return (
    <div style={{ display: "grid", gridTemplateColumns: "1fr 320px", gap: 16 }}>
      <div>
        <div className="filterbar">
          <div className="row gap-4" style={{ background: "var(--paper)", padding: 3, borderRadius: 4 }}>
            {[
              { v: "", l: `All ${findings.length}` },
              { v: "Error", l: `Errors ${errCount}`, c: "var(--red)" },
              { v: "Warning", l: `Warnings ${warnCount}`, c: "var(--amber)" },
            ].map(s => (
              <button key={s.v} onClick={() => setSeverity(s.v)}
                style={{ padding: "4px 10px", border: 0, borderRadius: 3, background: severity === s.v ? "#fff" : "transparent", fontWeight: severity === s.v ? 600 : 400, color: severity === s.v ? (s.c || "var(--ink)") : "var(--ink-mute)", cursor: "pointer", fontSize: 12 }}>{s.l}</button>
            ))}
          </div>
          <div className="grow" />
          <span className="tiny muted mono">Last run: {lastRunStr}</span>
          <button className="btn primary" onClick={onRefresh}><Icon name="refresh" size={14} /> Re-run</button>
        </div>

        <Card
          title={`Open findings · ${rows.length} row${rows.length !== 1 ? "s" : ""}${activeFilterCount ? ` (${activeFilterCount} filter${activeFilterCount > 1 ? "s" : ""} active)` : ""}`}
          sub="Errors block save · warnings allow save with notice"
          right={activeFilterCount > 0 && (
            <button className="btn ghost tiny" onClick={() => setColFilters({})}>
              <Icon name="x" size={11} /> Clear filters
            </button>
          )}>
          <div className="table-wrap" style={{ minHeight: 320 }}>
            <table className="table">
              <thead>
                <tr>
                  {fDrop("rule",          "Rule")}
                  {fDrop("severity",      "Severity")}
                  {fDrop("terminal",      "Terminal")}
                  {fDrop("cost_type",     "Cost Type")}
                  {fDrop("shipment_type", "Shipment Type")}
                  {fDrop("target",        "Target")}
                  <th style={{ padding: "2px 4px" }}>Detail</th>
                  <th style={{ padding: "2px 4px" }} />
                </tr>
              </thead>
              <tbody>
                {rows.map((r, i) => (
                  <tr key={i} style={{ background: r.severity === "Error" ? "#fef4f4" : "#fdf6e7" }}>
                    <td className="mono tiny">
                      <div className="bold">{r.rule}</div>
                      <div className="muted">{r.name}</div>
                    </td>
                    <td>
                      {r.severity === "Error" && <Pill className="rejected">Error</Pill>}
                      {r.severity === "Warning" && <Pill status="Pending">Warning</Pill>}
                    </td>
                    <td className="mono tiny">{r.terminal}</td>
                    <td className="tiny">{r.cost_type || <span className="muted">—</span>}</td>
                    <td className="tiny">{r.shipment_type || <span className="muted">—</span>}</td>
                    <td className="tiny mono">{r.target}</td>
                    <td className="tiny muted" style={{ maxWidth: 320 }}>{r.detail}</td>
                    <td className="row gap-4">
                      {r.rule === "QV-02" && (
                        <button className="btn tiny" style={{ height: 24, fontSize: 11, color: "var(--teal)" }}>Auto-fix</button>
                      )}
                      {r.rule === "QV-06" && r.severity === "Warning" && (() => {
                        const isFixing = fixing.has(r.target);
                        const isFixed  = fixed.has(r.target);
                        return (
                          <button className="btn tiny"
                            disabled={isFixing || isFixed}
                            onClick={() => handleAutoFix(r)}
                            style={{ height: 24, fontSize: 11,
                              color: isFixed ? "var(--green)" : "var(--teal)",
                              display: "flex", alignItems: "center", gap: 4 }}>
                            {isFixing
                              ? <><div className="ocms-spinner" style={{ width: 10, height: 10, borderWidth: 2 }} /> Fixing…</>
                              : isFixed ? "✓ Fixed" : "Auto-fix"}
                          </button>
                        );
                      })()}
                      {onOpenRate && r.target && (
                        <button className="btn ghost tiny" style={{ height: 24, fontSize: 11 }}
                          onClick={() => {
                            const id = r.target.replace(/^R-/i, "");
                            apiFetch(`/api/rates/${id}`).then(rate => { if (rate?.id) onOpenRate(rate); }).catch(() => {});
                          }}>View record</button>
                      )}
                    </td>
                  </tr>
                ))}
                {!loading && rows.length === 0 && (
                  <tr><td colSpan={8} style={{ textAlign: "center", color: "var(--ink-mute)", padding: 24 }}>No findings{severity ? ` for ${severity}` : ""}{ activeFilterCount ? " matching filters" : ""}</td></tr>
                )}
              </tbody>
            </table>
          </div>
        </Card>
      </div>

      <Card title="Validation Rules" sub="Server-side enforcement">
        <div style={{ padding: "4px 0" }}>
          {rules.map((r, i) => (
            <div key={r.id} style={{ padding: "10px 14px", borderTop: i === 0 ? 0 : "1px solid var(--line)" }}>
              <div className="row gap-6">
                <span className="mono bold tiny">{r.id}</span>
                <span className="bold tiny">{r.name}</span>
                <span style={{ marginLeft: "auto", color: r.severity === "Error" ? "var(--red)" : "var(--amber)" }} className="tiny">{r.severity === "Error" ? "Blocks save" : "Warns"}</span>
              </div>
              <div className="tiny muted" style={{ marginTop: 4, lineHeight: 1.45 }}>{r.desc}</div>
            </div>
          ))}
        </div>
      </Card>
    </div>
  );
}

const QV08_COLS = [
  { key: "containertype",      label: "Equipment Type" },
  { key: "container_size",     label: "Container Size" },
  { key: "container_fullness", label: "Full/Empty" },
  { key: "shipment_type",      label: "Shipment Type" },
  { key: "slab_type",          label: "Slab Type" },
  { key: "slab_unit",          label: "Slab Unit" },
];

function QV08Config({ data, perms, onSaved }) {
  const canEdit = perms?.edit || perms?.admin;
  // local draft state: { [cost_type_id]: { required_cols: string[], qv_severity: string, dirty: bool, saving: bool, saved: bool } }
  const [draft, setDraft] = useState({});

  useEffect(() => {
    if (!data) return;
    const init = {};
    data.forEach(ct => {
      init[ct.id] = {
        required_cols: ct.required_cols || [],
        qv_severity: ct.qv_severity || "Warning",
        dirty: false, saving: false, saved: false,
      };
    });
    setDraft(init);
  }, [data]);

  const toggleCol = (id, col) => {
    if (!canEdit) return;
    setDraft(prev => {
      const cur = prev[id] || { required_cols: [], qv_severity: "Warning" };
      const cols = cur.required_cols.includes(col)
        ? cur.required_cols.filter(c => c !== col)
        : [...cur.required_cols, col];
      return { ...prev, [id]: { ...cur, required_cols: cols, dirty: true, saved: false } };
    });
  };

  const setSeverity = (id, val) => {
    if (!canEdit) return;
    setDraft(prev => {
      const cur = prev[id] || { required_cols: [], qv_severity: "Warning" };
      return { ...prev, [id]: { ...cur, qv_severity: val, dirty: true, saved: false } };
    });
  };

  const saveRow = async (id) => {
    const row = draft[id];
    if (!row) return;
    setDraft(prev => ({ ...prev, [id]: { ...prev[id], saving: true } }));
    try {
      await apiFetch(`/api/health/config/required-cols/${id}`, {
        method: "PUT",
        body: JSON.stringify({ required_cols: row.required_cols, qv_severity: row.qv_severity }),
      });
      setDraft(prev => ({ ...prev, [id]: { ...prev[id], saving: false, dirty: false, saved: true } }));
      onSaved?.();
    } catch {
      setDraft(prev => ({ ...prev, [id]: { ...prev[id], saving: false } }));
    }
  };

  if (!data) {
    return (
      <Card title="QV-08 Required Field Config" sub="Configure which dimension fields must be filled per cost type">
        <div style={{ textAlign: "center", padding: 32, color: "var(--ink-mute)" }}>Loading…</div>
      </Card>
    );
  }

  const rows = (data || []).filter(ct => ct.is_active);

  return (
    <Card title="QV-08 Required Field Config"
      sub="Tick the dimensions that must NOT be blank for each cost type. A rate missing a required field will be flagged in Quality Validation.">
      {!canEdit && (
        <div style={{ marginBottom: 12, padding: "8px 12px", background: "rgba(0,0,0,.04)",
          borderRadius: 6, fontSize: 12, color: "var(--ink-mute)" }}>
          Read-only — contact an Admin to change these settings.
        </div>
      )}
      <div className="table-wrap">
        <table className="data-table">
          <thead>
            <tr>
              <th style={{ width: 180 }}>Cost Type</th>
              {QV08_COLS.map(c => (
                <th key={c.key} style={{ textAlign: "center", width: 90, whiteSpace: "normal", lineHeight: 1.3 }}>
                  {c.label}
                </th>
              ))}
              <th style={{ width: 110 }}>If Missing</th>
              {canEdit && <th style={{ width: 80 }}></th>}
            </tr>
          </thead>
          <tbody>
            {rows.map(ct => {
              const d = draft[ct.id] || { required_cols: ct.required_cols || [], qv_severity: ct.qv_severity || "Warning" };
              return (
                <tr key={ct.id}>
                  <td>
                    <div className="bold tiny">{ct.name}</div>
                    <div className="mono tiny muted">{ct.code}</div>
                  </td>
                  {QV08_COLS.map(col => {
                    const checked = d.required_cols.includes(col.key);
                    return (
                      <td key={col.key} style={{ textAlign: "center" }}>
                        <input type="checkbox" checked={checked} disabled={!canEdit}
                          onChange={() => toggleCol(ct.id, col.key)}
                          style={{ width: 16, height: 16, cursor: canEdit ? "pointer" : "default",
                            accentColor: "var(--teal)" }} />
                      </td>
                    );
                  })}
                  <td>
                    {canEdit ? (
                      <select value={d.qv_severity} onChange={e => setSeverity(ct.id, e.target.value)}
                        style={{ fontSize: 12, padding: "2px 6px", borderRadius: 4,
                          border: "1px solid var(--line)", background: "white",
                          color: d.qv_severity === "Error" ? "var(--red)" : "var(--amber)" }}>
                        <option value="Warning">Warning</option>
                        <option value="Error">Error</option>
                      </select>
                    ) : (
                      <span className="tiny" style={{ color: d.qv_severity === "Error" ? "var(--red)" : "var(--amber)" }}>
                        {d.qv_severity}
                      </span>
                    )}
                  </td>
                  {canEdit && (
                    <td>
                      {d.saving ? (
                        <div className="ocms-spinner" style={{ width: 16, height: 16, margin: "0 auto" }} />
                      ) : d.saved && !d.dirty ? (
                        <span style={{ color: "var(--teal)", fontSize: 13 }}>✓</span>
                      ) : (
                        <button className="btn ghost tiny" disabled={!d.dirty} onClick={() => saveRow(ct.id)}
                          style={{ opacity: d.dirty ? 1 : 0.4 }}>
                          Save
                        </button>
                      )}
                    </td>
                  )}
                </tr>
              );
            })}
            {rows.length === 0 && (
              <tr><td colSpan={QV08_COLS.length + 3} style={{ textAlign: "center", color: "var(--ink-mute)", padding: 24 }}>
                No active cost types configured.
              </td></tr>
            )}
          </tbody>
        </table>
      </div>
    </Card>
  );
}

Object.assign(window, { RateHealth });
