// Screens: Rate Calculator, AI Assistant, Export

// ─── Calculator helpers ───────────────────────────────────────────────────────

const UNIT_SHORT = {
  PER_TEU: "TEU", PER_FEU: "FEU", PER_BOX: "BOX",
  PER_DAY: "Day", PER_MOVE: "Move", PER_KWH: "kWh", UNIT: "Unit",
};

// Build pivot: cols = container combos, rows keyed by material/shipment/charge/slab
function buildPivot(rates) {
  const SIZE_ORD = { "20": 0, "40": 1, "45": 2, "All": 9 };
  const TYPE_ORD = { "DRY": 0, "REEFER": 1, "All": 9 };
  const FULL_ORD = { "Full": 0, "Empty": 1, "All": 9 };

  const colMap = new Map();
  for (const r of rates) {
    const cs = r.container_size || "All", ct = r.cargo_type || "All", cf = r.container_fullness || "All";
    const k = `${cf}|${cs}|${ct}`;
    if (!colMap.has(k)) colMap.set(k, { key: k, container_size: cs, cargo_type: ct, container_fullness: cf });
  }
  const cols = [...colMap.values()].sort((a, b) => {
    const f = (FULL_ORD[a.container_fullness] ?? 9) - (FULL_ORD[b.container_fullness] ?? 9); if (f) return f;
    const s = (SIZE_ORD[a.container_size] ?? 9) - (SIZE_ORD[b.container_size] ?? 9); if (s) return s;
    return (TYPE_ORD[a.cargo_type] ?? 9) - (TYPE_ORD[b.cargo_type] ?? 9);
  });

  const cells   = new Map();
  const rowMeta = new Map();

  for (const r of rates) {
    const cs = r.container_size || "All", ct = r.cargo_type || "All", cf = r.container_fullness || "All";
    const colKey = `${cf}|${cs}|${ct}`;
    const rowKey = [r.cost_type_name, r.shipment_type || "All",
                    r.charge_unit || "", r.slab_unit || "",
                    r.payable_by || "Maersk"].join("|");

    if (!rowMeta.has(rowKey)) {
      rowMeta.set(rowKey, {
        key: rowKey,
        cost_type_name: r.cost_type_name,
        cost_category: r.cost_category,
        shipment_type: r.shipment_type,
        charge_unit: r.charge_unit,
        slab_unit: r.slab_unit, isSlab: !!r.slab_unit,
        payable_by: r.payable_by || "Maersk",
      });
    }
    if (!cells.has(rowKey)) cells.set(rowKey, new Map());
    const rowCells = cells.get(rowKey);

    if (r.slab_unit) {
      if (!rowCells.has(colKey)) rowCells.set(colKey, { isSlab: true, tiers: [], payable_by: r.payable_by || "Maersk" });
      rowCells.get(colKey).tiers.push({
        id: r.id, rate_local: r.rate_local, currency: r.currency,
        slab_min: r.slab_min, slab_max: r.slab_max,
        slab_type: r.slab_type || "Step",
      });
    } else {
      rowCells.set(colKey, { isSlab: false, id: r.id, rate_local: r.rate_local, currency: r.currency, payable_by: r.payable_by || "Maersk" });
    }
  }

  for (const [, rowCells] of cells)
    for (const [, cell] of rowCells)
      if (cell.isSlab) cell.tiers.sort((a, b) => (a.slab_min ?? 0) - (b.slab_min ?? 0));

  const groups = new Map();
  const ctCategory = new Map();
  for (const [, meta] of rowMeta) {
    if (!groups.has(meta.cost_type_name)) groups.set(meta.cost_type_name, []);
    groups.get(meta.cost_type_name).push(meta);
    if (meta.cost_category) ctCategory.set(meta.cost_type_name, meta.cost_category);
  }

  return { cols, cells, groups, rowMeta, ctCategory };
}

// ─── Shared calc helpers (module-level for all tabs) ─────────────────────────

const normSlabType = s => (s || "").toUpperCase().replace(/[\s_]/g, "");
const isTierRateType = s => { const n = normSlabType(s); return n === "TIERRATE" || n === "TIER"; };

function calcCellResult(cell, inp, roeMap) {
  const qty = parseFloat(inp.qty);
  const slabVal = parseFloat(inp.slabVal);
  if (!qty || qty <= 0) return null;

  if (cell.isSlab) {
    if (!slabVal || slabVal <= 0) return null;
    let total_local = 0, currency = null;
    const activeTierIds = new Set();
    let anyHit = false;

    const tierRateTiers = cell.tiers.filter(t => isTierRateType(t.slab_type));
    if (tierRateTiers.length > 0) {
      const hit = tierRateTiers.find(t =>
        (t.slab_min == null || slabVal >= t.slab_min) &&
        (t.slab_max == null || slabVal <= t.slab_max)
      );
      if (hit) {
        const minSlabMin = Math.min(...tierRateTiers.map(t => t.slab_min ?? 0));
        const chargeableDays = slabVal - minSlabMin + 1;
        if (chargeableDays > 0) {
          total_local += parseFloat(hit.rate_local) * chargeableDays * qty;
          currency = hit.currency; activeTierIds.add(hit.id); anyHit = true;
        }
      }
    }

    for (const t of cell.tiers) {
      if (isTierRateType(t.slab_type)) continue;
      const st = normSlabType(t.slab_type || "Step");
      const inRange = (t.slab_min == null || slabVal >= t.slab_min) && (t.slab_max == null || slabVal <= t.slab_max);
      if (st === "LUMPSUM") {
        if (!inRange) continue;
        total_local += parseFloat(t.rate_local) * qty;
        currency = t.currency; activeTierIds.add(t.id); anyHit = true;
      } else {
        const rangeStart = t.slab_min ?? 1, rangeEnd = t.slab_max ?? slabVal;
        if (rangeStart > slabVal) continue;
        const unitsInBracket = Math.min(slabVal, rangeEnd) - rangeStart + 1;
        total_local += parseFloat(t.rate_local) * unitsInBracket * qty;
        currency = t.currency; activeTierIds.add(t.id); anyHit = true;
      }
    }

    if (!anyHit) return null;
    const roe = roeMap[currency] || 1;
    return { total_local, currency, total_usd: total_local / roe, activeTierIds };
  } else {
    const total_local = parseFloat(cell.rate_local) * qty;
    const roe = roeMap[cell.currency] || 1;
    return { total_local, currency: cell.currency, total_usd: total_local / roe };
  }
}

// ─── SingleRateTab ────────────────────────────────────────────────────────────

