/* global React */
/* Mock data + helpers */

const fmtMoney = (n) => '$' + n.toLocaleString(undefined, { maximumFractionDigits: 0 });
const fmtMoneyDec = (n) => '$' + n.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
window.fmtMoney = fmtMoney;
window.fmtMoneyDec = fmtMoneyDec;

window.PORTS = [
  { code: 'SIN', name: 'Singapore', country: 'SG', flag: '#D31E25' },
  { code: 'SHA', name: 'Shanghai', country: 'CN', flag: '#DE2910' },
  { code: 'HKG', name: 'Hong Kong', country: 'HK', flag: '#DE2910' },
  { code: 'RTM', name: 'Rotterdam', country: 'NL', flag: '#21468B' },
  { code: 'HAM', name: 'Hamburg', country: 'DE', flag: '#000000' },
  { code: 'JEB', name: 'Jebel Ali', country: 'AE', flag: '#00732F' },
  { code: 'DXB', name: 'Dubai', country: 'AE', flag: '#00732F' },
  { code: 'NSA', name: 'Nhava Sheva', country: 'IN', flag: '#FF9933' },
  { code: 'MAA', name: 'Chennai', country: 'IN', flag: '#FF9933' },
  { code: 'BOM', name: 'Mumbai', country: 'IN', flag: '#FF9933' },
  { code: 'LAX', name: 'Los Angeles', country: 'US', flag: '#3C3B6E' },
  { code: 'JFK', name: 'New York JFK', country: 'US', flag: '#3C3B6E' },
  { code: 'LHR', name: 'London Heathrow', country: 'GB', flag: '#012169' },
  { code: 'FRA', name: 'Frankfurt', country: 'DE', flag: '#000000' },
  { code: 'NHV', name: 'Le Havre', country: 'FR', flag: '#0055A4' },
];

window.PortByCode = Object.fromEntries(window.PORTS.map(p => [p.code, p]));

window.CARRIERS = [
  { name: 'Maersk', mode: 'sea', color: '#42B0D5' },
  { name: 'MSC', mode: 'sea', color: '#003D6B' },
  { name: 'CMA CGM', mode: 'sea', color: '#C8102E' },
  { name: 'Hapag-Lloyd', mode: 'sea', color: '#F39200' },
  { name: 'ONE', mode: 'sea', color: '#E91E63' },
  { name: 'Evergreen', mode: 'sea', color: '#157F3B' },
  { name: 'Emirates SkyCargo', mode: 'air', color: '#D71A21' },
  { name: 'Lufthansa Cargo', mode: 'air', color: '#FFAD00' },
  { name: 'Cathay Cargo', mode: 'air', color: '#006564' },
  { name: 'Qatar Airways Cargo', mode: 'air', color: '#5C0632' },
  { name: 'Singapore Airlines Cargo', mode: 'air', color: '#003D5B' },
  { name: 'Turkish Cargo', mode: 'air', color: '#C70A0C' },
  { name: 'Etihad Cargo', mode: 'air', color: '#BD8B13' },
  { name: 'Korean Air Cargo', mode: 'air', color: '#00256C' },
  { name: 'AF-KLM Cargo', mode: 'air', color: '#002157' },
  { name: 'Own Network', mode: 'multi', color: '#7C5CFF' },
];

/* ---------- RFQs ---------- */
window.RFQS = [
  {
    id: 'RFQ-2041',
    customer: 'Acme Logistics',
    origin: 'SIN', destination: 'RTM',
    mode: 'sea',
    status: 'awaiting_info',
    lastActivity: '2m ago',
    pending: true,
    agentAction: 'sent_clarification',
    incoterms: 'FOB',
    weight: '18,400 kg',
    volume: '24 CBM',
    shipmentType: '1× 40ft HC',
    cargo: 'Industrial filtration units',
    readyDate: 'May 18, 2026',
    missingFields: ['Required ready date', 'Insurance preference'],
    email: {
      from: 'sarah.chen@acmelogistics.com',
      subject: 'Quote needed — SG to NL — May shipment',
      receivedAt: 'Today, 11:42',
      body: `Hi team,

We need a quote for shipping 18,400 kg of industrial filtration units from Singapore to Rotterdam, departing some time in May. Roughly 24 CBM. One 40ft HC container should do.

Incoterms FOB. Standard sea freight, customer is fine with transit up to 28 days. Please quote ASAP.

Sarah Chen
Procurement, Acme Logistics`,
    },
    clarification: {
      sentAt: '11:47',
      body: `Hi Sarah, thanks for the request. To finalize the quote we need:
1. Required ready date (you mentioned "some time in May")
2. Insurance preference — All Risk or Marine + War?

Best,
Freight AI agent`,
    },
  },
  {
    id: 'RFQ-2039',
    customer: 'Bharat Industries',
    origin: 'NSA', destination: 'JEB',
    mode: 'sea',
    status: 'negotiating',
    lastActivity: '6m ago',
    pending: true,
    agentAction: 'counter_received',
    quotedPrice: 4920,
    customerCounter: 4640,
    markupPct: 8.0,
    markupCounter: 6.2,
    incoterms: 'CIF',
    weight: '12,800 kg',
    volume: '32 CBM',
    shipmentType: '1× 40ft HC',
    cargo: 'Auto parts (CKD)',
  },
  {
    id: 'RFQ-2038',
    customer: 'Pacific Foods Co',
    origin: 'SHA', destination: 'LAX',
    mode: 'sea',
    status: 'rates_fetched',
    lastActivity: '14m ago',
    pending: true,
    agentAction: 'rates_fetched',
  },
  {
    id: 'RFQ-2037',
    customer: 'Mira Pharmaceuticals',
    origin: 'BOM', destination: 'FRA',
    mode: 'air',
    status: 'sent',
    lastActivity: '1h ago',
  },
  {
    id: 'RFQ-2036',
    customer: 'NorthWind Apparel',
    origin: 'HKG', destination: 'JFK',
    mode: 'air',
    status: 'sent',
    lastActivity: '3h ago',
  },
  {
    id: 'RFQ-2035',
    customer: 'Acme Logistics',
    origin: 'SIN', destination: 'RTM',
    mode: 'sea',
    status: 'won',
    lastActivity: '4h ago',
  },
  {
    id: 'RFQ-2034',
    customer: 'Helios Solar',
    origin: 'SHA', destination: 'HAM',
    mode: 'sea',
    status: 'sent',
    lastActivity: '5h ago',
  },
  {
    id: 'RFQ-2033',
    customer: 'Bharat Industries',
    origin: 'MAA', destination: 'LHR',
    mode: 'sea',
    status: 'won',
    lastActivity: 'Yesterday',
  },
  {
    id: 'RFQ-2032',
    customer: 'Goldfield Mining',
    origin: 'JEB', destination: 'NSA',
    mode: 'sea',
    status: 'lost',
    lastActivity: 'Yesterday',
  },
  {
    id: 'RFQ-2031',
    customer: 'Pacific Foods Co',
    origin: 'SHA', destination: 'LAX',
    mode: 'sea',
    status: 'new',
    lastActivity: '2d ago',
  },
  {
    id: 'RFQ-2030',
    customer: 'OrbitTech',
    origin: 'HKG', destination: 'FRA',
    mode: 'air',
    status: 'won',
    lastActivity: '2d ago',
  },
  {
    id: 'RFQ-2029',
    customer: 'Vesta Chemicals',
    origin: 'NHV', destination: 'NSA',
    mode: 'sea',
    status: 'sent',
    lastActivity: '3d ago',
  },
];

window.STATUS_META = {
  new:           { label: 'New · parsing', color: 'b-neutral', dot: '#98A2B3' },
  awaiting_info: { label: 'Awaiting info', color: 'b-warning', dot: '#F79009' },
  rates_fetched: { label: 'Rates fetched', color: 'b-agent', dot: '#7C5CFF' },
  drafted:       { label: 'Quote drafted', color: 'b-agent', dot: '#7C5CFF' },
  sent:          { label: 'Sent to customer', color: 'b-blue', dot: '#1659CB' },
  negotiating:   { label: 'Negotiating', color: 'b-warning', dot: '#F79009' },
  won:           { label: 'Won', color: 'b-success', dot: '#12B76A' },
  lost:          { label: 'Lost', color: 'b-danger', dot: '#F04438' },
};