function SingleRateTab({ user, perms, today }) {
  const [terminals, setTerminals] = useState([]);
  const [costTypes, setCostTypes] = useState([]);
  const [brandCompanies, setBrandCompanies] = useState([]);
  const [roeMap, setRoeMap] = useState({});
  const [terminalCode, setTerminalCode] = useState("");
  const [costTypeCode, setCostTypeCode] = useState("");
  const [brandCompanyId, setBrandCompanyId] = useState("");
  const [rateRows, setRateRows] = useState([]);
  const [chargeUnitFilter, setChargeUnitFilter] = useState("");
  const [shipmentFilter, setShipmentFilter] = useState("");
  const [materialFilter, setMaterialFilter] = useState("");
  const [sizeFilter, setSizeFilter] = useState("");
  const [cargoTypeFilter, setCargoTypeFilter] = useState("");
  const [fullnessFilter, setFullnessFilter] = useState("");
  const [payableByFilter, setPayableByFilter] = useState("");
  const [sharedQty, setSharedQty] = useState("");
  const [sharedSlabVal, setSharedSlabVal] = useState("");
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const hideRate = !perms.viewRates;

  useEffect(() => {
    Promise.all([
      apiFetch("/api/metadata/terminals/scoped"),
      apiFetch("/api/metadata/cost-types"),
      apiFetch("/api/roe/latest"),
      apiFetch("/api/metadata/brand-companies").catch(() => []),
    ]).then(([terms, cts, roe, bcs]) => {
      setTerminals(Array.isArray(terms) ? terms : []);
      setCostTypes(Array.isArray(cts) ? cts : []);
      const m = {};
      (Array.isArray(roe) ? roe : []).forEach(r => { m[r.iso_code] = r.rate; });
      setRoeMap(m);
      setBrandCompanies(Array.isArray(bcs) ? bcs : []);
    }).catch(() => {});
  }, []);

  const loadRates = async () => {
    if (!terminalCode || !costTypeCode) return;
    setLoading(true); setError(null); setRateRows([]); setSharedQty(""); setSharedSlabVal("");
    setChargeUnitFilter(""); setShipmentFilter("");
    setMaterialFilter(""); setSizeFilter(""); setCargoTypeFilter(""); setFullnessFilter(""); setPayableByFilter("");
    try {
      const params = new URLSearchParams({ terminal_code: terminalCode });
      params.append("cost_type_codes", costTypeCode);
      if (brandCompanyId) params.set("brand_company_id", brandCompanyId);
      const data = await apiFetch(`/api/calculator/rates?${params}`);
      if (!Array.isArray(data)) throw new Error("Unexpected response");
      setRateRows(data);
    } catch (e) { setError(e.message || "Failed to load rates"); }
    setLoading(false);
  };


  // Faceted search: each filter's options = rateRows filtered by ALL OTHER active filters
  // matKey: use material_name; fall back to cost_type_name when material_name is null
  const matKey = r => r.material_name || r.cost_type_name || "";

  const allFilters = useMemo(() => ({ materialFilter, shipmentFilter, sizeFilter, cargoTypeFilter, fullnessFilter, chargeUnitFilter, payableByFilter }),
    [materialFilter, shipmentFilter, sizeFilter, cargoTypeFilter, fullnessFilter, chargeUnitFilter, payableByFilter]);

  const applyFilters = (rows, skip) => rows.filter(r =>
    (skip === 'material'   || !allFilters.materialFilter   || matKey(r)            === allFilters.materialFilter) &&
    (skip === 'shipment'   || !allFilters.shipmentFilter   || r.shipment_type      === allFilters.shipmentFilter) &&
    (skip === 'size'       || !allFilters.sizeFilter       || r.container_size     === allFilters.sizeFilter) &&
    (skip === 'cargoType'  || !allFilters.cargoTypeFilter  || r.cargo_type         === allFilters.cargoTypeFilter) &&
    (skip === 'fullness'   || !allFilters.fullnessFilter   || r.container_fullness === allFilters.fullnessFilter) &&
    (skip === 'chargeUnit' || !allFilters.chargeUnitFilter || r.charge_unit        === allFilters.chargeUnitFilter) &&
    (skip === 'payableBy'  || !allFilters.payableByFilter  || r.payable_by         === allFilters.payableByFilter)
  );

  const availableMaterials   = useMemo(() => [...new Set(applyFilters(rateRows, 'material').map(r => matKey(r)).filter(Boolean))].sort(),             [rateRows, allFilters]);
  const availableShipments   = useMemo(() => [...new Set(applyFilters(rateRows, 'shipment').map(r => r.shipment_type).filter(Boolean))],              [rateRows, allFilters]);
  const availableSizes       = useMemo(() => [...new Set(applyFilters(rateRows, 'size').map(r => r.container_size).filter(Boolean))].sort(),          [rateRows, allFilters]);
  const availableCargoTypes  = useMemo(() => [...new Set(applyFilters(rateRows, 'cargoType').map(r => r.cargo_type).filter(Boolean))].sort(),         [rateRows, allFilters]);
  const availableFullness    = useMemo(() => [...new Set(applyFilters(rateRows, 'fullness').map(r => r.container_fullness).filter(Boolean))].sort(),  [rateRows, allFilters]);
  const availableChargeUnits = useMemo(() => [...new Set(applyFilters(rateRows, 'chargeUnit').map(r => r.charge_unit).filter(Boolean))],              [rateRows, allFilters]);
  const availablePayableBy   = useMemo(() => [...new Set(applyFilters(rateRows, 'payableBy').map(r => r.payable_by).filter(Boolean))].sort(),         [rateRows, allFilters]);

  // Auto-clear stale selected values when they're no longer in the available set
  useEffect(() => { if (materialFilter   && !availableMaterials.includes(materialFilter))     setMaterialFilter(""); },   [availableMaterials]);
  useEffect(() => { if (shipmentFilter   && !availableShipments.includes(shipmentFilter))     setShipmentFilter(""); },   [availableShipments]);
  useEffect(() => { if (sizeFilter       && !availableSizes.includes(sizeFilter))             setSizeFilter(""); },       [availableSizes]);
  useEffect(() => { if (cargoTypeFilter  && !availableCargoTypes.includes(cargoTypeFilter))   setCargoTypeFilter(""); },  [availableCargoTypes]);
  useEffect(() => { if (fullnessFilter   && !availableFullness.includes(fullnessFilter))      setFullnessFilter(""); },   [availableFullness]);
  useEffect(() => { if (chargeUnitFilter && !availableChargeUnits.includes(chargeUnitFilter)) setChargeUnitFilter(""); }, [availableChargeUnits]);
  useEffect(() => { if (payableByFilter  && !availablePayableBy.includes(payableByFilter))    setPayableByFilter(""); },  [availablePayableBy]);

  const filteredRows = useMemo(() => applyFilters(rateRows, null), [rateRows, allFilters]);

  // Per-row results: rate × qty (each tier/row gets its own qty input)
  const sortedRows = useMemo(() => {
    const slab    = filteredRows.filter(r => r.slab_unit).sort((a, b) => (a.slab_min ?? 0) - (b.slab_min ?? 0));
    const nonSlab = filteredRows.filter(r => !r.slab_unit);
    return [...slab, ...nonSlab];
  }, [filteredRows]);

  const hasSlab = sortedRows.some(r => r.slab_unit);

  const rowResults = useMemo(() => {
    const qty = parseFloat(sharedQty);
    const slabVal = parseFloat(sharedSlabVal);
    if (!qty || qty <= 0) return {};
    const out = {};

    // Group slab rows by cost_type|shipment|charge_unit|slab_unit
    const slabGroups = new Map();
    for (const r of sortedRows) {
      if (!r.slab_unit) continue;
      const gk = `${r.cost_type_name}|${r.shipment_type || ""}|${r.charge_unit || ""}|${r.slab_unit}`;
      if (!slabGroups.has(gk)) slabGroups.set(gk, []);
      slabGroups.get(gk).push(r);
    }

    // Process each slab group using the same logic as calcCellResult
    for (const [, tiers] of slabGroups) {
      if (!slabVal || slabVal <= 0) {
        for (const t of tiers) out[t.id] = { total_local: 0, currency: t.currency, total_usd: 0, active: false };
        continue;
      }
      const sorted = [...tiers].sort((a, b) => (a.slab_min ?? 0) - (b.slab_min ?? 0));

      // TIER RATE: find matching tier, chargeableDays from global min slab_min
      const tierRateTiers = sorted.filter(t => isTierRateType(t.slab_type));
      if (tierRateTiers.length > 0) {
        const minSlabMin = Math.min(...tierRateTiers.map(t => t.slab_min ?? 0));
        const chargeableDays = slabVal - minSlabMin + 1;
        const hit = tierRateTiers.find(t =>
          (t.slab_min == null || slabVal >= t.slab_min) &&
          (t.slab_max == null || slabVal <= t.slab_max)
        );
        for (const t of tierRateTiers) {
          const roe = roeMap[t.currency] || 1;
          if (t === hit && chargeableDays > 0) {
            const total_local = parseFloat(t.rate_local) * chargeableDays * qty;
            out[t.id] = { total_local, currency: t.currency, total_usd: total_local / roe, active: true };
          } else {
            out[t.id] = { total_local: 0, currency: t.currency, total_usd: 0, active: false };
          }
        }
      }

      // LUMPSUM and STEP tiers
      for (const t of sorted) {
        if (isTierRateType(t.slab_type)) continue;
        const st = normSlabType(t.slab_type || "Step");
        const roe = roeMap[t.currency] || 1;
        const inRange = (t.slab_min == null || slabVal >= t.slab_min) && (t.slab_max == null || slabVal <= t.slab_max);
        if (st === "LUMPSUM") {
          if (inRange) {
            const total_local = parseFloat(t.rate_local) * qty;
            out[t.id] = { total_local, currency: t.currency, total_usd: total_local / roe, active: true };
          } else {
            out[t.id] = { total_local: 0, currency: t.currency, total_usd: 0, active: false };
          }
        } else {
          // STEP: accumulate units in bracket
          const rangeStart = t.slab_min ?? 1, rangeEnd = t.slab_max ?? slabVal;
          if (rangeStart > slabVal) {
            out[t.id] = { total_local: 0, currency: t.currency, total_usd: 0, active: false };
          } else {
            const unitsInBracket = Math.min(slabVal, rangeEnd) - rangeStart + 1;
            const total_local = parseFloat(t.rate_local) * unitsInBracket * qty;
            out[t.id] = { total_local, currency: t.currency, total_usd: total_local / roe, active: true };
          }
        }
      }
    }

    // Non-slab rows: rate × qty
    for (const r of sortedRows) {
      if (r.slab_unit) continue;
      const rate = parseFloat(r.rate_local);
      if (!rate && rate !== 0) continue;
      const roe = roeMap[r.currency] || 1;
      const total_local = rate * qty;
      out[r.id] = { total_local, currency: r.currency, total_usd: total_local / roe, active: true };
    }

    return out;
  }, [sortedRows, sharedQty, sharedSlabVal, roeMap]);

  const totalUsd = Object.values(rowResults).reduce((s, r) => s + (r?.active ? r.total_usd || 0 : 0), 0);
  const currenciesInUse = [...new Set(filteredRows.map(r => r.currency).filter(Boolean))];
  const selectedTerminal = terminals.find(t => t.code === terminalCode);
  const selectedCostType = costTypes.find(c => c.code === costTypeCode);

  // Slab cost curve: compute cost at each integer slab value across the full range
  const slabChartData = useMemo(() => {
    if (!hasSlab || !sharedQty) return null;
    const qty = parseFloat(sharedQty);
    if (!qty || qty <= 0) return null;

    const slabGroups = new Map();
    for (const r of sortedRows) {
      if (!r.slab_unit) continue;
      const gk = `${r.cost_type_name}|${r.shipment_type || ""}|${r.charge_unit || ""}|${r.slab_unit}`;
      if (!slabGroups.has(gk)) slabGroups.set(gk, []);
      slabGroups.get(gk).push(r);
    }
    if (slabGroups.size === 0) return null;

    const allTiers = [...slabGroups.values()].flat();
    const definedMaxes = allTiers.map(t => t.slab_max).filter(v => v != null);
    const definedMins  = allTiers.map(t => t.slab_min ?? 0);
    const curSV = parseFloat(sharedSlabVal) || 0;
    const xMax = definedMaxes.length > 0
      ? Math.max(Math.max(...definedMaxes) * 1.5, curSV)
      : Math.max(Math.max(...definedMins) * 1.5 + 30, curSV);
    const xMin = 0;

    const calcAt = sv => {
      let total = 0;
      for (const [, tiers] of slabGroups) {
        const sorted = [...tiers].sort((a, b) => (a.slab_min ?? 0) - (b.slab_min ?? 0));
        const trTiers = sorted.filter(t => isTierRateType(t.slab_type));
        if (trTiers.length > 0) {
          const minMin = Math.min(...trTiers.map(t => t.slab_min ?? 0));
          const days = sv - minMin + 1;
          const hit = trTiers.find(t =>
            (t.slab_min == null || sv >= t.slab_min) && (t.slab_max == null || sv <= t.slab_max));
          if (hit && days > 0) total += parseFloat(hit.rate_local) * days * qty / (roeMap[hit.currency] || 1);
        }
        for (const t of sorted) {
          if (isTierRateType(t.slab_type)) continue;
          const st = normSlabType(t.slab_type || "Step");
          const roe = roeMap[t.currency] || 1;
          const inRange = (t.slab_min == null || sv >= t.slab_min) && (t.slab_max == null || sv <= t.slab_max);
          if (st === "LUMPSUM") {
            if (inRange) total += parseFloat(t.rate_local) * qty / roe;
          } else {
            const rs = t.slab_min ?? 1, re = t.slab_max ?? sv;
            if (rs <= sv) total += parseFloat(t.rate_local) * (Math.min(sv, re) - rs + 1) * qty / roe;
          }
        }
      }
      return total;
    };

    const step = Math.max(1, Math.ceil(xMax / 80));
    const points = [];
    for (let x = xMin; x <= xMax; x += step) points.push({ x, y: calcAt(x) });
    if (points[points.length - 1]?.x !== xMax) points.push({ x: xMax, y: calcAt(xMax) });

    const slabRow = sortedRows.find(r => r.slab_unit);
    const slabUnitLabel = slabRow ? (UNIT_SHORT[slabRow.slab_unit] || slabRow.slab_unit) : "";
    const tierBoundaries = [...new Set(
      [...slabGroups.values()].flat()
        .flatMap(t => [t.slab_min, t.slab_max])
        .filter(v => v != null)
    )].sort((a, b) => a - b);
    return { points, xMin, xMax, slabUnitLabel, tierBoundaries };
  }, [hasSlab, sharedQty, sharedSlabVal, sortedRows, roeMap]);

  // Label for each row in left panel qty section and right breakdown
  const rowLabel = r => {
    if (!r.slab_unit) return matKey(r) || "—";
    const u = UNIT_SHORT[r.slab_unit] || r.slab_unit;
    if (r.slab_min != null && r.slab_max != null) return `${u} ${r.slab_min}–${r.slab_max}`;
    if (r.slab_min != null) return `${u} ${r.slab_min}+`;
    if (r.slab_max != null) return `≤${r.slab_max} ${u}`;
    return u;
  };
  const rowUnit = r => r.slab_unit ? (UNIT_SHORT[r.slab_unit] || r.slab_unit) : (UNIT_SHORT[r.charge_unit] || r.charge_unit || "");

  const filterBlock = (label, value, setter, options, renderOpt) => (
    <div key={label}>
      <div className="tiny muted mono" style={{ textTransform: "uppercase", letterSpacing: 0.06, fontSize: 11, marginBottom: 4 }}>{label}</div>
      <select className="select" value={value} onChange={e => setter(e.target.value)} style={{ width: "100%" }}>
        <option value="">All</option>
        {options.map(renderOpt)}
      </select>
    </div>
  );

  // Read-only badge for dimensions with only 1 available option (implied by other filters)
  const filterBadge = (label, value) => (
    <div key={label}>
      <div className="tiny muted mono" style={{ textTransform: "uppercase", letterSpacing: 0.06, fontSize: 11, marginBottom: 4 }}>{label}</div>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "5px 10px", background: "var(--paper-2)", borderRadius: 5, border: "1px solid var(--line)" }}>
        <span className="tiny mono bold">{value}</span>
        <span className="tiny muted" style={{ fontSize: 10 }}>auto</span>
      </div>
    </div>
  );

  const hasFilters = availableMaterials.length >= 1 ||
    availableShipments.length >= 1 ||
    availableSizes.length >= 1 ||
    availableCargoTypes.length >= 1 ||
    availableFullness.length >= 1 ||
    availableChargeUnits.length >= 1 ||
    availablePayableBy.length >= 1;

  // Show qty as soon as any filtered rates are available
  const allFiltersSet = filteredRows.length > 0;

  return (
    <div style={{ display: "grid", gridTemplateColumns: "380px 1fr", gap: 18, alignItems: "start" }}>

      {/* ── Left panel: unified Inputs card ── */}
      <div className="card" style={{ padding: "20px 18px", display: "flex", flexDirection: "column", gap: 0 }}>
        <div style={{ marginBottom: 18 }}>
          <div style={{ fontWeight: 700, fontSize: 15 }}>Inputs</div>
          <div className="tiny muted" style={{ marginTop: 2 }}>Active rate selected by today's date</div>
        </div>

        {/* Brand Company */}
        <div style={{ marginBottom: 14 }}>
          <div className="tiny muted mono" style={{ textTransform: "uppercase", letterSpacing: 0.06, fontSize: 11, marginBottom: 4 }}>Brand Company</div>
          <select className="select" style={{ width: "100%" }} value={brandCompanyId}
            onChange={e => { setBrandCompanyId(e.target.value); setRateRows([]); }}>
            <option value="">All</option>
            {brandCompanies.map(bc => <option key={bc.id} value={String(bc.id)}>{bc.name} ({bc.code})</option>)}
          </select>
        </div>

        {/* Terminal */}
        <div style={{ marginBottom: 14 }}>
          <div className="tiny muted mono" style={{ textTransform: "uppercase", letterSpacing: 0.06, fontSize: 11, marginBottom: 4 }}>Terminal / Port <span style={{ color: "var(--red)" }}>*</span></div>
          <SearchSelect options={terminals} value={terminalCode}
            onChange={v => { setTerminalCode(v); setRateRows([]); }}
            placeholder="Search terminal…"
            getValue={t => t.code} getLabel={t => `${t.name} (${t.code})`} />
        </div>

        {/* Cost Type */}
        <div style={{ marginBottom: 14 }}>
          <div className="tiny muted mono" style={{ textTransform: "uppercase", letterSpacing: 0.06, fontSize: 11, marginBottom: 4 }}>Cost Type <span style={{ color: "var(--red)" }}>*</span></div>
          <select className="select" style={{ width: "100%" }} value={costTypeCode}
            onChange={e => { setCostTypeCode(e.target.value); setRateRows([]); }}>
            <option value="">— select —</option>
            {(() => {
              const groups = new Map();
              for (const c of costTypes) {
                const cat = c.category || "Other";
                if (!groups.has(cat)) groups.set(cat, []);
                groups.get(cat).push(c);
              }
              return [...groups.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([cat, items]) => (
                <optgroup key={cat} label={cat}>
                  {items.map(c => <option key={c.code} value={c.code}>{c.name}</option>)}
                </optgroup>
              ));
            })()}
          </select>
        </div>

        {/* Load Rates button */}
        {error && <div className="tiny" style={{ color: "var(--red)", marginBottom: 8 }}>{error}</div>}
        <button className="btn teal" style={{ width: "100%", marginBottom: 14 }} disabled={!terminalCode || !costTypeCode || loading} onClick={loadRates}>
          <Icon name={loading ? "refresh" : "calculator"} size={14} />
          {loading ? "Loading…" : "Load Rates"}
        </button>

        {hideRate && (
          <div style={{ marginBottom: 14, padding: "8px 10px", background: "var(--teal-soft)", borderRadius: 6, border: "1px solid #c8e3ea", display: "flex", gap: 8, alignItems: "flex-start" }}>
            <Icon name="shield" size={13} stroke="var(--teal)" style={{ flexShrink: 0, marginTop: 1 }} />
            <div className="tiny">Rate values hidden (<b>rate_calculator</b> role)</div>
          </div>
        )}

        {/* Filters (shown after rates loaded) */}
        {rateRows.length > 0 && hasFilters && (
          <div style={{ borderTop: "1px solid var(--line)", paddingTop: 14, marginBottom: 14, display: "flex", flexDirection: "column", gap: 10 }}>
            {availableMaterials.length > 1
              ? filterBlock("Material", materialFilter, setMaterialFilter, availableMaterials, m => <option key={m} value={m}>{m}</option>)
              : availableMaterials.length === 1 ? filterBadge("Material", availableMaterials[0]) : null}
            {availableShipments.length > 1
              ? filterBlock("Shipment Type", shipmentFilter, setShipmentFilter, availableShipments, s => <option key={s} value={s}>{s}</option>)
              : availableShipments.length === 1 ? filterBadge("Shipment Type", availableShipments[0]) : null}
            {availableSizes.length > 1
              ? filterBlock("Container Size", sizeFilter, setSizeFilter, availableSizes, s => <option key={s} value={s}>{s}</option>)
              : availableSizes.length === 1 ? filterBadge("Container Size", availableSizes[0]) : null}
            {availableCargoTypes.length > 1
              ? filterBlock("Container Type", cargoTypeFilter, setCargoTypeFilter, availableCargoTypes, t => <option key={t} value={t}>{t}</option>)
              : availableCargoTypes.length === 1 ? filterBadge("Container Type", availableCargoTypes[0]) : null}
            {availableFullness.length > 1
              ? filterBlock("Fullness", fullnessFilter, setFullnessFilter, availableFullness, f => <option key={f} value={f}>{f}</option>)
              : availableFullness.length === 1 ? filterBadge("Fullness", availableFullness[0]) : null}
            {availableChargeUnits.length > 1
              ? filterBlock("Charge Unit", chargeUnitFilter, setChargeUnitFilter, availableChargeUnits, u => <option key={u} value={u}>{UNIT_SHORT[u] || u}</option>)
              : availableChargeUnits.length === 1 ? filterBadge("Charge Unit", UNIT_SHORT[availableChargeUnits[0]] || availableChargeUnits[0]) : null}
            {availablePayableBy.length > 1
              ? filterBlock("Payable By", payableByFilter, setPayableByFilter, availablePayableBy, v => <option key={v} value={v}>{v}</option>)
              : availablePayableBy.length === 1 ? filterBadge("Payable By", availablePayableBy[0]) : null}
          </div>
        )}

        {/* Quantity inputs */}
        {allFiltersSet && (
          <div style={{ borderTop: "1px solid var(--line)", paddingTop: 14, marginBottom: 14, display: "flex", flexDirection: "column", gap: 10 }}>
            {/* Quantity */}
            <div>
              <div className="tiny muted mono" style={{ textTransform: "uppercase", letterSpacing: 0.06, fontSize: 11, marginBottom: 6 }}>
                Quantity <span style={{ color: "var(--red)" }}>*</span>
              </div>
              <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                <input className="input mono" placeholder="0" type="number" min="0" step="1" value={sharedQty}
                  onChange={e => setSharedQty(e.target.value)}
                  style={{ flex: 1, height: 32, textAlign: "right", fontSize: 13 }} />
                {(() => {
                  const nonSlab = sortedRows.find(r => !r.slab_unit);
                  const u = nonSlab ? rowUnit(nonSlab) : (sortedRows[0] ? (UNIT_SHORT[sortedRows[0].charge_unit] || sortedRows[0].charge_unit || "") : "");
                  return u ? <span className="tiny muted" style={{ whiteSpace: "nowrap" }}>{u}</span> : null;
                })()}
              </div>
            </div>
            {/* Slab value — only when slab rows exist */}
            {hasSlab && (
              <div>
                <div className="tiny muted mono" style={{ textTransform: "uppercase", letterSpacing: 0.06, fontSize: 11, marginBottom: 6 }}>
                  {(() => {
                    const slabRow = sortedRows.find(r => r.slab_unit);
                    return slabRow ? (UNIT_SHORT[slabRow.slab_unit] || slabRow.slab_unit) : "Slab Value";
                  })()} <span style={{ color: "var(--red)" }}>*</span>
                </div>
                <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                  <input className="input mono" placeholder="0" type="number" min="0" step="1" value={sharedSlabVal}
                    onChange={e => setSharedSlabVal(e.target.value)}
                    style={{ flex: 1, height: 32, textAlign: "right", fontSize: 13 }} />
                  {(() => {
                    const slabRow = sortedRows.find(r => r.slab_unit);
                    const u = slabRow ? (UNIT_SHORT[slabRow.slab_unit] || slabRow.slab_unit) : "";
                    return u ? <span className="tiny muted" style={{ whiteSpace: "nowrap" }}>{u}</span> : null;
                  })()}
                </div>
              </div>
            )}
          </div>
        )}

        {/* ROE */}
        {currenciesInUse.length > 0 && (
          <div style={{ borderTop: "1px solid var(--line)", paddingTop: 12, marginBottom: 14, display: "flex", flexDirection: "column", gap: 5 }}>
            {currenciesInUse.map(iso => (
              <div key={iso} style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
                <span className="tiny muted">ROE in effect ({iso} / USD)</span>
                <span className="tiny mono bold">{hideRate ? "****" : roeMap[iso] ? roeMap[iso].toFixed(4) : "—"}</span>
              </div>
            ))}
          </div>
        )}

      </div>

      {/* ── Right panel ── */}
      <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>

        {/* Empty state */}
        {rateRows.length === 0 && (
          <div className="card" style={{ padding: "60px 0", textAlign: "center", color: "var(--ink-mute)" }}>
            <Icon name="calculator" size={40} stroke="var(--border)" />
            <div className="tiny" style={{ marginTop: 12 }}>Select terminal and cost type, then load rates</div>
          </div>
        )}

        {/* Estimated cost + optional slab chart */}
        {filteredRows.length > 0 && (
          <div style={{ display: "flex", gap: 14, alignItems: "stretch" }}>
            {/* Estimated Cost card */}
            <div className="card" style={{ padding: "24px 28px", minWidth: 200 }}>
              <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 8 }}>
                <span className="tiny muted mono" style={{ textTransform: "uppercase", letterSpacing: 0.08, fontSize: 11 }}>Estimated Cost</span>
                <span style={{ fontSize: 10, padding: "1px 6px", border: "1px solid var(--line)", borderRadius: 3, fontWeight: 600, color: "var(--ink-mute)" }}>USD</span>
              </div>
              <div style={{ fontSize: 40, fontWeight: 700, fontFamily: "var(--font-serif)", color: (sharedQty && totalUsd > 0) ? "var(--teal)" : "var(--ink-mute)", lineHeight: 1.1 }}>
                {sharedQty ? fmt.usd(totalUsd) : "—"}
              </div>
              {(selectedTerminal || selectedCostType) && (
                <div className="tiny muted" style={{ marginTop: 6 }}>
                  {[selectedTerminal?.name, selectedCostType?.name, `as of ${today}`].filter(Boolean).join(" · ")}
                </div>
              )}
            </div>

            {/* Slab cost curve chart */}
            {slabChartData && (() => {
              const { points, xMin, xMax, slabUnitLabel, tierBoundaries } = slabChartData;
              const VW = 320, VH = 150, PL = 52, PR = 12, PT = 12, PB = 32;
              const iW = VW - PL - PR, iH = VH - PT - PB;
              const yMax = Math.max(...points.map(p => p.y), 0.01);
              const toX = x => PL + ((x - xMin) / (xMax - xMin || 1)) * iW;
              const toY = y => PT + (1 - y / yMax) * iH;
              const poly = points.map(p => `${toX(p.x).toFixed(1)},${toY(p.y).toFixed(1)}`).join(" ");
              const area = `${PL},${PT + iH} ${poly} ${toX(xMax).toFixed(1)},${PT + iH}`;
              const curSV = parseFloat(sharedSlabVal);
              const curPt = curSV > 0 ? points.reduce((a, b) => Math.abs(b.x - curSV) < Math.abs(a.x - curSV) ? b : a) : null;
              const fmtY = v => v >= 10000 ? `${(v/1000).toFixed(0)}k` : v >= 1000 ? `${(v/1000).toFixed(1)}k` : v.toFixed(0);
              const yTicks = [0, yMax / 2, yMax];
              // X ticks: 0 + tier boundaries + current SlabVal, deduped, sorted, crowding removed
              const slabValTick = curSV > 0 && curSV <= xMax ? curSV : null;
              const xTicks = [...new Set([xMin, ...tierBoundaries.filter(v => v <= xMax), ...(slabValTick ? [slabValTick] : [])])]
                .sort((a, b) => a - b)
                .filter((v, i, arr) => i === 0 || (toX(v) - toX(arr[i - 1])) >= 20);
              return (
                <div className="card" style={{ flex: 1, padding: "14px 16px", minWidth: 0, display: "flex", flexDirection: "column" }}>
                  <div className="tiny muted mono" style={{ textTransform: "uppercase", letterSpacing: 0.06, fontSize: 9, marginBottom: 6 }}>
                    Cost vs {slabUnitLabel}
                  </div>
                  <svg viewBox={`0 0 ${VW} ${VH}`} style={{ width: "100%", flex: 1 }}>
                    {/* Grid */}
                    {yTicks.map((y, i) => (
                      <line key={i} x1={PL} y1={toY(y)} x2={VW - PR} y2={toY(y)} stroke="var(--line)" strokeWidth={0.6} />
                    ))}
                    {/* Area fill */}
                    <polygon points={area} fill="var(--teal)" opacity={0.08} />
                    {/* Line */}
                    <polyline points={poly} fill="none" stroke="var(--teal)" strokeWidth={1.8} strokeLinejoin="round" strokeLinecap="round" />
                    {/* Current slab val marker */}
                    {curPt && (
                      <>
                        <line x1={toX(curPt.x)} y1={PT} x2={toX(curPt.x)} y2={PT + iH} stroke="var(--amber)" strokeWidth={1} strokeDasharray="3,2" />
                        <circle cx={toX(curPt.x)} cy={toY(curPt.y)} r={3.5} fill="var(--amber)" />
                        <text x={toX(curPt.x)} y={PT - 2} textAnchor="middle" fontSize={8} fill="var(--amber)" fontWeight="600">{fmtY(curPt.y)}</text>
                      </>
                    )}
                    {/* Axes */}
                    <line x1={PL} y1={PT} x2={PL} y2={PT + iH} stroke="var(--border)" strokeWidth={1} />
                    <line x1={PL} y1={PT + iH} x2={VW - PR} y2={PT + iH} stroke="var(--border)" strokeWidth={1} />
                    {/* Y labels */}
                    {yTicks.map((y, i) => (
                      <text key={i} x={PL - 4} y={toY(y) + 3} textAnchor="end" fontSize={8} fill="var(--ink-mute)">{fmtY(y)}</text>
                    ))}
                    {/* X labels: tier boundaries in grey, SlabVal in amber */}
                    {xTicks.map((x, i) => {
                      const isSlabVal = slabValTick != null && x === slabValTick;
                      return (
                        <text key={i} x={toX(x)} y={PT + iH + 11} textAnchor="middle" fontSize={8}
                          fill={isSlabVal ? "var(--amber)" : "var(--ink-mute)"}
                          fontWeight={isSlabVal ? "700" : "400"}>
                          {x}
                        </text>
                      );
                    })}
                    {/* X axis unit */}
                    <text x={PL + iW / 2} y={VH} textAnchor="middle" fontSize={8} fill="var(--ink-mute)">{slabUnitLabel}</text>
                  </svg>
                </div>
              );
            })()}
          </div>
        )}

        {/* Breakdown table — only when cost is calculated */}
        {sharedQty && totalUsd > 0 && (
          <div className="card" style={{ padding: 0, overflow: "hidden" }}>
            <div className="tiny muted mono" style={{ textTransform: "uppercase", letterSpacing: 0.08, fontSize: 11, padding: "14px 20px 10px", borderBottom: "1px solid var(--line)" }}>
              Multi-tier Breakdown
            </div>
            <table style={{ borderCollapse: "collapse", width: "100%" }}>
              <tbody>
                {sortedRows.length === 0 ? (
                  <tr><td colSpan={3} style={{ padding: "24px 20px", textAlign: "center" }}>
                    <span className="tiny muted">No rates match current filters</span>
                  </td></tr>
                ) : sortedRows.map(r => {
                  const res = rowResults[r.id];
                  const qty = parseFloat(sharedQty);
                  const label = rowLabel(r);
                  const unit = rowUnit(r);
                  const isActive = res?.active;
                  const subtitle = (!hideRate && r.rate_local && qty && isActive)
                    ? `${parseFloat(r.rate_local).toFixed(2)} ${r.currency} × ${res.total_local / parseFloat(r.rate_local) > qty ? `${(res.total_local / parseFloat(r.rate_local) / qty).toFixed(0)} ${unit} × ${qty}` : `${qty} ${unit}`}`
                    : null;
                  return (
                    <tr key={r.id} style={{ borderBottom: "1px solid var(--line)", background: isActive ? "var(--teal-soft)" : undefined, opacity: res && !isActive ? 0.4 : 1 }}>
                      <td style={{ padding: "12px 20px", minWidth: 180 }}>
                        <div style={{ fontWeight: 600, fontSize: 13 }}>{label}</div>
                        {subtitle && <div className="tiny muted" style={{ marginTop: 2 }}>{subtitle}</div>}
                      </td>
                      <td style={{ padding: "12px 16px", textAlign: "right", whiteSpace: "nowrap" }}>
                        {hideRate
                          ? <span className="mono muted" style={{ letterSpacing: 2 }}>****</span>
                          : isActive ? <span className="mono" style={{ fontSize: 13 }}>{res.total_local.toFixed(2)} <span className="tiny muted">{res.currency}</span></span> : <span className="tiny muted">—</span>}
                      </td>
                      <td style={{ padding: "12px 20px", textAlign: "right", whiteSpace: "nowrap" }}>
                        {hideRate
                          ? <span className="mono muted" style={{ letterSpacing: 2 }}>****</span>
                          : isActive ? <span className="mono bold" style={{ color: "var(--teal)", fontSize: 13 }}>{fmt.usd(res.total_usd)}</span> : <span className="tiny muted">—</span>}
                      </td>
                    </tr>
                  );
                })}
              </tbody>
              {sortedRows.length > 0 && (
                <tfoot>
                  <tr style={{ background: "var(--paper-2)", borderTop: "2px solid var(--ink)" }}>
                    <td style={{ padding: "14px 20px", fontWeight: 700, fontSize: 13 }}>Total (USD)</td>
                    <td />
                    <td style={{ padding: "14px 20px", textAlign: "right", fontWeight: 700, fontSize: 15, color: (!hideRate && sharedQty && totalUsd > 0) ? "var(--teal)" : "var(--ink-mute)" }}>
                      {hideRate ? <span style={{ letterSpacing: 2 }}>****</span> : sharedQty ? fmt.usd(totalUsd) : "—"}
                    </td>
                  </tr>
                </tfoot>
              )}
            </table>
          </div>
        )}
      </div>
    </div>
  );
}