/* ---------- Rates for the main RFQ (RFQ-2041 SIN→RTM) ---------- */
window.RATES_2041 = [
  { carrier: 'Maersk', mode: 'sea', transitDays: 24, baseRate: 3850, source: 'Contracted', validity: 'Jun 30', recommended: true, margin: 12.4 },
  { carrier: 'MSC', mode: 'sea', transitDays: 28, baseRate: 3620, source: 'Contracted', validity: 'Jul 15', margin: 10.1 },
  { carrier: 'CMA CGM', mode: 'sea', transitDays: 26, baseRate: 3940, source: 'Spot API', validity: 'May 31', margin: 9.6 },
  { carrier: 'Hapag-Lloyd', mode: 'sea', transitDays: 25, baseRate: 4120, source: 'Spot API', validity: 'May 31', margin: 8.8 },
  { carrier: 'ONE', mode: 'sea', transitDays: 27, baseRate: 3780, source: 'Contracted', validity: 'Jun 30', margin: 11.2 },
  { carrier: 'Evergreen', mode: 'sea', transitDays: 29, baseRate: 3590, source: 'Spot API', validity: 'May 31', margin: 9.8 },
  { carrier: 'Own Network', mode: 'sea', transitDays: 30, baseRate: 3510, source: 'Own network', validity: 'Open', margin: 13.5 },
];

/* ---------- Rate Matrix — 8x8 condensed ---------- */
window.MATRIX_ORIGINS = ['SIN', 'SHA', 'HKG', 'NSA', 'JEB', 'BOM', 'MAA', 'NHV'];
window.MATRIX_DESTS   = ['RTM', 'HAM', 'LHR', 'FRA', 'LAX', 'JFK', 'JEB', 'DXB'];

/* base $ per 40ft for mock matrix; null = no rate */
window.MATRIX_RATES = (() => {
  // seeded pseudo data
  const seed = [
    [3850, 4120, 4450, 4380, 5200, 5650, 980, 1020],
    [3620, 3940, 4280, 4150, 4350, 4720, 1450, 1390],
    [3580, 3890, 4200, 4080, 4280, 4640, 1320, 1280],
    [null, 2890, 3450, 3210, null, 5980, 720, 680],
    [2540, 2780, 3120, 2980, null, null, 0, 580],
    [null, 2940, 3500, 3260, 5840, 6020, 730, 690],
    [2680, 2820, 3210, 3040, 5640, 5890, 770, 720],
    [null, 980, 1240, 1180, 4980, 5240, 3450, 3520],
  ];
  // benchmark deltas (-1 best, +1 worst); negative = below benchmark = green
  const benchmark = [
    [-0.85, -0.40,  0.15,  0.05,  0.55,  0.80, -0.65, -0.55],
    [-0.55, -0.10,  0.20,  0.10,  0.30,  0.65, -0.20, -0.10],
    [-0.60, -0.20,  0.15,  0.00,  0.25,  0.50, -0.35, -0.30],
    [ null, -0.75, -0.25, -0.40,  null,  0.85, -0.85, -0.90],
    [-0.90, -0.60, -0.30, -0.55,  null,  null,  null, -0.95],
    [ null, -0.65, -0.15, -0.35,  0.50,  0.70, -0.80, -0.85],
    [-0.80, -0.70, -0.40, -0.60,  0.30,  0.55, -0.75, -0.80],
    [ null, -0.95, -0.85, -0.90,  0.10,  0.40,  0.20,  0.25],
  ];
  return { seed, benchmark };
})();

/* ---------- AIR Rate Matrix — 8x8 ---------- */
window.AIR_MATRIX_ORIGINS = ['HKG', 'SHA', 'BOM', 'MAA', 'SIN', 'DXB', 'JFK', 'FRA'];
window.AIR_MATRIX_DESTS   = ['FRA', 'LHR', 'JFK', 'LAX', 'DXB', 'HKG', 'HAM', 'SIN'];

/* base $/kg for mock matrix; null = no rate */
window.AIR_MATRIX_RATES = (() => {
  // $ per kg
  const seed = [
    /* HKG */ [4.20, 4.50, 5.20, 4.80, 2.40, null, 4.30, 1.60],
    /* SHA */ [4.40, 4.70, 5.50, 5.10, 2.80, 1.20, 4.50, 1.80],
    /* BOM */ [3.60, 3.80, 4.90, 4.60, 1.40, 2.30, 3.70, 1.90],
    /* MAA */ [3.50, 3.70, 4.80, 4.50, 1.30, 2.20, 3.60, 1.80],
    /* SIN */ [3.90, 4.20, 5.40, 4.90, 2.10, 1.80, 4.00, null],
    /* DXB */ [2.80, 2.60, 4.10, 3.80, null, 2.40, 2.90, 2.10],
    /* JFK */ [3.10, 2.90, null, 1.40, 4.20, 5.10, 3.20, 5.40],
    /* FRA */ [null, 1.20, 3.40, 3.20, 2.70, 4.30, 0.80, 4.00],
  ];
  // benchmark deltas (-1 best, +1 worst); negative = below benchmark = green
  const benchmark = [
    [-0.55, -0.30,  0.15,  0.05,  0.45,  null, -0.45,  0.10],
    [-0.40, -0.10,  0.25,  0.20,  0.30,  0.55, -0.20,  0.00],
    [-0.85, -0.60, -0.15, -0.25,  0.65, -0.45, -0.75, -0.20],
    [-0.80, -0.65, -0.20, -0.30,  0.70, -0.50, -0.70, -0.15],
    [-0.50, -0.30,  0.10,  0.05,  0.20,  0.40, -0.30,  null],
    [ 0.10,  0.20,  0.30,  0.40,  null,  0.05,  0.15, -0.60],
    [-0.20, -0.40,  null, -0.85,  0.30,  0.55, -0.25,  0.45],
    [ null, -0.95,  0.30,  0.20, -0.10,  0.25, -0.90,  0.15],
  ];
  return { seed, benchmark };
})();

/* ---------- Customer intelligence ---------- */
window.CUSTOMERS = [
  { name: 'Acme Logistics', rfqs: 47, conversion: 31, markup: 7.8, volume: '$1.42M', trend: [3,5,4,6,7,8,6,9,8,10,9,11], flag: 'Markup may be too high — competitor wins suggest 6.5% would convert', flagSeverity: 'warning' },
  { name: 'Bharat Industries', rfqs: 38, conversion: 42, markup: 6.4, volume: '$980K', trend: [4,4,5,5,6,7,7,8,7,8,9,9], flag: 'Premium customer, increase markup tested at 9%', flagSeverity: 'info' },
  { name: 'Pacific Foods Co', rfqs: 29, conversion: 38, markup: 7.2, volume: '$760K', trend: [6,5,6,7,6,7,8,7,8,9,8,9] },
  { name: 'Mira Pharmaceuticals', rfqs: 24, conversion: 58, markup: 9.1, volume: '$680K', trend: [4,5,6,6,7,8,8,9,9,10,11,12], flag: 'Strong loyalty — test 9.5% on next 3 RFQs', flagSeverity: 'info' },
  { name: 'NorthWind Apparel', rfqs: 22, conversion: 27, markup: 8.4, volume: '$420K', trend: [5,5,4,6,5,7,6,8,7,8,9,8] },
  { name: 'Helios Solar', rfqs: 19, conversion: 21, markup: 9.6, volume: '$310K', trend: [3,4,3,5,4,5,6,5,7,6,7,8], flag: 'Conversion drop — competitor undercut on 3 of last 5', flagSeverity: 'danger' },
  { name: 'OrbitTech', rfqs: 16, conversion: 50, markup: 8.2, volume: '$280K', trend: [4,4,5,5,6,7,8,7,8,9,10,11] },
  { name: 'Vesta Chemicals', rfqs: 14, conversion: 35, markup: 7.5, volume: '$240K', trend: [5,5,6,6,7,7,8,8,9,9,10,10] },
  { name: 'Goldfield Mining', rfqs: 11, conversion: 18, markup: 10.2, volume: '$140K', trend: [3,4,4,3,4,5,4,5,4,5,5,4], flag: 'Volume too low to support contracted rates', flagSeverity: 'warning' },
];