// ─── TerminalMatrixTab ────────────────────────────────────────────────────────

function TerminalMatrixTab({ user, perms, today }) {
  const [terminals, setTerminals] = useState([]);
  const [costTypes, setCostTypes] = useState([]);
  const [brandCompanies, setBrandCompanies] = useState([]);
  const [roeMap, setRoeMap] = useState({});
  const [terminalFilter, setTerminalFilter] = useState("");
  const [costTypeFilter, setCostTypeFilter] = useState([]);
  const [brandCompanyId, setBrandCompanyId] = useState("");
  const [pivot, setPivot] = useState(null);
  const [loading, setLoading] = useState(false);
  const [loadError, setLoadError] = useState(null);
  const [cellInputs, setCellInputs] = useState({});
  const [hoveredCell, setHoveredCell] = useState(null);
  const [boxTypeFilter, setBoxTypeFilter] = useState(new Set());

  const hideRate = !perms.viewRates;

  useEffect(() => {
    Promise.all([
      apiFetch("/api/metadata/terminals/scoped"),
      apiFetch("/api/metadata/cost-types"),
      apiFetch("/api/roe/latest"),
      apiFetch("/api/metadata/brand-companies").catch(() => []),
    ]).then(([terms, cts, roe, bcs]) => {
      setTerminals(Array.isArray(terms) ? terms : []);
      setCostTypes(Array.isArray(cts) ? cts : []);
      const m = {};
      (Array.isArray(roe) ? roe : []).forEach(r => { m[r.iso_code] = r.rate; });
      setRoeMap(m);
      setBrandCompanies(Array.isArray(bcs) ? bcs : []);
    }).catch(() => {});
  }, []);

  const toggleCostType = code => {
    setCostTypeFilter(prev => prev.includes(code) ? prev.filter(c => c !== code) : [...prev, code]);
    setPivot(null); setCellInputs({});
  };

  const loadRates = async () => {
    if (!terminalFilter || costTypeFilter.length === 0) return;
    setLoading(true); setLoadError(null); setPivot(null); setCellInputs({}); setBoxTypeFilter(new Set());
    try {
      const params = new URLSearchParams({ terminal_code: terminalFilter });
      costTypeFilter.forEach(c => params.append("cost_type_codes", c));
      if (brandCompanyId) params.set("brand_company_id", brandCompanyId);
      const data = await apiFetch(`/api/calculator/rates?${params}`);
      if (!Array.isArray(data)) throw new Error("Unexpected response");
      setPivot(buildPivot(data));
    } catch (e) { setLoadError(e.message || "Failed to load rates"); }
    setLoading(false);
  };

  const setCellInput = (rowKey, colKey, field, val) => {
    const k = `${rowKey}|${colKey}`;
    setCellInputs(prev => ({ ...prev, [k]: { ...prev[k], [field]: val } }));
  };

  const cellResults = useMemo(() => {
    if (!pivot) return {};
    const results = {};
    for (const [rowKey, rowCells] of pivot.cells) {
      for (const [colKey, cell] of rowCells) {
        const cellKey = `${rowKey}|${colKey}`;
        const res = calcCellResult(cell, cellInputs[cellKey] || {}, roeMap);
        if (res) results[cellKey] = res;
      }
    }
    return results;
  }, [pivot, cellInputs, roeMap]);

  const grandTotal = useMemo(() => {
    let total = 0, hasAny = false;
    for (const r of Object.values(cellResults)) {
      if (r?.total_usd != null) { total += r.total_usd; hasAny = true; }
    }
    return hasAny ? total : null;
  }, [cellResults]);

  const categoryTotals = useMemo(() => {
    if (!pivot) return [];
    // Map<cat, { total, byType: Map<ctName, number> }>
    const map = new Map();
    for (const [rowKey, rowCells] of pivot.cells) {
      const meta = pivot.rowMeta.get(rowKey);
      const cat = meta?.cost_category || "Other";
      const ctName = meta?.cost_type_name || "—";
      if (!map.has(cat)) map.set(cat, { total: 0, byType: new Map() });
      const entry = map.get(cat);
      for (const [colKey] of rowCells) {
        const res = cellResults[`${rowKey}|${colKey}`];
        if (res?.total_usd) {
          entry.total += res.total_usd;
          entry.byType.set(ctName, (entry.byType.get(ctName) || 0) + res.total_usd);
        }
      }
    }
    return [...map.entries()]
      .filter(([, e]) => e.total > 0)
      .sort((a, b) => b[1].total - a[1].total);
  }, [cellResults, pivot]);

  const payableByTotals = useMemo(() => {
    if (!pivot) return [];
    const map = new Map();
    for (const [rowKey, rowCells] of pivot.cells) {
      for (const [colKey, cell] of rowCells) {
        const res = cellResults[`${rowKey}|${colKey}`];
        if (!res?.total_usd) continue;
        const pb = cell.payable_by || "Maersk";
        map.set(pb, (map.get(pb) || 0) + res.total_usd);
      }
    }
    return [...map.entries()].filter(([, v]) => v > 0).sort((a, b) => b[1] - a[1]);
  }, [cellResults, pivot]);

  const selectedTerminal = terminals.find(t => t.code === terminalFilter);
  const COL_W = 110;
  const visibleCols = pivot
    ? pivot.cols.filter(col => boxTypeFilter.size === 0 || boxTypeFilter.has(col.key))
    : [];

  const toggleBoxType = colKey => {
    setBoxTypeFilter(prev => {
      const base = prev.size === 0 ? new Set(pivot.cols.map(c => c.key)) : new Set(prev);
      if (base.has(colKey)) base.delete(colKey); else base.add(colKey);
      return base.size === pivot.cols.length ? new Set() : base;
    });
  };

  return (
    <div>
      {/* Compact filter bar */}
      <Card style={{ marginBottom: 14 }}>
        <div style={{ padding: "12px 16px", display: "grid", gridTemplateColumns: "240px 180px 1fr", gap: 14, alignItems: "start" }}>
          <Field label="Terminal" required>
            <SearchSelect options={terminals} value={terminalFilter}
              onChange={v => { setTerminalFilter(v); setPivot(null); setCellInputs({}); }}
              placeholder="Search terminal…"
              getValue={t => t.code} getLabel={t => `${t.name} (${t.code})`} />
          </Field>
          <Field label="Brand Company">
            <select className="select" style={{ width: "100%" }} value={brandCompanyId}
              onChange={e => { setBrandCompanyId(e.target.value); setPivot(null); setCellInputs({}); }}>
              <option value="">All</option>
              {brandCompanies.map(bc => <option key={bc.id} value={String(bc.id)}>{bc.name} ({bc.code})</option>)}
            </select>
          </Field>
          <Field label={<b>Cost Types</b>} hint="Multi-select">
            <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
              {(() => {
                const groups = new Map();
                for (const ct of costTypes) {
                  const cat = ct.category || "Other";
                  if (!groups.has(cat)) groups.set(cat, []);
                  groups.get(cat).push(ct);
                }
                return [...groups.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([cat, items]) => {
                  const codes = items.map(i => i.code);
                  const allOn = codes.every(c => costTypeFilter.includes(c));
                  const toggleCat = () => {
                    if (allOn) {
                      setCostTypeFilter(prev => prev.filter(c => !codes.includes(c)));
                    } else {
                      setCostTypeFilter(prev => [...new Set([...prev, ...codes])]);
                    }
                    setPivot(null); setCellInputs({});
                  };
                  return (
                    <div key={cat}>
                      <div style={{ display: "flex", alignItems: "center", gap: 5, marginBottom: 4 }}>
                        <span style={{ fontSize: 12, fontWeight: 700, textTransform: "uppercase", letterSpacing: 0.06, color: "var(--ink)" }}>{cat}</span>
                        <button onClick={toggleCat} style={{
                          fontSize: 9, padding: "1px 5px", height: 16, border: "1px solid",
                          borderRadius: 3, cursor: "pointer", fontFamily: "inherit",
                          borderColor: allOn ? "var(--teal)" : "var(--line)",
                          background: allOn ? "var(--teal-soft)" : "var(--paper)",
                          color: allOn ? "var(--teal)" : "var(--ink-mute)",
                        }}>{allOn ? "✓ All" : "Select all"}</button>
                      </div>
                      <div style={{ display: "flex", flexWrap: "wrap", gap: 5 }}>
                        {items.map(ct => {
                          const on = costTypeFilter.includes(ct.code);
                          return (
                            <button key={ct.code} onClick={() => toggleCostType(ct.code)} style={{
                              height: 26, padding: "0 8px", fontSize: 11, border: "1px solid",
                              borderRadius: 4, cursor: "pointer", fontFamily: "inherit",
                              borderColor: on ? "var(--teal)" : "var(--line)",
                              background: on ? "var(--teal-soft)" : "var(--paper)",
                              color: on ? "var(--teal)" : "var(--ink-mute)", fontWeight: on ? 600 : 400,
                            }}>{on && "✓ "}{ct.name}</button>
                          );
                        })}
                      </div>
                    </div>
                  );
                });
              })()}
            </div>
          </Field>
        </div>
        <div style={{ padding: "0 16px 12px", display: "flex", gap: 8, alignItems: "center" }}>
          {loadError && <span className="tiny" style={{ color: "var(--red)" }}>{loadError}</span>}
          <div className="grow" />
          {pivot && <button className="btn ghost tiny" onClick={() => setCellInputs({})}>Reset all</button>}
          <button className="btn teal"
            disabled={!terminalFilter || costTypeFilter.length === 0 || loading}
            onClick={loadRates}>
            <Icon name={loading ? "refresh" : "table"} size={14} />
            {loading ? "Loading…" : "Load Rate Sheet"}
          </button>
        </div>
      </Card>

      {/* Box type filter chips */}
      {pivot && pivot.cols.length > 0 && (
        <div style={{ marginBottom: 12, display: "flex", gap: 6, flexWrap: "wrap", alignItems: "center" }}>
          <span className="tiny muted" style={{ marginRight: 4 }}>Box Types:</span>
          <button className="btn ghost tiny" style={{ height: 24 }} onClick={() => setBoxTypeFilter(new Set())}>All</button>
          <button className="btn ghost tiny" style={{ height: 24 }}
            onClick={() => setBoxTypeFilter(new Set(pivot.cols.filter(c => c.cargo_type === "DRY" || c.cargo_type === "All").map(c => c.key)))}>
            Dry only</button>
          <button className="btn ghost tiny" style={{ height: 24 }}
            onClick={() => setBoxTypeFilter(new Set(pivot.cols.filter(c => c.cargo_type === "REEFER" || c.cargo_type === "All").map(c => c.key)))}>
            Reefer only</button>
          <button className="btn ghost tiny" style={{ height: 24 }}
            onClick={() => setBoxTypeFilter(new Set(pivot.cols.filter(c => c.container_fullness === "Full" || c.container_fullness === "All").map(c => c.key)))}>
            Full only</button>
          <button className="btn ghost tiny" style={{ height: 24 }}
            onClick={() => setBoxTypeFilter(new Set(pivot.cols.filter(c => c.container_fullness === "Empty" || c.container_fullness === "All").map(c => c.key)))}>
            Empty only</button>
          <div style={{ width: 1, height: 18, background: "var(--line)", margin: "0 4px" }} />
          {pivot.cols.map(col => {
            const on = boxTypeFilter.size === 0 || boxTypeFilter.has(col.key);
            const label = [col.container_fullness !== "All" ? col.container_fullness : null,
                           col.container_size !== "All" ? col.container_size : null,
                           col.cargo_type !== "All" ? col.cargo_type : null].filter(Boolean).join(" ") || "All";
            return (
              <button key={col.key} onClick={() => toggleBoxType(col.key)} style={{
                height: 24, padding: "0 8px", fontSize: 11, border: "1px solid",
                borderRadius: 4, cursor: "pointer", fontFamily: "inherit",
                borderColor: on ? "var(--teal)" : "var(--line)",
                background: on ? "var(--teal-soft)" : "var(--paper)",
                color: on ? "var(--teal)" : "var(--ink-mute)", fontWeight: on ? 600 : 400,
              }}>{label}</button>
            );
          })}
        </div>
      )}

      {pivot && pivot.cols.length === 0 && (
        <Card>
          <div style={{ padding: "40px 28px", textAlign: "center", color: "var(--ink-mute)" }}>
            <Icon name="search" size={32} stroke="var(--border)" />
            <div className="tiny" style={{ marginTop: 12 }}>No active rates with Calculator flag found.</div>
          </div>
        </Card>
      )}

      {pivot && pivot.cols.length > 0 && (
        <div style={{ display: "grid", gridTemplateColumns: "1fr 210px", gap: 14, alignItems: "start" }}>
          {/* Matrix table — minWidth:0 lets the grid item shrink so overflow-x:auto works */}
          <div style={{ minWidth: 0 }}>
          <Card title={`Rate Sheet · ${selectedTerminal?.name || terminalFilter}`}>
            <div style={{ overflowX: "auto" }}>
              <table style={{ borderCollapse: "collapse", minWidth: 180 + COL_W * visibleCols.length }}>
                <thead>
                  <tr>
                    <th style={{ width: 180, minWidth: 180, padding: "10px 14px", textAlign: "left", borderBottom: "2px solid var(--line)", background: "var(--paper-2)", position: "sticky", left: 0, zIndex: 2 }}>
                      <span className="tiny mono muted" style={{ textTransform: "uppercase", fontSize: 10 }}>Shipment Type / Payable By</span>
                    </th>
                    {visibleCols.map(col => {
                      const fullPart = col.container_fullness !== "All" ? col.container_fullness : null;
                      const sizePart = col.container_size !== "All" ? col.container_size : null;
                      const typePart = col.cargo_type !== "All" ? col.cargo_type : null;
                      const bottomLabel = [sizePart, typePart].filter(Boolean).join(" ") || "All";
                      return (
                        <th key={col.key} style={{ width: COL_W, minWidth: COL_W, padding: "8px 10px", textAlign: "center", borderBottom: "2px solid var(--line)", borderLeft: "1px solid var(--line)", background: "var(--paper-2)" }}>
                          {fullPart && <div className="bold" style={{ fontSize: 13 }}>{fullPart}</div>}
                          <div className={fullPart ? "tiny muted" : "bold"} style={{ fontSize: fullPart ? 12 : 13 }}>{bottomLabel}</div>
                        </th>
                      );
                    })}
                  </tr>
                </thead>
                <tbody>
                  {[...pivot.groups.entries()]
                    .sort(([a], [b]) => {
                      const ca = pivot.ctCategory.get(a) || "", cb = pivot.ctCategory.get(b) || "";
                      return ca !== cb ? ca.localeCompare(cb) : a.localeCompare(b);
                    })
                    .map(([ctName, rows]) => (
                    <React.Fragment key={ctName}>
                      <tr>
                        <td style={{ padding: "7px 16px", background: "var(--paper-2)", borderTop: "1px solid var(--line)", borderBottom: "1px solid var(--line)", position: "sticky", left: 0, zIndex: 2 }}>
                          <span className="bold tiny" style={{ color: "var(--teal)", textTransform: "uppercase", letterSpacing: 0.06 }}>{ctName}</span>
                          {pivot.ctCategory.get(ctName) && (
                            <span className="tiny muted" style={{ marginLeft: 8, fontWeight: 400, textTransform: "none", letterSpacing: 0 }}>{pivot.ctCategory.get(ctName)}</span>
                          )}
                        </td>
                        {visibleCols.map(col => (
                          <td key={col.key} style={{ background: "var(--paper-2)", borderTop: "1px solid var(--line)", borderBottom: "1px solid var(--line)", borderLeft: "1px solid var(--line)" }} />
                        ))}
                      </tr>
                      {[...rows].sort((a, b) => {
                          const st = (a.shipment_type || "All").localeCompare(b.shipment_type || "All");
                          if (st !== 0) return st;
                          return (a.payable_by || "Maersk").localeCompare(b.payable_by || "Maersk");
                        }).map(meta => {
                        const rowCells = pivot.cells.get(meta.key) || new Map();
                        const unitLabel = UNIT_SHORT[meta.charge_unit] || meta.charge_unit || "";
                        return (
                          <tr key={meta.key} style={{ borderBottom: "1px solid var(--line)" }}>
                            <td style={{ padding: "10px 14px", verticalAlign: "top", background: "var(--paper)", borderRight: "2px solid var(--line)", position: "sticky", left: 0, zIndex: 1, minWidth: 180 }}>
                              <div className="bold" style={{ fontSize: 13 }}>
                                {meta.shipment_type && meta.shipment_type.toLowerCase() !== "all"
                                  ? meta.shipment_type.charAt(0).toUpperCase() + meta.shipment_type.slice(1).toLowerCase()
                                  : "All"}
                              </div>
                              {meta.payable_by && (
                                <div style={{ marginTop: 3 }}>
                                  <span style={{ fontSize: 10, padding: "1px 6px", borderRadius: 3, border: "1px solid", fontWeight: 600,
                                    borderColor: meta.payable_by === "Customer" ? "#a78bfa" : "#6ee7b7",
                                    background:  meta.payable_by === "Customer" ? "#f5f3ff" : "#ecfdf5",
                                    color:       meta.payable_by === "Customer" ? "#7c3aed" : "#059669",
                                  }}>{meta.payable_by}</span>
                                </div>
                              )}
                            </td>
                            {visibleCols.map(col => {
                              const cell = rowCells.get(col.key);
                              const cellKey = `${meta.key}|${col.key}`;
                              const inp = cellInputs[cellKey] || {};
                              const res = cellResults[cellKey];

                              if (!cell) return (
                                <td key={col.key} style={{ padding: "10px", textAlign: "center", borderLeft: "1px solid var(--line)", background: "#fafbfc", color: "var(--ink-mute)", fontSize: 18, verticalAlign: "middle" }}>—</td>
                              );

                              return (
                                <td key={col.key}
                                  onMouseEnter={() => !hideRate && setHoveredCell(cellKey)}
                                  onMouseLeave={() => !hideRate && setHoveredCell(null)}
                                  style={{ padding: "6px 8px", verticalAlign: "top", borderLeft: "1px solid var(--line)", position: "relative", background: res ? "var(--teal-soft)" : undefined }}>

                                  {!hideRate && hoveredCell === cellKey && (cell.isSlab || res) && (
                                    <div style={{ position: "absolute", bottom: "calc(100% + 4px)", left: "50%", transform: "translateX(-50%)", background: "var(--paper)", border: "1px solid var(--line)", borderRadius: 6, padding: "8px 10px", boxShadow: "0 4px 16px rgba(0,0,0,0.13)", zIndex: 200, minWidth: 150, pointerEvents: "none", whiteSpace: "nowrap" }}>
                                      {cell.isSlab && (
                                        <div style={{ display: "flex", flexDirection: "column", gap: 3, marginBottom: res ? 6 : 0 }}>
                                          {cell.tiers.map((t, ti) => {
                                            const isActive = res?.activeTierIds instanceof Set ? res.activeTierIds.has(t.id) : false;
                                            const range = t.slab_min != null && t.slab_max != null ? `${t.slab_min}–${t.slab_max}` : t.slab_min != null ? `≥${t.slab_min}` : t.slab_max != null ? `≤${t.slab_max}` : "All";
                                            const _st = (t.slab_type || "Step").toUpperCase().replace(/[\s_]/g, "");
                                            const isLump = _st === "LUMPSUM", isTier = _st === "TIERRATE" || _st === "TIER";
                                            const bg = isLump ? "#e8f4fd" : isTier ? "#fff0e6" : "rgba(0,0,0,0.06)";
                                            const color = isLump ? "#0066cc" : isTier ? "#c45000" : "var(--ink-mute)";
                                            const bdColor = isLump ? "#90c8f0" : isTier ? "#f4a86b" : "transparent";
                                            const label = isLump ? "LUMPSUM" : isTier ? "TIER" : "STEP";
                                            return (
                                              <div key={ti} style={{ fontSize: 10, padding: "2px 5px", borderRadius: 3, border: "1px solid", borderColor: isActive ? "var(--teal)" : "var(--line)", background: isActive ? "var(--teal-soft)" : "transparent", color: isActive ? "var(--teal)" : "var(--ink-mute)", fontWeight: isActive ? 600 : 400, display: "flex", alignItems: "center", gap: 4 }}>
                                                <span>{range}</span>
                                                <span style={{ fontSize: 8, padding: "0 3px", borderRadius: 2, background: bg, color, border: "1px solid", borderColor: bdColor, fontWeight: 700, textTransform: "uppercase" }}>{label}</span>
                                              </div>
                                            );
                                          })}
                                        </div>
                                      )}
                                      {res && (
                                        <div style={{ borderTop: cell.isSlab ? "1px solid var(--line)" : 0, paddingTop: cell.isSlab ? 6 : 0, textAlign: "right" }}>
                                          <span className="mono bold" style={{ fontSize: 12, color: "var(--teal)" }}>{fmt.usd(res.total_usd)}</span>
                                        </div>
                                      )}
                                    </div>
                                  )}

                                  <div style={{ display: "flex", alignItems: "center", gap: 3 }}>
                                    <input className="input mono" placeholder="0" type="text" inputMode="numeric" value={inp.qty || ""}
                                      onChange={e => setCellInput(meta.key, col.key, "qty", e.target.value.replace(/[^0-9.]/g, ""))}
                                      style={{ width: "100%", height: 26, textAlign: "right", fontSize: 12 }} />
                                    {unitLabel && <span className="tiny muted" style={{ whiteSpace: "nowrap", fontSize: 11 }}>{unitLabel}</span>}
                                  </div>
                                  {cell.isSlab && (
                                    <div style={{ display: "flex", alignItems: "center", gap: 3, marginTop: 4 }}>
                                      <input className="input mono" placeholder="0" type="text" inputMode="numeric" value={inp.slabVal || ""}
                                        onChange={e => setCellInput(meta.key, col.key, "slabVal", e.target.value.replace(/[^0-9.]/g, ""))}
                                        style={{ width: "100%", height: 26, textAlign: "right", fontSize: 12 }} />
                                      <span className="tiny muted" style={{ whiteSpace: "nowrap", fontSize: 11 }}>{meta.slab_unit}</span>
                                    </div>
                                  )}
                                </td>
                              );
                            })}
                          </tr>
                        );
                      })}
                    </React.Fragment>
                  ))}

                  {grandTotal != null && (
                    <tr style={{ borderTop: "2px solid var(--ink)", background: "var(--paper-2)" }}>
                      <td style={{ padding: "12px 14px", fontWeight: 700, fontSize: 13, position: "sticky", left: 0, background: "var(--paper-2)" }}>Grand Total (USD)</td>
                      {visibleCols.map(col => {
                        let colSum = 0;
                        for (const [rowKey] of pivot.cells) {
                          const r = cellResults[`${rowKey}|${col.key}`];
                          if (r?.total_usd) colSum += r.total_usd;
                        }
                        return (
                          <td key={col.key} style={{ padding: "12px 10px", textAlign: "right", borderLeft: "1px solid var(--line)" }}>
                            {colSum > 0 ? <span className="mono bold">{fmt.usd(colSum)}</span> : <span className="tiny muted">—</span>}
                          </td>
                        );
                      })}
                    </tr>
                  )}
                </tbody>
              </table>
            </div>
          </Card>
          </div>{/* end minWidth:0 wrapper */}

          {/* Right sidebar */}
          <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
            <Card>
              <div style={{ padding: "16px 14px" }}>
                <div className="tiny muted mono" style={{ textTransform: "uppercase", letterSpacing: 0.06, fontSize: 11 }}>Estimated Total</div>
                <div style={{ fontSize: 26, fontWeight: 700, color: "var(--teal)", fontFamily: "var(--font-serif)", marginTop: 4 }}>
                  {grandTotal != null ? fmt.usd(grandTotal) : "—"}
                </div>
                <div className="tiny muted" style={{ marginTop: 4 }}>{Object.keys(cellResults).length} cells filled</div>
              </div>
            </Card>

            {categoryTotals.length > 0 && (
              <Card title="By Category">
                <div style={{ padding: "10px 14px", display: "flex", flexDirection: "column", gap: 10 }}>
                  {categoryTotals.map(([cat, entry]) => {
                    const pct = grandTotal ? (entry.total / grandTotal) * 100 : 0;
                    const ctRows = [...entry.byType.entries()].sort((a, b) => b[1] - a[1]);
                    return (
                      <div key={cat}>
                        {/* Category row + bar */}
                        <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 3 }}>
                          <span className="tiny bold" style={{ textTransform: "uppercase", letterSpacing: 0.04 }}>{cat}</span>
                          <span className="tiny mono muted">{fmt.usd(entry.total)}</span>
                        </div>
                        <div style={{ height: 6, background: "var(--line)", borderRadius: 3, overflow: "hidden", marginBottom: 6 }}>
                          <div style={{ width: `${pct}%`, height: "100%", background: "var(--teal)", borderRadius: 3 }} />
                        </div>
                        {/* Cost type breakdown */}
                        {ctRows.map(([ctName, ctVal]) => {
                          const ctPct = entry.total ? (ctVal / entry.total) * 100 : 0;
                          return (
                            <div key={ctName} style={{ paddingLeft: 8, marginBottom: 4, borderLeft: "2px solid var(--line)" }}>
                              <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 2 }}>
                                <span style={{ fontSize: 11, color: "var(--ink-mute)" }}>{ctName}</span>
                                <span style={{ fontSize: 11, fontFamily: "var(--font-mono)", color: "var(--ink-mute)" }}>{fmt.usd(ctVal)}</span>
                              </div>
                              <div style={{ height: 3, background: "var(--line)", borderRadius: 2, overflow: "hidden" }}>
                                <div style={{ width: `${ctPct}%`, height: "100%", background: "var(--teal)", opacity: 0.5, borderRadius: 2 }} />
                              </div>
                            </div>
                          );
                        })}
                      </div>
                    );
                  })}
                </div>
              </Card>
            )}

            {payableByTotals.length > 0 && (
              <Card title="By Payable By">
                <div style={{ padding: "10px 14px", display: "flex", flexDirection: "column", gap: 8 }}>
                  {payableByTotals.map(([pb, val]) => {
                    const pct = grandTotal ? (val / grandTotal) * 100 : 0;
                    return (
                      <div key={pb}>
                        <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 3 }}>
                          <span className="tiny bold">{pb}</span>
                          <span className="tiny mono muted">{fmt.usd(val)}</span>
                        </div>
                        <div style={{ height: 6, background: "var(--line)", borderRadius: 3, overflow: "hidden" }}>
                          <div style={{ width: `${pct}%`, height: "100%", background: "#6366f1", borderRadius: 3 }} />
                        </div>
                      </div>
                    );
                  })}
                </div>
              </Card>
            )}

            {hideRate && (
              <div className="card" style={{ padding: 10, background: "var(--teal-soft)", borderColor: "#c8e3ea" }}>
                <div className="row gap-6">
                  <Icon name="shield" size={14} stroke="var(--teal)" />
                  <div className="tiny">Rate values hidden (<b>rate_calculator</b> role)</div>
                </div>
              </div>
            )}
          </div>
        </div>
      )}
    </div>
  );
}