/* ---------- Shipments ---------- */
window.SHIPMENTS = [
  {
    id: 'SHP-104822',
    customer: 'Acme Logistics',
    origin: 'SIN', destination: 'RTM',
    mode: 'sea', carrier: 'Maersk',
    container: 'MSKU-7826451',
    containerType: '1× 40ft HC',
    rfqId: 'RFQ-2035',
    stage: 'on_vessel',
    stageIdx: 3,
    eta: 'Jun 12, 2026',
    bookedRate: 4310,
    detail: true,
    stages: [
      { name: 'Booked', ts: 'May 04, 09:12' },
      { name: 'Pickup', ts: 'May 05, 14:30' },
      { name: 'Origin transit', ts: 'May 06, 18:00' },
      { name: 'On vessel', ts: 'May 09, 04:20' },
      { name: 'At destination', ts: null },
      { name: 'Customs', ts: null },
      { name: 'Delivered', ts: null },
    ],
    docs: [
      { type: 'Bill of Lading', status: 'Verified', uploader: 'Maersk', ts: 'May 09, 06:14' },
      { type: 'Commercial Invoice', status: 'Verified', uploader: 'Acme Logistics', ts: 'May 06, 11:02' },
      { type: 'Packing List', status: 'Verified', uploader: 'Acme Logistics', ts: 'May 06, 11:02' },
      { type: 'Certificate of Origin', status: 'Extracted', uploader: 'Singapore CCI', ts: 'May 07, 09:30' },
      { type: 'Bill of Entry', status: 'Pending', uploader: null, ts: null },
    ],
    extracting: {
      fields: [
        { label: 'HS Code', value: '8421.39.00', confidence: 0.98 },
        { label: 'Declared Value', value: '$184,300 USD', confidence: 0.99 },
        { label: 'Gross Weight', value: '18,420 kg', confidence: 0.97 },
        { label: 'Net Weight', value: '17,840 kg', confidence: 0.96 },
        { label: 'Container No.', value: 'MSKU-7826451', confidence: 1.0 },
        { label: 'Port of Loading', value: 'SGSIN — Singapore', confidence: 0.99 },
      ],
    },
    shipmentObject: [
      { label: 'HS Code', value: '8421.39.00', mono: true, source: 'Bill of Lading' },
      { label: 'Declared Value', value: '$184,300 USD', source: 'Commercial Invoice' },
      { label: 'Gross Weight', value: '18,420 kg', source: 'Packing List' },
      { label: 'Net Weight', value: '17,840 kg', source: 'Packing List' },
      { label: 'Container No.', value: 'MSKU-7826451', mono: true, source: 'Bill of Lading' },
      { label: 'Port of Loading', value: 'SGSIN — Singapore', source: 'Bill of Lading' },
      { label: 'Port of Discharge', value: 'NLRTM — Rotterdam', source: 'Bill of Lading' },
      { label: 'Vessel', value: 'Maersk Halifax / 524W', source: 'Bill of Lading' },
    ],
    comms: [
      { from: 'Freight AI Agent', to: 'Maersk', subject: 'Booking confirmation — MSKU-7826451', ts: 'May 04, 09:14', byAgent: true, dir: 'out' },
      { from: 'Maersk', to: 'Freight AI Agent', subject: 'Pickup window confirmed for May 05', ts: 'May 04, 16:42', dir: 'in' },
      { from: 'Freight AI Agent', to: 'Acme Logistics', subject: 'Pickup scheduled — please confirm dock window', ts: 'May 05, 08:30', byAgent: true, dir: 'out', summary: 'Confirmed pickup at Gate 3, 14:30–16:00 window.' },
      { from: 'Maersk', to: 'Freight AI Agent', subject: 'Vessel departure notice — Maersk Halifax 524W', ts: 'May 09, 04:25', dir: 'in', summary: 'Departed Singapore on schedule. ETA Rotterdam Jun 12.' },
    ],
  },
  {
    id: 'SHP-104819',
    customer: 'Bharat Industries',
    origin: 'NSA', destination: 'JEB',
    mode: 'sea', carrier: 'ONE',
    containerType: '1× 40ft HC',
    rfqId: 'RFQ-2039',
    stage: 'pickup',
    stageIdx: 1,
    eta: 'May 22, 2026',
    bookedRate: 1320,
    stages: [
      { name: 'Booked', ts: 'May 10, 16:40' },
      { name: 'Pickup', ts: 'May 11, 09:00' },
      { name: 'Origin transit', ts: null },
      { name: 'On vessel', ts: null },
      { name: 'At destination', ts: null },
      { name: 'Customs', ts: null },
      { name: 'Delivered', ts: null },
    ],
    docs: [
      { type: 'Bill of Lading', status: 'Pending', uploader: null, ts: null },
      { type: 'Commercial Invoice', status: 'Uploaded', uploader: 'Bharat Industries', ts: 'May 11, 10:30' },
      { type: 'Packing List', status: 'Pending', uploader: null, ts: null },
      { type: 'Certificate of Origin', status: 'Pending', uploader: null, ts: null },
      { type: 'Bill of Entry', status: 'Pending', uploader: null, ts: null },
    ],
    shipmentObject: [
      { label: 'HS Code', value: '8708.99.00', mono: true },
      { label: 'Declared Value', value: '$62,400 USD' },
      { label: 'Gross Weight', value: '12,800 kg' },
      { label: 'Container No.', value: 'Pending', mono: true },
      { label: 'Port of Loading', value: 'INNSA — Nhava Sheva' },
      { label: 'Port of Discharge', value: 'AEJEA — Jebel Ali' },
    ],
    comms: [
      { from: 'Freight AI Agent', to: 'ONE', subject: 'Booking request — NSA→JEB, 1× 40HC', ts: 'May 10, 16:42', byAgent: true, dir: 'out' },
      { from: 'ONE', to: 'Freight AI Agent', subject: 'Booking confirmed — ONEU-3847291', ts: 'May 10, 17:15', dir: 'in', summary: 'Pickup scheduled for May 11, CFS Gate 2.' },
    ],
  },
  {
    id: 'SHP-104815',
    customer: 'Mira Pharmaceuticals',
    origin: 'BOM', destination: 'FRA',
    mode: 'air', carrier: 'Lufthansa Cargo',
    containerType: '2× PMC pallets',
    rfqId: 'RFQ-2037',
    stage: 'delivered',
    stageIdx: 6,
    eta: 'Delivered May 03',
    bookedRate: 6840,
    stages: [
      { name: 'Booked', ts: 'Apr 28, 11:00' },
      { name: 'Pickup', ts: 'Apr 29, 08:30' },
      { name: 'Origin transit', ts: 'Apr 29, 14:00' },
      { name: 'In flight', ts: 'Apr 30, 02:15' },
      { name: 'At destination', ts: 'May 01, 06:40' },
      { name: 'Customs', ts: 'May 02, 14:20' },
      { name: 'Delivered', ts: 'May 03, 10:45' },
    ],
    docs: [
      { type: 'Air Waybill', status: 'Verified', uploader: 'Lufthansa Cargo', ts: 'Apr 30, 03:10' },
      { type: 'Commercial Invoice', status: 'Verified', uploader: 'Mira Pharmaceuticals', ts: 'Apr 29, 09:00' },
      { type: 'Packing List', status: 'Verified', uploader: 'Mira Pharmaceuticals', ts: 'Apr 29, 09:00' },
      { type: 'GDP Certificate', status: 'Verified', uploader: 'Mira Pharmaceuticals', ts: 'Apr 29, 09:15' },
      { type: 'Customs Declaration', status: 'Verified', uploader: 'DE Customs', ts: 'May 02, 14:20' },
    ],
    shipmentObject: [
      { label: 'AWB Number', value: '020-8842 1956', mono: true, source: 'Air Waybill' },
      { label: 'HS Code', value: '3004.90.00', mono: true, source: 'Commercial Invoice' },
      { label: 'Declared Value', value: '$342,000 USD', source: 'Commercial Invoice' },
      { label: 'Gross Weight', value: '1,240 kg', source: 'Air Waybill' },
      { label: 'Temperature', value: '2–8°C controlled', source: 'GDP Certificate' },
      { label: 'Origin Airport', value: 'VABB — Mumbai', source: 'Air Waybill' },
      { label: 'Dest Airport', value: 'EDDF — Frankfurt', source: 'Air Waybill' },
    ],
    comms: [
      { from: 'Freight AI Agent', to: 'Lufthansa Cargo', subject: 'Booking — BOM→FRA, temp-controlled pharma', ts: 'Apr 28, 11:05', byAgent: true, dir: 'out' },
      { from: 'Lufthansa Cargo', to: 'Freight AI Agent', subject: 'Booking confirmed — LH8842', ts: 'Apr 28, 14:30', dir: 'in' },
      { from: 'Freight AI Agent', to: 'Mira Pharmaceuticals', subject: 'Shipment delivered — POD attached', ts: 'May 03, 11:00', byAgent: true, dir: 'out', summary: 'Delivered to Frankfurt warehouse. All docs verified, no exceptions.' },
    ],
  },
  {
    id: 'SHP-104810',
    customer: 'NorthWind Apparel',
    origin: 'HKG', destination: 'JFK',
    mode: 'air', carrier: 'Cathay Cargo',
    containerType: '4× LD3 ULDs',
    rfqId: 'RFQ-2036',
    stage: 'origin',
    stageIdx: 2,
    eta: 'May 15, 2026',
    bookedRate: 5240,
    stages: [
      { name: 'Booked', ts: 'May 08, 10:00' },
      { name: 'Pickup', ts: 'May 09, 15:20' },
      { name: 'Origin transit', ts: 'May 10, 08:45' },
      { name: 'In flight', ts: null },
      { name: 'At destination', ts: null },
      { name: 'Customs', ts: null },
      { name: 'Delivered', ts: null },
    ],
    docs: [
      { type: 'Air Waybill', status: 'Extracted', uploader: 'Cathay Cargo', ts: 'May 10, 09:00' },
      { type: 'Commercial Invoice', status: 'Verified', uploader: 'NorthWind Apparel', ts: 'May 09, 16:00' },
      { type: 'Packing List', status: 'Verified', uploader: 'NorthWind Apparel', ts: 'May 09, 16:00' },
      { type: 'Certificate of Origin', status: 'Pending', uploader: null, ts: null },
      { type: 'Customs Declaration', status: 'Pending', uploader: null, ts: null },
    ],
    shipmentObject: [
      { label: 'AWB Number', value: '160-4421 8830', mono: true, source: 'Air Waybill' },
      { label: 'HS Code', value: '6204.62.00', mono: true, source: 'Commercial Invoice' },
      { label: 'Declared Value', value: '$186,500 USD', source: 'Commercial Invoice' },
      { label: 'Gross Weight', value: '2,840 kg', source: 'Air Waybill' },
      { label: 'Origin Airport', value: 'VHHH — Hong Kong', source: 'Air Waybill' },
      { label: 'Dest Airport', value: 'KJFK — New York JFK', source: 'Air Waybill' },
    ],
    comms: [
      { from: 'Freight AI Agent', to: 'Cathay Cargo', subject: 'Booking — HKG→JFK, 4× LD3 apparel', ts: 'May 08, 10:05', byAgent: true, dir: 'out' },
      { from: 'Cathay Cargo', to: 'Freight AI Agent', subject: 'Booking confirmed — CX4421', ts: 'May 08, 12:40', dir: 'in', summary: 'Flight CX840, departing May 12 22:15 HKT.' },
      { from: 'Freight AI Agent', to: 'NorthWind Apparel', subject: 'Cargo at origin — awaiting flight', ts: 'May 10, 09:30', byAgent: true, dir: 'out' },
    ],
  },
  {
    id: 'SHP-104807',
    customer: 'Pacific Foods Co',
    origin: 'SHA', destination: 'LAX',
    mode: 'sea', carrier: 'Evergreen',
    containerType: '2× 40ft Reefer',
    rfqId: 'RFQ-2031',
    stage: 'customs',
    stageIdx: 5,
    eta: 'May 12, 2026',
    bookedRate: 4420,
    stages: [
      { name: 'Booked', ts: 'Apr 10, 14:00' },
      { name: 'Pickup', ts: 'Apr 11, 09:30' },
      { name: 'Origin transit', ts: 'Apr 12, 16:00' },
      { name: 'On vessel', ts: 'Apr 14, 06:20' },
      { name: 'At destination', ts: 'May 08, 18:40' },
      { name: 'Customs', ts: 'May 10, 10:15' },
      { name: 'Delivered', ts: null },
    ],
    docs: [
      { type: 'Bill of Lading', status: 'Verified', uploader: 'Evergreen', ts: 'Apr 14, 07:00' },
      { type: 'Commercial Invoice', status: 'Verified', uploader: 'Pacific Foods Co', ts: 'Apr 11, 10:00' },
      { type: 'Packing List', status: 'Verified', uploader: 'Pacific Foods Co', ts: 'Apr 11, 10:00' },
      { type: 'Phytosanitary Certificate', status: 'Verified', uploader: 'CN Customs', ts: 'Apr 12, 11:30' },
      { type: 'FDA Prior Notice', status: 'Verified', uploader: 'US FDA', ts: 'May 08, 20:00' },
    ],
    shipmentObject: [
      { label: 'HS Code', value: '2005.99.00', mono: true, source: 'Bill of Lading' },
      { label: 'Declared Value', value: '$94,200 USD', source: 'Commercial Invoice' },
      { label: 'Gross Weight', value: '38,600 kg', source: 'Bill of Lading' },
      { label: 'Container No.', value: 'EISU-4182730, EISU-4182731', mono: true, source: 'Bill of Lading' },
      { label: 'Temperature', value: '-18°C frozen', source: 'Packing List' },
      { label: 'Port of Loading', value: 'CNSHA — Shanghai', source: 'Bill of Lading' },
      { label: 'Port of Discharge', value: 'USLAX — Los Angeles', source: 'Bill of Lading' },
      { label: 'Vessel', value: 'Ever Golden / 0428W', source: 'Bill of Lading' },
    ],
    comms: [
      { from: 'Freight AI Agent', to: 'Evergreen', subject: 'Booking — SHA→LAX, 2× 40RF reefer', ts: 'Apr 10, 14:05', byAgent: true, dir: 'out' },
      { from: 'Evergreen', to: 'Freight AI Agent', subject: 'Booking confirmed — EISU-4182730/31', ts: 'Apr 10, 18:20', dir: 'in' },
      { from: 'Freight AI Agent', to: 'Pacific Foods Co', subject: 'Vessel arrived LAX — customs in progress', ts: 'May 09, 08:00', byAgent: true, dir: 'out', summary: 'Containers at LAX. FDA inspection scheduled, customs clearance expected May 10–11.' },
      { from: 'US Customs', to: 'Freight AI Agent', subject: 'CBP release — EISU-4182730/31', ts: 'May 10, 10:15', dir: 'in', summary: 'Both containers released. No holds. Ready for delivery scheduling.' },
    ],
  },
];