// ─── Compare helpers ──────────────────────────────────────────────────────────

// Group filtered rates into logical rate units.
// Slab rates sharing (slab_type, slab_unit) + same dimension fields are one group (tiers).
// Non-slab rates are each their own group.
function groupRates(rates, matKeyFn) {
  const nonSlab = rates.filter(r => !r.slab_unit);
  const slabRates = rates.filter(r => r.slab_unit);
  const dimFields = r => ({
    shipment_type:    r.shipment_type    || null,
    cargo_type:       r.cargo_type       || null,
    container_size:   r.container_size   || null,
    container_fullness: r.container_fullness || null,
    material_name:    matKeyFn(r)        || null,
    currency:         r.currency         || null,
    charge_unit:      r.charge_unit      || null,
    effective_date:   r.effective_date   || null,
    expiry_date:      r.expiry_date      || null,
    transport_type:   r.transport_type   || null,
    payment_terms:    r.payment_terms    || null,
    sap_vendor_code:  r.sap_vendor_code  || null,
    contract_ref:     r.contract_ref     || null,
    contract_owner:   r.contract_owner   || null,
    corridor_from:    r.corridor_from    || null,
    corridor_to:      r.corridor_to      || null,
    route:            r.route            || null,
    is_invoicerate:   r.is_invoicerate != null ? String(r.is_invoicerate) : null,
    stcy_flag:        r.stcy_flag        != null ? String(r.stcy_flag)    : null,
    notes:            r.notes            || null,
    brand_company_code: r.brand_company_code || null,
    payable_by:       r.payable_by       || null,
  });
  const groups = nonSlab.map(r => ({
    isSlab: false,
    ...dimFields(r),
    slab_unit: null,
    slab_type: null,
    rates: [r],
    total_usd: null,
  }));
  const slabMap = new Map();
  for (const r of slabRates) {
    const key = [
      r.slab_type, r.slab_unit, r.charge_unit,
      r.shipment_type, r.cargo_type, r.container_size, r.container_fullness, matKeyFn(r),
      r.brand_company_code || "", r.payable_by || "",
    ].join("|||");
    if (!slabMap.has(key)) {
      slabMap.set(key, {
        isSlab: true,
        ...dimFields(r),
        slab_unit: r.slab_unit,
        slab_type: r.slab_type,
        rates: [],
        total_usd: null,
      });
    }
    slabMap.get(key).rates.push(r);
  }
  groups.push(...slabMap.values());
  return groups;
}