/* ---------- Activity stream ---------- */
window.ACTIVITY = [
  { kind: 'agent',   text: 'Sent clarification email to Acme Logistics re: RFQ-2041', meta: 'missing ready date + insurance pref', ts: '2m ago', module: 'demand', target: 'RFQ-2041' },
  { kind: 'agent',   text: 'Fetched updated spot rates on SIN→RTM', meta: '12 rates from 4 carriers', ts: '4m ago', module: 'supply' },
  { kind: 'inbound', text: 'Bharat Industries replied to RFQ-2039 with counter-offer', meta: 'quoted $4,920 → counter $4,640', ts: '6m ago', module: 'demand', target: 'RFQ-2039' },
  { kind: 'agent',   text: 'Drafted quote for Pacific Foods Co · RFQ-2038', meta: 'Evergreen via Spot API · 27 days', ts: '14m ago', module: 'demand', target: 'RFQ-2038' },
  { kind: 'agent',   text: 'Extracted Certificate of Origin · SHP-104822', meta: '8 fields · avg conf 97%', ts: '23m ago', module: 'operations', target: 'SHP-104822' },
  { kind: 'agent',   text: 'Flagged 3 customers with conversion drop', meta: 'Helios Solar, Goldfield, Vesta', ts: '38m ago', module: 'planning' },
  { kind: 'system',  text: 'Vessel departure notice — Maersk Halifax 524W', meta: 'SHP-104822 · on vessel', ts: '1h ago', module: 'operations', target: 'SHP-104822' },
  { kind: 'agent',   text: 'Forecast updated for top 5 lanes', meta: '4-week horizon refreshed', ts: '1h ago', module: 'planning' },
  { kind: 'inbound', text: 'New RFQ from OrbitTech via email — parsing…', meta: 'HKG → FRA · Air', ts: '2h ago', module: 'demand' },
  { kind: 'agent',   text: 'Sent rate request to Maersk · 3 lanes', meta: 'SIN→RTM, SHA→HAM, NSA→JEB', ts: '3h ago', module: 'supply' },
  { kind: 'system',  text: 'Pacific Foods Co accepted quote · RFQ-2035', meta: '$4,420 · 7.8% markup', ts: '4h ago', module: 'demand', target: 'RFQ-2035' },
  { kind: 'agent',   text: 'Renegotiation email drafted · MSC SIN→RTM', meta: 'references $1.42M historical volume', ts: '5h ago', module: 'supply' },
];

/* ---------- Planning insights ---------- */
window.TOP_LANES = [
  { origin: 'SIN', dest: 'RTM', count: 84 },
  { origin: 'SHA', dest: 'LAX', count: 71 },
  { origin: 'NSA', dest: 'JEB', count: 62 },
  { origin: 'HKG', dest: 'JFK', count: 54 },
  { origin: 'BOM', dest: 'FRA', count: 47 },
  { origin: 'SHA', dest: 'HAM', count: 41 },
  { origin: 'MAA', dest: 'LHR', count: 38 },
  { origin: 'HKG', dest: 'FRA', count: 33 },
  { origin: 'BOM', dest: 'DXB', count: 28 },
  { origin: 'NHV', dest: 'NSA', count: 22 },
];