// Given the logical groups for ONE terminal, return labels of fields
// that have more than one distinct value across groups (the "why" of multi-rate).
const DIFF_DIMS = [
  { key: "shipment_type",      label: "Shipment Type" },
  { key: "cargo_type",         label: "Cargo Type" },
  { key: "container_size",     label: "Container Size" },
  { key: "container_fullness", label: "Fullness" },
  { key: "material_name",      label: "Material" },
  { key: "charge_unit",        label: "Charge Unit" },
  { key: "currency",           label: "Currency" },
  { key: "slab_unit",          label: "Slab Unit" },
  { key: "effective_date",     label: "Effective Date" },
  { key: "expiry_date",        label: "Expiry Date" },
  { key: "transport_type",     label: "Transport Type" },
  { key: "payment_terms",      label: "Payment Terms" },
  { key: "sap_vendor_code",    label: "Vendor" },
  { key: "contract_ref",       label: "Contract Ref" },
  { key: "contract_owner",     label: "Contract Owner" },
  { key: "corridor_from",      label: "Corridor From" },
  { key: "corridor_to",        label: "Corridor To" },
  { key: "route",              label: "Route" },
  { key: "is_invoicerate",     label: "Invoice Rate" },
  { key: "stcy_flag",          label: "STCY Flag" },
  { key: "notes",              label: "Notes" },
  { key: "brand_company_code", label: "Brand Company" },
  { key: "payable_by",         label: "Payable By" },
];
function findDifferingFields(groups) {
  if (groups.length <= 1) return [];
  return DIFF_DIMS
    .filter(d => new Set(groups.map(g => g[d.key] || "")).size > 1)
    .map(d => d.label);
}

// ─── MultiTerminalSelect ──────────────────────────────────────────────────────

function MultiTerminalSelect({ options, selected, onChange, disabled, placeholder }) {
  const [open, setOpen] = React.useState(false);
  const [search, setSearch] = React.useState("");
  const wrapRef = React.useRef(null);

  React.useEffect(() => {
    if (!open) return;
    const handler = e => {
      if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false);
    };
    document.addEventListener("mousedown", handler);
    return () => document.removeEventListener("mousedown", handler);
  }, [open]);

  const norm = s => (s || "").toLowerCase();
  const tokens = norm(search).split(/\s+/).filter(Boolean);
  const filtered = options.filter(t => {
    if (!tokens.length) return true;
    const hay = norm(`${t.name} ${t.code}`);
    return tokens.every(tok => hay.includes(tok));
  });

  const selectedCodes = new Set(selected.map(t => t.code));

  const toggle = t => {
    if (selectedCodes.has(t.code)) onChange(selected.filter(s => s.code !== t.code));
    else onChange([...selected, { code: t.code, name: t.name }]);
  };

  const selectAll = () => {
    const extra = filtered.filter(t => !selectedCodes.has(t.code));
    onChange([...selected, ...extra]);
  };

  const clearAll = () => onChange([]);

  const triggerLabel = selected.length === 0
    ? (placeholder || "Select terminals…")
    : `${selected.length} terminal${selected.length !== 1 ? "s" : ""} selected`;

  return (
    <div ref={wrapRef} style={{ position: "relative" }}>
      <button
        type="button"
        className="select"
        disabled={disabled}
        onClick={() => !disabled && setOpen(o => !o)}
        style={{
          width: "100%", textAlign: "left", cursor: disabled ? "not-allowed" : "pointer",
          display: "flex", alignItems: "center", justifyContent: "space-between",
          color: selected.length === 0 ? "var(--ink-mute)" : "var(--ink)",
        }}
      >
        <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{triggerLabel}</span>
        <svg width="10" height="6" viewBox="0 0 10 6" style={{ flexShrink: 0, marginLeft: 6, opacity: 0.5 }}>
          <path d={open ? "M1 5l4-4 4 4" : "M1 1l4 4 4-4"} stroke="currentColor" strokeWidth="1.5" fill="none" strokeLinecap="round" />
        </svg>
      </button>

      {open && (
        <div style={{
          position: "absolute", top: "calc(100% + 4px)", left: 0, right: 0,
          background: "var(--card)", border: "1px solid var(--line)", borderRadius: 6,
          boxShadow: "0 4px 16px rgba(0,0,0,0.12)", zIndex: 200,
          display: "flex", flexDirection: "column", maxHeight: 320,
        }}>
          <div style={{ padding: "8px 10px", borderBottom: "1px solid var(--line)", display: "flex", gap: 6 }}>
            <input
              autoFocus
              className="input"
              placeholder="Search…"
              value={search}
              onChange={e => setSearch(e.target.value)}
              style={{ flex: 1, height: 28, fontSize: 12, padding: "0 8px" }}
            />
            <button type="button" className="btn" style={{ height: 28, fontSize: 11, padding: "0 8px" }}
              onClick={selectAll} disabled={filtered.length === 0}>
              All ({filtered.length})
            </button>
            <button type="button" className="btn" style={{ height: 28, fontSize: 11, padding: "0 8px" }}
              onClick={clearAll} disabled={selected.length === 0}>
              Clear
            </button>
          </div>

          <div style={{ overflowY: "auto", flex: 1 }}>
            {filtered.length === 0 && (
              <div className="tiny muted" style={{ padding: "12px 12px", textAlign: "center" }}>No terminals found</div>
            )}
            {filtered.map(t => {
              const checked = selectedCodes.has(t.code);
              return (
                <label key={t.code} style={{
                  display: "flex", alignItems: "center", gap: 8, padding: "7px 12px",
                  cursor: "pointer", fontSize: 12,
                  background: checked ? "var(--teal-soft)" : "transparent",
                  borderBottom: "1px solid var(--line)",
                }}>
                  <input type="checkbox" checked={checked} onChange={() => toggle(t)} style={{ flexShrink: 0 }} />
                  <span className="bold" style={{ color: "var(--teal)", minWidth: 52 }}>{t.code}</span>
                  <span style={{ color: "var(--ink-mute)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{t.name}</span>
                </label>
              );
            })}
          </div>
        </div>
      )}
    </div>
  );
}

// ─── MultiCompareTab ──────────────────────────────────────────────────────────

function MultiCompareTab({ user, perms, today }) {
  const [terminals, setTerminals] = useState([]);
  const [costTypes, setCostTypes] = useState([]);
  const [brandCompanies, setBrandCompanies] = useState([]);
  const [roeMap, setRoeMap] = useState({});
  const [costTypeCode, setCostTypeCode] = useState("");
  const [materialFilter, setMaterialFilter] = useState("");
  const [shipmentFilter, setShipmentFilter] = useState("");
  const [sizeFilter, setSizeFilter] = useState("");
  const [cargoTypeFilter, setCargoTypeFilter] = useState("");
  const [fullnessFilter, setFullnessFilter] = useState("");
  const [payableByFilter, setPayableByFilter] = useState("");
  const [brandCompanyId, setBrandCompanyId] = useState("");
  const [qty, setQty] = useState("");
  const [slabVal, setSlabVal] = useState("");
  const [materials, setMaterials] = useState([]);
  const [qualifiedTerminals, setQualifiedTerminals] = useState([]);
  const [moveTypes, setMoveTypes] = useState([]);
  const [equipmentTypes, setEquipmentTypes] = useState([]);
  const [loadingTerminals, setLoadingTerminals] = useState(false);
  const [selectedTerminals, setSelectedTerminals] = useState([]);
  const [compareResults, setCompareResults] = useState([]);
  const [displayOrder, setDisplayOrder] = useState([]);
  const [expandedTerminals, setExpandedTerminals] = useState(new Set());
  const [checkedGroups, setCheckedGroups] = useState({});
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    Promise.all([
      apiFetch("/api/metadata/terminals/scoped"),
      apiFetch("/api/metadata/cost-types"),
      apiFetch("/api/roe/latest"),
      apiFetch("/api/metadata/materials"),
      apiFetch("/api/metadata/move-types"),
      apiFetch("/api/metadata/equipment-types"),
      apiFetch("/api/metadata/brand-companies").catch(() => []),
    ]).then(([terms, cts, roe, mats, mvt, eqt, bcs]) => {
      setTerminals(Array.isArray(terms) ? terms : []);
      setCostTypes(Array.isArray(cts) ? cts : []);
      const m = {};
      (Array.isArray(roe) ? roe : []).forEach(r => { m[r.iso_code] = r.rate; });
      setRoeMap(m);
      setMaterials(Array.isArray(mats) ? mats : []);
      setMoveTypes(Array.isArray(mvt) ? mvt : []);
      setEquipmentTypes(Array.isArray(eqt) ? eqt : []);
      setBrandCompanies(Array.isArray(bcs) ? bcs : []);
    }).catch(() => {});
  }, []);

  // Single query: SELECT DISTINCT terminals with calculator=TRUE rates matching all Step 1 filters.
  // Runs whenever cost type or any filter changes.
  useEffect(() => {
    if (!costTypeCode) {
      setQualifiedTerminals([]); setSelectedTerminals([]); setCompareResults([]); return;
    }
    let cancelled = false;
    setLoadingTerminals(true);
    setSelectedTerminals([]); setCompareResults([]);
    const p = new URLSearchParams({ cost_type_code: costTypeCode });
    if (materialFilter)   p.set("material_name",    materialFilter);
    if (shipmentFilter)   p.set("shipment_type",    shipmentFilter);
    if (sizeFilter)       p.set("container_size",   sizeFilter);
    if (cargoTypeFilter)  p.set("cargo_type",       cargoTypeFilter);
    if (fullnessFilter)   p.set("container_fullness", fullnessFilter);
    if (brandCompanyId)   p.set("brand_company_id", brandCompanyId);
    apiFetch(`/api/calculator/terminals?${p}`)
      .then(data => {
        if (cancelled) return;
        setQualifiedTerminals(Array.isArray(data) ? data : []);
        setLoadingTerminals(false);
      })
      .catch(() => { if (!cancelled) { setQualifiedTerminals([]); setLoadingTerminals(false); } });
    return () => { cancelled = true; };
  }, [costTypeCode, materialFilter, shipmentFilter, sizeFilter, cargoTypeFilter, fullnessFilter, brandCompanyId]);

  const matKey = r => r.material_name || r.cost_type_name || "";

  const removeTerminal = code => {
    setSelectedTerminals(prev => prev.filter(t => t.code !== code));
    setCompareResults([]);
  };

  const runCompare = async () => {
    if (selectedTerminals.length === 0 || !costTypeCode || !qty) return;
    setLoading(true); setError(null); setCompareResults([]); setDisplayOrder([]); setExpandedTerminals(new Set()); setCheckedGroups({});
    try {
      const results = await Promise.all(selectedTerminals.map(async term => {
        const empty = (err) => ({ terminal: term, total_usd: null, error: err, rateGroups: [], differingFields: [] });
        try {
          const p = new URLSearchParams({ terminal_code: term.code });
          p.append("cost_type_codes", costTypeCode);
          if (brandCompanyId) p.set("brand_company_id", brandCompanyId);
          const data = await apiFetch(`/api/calculator/rates?${p}`);
          if (!Array.isArray(data)) return empty("No data");
          const filtered = data.filter(r =>
            (!materialFilter  || matKey(r)            === materialFilter) &&
            (!shipmentFilter  || r.shipment_type      === shipmentFilter) &&
            (!sizeFilter      || r.container_size     === sizeFilter) &&
            (!cargoTypeFilter || r.cargo_type         === cargoTypeFilter) &&
            (!fullnessFilter  || r.container_fullness === fullnessFilter) &&
            (!payableByFilter || r.payable_by         === payableByFilter)
          );
          if (filtered.length === 0) return empty("No matching rates");

          const rateGroups = groupRates(filtered, matKey);
          const differingFields = findDifferingFields(rateGroups);

          let total_usd = 0;
          for (const grp of rateGroups) {
            let res;
            if (grp.isSlab) {
              const cell = { isSlab: true, tiers: grp.rates.map(r => ({ id: r.id, rate_local: r.rate_local, currency: r.currency, slab_min: r.slab_min, slab_max: r.slab_max, slab_type: r.slab_type || "Step" })) };
              res = calcCellResult(cell, { qty: parseFloat(qty), slabVal: parseFloat(slabVal) }, roeMap);
            } else {
              const r = grp.rates[0];
              res = calcCellResult({ isSlab: false, rate_local: r.rate_local, currency: r.currency }, { qty: parseFloat(qty) }, roeMap);
            }
            grp.total_usd = res?.total_usd ?? null;
            if (grp.total_usd) total_usd += grp.total_usd;
          }
          return { terminal: term, total_usd: total_usd, error: null, rateGroups, differingFields };
        } catch { return empty("Request failed"); }
      }));
      const sorted = results.sort((a, b) => {
        if (a.total_usd == null && b.total_usd == null) return 0;
        if (a.total_usd == null) return 1;
        if (b.total_usd == null) return -1;
        return a.total_usd - b.total_usd;
      });
      setCompareResults(sorted);
      setDisplayOrder(sorted.map(r => r.terminal.code));
    } catch (e) { setError(e.message || "Comparison failed"); }
    setLoading(false);
  };

  const isGroupChecked = (termCode, gi) => (checkedGroups[`${termCode}:${gi}`] ?? true);

  const resultByCode = useMemo(() => {
    const map = new Map();
    for (const r of compareResults) {
      const checkedTotal = (r.rateGroups || []).reduce((sum, grp, gi) =>
        isGroupChecked(r.terminal.code, gi) && grp.total_usd != null ? sum + grp.total_usd : sum, 0);
      map.set(r.terminal.code, { ...r, display_total: r.error ? null : checkedTotal });
    }
    return map;
  }, [compareResults, checkedGroups]);

  const displayResults = useMemo(() =>
    displayOrder.map(code => resultByCode.get(code)).filter(Boolean),
    [displayOrder, resultByCode]
  );

  const refreshRanking = () => {
    setDisplayOrder([...resultByCode.values()]
      .sort((a, b) => {
        if (a.display_total == null && b.display_total == null) return 0;
        if (a.display_total == null) return 1;
        if (b.display_total == null) return -1;
        return a.display_total - b.display_total;
      })
      .map(r => r.terminal.code)
    );
  };

  const validResults = displayResults.filter(r => r.display_total != null);
  const maxUsd = validResults.length ? Math.max(...validResults.map(r => r.display_total)) : 1;
  const minUsd = validResults.length ? Math.min(...validResults.map(r => r.display_total)) : 0;
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      <Card title="Step 1 — Comparison Parameters">
        <div style={{ padding: "14px 16px", display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", gap: 12 }}>
          <Field label="Cost Type" required>
            <select className="select" value={costTypeCode} onChange={e => { setCostTypeCode(e.target.value); setMaterialFilter(""); setCompareResults([]); }}>
              <option value="">— select —</option>
              {(() => {
                const groups = new Map();
                for (const c of costTypes) {
                  const cat = c.category || "Other";
                  if (!groups.has(cat)) groups.set(cat, []);
                  groups.get(cat).push(c);
                }
                return [...groups.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([cat, items]) => (
                  <optgroup key={cat} label={cat}>
                    {items.map(c => <option key={c.code} value={c.code}>{c.name}</option>)}
                  </optgroup>
                ));
              })()}
            </select>
          </Field>
          <Field label="Material">
            {(() => {
              const selectedCt = costTypes.find(c => c.code === costTypeCode);
              const opts = costTypeCode
                ? [{ value: "", label: "All" }, ...materials.filter(m => m.cost_type_name === selectedCt?.name).map(m => m.name).filter(Boolean).map(n => ({ value: n, label: n }))]
                : [{ value: "", label: "All" }];
              return (
                <SearchSelect
                  options={opts}
                  value={materialFilter}
                  onChange={v => { setMaterialFilter(v); setCompareResults([]); }}
                  placeholder={costTypeCode ? "All" : "Select cost type first"}
                  getValue={o => o.value}
                  getLabel={o => o.label}
                  disabled={!costTypeCode}
                />
              );
            })()}
          </Field>
          <Field label="Shipment Type">
            <select className="select" value={shipmentFilter} onChange={e => { setShipmentFilter(e.target.value); setCompareResults([]); }}>
              <option value="">All</option>
              {moveTypes.map(m => <option key={m.code} value={m.code}>{m.name}</option>)}
            </select>
          </Field>
          <Field label="Container Size">
            <select className="select" value={sizeFilter} onChange={e => { setSizeFilter(e.target.value); setCompareResults([]); }}>
              <option value="">All</option>
              <option value="20">20</option>
              <option value="40">40</option>
              <option value="45">45</option>
            </select>
          </Field>
          <Field label="Container Type">
            <select className="select" value={cargoTypeFilter} onChange={e => { setCargoTypeFilter(e.target.value); setCompareResults([]); }}>
              <option value="">All</option>
              {equipmentTypes.map(e => <option key={e.code} value={e.code}>{e.name}</option>)}
            </select>
          </Field>
          <Field label="Fullness">
            <select className="select" value={fullnessFilter} onChange={e => { setFullnessFilter(e.target.value); setCompareResults([]); }}>
              <option value="">All</option>
              <option value="Full">Full</option>
              <option value="Empty">Empty</option>
            </select>
          </Field>
          <Field label="Brand Company">
            <select className="select" value={brandCompanyId} onChange={e => { setBrandCompanyId(e.target.value); setCompareResults([]); }}>
              <option value="">All</option>
              {brandCompanies.map(bc => <option key={bc.id} value={String(bc.id)}>{bc.name} ({bc.code})</option>)}
            </select>
          </Field>
          <Field label="Payable By">
            <select className="select" value={payableByFilter} onChange={e => { setPayableByFilter(e.target.value); setCompareResults([]); }}>
              <option value="">All</option>
              <option value="Maersk">Maersk</option>
              <option value="Customer">Customer</option>
            </select>
          </Field>
          <Field label="Quantity" required>
            <input className="input mono" placeholder="0" value={qty}
              onKeyDown={e => { if (!/[\d.]/.test(e.key) && !["Backspace","Delete","ArrowLeft","ArrowRight","Tab"].includes(e.key)) e.preventDefault(); }}
              onChange={e => { setQty(e.target.value); setCompareResults([]); }} />
          </Field>
          <Field label="Slab Value">
            <input className="input mono" placeholder="optional" value={slabVal}
              onKeyDown={e => { if (!/[\d.]/.test(e.key) && !["Backspace","Delete","ArrowLeft","ArrowRight","Tab"].includes(e.key)) e.preventDefault(); }}
              onChange={e => { setSlabVal(e.target.value); setCompareResults([]); }} />
          </Field>
        </div>
      </Card>

      <Card title="Step 2 — Select Terminals">
        <div style={{ padding: "14px 16px" }}>
          {loadingTerminals && (
            <div className="tiny muted" style={{ marginBottom: 10 }}>
              <Icon name="refresh" size={12} /> Checking terminals for matching rates…
            </div>
          )}
          {!loadingTerminals && costTypeCode && (
            <div className="tiny muted" style={{ marginBottom: 10 }}>
              {qualifiedTerminals.length} terminal{qualifiedTerminals.length !== 1 ? "s" : ""} with matching rates
            </div>
          )}
          <Field label="Terminals">
            <MultiTerminalSelect
              options={qualifiedTerminals}
              selected={selectedTerminals}
              onChange={v => { setSelectedTerminals(v); setCompareResults([]); }}
              disabled={!costTypeCode || loadingTerminals}
              placeholder={!costTypeCode ? "Select a cost type first" : loadingTerminals ? "Loading…" : "Select terminals…"}
            />
          </Field>
          {selectedTerminals.length === 0 && !loadingTerminals && (
            <div className="tiny muted" style={{ textAlign: "center", padding: "10px 0" }}>Select at least 2 terminals to compare</div>
          )}
          {selectedTerminals.length > 0 && (
            <div style={{ display: "flex", flexWrap: "wrap", gap: 6, marginTop: 10 }}>
              {selectedTerminals.map(t => (
                <div key={t.code} style={{ display: "flex", alignItems: "center", gap: 6, padding: "4px 10px", background: "var(--teal-soft)", borderRadius: 4, border: "1px solid var(--teal)", fontSize: 12 }}>
                  <span className="bold" style={{ color: "var(--teal)" }}>{t.code}</span>
                  <span style={{ color: "var(--ink-mute)" }}>{t.name}</span>
                  <button onClick={() => removeTerminal(t.code)} style={{ background: "none", border: "none", cursor: "pointer", padding: "0 0 0 4px", color: "var(--ink-mute)", fontSize: 14, lineHeight: 1 }}>×</button>
                </div>
              ))}
            </div>
          )}
          <div style={{ marginTop: 14, display: "flex", gap: 10, alignItems: "center" }}>
            {error && <span className="tiny" style={{ color: "var(--red)" }}>{error}</span>}
            <div className="grow" />
            <button className="btn teal"
              disabled={selectedTerminals.length < 2 || !costTypeCode || !qty || loading}
              onClick={runCompare}>
              <Icon name={loading ? "refresh" : "bolt"} size={14} />
              {loading ? "Comparing…" : "Compare"}
            </button>
          </div>
        </div>
      </Card>

      {compareResults.length > 0 && (() => {
        const allDiffFields = [...new Set(displayResults.flatMap(r => r.differingFields || []))];
        const hasMultiRate = displayResults.some(r => (r.rateGroups || []).length > 1);
        const toggleExpand = code => setExpandedTerminals(prev => {
          const next = new Set(prev);
          next.has(code) ? next.delete(code) : next.add(code);
          return next;
        });
        return (
          <Card title="Results — Ranked by Cost (USD ascending)"
            right={<button className="btn" style={{ fontSize: 11, height: 28, padding: "0 10px" }} onClick={refreshRanking}><Icon name="refresh" size={12} /> Refresh Ranking</button>}>
            <div>
              {hasMultiRate && (
                <div style={{ padding: "10px 16px", background: "#fffbeb", borderBottom: "1px solid #fde68a", display: "flex", gap: 8, alignItems: "flex-start" }}>
                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#d97706" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0, marginTop: 1 }}><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
                  <span className="tiny" style={{ color: "#92400e" }}>
                    Some terminals below sum <b>multiple rates</b> (differing: <b>{allDiffFields.join(", ")}</b>).
                    Apply the relevant filters in Step 1 for a like-for-like comparison. Click a row to see the breakdown.
                  </span>
                </div>
              )}
              <div style={{ display: "grid", gridTemplateColumns: "1fr 260px" }}>
                {/* ── Left: ranked list ── */}
                <div style={{ borderRight: "1px solid var(--line)" }}>
                  {displayResults.map((r, i) => {
                    const groups = r.rateGroups || [];
                    const multiRate = groups.length > 1;
                    const isExpanded = expandedTerminals.has(r.terminal.code);
                    return (
                      <div key={r.terminal.code} style={{ borderBottom: i < displayResults.length - 1 ? "1px solid var(--line)" : 0 }}>
                        <div
                          onClick={() => multiRate && toggleExpand(r.terminal.code)}
                          style={{ padding: "10px 14px", display: "grid", gridTemplateColumns: "26px 1fr auto 110px", gap: 10, alignItems: "center", cursor: multiRate ? "pointer" : "default" }}
                        >
                          <div style={{ fontSize: 13, fontWeight: 700, color: i === 0 && r.display_total ? "var(--teal)" : "var(--ink-mute)", textAlign: "center" }}>#{i + 1}</div>
                          <div>
                            <div style={{ display: "flex", alignItems: "center", gap: 5 }}>
                              <span className="bold" style={{ fontSize: 13 }}>{r.terminal.name}</span>
                              {multiRate && (
                                <span style={{ fontSize: 10, padding: "1px 5px", background: "#fef3c7", border: "1px solid #fcd34d", borderRadius: 10, color: "#92400e", fontWeight: 600 }}>
                                  Σ {groups.length} {isExpanded ? "▲" : "▼"}
                                </span>
                              )}
                            </div>
                            <div style={{ display: "flex", gap: 4, flexWrap: "wrap", marginTop: 2 }}>
                              <span className="tiny mono muted">{r.terminal.code}</span>
                              {(r.differingFields || []).map(f => (
                                <span key={f} style={{ fontSize: 10, padding: "1px 4px", background: "#e0f2fe", border: "1px solid #bae6fd", borderRadius: 8, color: "#075985" }}>{f}</span>
                              ))}
                            </div>
                          </div>
                          <div style={{ height: 12, width: 100, background: "var(--paper-2)", borderRadius: 3, overflow: "hidden" }}>
                            {r.display_total > 0 && <div style={{ width: `${(r.display_total / maxUsd) * 100}%`, height: "100%", background: i === 0 ? "var(--teal)" : "var(--line-strong)", borderRadius: 3 }} />}
                          </div>
                          <div className="mono bold" style={{ textAlign: "right", color: i === 0 && r.display_total > 0 ? "var(--teal)" : "var(--ink)", fontSize: 13 }}>
                            {r.display_total != null ? fmt.usd(r.display_total) : <span className="tiny muted">{r.error || "N/A"}</span>}
                          </div>
                        </div>
                        {multiRate && isExpanded && (
                          <div style={{ background: "var(--paper-2)", borderTop: "1px solid var(--line)", padding: "8px 14px 8px 50px" }}>
                            {groups.map((grp, gi) => {
                              const checked = isGroupChecked(r.terminal.code, gi);
                              return (
                              <div key={gi} style={{ marginBottom: gi < groups.length - 1 ? 8 : 0, opacity: checked ? 1 : 0.4 }}>
                                <div style={{ display: "flex", alignItems: "center", gap: 5, flexWrap: "wrap" }}>
                                  <input type="checkbox" checked={checked}
                                    onClick={e => e.stopPropagation()}
                                    onChange={e => { e.stopPropagation(); setCheckedGroups(prev => ({ ...prev, [`${r.terminal.code}:${gi}`]: e.target.checked })); }}
                                    style={{ flexShrink: 0, cursor: "pointer" }} />
                                  {DIFF_DIMS.filter(d => (r.differingFields || []).includes(d.label) && d.key !== "slab_unit").map(d => {
                                    const val = grp[d.key];
                                    const display = val != null ? val : "—";
                                    return <span key={d.key} style={{ fontSize: 11, padding: "1px 5px", background: "var(--card)", border: "1px solid var(--line)", borderRadius: 4, color: val != null ? "var(--ink)" : "var(--ink-mute)" }}>{display}</span>;
                                  })}
                                  {grp.isSlab && <span style={{ fontSize: 10, padding: "1px 5px", background: "var(--teal-soft)", border: "1px solid var(--teal)", borderRadius: 4, color: "var(--teal)" }}>TIER · {grp.slab_unit}</span>}
                                  <div style={{ flex: 1 }} />
                                  <span className="mono bold" style={{ fontSize: 11 }}>{grp.total_usd != null ? fmt.usd(grp.total_usd) : "—"}</span>
                                </div>
                                {grp.isSlab && (
                                  <div style={{ paddingLeft: 10, borderLeft: "2px solid var(--line)", marginLeft: 4, marginTop: 3 }}>
                                    {[...grp.rates].sort((a, b) => (a.slab_min ?? 0) - (b.slab_min ?? 0)).map(sr => (
                                      <div key={sr.id} style={{ display: "flex", gap: 10, fontSize: 11, color: "var(--ink-mute)", padding: "1px 0" }}>
                                        <span>{sr.slab_min ?? 0} – {sr.slab_max ?? "∞"} {grp.slab_unit}</span>
                                        <span>@ {sr.rate_local} {sr.currency}</span>
                                      </div>
                                    ))}
                                  </div>
                                )}
                              </div>
                            ); })}
                          </div>
                        )}
                      </div>
                    );
                  })}
                </div>

                {/* ── Right: Range across selection ── */}
                <div style={{ padding: "18px 16px", display: "flex", flexDirection: "column", gap: 0 }}>
                  <div className="tiny muted" style={{ letterSpacing: "0.06em", textTransform: "uppercase", marginBottom: 10 }}>Range across selection</div>
                  {validResults.length >= 2 ? (
                    <>
                      <div style={{ fontSize: 18, fontWeight: 700, color: "var(--ink)", lineHeight: 1.3, marginBottom: 4 }}>
                        {fmt.usd(minUsd)}
                      </div>
                      <div style={{ fontSize: 12, color: "var(--ink-mute)", marginBottom: 2 }}>to</div>
                      <div style={{ fontSize: 18, fontWeight: 700, color: "var(--ink)", lineHeight: 1.3, marginBottom: 14 }}>
                        {fmt.usd(maxUsd)}
                      </div>
                      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "8px 0", borderTop: "1px solid var(--line)" }}>
                        <span className="tiny muted">Spread</span>
                        <span className="mono bold" style={{ fontSize: 13, color: maxUsd - minUsd > 0 ? "var(--amber)" : "var(--teal)" }}>{fmt.usd(maxUsd - minUsd)}</span>
                      </div>
                      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "6px 0", borderTop: "1px solid var(--line)" }}>
                        <span className="tiny muted">Savings vs highest</span>
                        <span className="mono bold" style={{ fontSize: 12, color: "var(--teal)" }}>{maxUsd > 0 ? ((maxUsd - minUsd) / maxUsd * 100).toFixed(1) : 0}%</span>
                      </div>
                      <div style={{ borderTop: "1px solid var(--line)", marginTop: 8, paddingTop: 10 }}>
                        <div className="tiny muted" style={{ marginBottom: 6 }}>All terminals</div>
                        {displayResults.map((r, i) => (
                          <div key={r.terminal.code} style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "4px 0", gap: 6 }}>
                            <div style={{ display: "flex", alignItems: "center", gap: 5, minWidth: 0 }}>
                              <span style={{ fontSize: 10, fontWeight: 700, color: i === 0 ? "var(--teal)" : "var(--ink-mute)", flexShrink: 0 }}>#{i + 1}</span>
                              <span style={{ fontSize: 11, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", color: "var(--ink)" }}>{r.terminal.code}</span>
                            </div>
                            <span className="mono" style={{ fontSize: 11, fontWeight: 600, color: i === 0 && r.display_total > 0 ? "var(--teal)" : "var(--ink)", flexShrink: 0 }}>
                              {r.display_total != null ? fmt.usd(r.display_total) : <span style={{ color: "var(--ink-mute)" }}>N/A</span>}
                            </span>
                          </div>
                        ))}
                      </div>
                    </>
                  ) : (
                    <div className="tiny muted">Compare 2+ terminals to see range.</div>
                  )}
                </div>
              </div>
            </div>
          </Card>
        );
      })()}
    </div>
  );
}