window.MISSED_BOOKINGS = [
  { lane: 'JEB → NSA', conversion: 8, cause: 'No contracted rate available', causeKind: 'rate', cta: 'Acquire rate' },
  { lane: 'LAX → SHA', conversion: 12, cause: 'Quoted 12% above market benchmark', causeKind: 'price', cta: 'Review pricing' },
  { lane: 'FRA → BOM', conversion: 14, cause: 'Lost to competitor — 3 cases', causeKind: 'compete', cta: 'Review pricing' },
  { lane: 'JFK → LHR', conversion: 16, cause: 'Transit time uncompetitive (+4 days)', causeKind: 'transit', cta: 'Acquire rate' },
  { lane: 'DXB → BOM', conversion: 18, cause: 'No carrier capacity on requested dates', causeKind: 'rate', cta: 'Acquire rate' },
];

window.BENCHMARK_LANES = [
  { lane: 'SIN → RTM', ourPrice: 4310, benchmark: 4180, delta: 3.1 },
  { lane: 'SHA → HAM', ourPrice: 4280, benchmark: 3940, delta: 8.6 },
  { lane: 'NSA → JEB', ourPrice: 1450, benchmark: 1320, delta: 9.8 },
  { lane: 'BOM → FRA', ourPrice: 6840, benchmark: 6420, delta: 6.5 },
  { lane: 'HKG → LAX', ourPrice: 4640, benchmark: 4180, delta: 11.0 },
];

window.MISMATCH = (() => {
  // each cell: demand 0-1, supply 0-1; mismatch = demand high + supply low
  const origins = ['SIN','SHA','HKG','NSA','BOM'];
  const dests = ['RTM','LAX','JFK','FRA','HAM'];
  const cells = [
    [{d:0.9,s:0.8},{d:0.6,s:0.7},{d:0.5,s:0.5},{d:0.7,s:0.6},{d:0.8,s:0.6}],
    [{d:0.8,s:0.7},{d:0.95,s:0.9},{d:0.7,s:0.5},{d:0.6,s:0.4},{d:0.5,s:0.5}],
    [{d:0.5,s:0.5},{d:0.6,s:0.6},{d:0.85,s:0.7},{d:0.8,s:0.3},{d:0.4,s:0.4}],
    [{d:0.7,s:0.4},{d:0.4,s:0.3},{d:0.3,s:0.3},{d:0.5,s:0.4},{d:0.4,s:0.3}],
    [{d:0.5,s:0.5},{d:0.4,s:0.3},{d:0.3,s:0.2},{d:0.7,s:0.4},{d:0.5,s:0.4}],
  ];
  return { origins, dests, cells };
})();

window.FORECAST = {
  lanes: [
    { lane: 'SIN→RTM', color: '#1659CB', pts: [78, 82, 80, 85, 88, 90, 87, 92], bandLow: [70,73,72,76,78,80,77,82], bandHi: [86,91,88,94,98,100,97,102] },
    { lane: 'SHA→LAX', color: '#7C5CFF', pts: [62, 66, 69, 71, 75, 73, 78, 81], bandLow: [55,58,62,64,67,65,70,72], bandHi: [69,74,76,78,83,81,86,90] },
    { lane: 'NSA→JEB', color: '#12B76A', pts: [55, 58, 60, 62, 64, 65, 67, 70], bandLow: [50,52,54,55,57,58,60,62], bandHi: [60,64,66,69,71,72,74,78] },
    { lane: 'HKG→JFK', color: '#F79009', pts: [42, 44, 48, 51, 49, 53, 56, 58], bandLow: [38,40,43,46,44,48,50,52], bandHi: [46,48,53,56,54,58,62,64] },
    { lane: 'BOM→FRA', color: '#EC4899', pts: [35, 37, 39, 41, 43, 42, 45, 47], bandLow: [31,33,35,37,38,38,40,42], bandHi: [39,41,43,45,48,46,50,52] },
  ],
  weeks: ['Now', '+1w', '+2w', '+3w', '+4w', '+5w', '+6w', '+7w'],
};

/* ---------- Historical rate chart data — SIN→RTM ---------- */
window.HIST_RATES = {
  months: ['Jun','Jul','Aug','Sep','Oct','Nov','Dec','Jan','Feb','Mar','Apr','May'],
  values: [3210, 3340, 3420, 3580, 4280, 4720, 4560, 4150, 3940, 3820, 3760, 3850],
  events: [
    { idx: 4, label: 'Red Sea disruption', kind: 'warning' },
    { idx: 10, label: 'Maersk contract renewal', kind: 'info' },
  ],
};


/* ---------- Planning aliases (compat with screen-planning.jsx) ---------- */
window.VOLUME_LANES = window.TOP_LANES.map(l => ({ lane: l.origin + ' → ' + l.dest, count: l.count }));

window.MISSED_BOOKINGS = window.MISSED_BOOKINGS.map((m, i) => ({ ...m, rfqs: [42, 38, 28, 22, 18][i] || 15 }));

window.BENCHMARK = window.BENCHMARK_LANES.map(l => ({ lane: l.lane, ours: l.ourPrice, delta: l.delta }));

window.CUSTOMER_INTEL = window.CUSTOMERS.slice(0, 6).map(c => {
  const last = c.trend[c.trend.length - 1], first = c.trend[0];
  return {
    name: c.name,
    rfqs: c.rfqs,
    conversion: c.conversion,
    markup: c.markup,
    flag: c.flag,
    trend: c.trend,
    trendDir: last > first + 1 ? 'up' : last < first - 1 ? 'down' : 'flat',
  };
});

/* Mismatch flat list */
window.MISMATCH = (() => {
  const m = window.MISMATCH;
  const out = [];
  for (let i = 0; i < m.origins.length; i++) {
    for (let j = 0; j < m.dests.length; j++) {
      const c = m.cells[i][j];
      out.push({
        lane: m.origins[i] + '→' + m.dests[j],
        rfqs: Math.round(c.d * 24),
        rates: Math.round(c.s * 6),
      });
    }
  }
  return out.sort((a, b) => (b.rfqs - b.rates * 3) - (a.rfqs - a.rates * 3)).slice(0, 10);
})();

/* Forecast as array per lane */
window.FORECAST = window.FORECAST.lanes.map(l => {
  const hist = l.pts.slice(0, 4);
  const fc = l.pts.slice(3);
  return {
    lane: l.lane,
    color: l.color,
    weeks: hist,
    forecast: fc,
    bandLow: l.bandLow.slice(3),
    bandHigh: l.bandHi.slice(3),
    note: 'Driven by seasonal uptick + 2 new contracts',
    delta: Math.round(((fc[fc.length-1] - hist[0]) / hist[0]) * 100),
  };
});


/* ---------- Realistic rate breakdowns for RFQ-2041 (SIN→RTM, 1× 40ft HC) ---------- */
window.RATE_BREAKDOWN_2041 = [
  {
    carrier: 'Maersk',
    service: 'AE7 · Maersk Halifax',
    vessel: 'Maersk Halifax 524W',
    transitDays: 24,
    validity: 'Jun 30, 2026',
    freeDays: 14,
    estMargin: 12.4,
    badges: ['Recommended', 'Contracted', 'Fastest'],
    notes: 'Contracted rate locks through Jun 30. Maersk has handled 11 of last 14 Acme shipments — 0 service incidents.',
    lineItems: [
      { group: 'Ocean freight',     label: 'Ocean Freight · 40ft HC',                code: 'OFR',  basis: '1 × $2,850',    amount: 2850 },
      { group: 'Ocean freight',     label: 'Bunker Adjustment Factor',                code: 'BAF',  basis: 'fuel index',    amount: 480 },
      { group: 'Ocean freight',     label: 'Currency Adjustment Factor',              code: 'CAF',  basis: '2.5%',          amount: 95 },
      { group: 'Origin charges',    label: 'Terminal Handling Charge · Singapore',    code: 'THC',  basis: 'per container', amount: 225 },
      { group: 'Origin charges',    label: 'ISPS Security · Origin',                  code: 'ISPS', basis: 'per container', amount: 25 },
      { group: 'Origin charges',    label: 'B/L Documentation Fee',                   code: 'DOC',  basis: 'per B/L',       amount: 80 },
      { group: 'Destination charges', label: 'Terminal Handling Charge · Rotterdam', code: 'THC',  basis: 'per container', amount: 310 },
      { group: 'Destination charges', label: 'ENS Filing (EU advance manifest)',     code: 'ENS',  basis: 'per shipment',  amount: 35 },
      { group: 'Destination charges', label: 'Inland Haulage · Rotterdam ⇢ delivery',code: 'INL',  basis: 'door',          amount: 420 },
      { group: 'Insurance & customs', label: 'Marine Insurance · All Risk',           code: 'INS',  basis: '0.18% of CIF',  amount: 332 },
      { group: 'Insurance & customs', label: 'Customs Clearance · Origin',            code: 'CCO',  basis: 'per shipment',  amount: 95 },
      { group: 'Insurance & customs', label: 'Customs Clearance · Destination',       code: 'CCD',  basis: 'per shipment',  amount: 110 },
    ],
  },
  {
    carrier: 'MSC',
    service: 'Swan · MSC Beryl',
    vessel: 'MSC Beryl 521W',
    transitDays: 28,
    validity: 'Jul 15, 2026',
    freeDays: 10,
    estMargin: 10.1,
    badges: ['Contracted', 'Best price'],
    notes: '4 days slower transit. MSC has 2 schedule delays on this lane in last 90 days — agent flagged risk.',
    lineItems: [
      { group: 'Ocean freight',     label: 'Ocean Freight · 40ft HC',                code: 'OFR',  basis: '1 × $2,620',    amount: 2620 },
      { group: 'Ocean freight',     label: 'Bunker Adjustment Factor',                code: 'BAF',  basis: 'fuel index',    amount: 480 },
      { group: 'Ocean freight',     label: 'Currency Adjustment Factor',              code: 'CAF',  basis: '2.5%',          amount: 90 },
      { group: 'Origin charges',    label: 'Terminal Handling Charge · Singapore',    code: 'THC',  basis: 'per container', amount: 220 },
      { group: 'Origin charges',    label: 'ISPS Security · Origin',                  code: 'ISPS', basis: 'per container', amount: 25 },
      { group: 'Origin charges',    label: 'B/L Documentation Fee',                   code: 'DOC',  basis: 'per B/L',       amount: 80 },
      { group: 'Destination charges', label: 'Terminal Handling Charge · Rotterdam', code: 'THC',  basis: 'per container', amount: 305 },
      { group: 'Destination charges', label: 'ENS Filing (EU advance manifest)',     code: 'ENS',  basis: 'per shipment',  amount: 35 },
      { group: 'Destination charges', label: 'Inland Haulage · Rotterdam ⇢ delivery',code: 'INL',  basis: 'door',          amount: 420 },
      { group: 'Insurance & customs', label: 'Marine Insurance · All Risk',           code: 'INS',  basis: '0.18% of CIF',  amount: 332 },
      { group: 'Insurance & customs', label: 'Customs Clearance · Origin',            code: 'CCO',  basis: 'per shipment',  amount: 95 },
      { group: 'Insurance & customs', label: 'Customs Clearance · Destination',       code: 'CCD',  basis: 'per shipment',  amount: 110 },
    ],
  },
  {
    carrier: 'Hapag-Lloyd',
    service: 'FE3 · Hong Kong Express',
    vessel: 'Hong Kong Express',
    transitDays: 25,
    validity: 'May 31, 2026',
    freeDays: 7,
    estMargin: 8.8,
    badges: ['Spot API'],
    notes: 'Spot rate expires May 31 (18 days). Only 7 free days at destination — Acme typically needs 10+.',
    lineItems: [
      { group: 'Ocean freight',     label: 'Ocean Freight · 40ft HC',                code: 'OFR',  basis: '1 × $3,120',    amount: 3120 },
      { group: 'Ocean freight',     label: 'Bunker Adjustment Factor',                code: 'BAF',  basis: 'fuel index',    amount: 480 },
      { group: 'Ocean freight',     label: 'Currency Adjustment Factor',              code: 'CAF',  basis: '2.5%',          amount: 90 },
      { group: 'Ocean freight',     label: 'Peak Season Surcharge',                   code: 'PSS',  basis: 'May–Jul',       amount: 180 },
      { group: 'Origin charges',    label: 'Terminal Handling Charge · Singapore',    code: 'THC',  basis: 'per container', amount: 225 },
      { group: 'Origin charges',    label: 'ISPS Security · Origin',                  code: 'ISPS', basis: 'per container', amount: 25 },
      { group: 'Origin charges',    label: 'B/L Documentation Fee',                   code: 'DOC',  basis: 'per B/L',       amount: 90 },
      { group: 'Destination charges', label: 'Terminal Handling Charge · Rotterdam', code: 'THC',  basis: 'per container', amount: 310 },
      { group: 'Destination charges', label: 'ENS Filing (EU advance manifest)',     code: 'ENS',  basis: 'per shipment',  amount: 40 },
      { group: 'Destination charges', label: 'Inland Haulage · Rotterdam ⇢ delivery',code: 'INL',  basis: 'door',          amount: 445 },
      { group: 'Insurance & customs', label: 'Marine Insurance · All Risk',           code: 'INS',  basis: '0.18% of CIF',  amount: 332 },
      { group: 'Insurance & customs', label: 'Customs Clearance · Origin',            code: 'CCO',  basis: 'per shipment',  amount: 95 },
      { group: 'Insurance & customs', label: 'Customs Clearance · Destination',       code: 'CCD',  basis: 'per shipment',  amount: 110 },
    ],
  },
];


/* ---------- Lane health data (per-cell supply depth, demand, capacity) ---------- */
function buildLaneHealth({ origins, dests, seed, benchmark, carriers, mode }) {
  const rand = (i, j, salt = 1) => {
    const x = Math.sin((i * 137 + j * 91 + salt * 313)) * 10000;
    return x - Math.floor(x);
  };
  const data = [];
  for (let i = 0; i < origins.length; i++) {
    const row = [];
    for (let j = 0; j < dests.length; j++) {
      if (seed[i][j] === null) { row.push(null); continue; }
      const bench = benchmark[i][j];

      // Carrier count: 1–6, weighted by lane
      const isShortHaul = mode === 'sea'
        ? ['NSA','BOM','MAA','JEB','DXB','NHV'].includes(origins[i]) && ['JEB','DXB','NSA','MAA','BOM'].includes(dests[j])
        : ['BOM','MAA','SIN','HKG'].includes(origins[i]) && ['DXB','SIN','HKG'].includes(dests[j]);
      const carrierBase = isShortHaul ? 1 + Math.floor(rand(i,j,1) * 3) : 2 + Math.floor(rand(i,j,1) * 5);

      // Capacity (units/week our network can move)
      // sea: TEU/week; air: tons/week
      const capacity = mode === 'air'
        ? Math.round(8 + rand(i,j,2) * 60 + carrierBase * 6)   // 14–110 tons/wk
        : Math.round(40 + rand(i,j,2) * 240 + carrierBase * 15);

      // Typical weekly demand 0.4..1.8 of capacity
      const demandRatio = 0.4 + rand(i,j,3) * 1.4;
      const demand = Math.round(capacity * demandRatio);

      // Sub-scores 0–100
      const rateScore = Math.max(0, Math.min(100, Math.round(50 - bench * 55)));
      const supplyScore = Math.max(0, Math.min(100, Math.round(carrierBase * 17 + 12)));
      const ratio = capacity / Math.max(demand, 1);
      const demandScore = Math.max(0, Math.min(100, Math.round(40 + (ratio - 1) * 65)));
      const composite = Math.round(rateScore * 0.30 + supplyScore * 0.30 + demandScore * 0.40);

      // Top carrier from this mode
      const topCarrier = carriers[Math.floor(rand(i,j,4) * carriers.length)];

      const trend = Array.from({length: 8}, (_, k) => Math.max(20, Math.min(95, composite + Math.round((rand(i,j,5+k) - 0.5) * 16))));

      const flags = [];
      if (rateScore < 35) flags.push({ kind: 'rate', label: 'Above market', severity: 'danger' });
      else if (rateScore < 55) flags.push({ kind: 'rate', label: 'At market', severity: 'warning' });
      if (supplyScore < 35) flags.push({ kind: 'supply', label: 'Thin supply', severity: 'danger' });
      else if (carrierBase < 3) flags.push({ kind: 'supply', label: 'Single source', severity: 'warning' });
      if (demandScore < 35) flags.push({ kind: 'demand', label: 'Capacity strain', severity: 'danger' });
      else if (ratio < 1.1) flags.push({ kind: 'demand', label: 'Tight capacity', severity: 'warning' });

      row.push({
        carrierCount: carrierBase,
        topCarrier,
        capacity,
        demand,
        ratio,
        rateScore, supplyScore, demandScore, composite,
        bench, rate: seed[i][j],
        trend, flags,
        rfqs90d: Math.round(60 + rand(i,j,9) * 120),
        booked90d: Math.round((20 + rand(i,j,9) * 80) * Math.min(ratio, 1)),
        revenue90d: Math.round((200 + rand(i,j,10) * 800)) * 1000,
        nextRenewal: ['Jun 30, 2026','Jul 15, 2026','May 31, 2026','Aug 30, 2026','Open'][Math.floor(rand(i,j,11) * 5)],
      });
    }
    data.push(row);
  }
  return data;
}