// ─── Calculator top-level ─────────────────────────────────────────────────────

function Calculator({ user, perms }) {
  const [activeTab, setActiveTab] = useState("single");
  const today = new Date().toISOString().slice(0, 10);

  const tabs = [
    { id: "single",  label: "Single Rate" },
    { id: "matrix",  label: "Terminal Matrix" },
    { id: "compare", label: "Multi-terminal Compare" },
  ];

  return (
    <div>
      <PageHeader title="Rate Calculator" sub="Estimate terminal costs across one or more terminals" />
      <div style={{ display: "flex", borderBottom: "2px solid var(--line)", marginBottom: 18 }}>
        {tabs.map(t => (
          <button key={t.id} onClick={() => setActiveTab(t.id)} style={{
            padding: "9px 20px", fontSize: 13, fontFamily: "inherit", border: 0,
            background: "transparent", cursor: "pointer",
            borderBottom: activeTab === t.id ? "2px solid var(--teal)" : "2px solid transparent",
            marginBottom: -2, fontWeight: activeTab === t.id ? 700 : 400,
            color: activeTab === t.id ? "var(--teal)" : "var(--ink-mute)",
          }}>{t.label}</button>
        ))}
      </div>
      <div style={{ display: activeTab === "single"  ? "block" : "none" }}><SingleRateTab  user={user} perms={perms} today={today} /></div>
      <div style={{ display: activeTab === "matrix"  ? "block" : "none" }}><TerminalMatrixTab user={user} perms={perms} today={today} /></div>
      <div style={{ display: activeTab === "compare" ? "block" : "none" }}><MultiCompareTab user={user} perms={perms} today={today} /></div>
    </div>
  );
}