const SEA_CARRIERS_LIST = ['Maersk','MSC','CMA CGM','Hapag-Lloyd','ONE','Evergreen','Own Network'];
const AIR_CARRIERS_LIST = ['Emirates SkyCargo','Qatar Airways Cargo','Cathay Cargo','Lufthansa Cargo','Singapore Airlines Cargo','Turkish Cargo','Etihad Cargo','AF-KLM Cargo'];

window.MATRIX_BY_MODE = {
  sea: {
    origins: window.MATRIX_ORIGINS,
    dests: window.MATRIX_DESTS,
    rates: window.MATRIX_RATES,
    carriers: SEA_CARRIERS_LIST,
    unit: '/40ft',
    unitLong: '$/40ft HC',
    rateLabel: 'best $/40ft per lane',
    originLabel: 'POL · Port of Loading',
    destLabel: 'POD · Port of Discharge',
    capacityUnit: 'TEU/wk',
    equipmentBadge: 'Sea · 40ft HC',
    icon: 'ship',
    rateFmt: (r) => '$' + Math.round(r).toLocaleString(),
    cellFmt: (r) => '$' + Math.round(r).toLocaleString(),
  },
  air: {
    origins: window.AIR_MATRIX_ORIGINS,
    dests: window.AIR_MATRIX_DESTS,
    rates: window.AIR_MATRIX_RATES,
    carriers: AIR_CARRIERS_LIST,
    unit: '/kg',
    unitLong: '$/kg',
    rateLabel: 'best $/kg per lane',
    originLabel: 'Origin Airport',
    destLabel: 'Destination Airport',
    capacityUnit: 'tons/wk',
    equipmentBadge: 'Air · per kg',
    icon: 'plane',
    rateFmt: (r) => '$' + r.toFixed(2),
    cellFmt: (r) => '$' + r.toFixed(2),
  },
};

window.MATRIX_BY_MODE.sea.health = buildLaneHealth({
  origins: window.MATRIX_ORIGINS,
  dests: window.MATRIX_DESTS,
  seed: window.MATRIX_RATES.seed,
  benchmark: window.MATRIX_RATES.benchmark,
  carriers: SEA_CARRIERS_LIST,
  mode: 'sea',
});
window.MATRIX_BY_MODE.air.health = buildLaneHealth({
  origins: window.AIR_MATRIX_ORIGINS,
  dests: window.AIR_MATRIX_DESTS,
  seed: window.AIR_MATRIX_RATES.seed,
  benchmark: window.AIR_MATRIX_RATES.benchmark,
  carriers: AIR_CARRIERS_LIST,
  mode: 'air',
});
// Multimodal = use sea matrix as fallback view
window.MATRIX_BY_MODE.multimodal = { ...window.MATRIX_BY_MODE.sea, equipmentBadge: 'Multimodal · best mode' };

// Back-compat alias (sea)
window.LANE_HEALTH = window.MATRIX_BY_MODE.sea.health;