// ============== AI ASSISTANT ==============
function AIAssistant({ user, perms, drawerMode = false, onClose }) {
  const [thread, setThread] = useState([
    { role: "ai", content: "Hello Ravi — I have read access to all APA region rate data. Ask me to compare terminals, run trend analysis, simulate cost impacts, or look up contracts." },
    { role: "user", content: "Compare CY Handling costs across all North Asia terminals for Q2 2025." },
    { role: "ai", type: "comparison" },
    { role: "user", content: "If CY Handling at Shanghai increases by 10%, what is the total cost impact on 5,000 TEU monthly volume?" },
    { role: "ai", type: "simulation" },
  ]);
  const [input, setInput] = useState("");

  const prompts = [
    "Show me how Storage rates at Ningbo have changed over the past 2 years",
    "Which terminal has the lowest reefer plug-in rate in Southeast Asia?",
    "What contract covers Tianjin Storage rates?",
    "Compare gate move costs across SEA terminals for 2025",
  ];

  const Body = (
    <>
      <div style={{ padding: drawerMode ? "16px 20px" : "0 0 18px", display: "flex", flexDirection: "column", gap: 16 }}>
        {thread.map((m, i) => (
          <div key={i} style={{ display: "flex", flexDirection: "column", alignItems: m.role === "user" ? "flex-end" : "flex-start", gap: 4 }}>
            {!m.type && <div className={`chat-bubble ${m.role === "user" ? "chat-user" : "chat-ai"}`}>{m.content}</div>}
            {m.type === "comparison" && <AIComparison />}
            {m.type === "simulation" && <AISimulation />}
            <div className="tiny muted mono" style={{ padding: "0 4px" }}>
              {m.role === "user" ? user.name : "Rate Repository AI · DIFY"} · 10:4{i} am
            </div>
          </div>
        ))}
      </div>

      <div style={{ padding: drawerMode ? "0 20px 12px" : "0 0 12px", display: "flex", gap: 6, flexWrap: "wrap" }}>
        {prompts.slice(0, drawerMode ? 2 : 4).map((p, i) => (
          <button key={i} className="btn ghost tiny" style={{ height: 26, fontSize: 11.5, background: "var(--paper)" }}>
            <Icon name="sparkle" size={11} stroke="var(--teal)" />
            {p}
          </button>
        ))}
      </div>

      <div style={{ padding: drawerMode ? "12px 20px 20px" : "12px 0", borderTop: "1px solid var(--line)" }}>
        <div className="card" style={{ padding: 10, display: "flex", gap: 8, alignItems: "flex-end" }}>
          <textarea
            placeholder="Ask about rates, comparisons, trends, simulations…"
            value={input}
            onChange={e => setInput(e.target.value)}
            style={{ flex: 1, border: 0, outline: 0, resize: "none", fontFamily: "inherit", fontSize: 13, minHeight: 24, maxHeight: 100, background: "transparent" }}
          />
          <button className="btn teal" style={{ height: 32 }}>
            <Icon name="bolt" size={12} />
            Ask
          </button>
        </div>
        <div className="row gap-12 tiny muted" style={{ marginTop: 8 }}>
          <span><Icon name="shield" size={12} /> Scoped to: <b>{user.regions.join(", ")}</b></span>
          <span><Icon name="sparkle" size={12} stroke="var(--teal)" /> Powered by DIFY workflow</span>
          <span style={{ marginLeft: "auto" }}>{thread.filter(m => m.role === "user").length} messages</span>
        </div>
      </div>
    </>
  );

  if (drawerMode) {
    return (
      <SlideOver open={true} onClose={onClose} wide title="AI Assistant" sub="DIFY · Rate Q&A · scoped to your access">
        {Body}
      </SlideOver>
    );
  }

  return (
    <div>
      <PageHeader
        title="AI Assistant"
        sub="Natural-language Q&A over rate data · DIFY-powered · enforces your regional scope"
        right={
          <>
            <button className="btn"><Icon name="history" size={14} /> Conversations</button>
            <button className="btn"><Icon name="plus" size={14} /> New thread</button>
          </>
        }
      />
      <div style={{ display: "grid", gridTemplateColumns: "1fr 280px", gap: 18 }}>
        <Card>{Body}</Card>
        <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
          <Card title="Scope">
            <div style={{ padding: 14 }}>
              <div className="tiny mono muted" style={{ textTransform: "uppercase", letterSpacing: 0.06 }}>Regions in scope</div>
              <div style={{ marginTop: 8, display: "flex", flexDirection: "column", gap: 6 }}>
                {user.regions.map(r => (
                  <div key={r} className="row gap-6"><Icon name="pin" size={12} stroke="var(--teal)" /><span className="tiny">{r}</span></div>
                ))}
              </div>
            </div>
          </Card>
          <Card title="Supported query types">
            <div style={{ padding: 14, display: "flex", flexDirection: "column", gap: 10 }}>
              {[
                ["Comparison", "Cross-terminal rate analysis"],
                ["Trend", "Historical change over time"],
                ["Simulation", "What-if cost impact modelling"],
                ["Lookup", "Contract / source attribution"],
              ].map(([k, v]) => (
                <div key={k}>
                  <div className="bold tiny" style={{ color: "var(--teal)" }}>{k}</div>
                  <div className="tiny muted">{v}</div>
                </div>
              ))}
            </div>
          </Card>
        </div>
      </div>
    </div>
  );
}

function AIComparison() {
  const data = [
    { t: "Shanghai (Yangshan)", code: "CNSHA", v: 620.00, ccy: "CNY", usd: 85.47 },
    { t: "Ningbo Zhoushan", code: "CNNGB", v: 595.00, ccy: "CNY", usd: 82.02 },
    { t: "Qingdao Qianwan", code: "CNTAO", v: 580.00, ccy: "CNY", usd: 79.95 },
    { t: "Tianjin Xingang", code: "CNTSN", v: 645.00, ccy: "CNY", usd: 88.91 },
    { t: "Shenzhen Yantian", code: "CNYTN", v: 670.00, ccy: "CNY", usd: 92.36 },
    { t: "Hong Kong (Kwai Tsing)", code: "HKHKG", v: 720.00, ccy: "HKD", usd: 92.07 },
    { t: "Busan New Port", code: "KRPUS", v: 102000, ccy: "KRW", usd: 75.99 },
    { t: "Yokohama", code: "JPYOK", v: 13800, ccy: "JPY", usd: 90.55 },
  ];
  const max = Math.max(...data.map(d => d.usd));
  return (
    <div className="chat-bubble chat-ai" style={{ padding: 14, maxWidth: "92%", width: "92%", borderRadius: 8 }}>
      <div className="tiny muted" style={{ marginBottom: 8 }}>CY Handling · Per TEU · North Asia · Q2 2025</div>
      <div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
        {data.map(d => (
          <div key={d.code} style={{ display: "grid", gridTemplateColumns: "180px 1fr 90px 60px", gap: 10, alignItems: "center", fontSize: 12 }}>
            <div className="bold" style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{d.t}</div>
            <div style={{ height: 14, background: "#fff", borderRadius: 2, overflow: "hidden" }}>
              <div style={{ width: `${(d.usd / max) * 100}%`, height: "100%", background: "var(--teal)" }} />
            </div>
            <div className="mono" style={{ textAlign: "right", color: "var(--ink-mute)" }}>{fmt.num(d.v, d.ccy === "KRW" || d.ccy === "JPY" ? 0 : 2)} {d.ccy}</div>
            <div className="mono bold" style={{ textAlign: "right" }}>${d.usd.toFixed(2)}</div>
          </div>
        ))}
      </div>
      <div className="tiny muted" style={{ marginTop: 10, paddingTop: 10, borderTop: "1px dashed var(--line-strong)" }}>
        <b style={{ color: "var(--ink)" }}>Insight:</b> Qingdao offers the lowest CY Handling cost in North Asia (USD 79.95 / TEU),
        while Shenzhen Yantian is the most expensive at USD 92.36 / TEU — a 15.5% spread.
      </div>
    </div>
  );
}

function AISimulation() {
  return (
    <div className="chat-bubble chat-ai" style={{ padding: 14, maxWidth: "92%", width: "92%", borderRadius: 8 }}>
      <div className="tiny muted" style={{ marginBottom: 10 }}>Scenario: CY Handling at <b style={{ color: "var(--ink)" }}>CNSHA</b> + 10% · 5,000 TEU / month</div>
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 12 }}>
        <div style={{ background: "#fff", padding: 12, borderRadius: 6 }}>
          <div className="tiny muted mono" style={{ textTransform: "uppercase" }}>Current monthly</div>
          <div className="serif" style={{ fontSize: 22, fontWeight: 500, marginTop: 2 }}>$427,350</div>
          <div className="tiny muted">620.00 CNY × 5,000 TEU</div>
        </div>
        <div style={{ background: "#fff", padding: 12, borderRadius: 6, borderLeft: "3px solid var(--amber)" }}>
          <div className="tiny muted mono" style={{ textTransform: "uppercase" }}>Projected (+10%)</div>
          <div className="serif" style={{ fontSize: 22, fontWeight: 500, marginTop: 2 }}>$470,085</div>
          <div className="tiny muted">682.00 CNY × 5,000 TEU</div>
        </div>
        <div style={{ background: "#fff", padding: 12, borderRadius: 6 }}>
          <div className="tiny muted mono" style={{ textTransform: "uppercase" }}>Annual impact</div>
          <div className="serif" style={{ fontSize: 22, fontWeight: 500, marginTop: 2, color: "var(--amber)" }}>+$512,820</div>
          <div className="tiny muted">12 months · USD</div>
        </div>
      </div>
      <div className="tiny muted" style={{ marginTop: 10, paddingTop: 10, borderTop: "1px dashed var(--line-strong)" }}>
        <b style={{ color: "var(--ink)" }}>Note:</b> Calculation uses ROE 7.254 CNY/USD (eff. 2025-01-01). For longer horizons, consider locking ROE assumption or using rolling 12-month average.
      </div>
    </div>
  );
}

// ============== EXPORT ==============
function ExportPage({ user }) {
  const [filters, setFilters] = useState({
    region: "", area: "", terminal: "", costType: "", from: "2025-01-01", to: "2025-12-31", status: "Active", format: "xlsx",
  });
  const [terminals, setTerminals] = React.useState([]);
  const [costTypes, setCostTypes] = React.useState([]);
  React.useEffect(() => {
    apiFetch("/api/metadata/terminals/scoped").then(r => setTerminals(Array.isArray(r) ? r : [])).catch(() => {});
    apiFetch("/api/metadata/cost-types").then(r => setCostTypes(Array.isArray(r) ? r : [])).catch(() => {});
  }, []);
  const rowCount = 1284;
  const recent = [
    { ts: "2025-05-12 14:51", user: "R. Singh", file: "rate_export_NA_2025Q2.xlsx", rows: 1284, scope: "North Asia · Active" },
    { ts: "2025-05-10 09:11", user: "S. Lindqvist", file: "rate_export_SEA_full.csv", rows: 842, scope: "SE Asia · All" },
    { ts: "2025-05-08 16:33", user: "R. Singh", file: "rate_export_NA_storage.xlsx", rows: 96, scope: "North Asia · Storage" },
    { ts: "2025-05-02 11:24", user: "D. Park", file: "rate_export_APA_audit.xlsx", rows: 4218, scope: "APA · All statuses" },
  ];
  return (
    <div>
      <PageHeader
        title="Export Rate Sheet"
        sub="Filtered export for analysis · all actions audit-logged"
      />
      <div style={{ display: "grid", gridTemplateColumns: "1fr 380px", gap: 18 }}>
        <Card title="Export filters">
          <div style={{ padding: 18, display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
            <Field label="Region"><select className="select" value={filters.region} onChange={e => setFilters({ ...filters, region: e.target.value })}>
              <option value="">All in scope</option>
              <option>North Asia</option>
              <option>Southeast Asia</option>
              <option>South Asia Sub-Continent</option>
              <option>Oceania</option>
            </select></Field>
            <Field label="Area"><select className="select" value={filters.area} onChange={e => setFilters({ ...filters, area: e.target.value })}>
              <option value="">All</option><option>East China</option><option>North China</option><option>South China</option>
            </select></Field>
            <Field label="Terminal"><select className="select"><option value="">All terminals</option>{terminals.map(t=><option key={t.id} value={t.code}>{t.name}</option>)}</select></Field>
            <Field label="Cost Type"><select className="select"><option value="">All</option>{costTypes.map(c => <option key={c.id} value={c.code}>{c.name}</option>)}</select></Field>
            <Field label="Effective from"><input className="input mono" type="date" value={filters.from} onChange={e => setFilters({ ...filters, from: e.target.value })} /></Field>
            <Field label="Effective to"><input className="input mono" type="date" value={filters.to} onChange={e => setFilters({ ...filters, to: e.target.value })} /></Field>
            <Field label="Status"><select className="select" value={filters.status} onChange={e => setFilters({ ...filters, status: e.target.value })}>
              <option>Active</option><option>Active + Draft</option><option>All</option></select></Field>
            <Field label="Format">
              <div className="row gap-6">
                <label className="check"><input type="radio" checked={filters.format==="xlsx"} onChange={()=>setFilters({...filters, format:"xlsx"})} /> Excel (.xlsx)</label>
                <label className="check"><input type="radio" checked={filters.format==="csv"} onChange={()=>setFilters({...filters, format:"csv"})} /> CSV</label>
              </div>
            </Field>
          </div>
          <div className="card-head" style={{ borderBottom: 0, borderTop: "1px solid var(--line)", background: "#fafbfc" }}>
            <div className="row gap-12">
              <div><div className="tiny muted mono" style={{ textTransform: "uppercase" }}>Matching rows</div><div className="serif" style={{ fontSize: 22, fontWeight: 500 }}>{rowCount.toLocaleString()}</div></div>
              <div><div className="tiny muted mono" style={{ textTransform: "uppercase" }}>Est. size</div><div className="serif" style={{ fontSize: 22, fontWeight: 500 }}>2.1 MB</div></div>
            </div>
            <div className="grow" />
            <button className="btn ghost">Preview rows</button>
            <button className="btn primary"><Icon name="download" size={14} /> Generate export</button>
          </div>
        </Card>

        <Card title="Recent exports" sub="Last 30 days · per-user history">
          <div>
            {recent.map((r, i) => (
              <div key={i} style={{ padding: "12px 16px", borderBottom: i < recent.length - 1 ? "1px solid var(--line)" : 0, display: "flex", gap: 10, alignItems: "flex-start" }}>
                <div style={{ width: 32, height: 32, borderRadius: 4, background: "var(--paper-2)", display: "grid", placeItems: "center" }}>
                  <Icon name={r.file.endsWith("xlsx") ? "table" : "file"} size={16} stroke="var(--ink-mute)" />
                </div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div className="bold tiny" style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{r.file}</div>
                  <div className="tiny muted">{r.scope} · {r.rows.toLocaleString()} rows</div>
                  <div className="tiny muted mono">{r.ts} · {r.user}</div>
                </div>
                <button className="icon-btn" style={{ width: 24, height: 24 }}><Icon name="download" size={12} /></button>
              </div>
            ))}
          </div>
        </Card>
      </div>
    </div>
  );
}

Object.assign(window, { Calculator, AIAssistant, ExportPage });