/* ---------- Enriched customer data for Demand Planning ---------- */
window.CUSTOMER_HEALTH = (() => {
  const ports = ['SIN','SHA','HKG','NSA','BOM','MAA','JEB','RTM','HAM','LHR','FRA','LAX','JFK','DXB'];
  /* Use deterministic generation */
  const make = (idx, base) => {
    const seed = (n) => { const x = Math.sin((idx + 1) * 17 + n * 41) * 10000; return x - Math.floor(x); };

    /* Conversion trend (12 months, percentages) */
    const convStart = base.conversion + Math.round((seed(1) - 0.5) * 10);
    const convTrend = Array.from({ length: 12 }, (_, i) => Math.max(8, Math.min(75,
      convStart + Math.round((i - 6) * base.convSlope) + Math.round((seed(20 + i) - 0.5) * 6)
    )));
    const convCurrent = convTrend[convTrend.length - 1];
    const convPrev = convTrend.slice(0, 6).reduce((a, b) => a + b, 0) / 6;
    const convNow = convTrend.slice(-3).reduce((a, b) => a + b, 0) / 3;
    const convDelta = Math.round(convNow - convPrev);

    /* Demand trend — RFQs per month (12 mo) */
    const demandBase = Math.round(base.rfqs / 12);
    const demandTrend = Array.from({ length: 12 }, (_, i) => Math.max(1, Math.round(
      demandBase + (i - 6) * base.demandSlope + (seed(40 + i) - 0.5) * demandBase * 0.4
    )));
    const demandNow = demandTrend.slice(-3).reduce((a, b) => a + b, 0) / 3;
    const demandPrev = demandTrend.slice(0, 6).reduce((a, b) => a + b, 0) / 6;
    const demandDeltaPct = Math.round(((demandNow - demandPrev) / demandPrev) * 100);

    /* Margin: quoted vs accepted history */
    const quotedAvg = base.markup + 0.6;
    const acceptedAvg = base.markup;
    const marginHistory = Array.from({ length: 12 }, (_, i) => ({
      month: ['Jun','Jul','Aug','Sep','Oct','Nov','Dec','Jan','Feb','Mar','Apr','May'][i],
      quoted: +(quotedAvg + (seed(60 + i) - 0.5) * 0.8 + (i - 6) * base.marginSlope).toFixed(1),
      accepted: +(acceptedAvg + (seed(80 + i) - 0.5) * 0.6 + (i - 6) * base.marginSlope * 0.7).toFixed(1),
    }));
    const marginVariance = +(Math.max(...marginHistory.map(m => m.accepted)) - Math.min(...marginHistory.map(m => m.accepted))).toFixed(1);

    /* Lane distribution: top 5 lanes + others */
    const lanePairs = base.lanes || [
      [ports[idx % ports.length], ports[(idx + 5) % ports.length]],
      [ports[(idx + 2) % ports.length], ports[(idx + 7) % ports.length]],
      [ports[(idx + 3) % ports.length], ports[(idx + 8) % ports.length]],
      [ports[(idx + 4) % ports.length], ports[(idx + 9) % ports.length]],
      [ports[(idx + 1) % ports.length], ports[(idx + 6) % ports.length]],
    ];
    /* concentration parameter — higher = more concentrated on top lane */
    const k = base.concentration || 0.4;
    const rawShares = lanePairs.map((_, i) => Math.pow(0.5, i * k * 4) + seed(100 + i) * 0.15);
    const sumShares = rawShares.reduce((a, b) => a + b, 0);
    const lanes = lanePairs.map((p, i) => ({
      origin: p[0], dest: p[1],
      sharePct: Math.round((rawShares[i] / sumShares) * (base.lanesCovered || 78)),
      rfqs: Math.round((rawShares[i] / sumShares) * base.rfqs),
      mode: i % 3 === 0 ? 'air' : 'sea',
    })).sort((a, b) => b.sharePct - a.sharePct);
    const otherShare = Math.max(0, 100 - lanes.reduce((s, l) => s + l.sharePct, 0));

    /* Herfindahl-style concentration index (0..1) */
    const hhi = lanes.reduce((s, l) => s + (l.sharePct / 100) ** 2, 0);
    const concentrationLabel = hhi > 0.4 ? 'Highly concentrated' : hhi > 0.25 ? 'Concentrated' : hhi > 0.15 ? 'Moderate' : 'Well distributed';

    /* Sub-scores */
    const conversionScore = Math.max(0, Math.min(100, Math.round(
      40 + (convCurrent - 25) * 1.5 + convDelta * 2.5
    )));
    /* demand: penalize both extreme drops (drop = investigate) AND huge increases (need to act on supply); modest growth = best */
    const demandScore = Math.max(0, Math.min(100,
      demandDeltaPct < -25 ? 25 + (demandDeltaPct + 25) * 1.2 :
      demandDeltaPct < 0 ? 55 + demandDeltaPct * 1 :
      demandDeltaPct < 20 ? 80 + Math.round(demandDeltaPct * 0.7) :
      demandDeltaPct < 60 ? 92 - (demandDeltaPct - 20) * 0.4 :
      62 - Math.round((demandDeltaPct - 60) * 0.5)
    ));
    const marginScore = Math.max(0, Math.min(100, Math.round(
      35 + (base.markup - 5) * 12 + (base.marginSlope > 0 ? 5 : -3)
    )));
    const composite = Math.round(conversionScore * 0.40 + demandScore * 0.30 + marginScore * 0.30);

    /* Flags / interventions */
    const flags = [];
    if (convDelta <= -8) flags.push({ kind: 'conversion', label: 'Conversion drop', severity: 'danger', detail: `Down ${Math.abs(convDelta)}pp vs 6m avg` });
    else if (convDelta < -3) flags.push({ kind: 'conversion', label: 'Conversion softening', severity: 'warning', detail: `Down ${Math.abs(convDelta)}pp vs 6m avg` });
    if (demandDeltaPct >= 45) flags.push({ kind: 'demand', label: 'Demand surging', severity: 'warning', detail: `+${demandDeltaPct}% vs prior 6m` });
    else if (demandDeltaPct <= -25) flags.push({ kind: 'demand', label: 'Demand dropping', severity: 'danger', detail: `${demandDeltaPct}% vs prior 6m` });
    if (base.markup < 6.5) flags.push({ kind: 'margin', label: 'Margin compressed', severity: 'warning', detail: `Below ${6.5}% floor` });
    if (base.markup > 9.5 && convDelta < 0) flags.push({ kind: 'margin', label: 'Possibly over-priced', severity: 'warning', detail: `Markup ${base.markup}% vs portfolio ${7.8}%` });

    /* Suggested actions */
    const actions = [];
    if (convDelta <= -5) actions.push({
      icon: 'sparkles', tone: 'agent',
      title: 'Investigate conversion drop',
      reason: 'Schedule QBR call to understand competitive pressure. Agent has prepared lost-RFQ summary.',
      cta: 'Open QBR brief',
    });
    if (demandDeltaPct >= 45) actions.push({
      icon: 'trend-up', tone: 'warning',
      title: 'Confirm upcoming demand with customer',
      reason: `Demand up ${demandDeltaPct}% in 6 months. Reach out to validate forecast and pre-block capacity.`,
      cta: 'Draft outreach',
    });
    if (demandDeltaPct <= -25) actions.push({
      icon: 'alert', tone: 'warning',
      title: 'Investigate demand drop',
      reason: 'Demand has materially decreased. Likely budget cut or competitor share gain — agent suggests AM call.',
      cta: 'Schedule call',
    });
    if (base.markup < 6.5 && convCurrent > 35) actions.push({
      icon: 'trend-up', tone: 'agent',
      title: 'Test higher markup on next 3 RFQs',
      reason: `Strong conversion at low markup signals customer values service over price. Test +1pt incrementally.`,
      cta: 'Apply uplift',
    });
    if (base.markup > 9.5 && convDelta < -3) actions.push({
      icon: 'trend-down', tone: 'agent',
      title: 'Lower markup to win back share',
      reason: `Markup ${base.markup}% well above portfolio avg. Reduce to 8.5% to test elasticity.`,
      cta: 'Apply reduction',
    });
    if (hhi > 0.4) actions.push({
      icon: 'route', tone: 'brand',
      title: 'Diversify lane exposure',
      reason: `Top lane drives ${lanes[0].sharePct}% of demand — disruption risk. Identify cross-sell on adjacent lanes.`,
      cta: 'See opportunities',
    });
    if (actions.length === 0 && composite >= 65) actions.push({
      icon: 'check-circle', tone: 'success',
      title: 'Schedule expansion QBR',
      reason: 'Health is strong. Use QBR to introduce 2 new lanes identified by agent.',
      cta: 'Plan QBR',
    });

    /* 4-week forecast */
    const baseDemand = demandTrend[demandTrend.length - 1];
    const forecast = Array.from({ length: 4 }, (_, i) => ({
      week: `+${i + 1}w`,
      expected: Math.round(baseDemand + base.demandSlope * (12 + i)),
      low: Math.round((baseDemand + base.demandSlope * (12 + i)) * 0.85),
      high: Math.round((baseDemand + base.demandSlope * (12 + i)) * 1.18),
    }));

    return {
      ...base,
      composite,
      conversionScore, demandScore, marginScore,
      convCurrent, convDelta, convTrend,
      demandTrend, demandNow: Math.round(demandNow), demandPrev: Math.round(demandPrev), demandDeltaPct,
      quotedAvg, acceptedAvg, marginVariance, marginHistory,
      lanes, otherShare, hhi, concentrationLabel,
      flags, actions,
      forecast,
    };
  };

  const customers = [
    { name: 'Acme Logistics',       tier: 'Enterprise', tenure: '4.2 yrs', am: 'Priya K.', industry: 'Industrial mfg',
      rfqs: 47, conversion: 31, markup: 7.8, volume: 1420000, openRfqs: 3, lastActivity: '11:42',
      convSlope: -0.4, demandSlope: 0.8, marginSlope: -0.04, concentration: 0.6, lanesCovered: 82,
      lanes: [['SIN','RTM'],['SIN','HAM'],['SHA','RTM'],['HKG','LHR'],['BOM','FRA']],
    },
    { name: 'Bharat Industries',    tier: 'Enterprise', tenure: '6.8 yrs', am: 'Rohan N.', industry: 'Automotive',
      rfqs: 38, conversion: 42, markup: 6.4, volume: 980000, openRfqs: 2, lastActivity: '6m ago',
      convSlope: 0.1, demandSlope: 1.4, marginSlope: -0.02, concentration: 0.35, lanesCovered: 76,
      lanes: [['NSA','JEB'],['MAA','LHR'],['BOM','DXB'],['NSA','RTM'],['MAA','JFK']],
    },
    { name: 'Pacific Foods Co',     tier: 'Growth',     tenure: '2.1 yrs', am: 'Mei L.',   industry: 'Food & bev',
      rfqs: 29, conversion: 38, markup: 7.2, volume: 760000, openRfqs: 1, lastActivity: '14m ago',
      convSlope: 0.2, demandSlope: 0.6, marginSlope: 0.05, concentration: 0.5, lanesCovered: 71,
      lanes: [['SHA','LAX'],['SHA','JFK'],['HKG','LAX'],['SIN','LAX'],['SHA','SEA']],
    },
    { name: 'Mira Pharmaceuticals', tier: 'Enterprise', tenure: '5.4 yrs', am: 'Priya K.', industry: 'Pharma',
      rfqs: 24, conversion: 58, markup: 9.1, volume: 680000, openRfqs: 1, lastActivity: '1h ago',
      convSlope: 0.5, demandSlope: 0.4, marginSlope: 0.08, concentration: 0.45, lanesCovered: 78,
      lanes: [['BOM','FRA'],['HYD','BRU'],['BOM','LHR'],['MAA','FRA'],['BOM','JFK']],
    },
    { name: 'NorthWind Apparel',    tier: 'Growth',     tenure: '1.6 yrs', am: 'Mei L.',   industry: 'Apparel',
      rfqs: 22, conversion: 27, markup: 8.4, volume: 420000, openRfqs: 2, lastActivity: '3h ago',
      convSlope: -0.8, demandSlope: -0.3, marginSlope: 0.02, concentration: 0.55, lanesCovered: 68,
      lanes: [['HKG','JFK'],['SHA','JFK'],['HKG','LAX'],['DHA','MIA'],['HKG','LGW']],
    },
    { name: 'Helios Solar',         tier: 'Growth',     tenure: '2.8 yrs', am: 'Rohan N.', industry: 'Renewables',
      rfqs: 19, conversion: 21, markup: 9.6, volume: 310000, openRfqs: 0, lastActivity: 'Yesterday',
      convSlope: -1.2, demandSlope: -0.7, marginSlope: 0.06, concentration: 0.75, lanesCovered: 62,
      lanes: [['SHA','HAM'],['SHA','RTM'],['HKG','HAM'],['HKG','BCN'],['SHA','VLC']],
    },
    { name: 'OrbitTech',            tier: 'Growth',     tenure: '0.9 yrs', am: 'Mei L.',   industry: 'Electronics',
      rfqs: 16, conversion: 50, markup: 8.2, volume: 280000, openRfqs: 1, lastActivity: '2h ago',
      convSlope: 0.7, demandSlope: 1.8, marginSlope: 0.04, concentration: 0.30, lanesCovered: 74,
      lanes: [['HKG','FRA'],['SHA','FRA'],['HKG','AMS'],['SHA','AMS'],['HKG','LHR']],
    },
    { name: 'Vesta Chemicals',      tier: 'Standard',   tenure: '3.4 yrs', am: 'Priya K.', industry: 'Chemicals',
      rfqs: 14, conversion: 35, markup: 7.5, volume: 240000, openRfqs: 0, lastActivity: '3d ago',
      convSlope: 0.0, demandSlope: 0.1, marginSlope: 0.0, concentration: 0.40, lanesCovered: 70,
      lanes: [['NHV','NSA'],['LEH','BOM'],['RTM','NSA'],['HAM','MAA'],['BCN','NSA']],
    },
    { name: 'Goldfield Mining',     tier: 'Standard',   tenure: '1.2 yrs', am: 'Rohan N.', industry: 'Mining',
      rfqs: 11, conversion: 18, markup: 10.2, volume: 140000, openRfqs: 0, lastActivity: '5d ago',
      convSlope: -0.6, demandSlope: -0.9, marginSlope: 0.10, concentration: 0.62, lanesCovered: 55,
      lanes: [['JEB','NSA'],['DXB','MAA'],['JEB','BOM'],['DMM','MAA'],['DOH','BOM']],
    },
  ];

  return customers.map((c, i) => make(i, c));
})();
