// api.jsx — OPS API data layer for AeroLog.
// ─────────────────────────────────────────────────────────────────────────────
// Sources (selected by API.source), all reached through serve.py's same-origin proxy:
//   "ops"   — FULL real data:
//               • taaplus  /taaplus/flights   → live flight board
//               • taaplus  /taaplus/aircraft  → fleet (type, fuel cap, pax cap)
//               • sheet    AircraftData       → MSN / SELCAL / W&B
//               • sheet    MEL                → deferred defects (MR2)
//               • sheet    autoland / fumigation → next-due dates
//   "mock"  — local JSON in /api (works under any static server, no proxy)
//
// Registrations are normalized to "HS-XXX" (taaplus uses the 3-letter suffix).
// serve.py attaches the referrer-locked Sheets key and the taaplus session cookie.
// ─────────────────────────────────────────────────────────────────────────────

const API = {
  source: "ops",            // "ops" (full real, needs serve.py) | "mock"
  mockBase: "api",
  proxyBase: "sheets",      // serve.py → /sheets/<name>
  taaplusBase: "taaplus",   // serve.py → /taaplus/<name>
  flightLimit: 0,           // 0 = keep all; >0 caps the stored flight count
  timeoutMs: 20000,
};

// ── low-level GET (timeout + JSON) ───────────────────────────────────────────
async function apiGet(path) {
  const ctrl = new AbortController();
  const timer = setTimeout(() => ctrl.abort(), API.timeoutMs);
  try {
    const res = await fetch(path, { headers: { Accept: "application/json" }, signal: ctrl.signal, cache: "no-store" });
    if (!res.ok) throw new Error(`${path}: HTTP ${res.status} ${res.statusText}`);
    return await res.json();
  } catch (e) {
    if (e.name === "AbortError") throw new Error(`${path}: timed out`);
    throw e;
  } finally {
    clearTimeout(timer);
  }
}

// ── helpers ──────────────────────────────────────────────────────────────────
// Thai AirAsia X A330-300 fleet (HS-XT*) — not on the FD/taaplus board, added here so
// they can be assigned + worked. Add/remove regs to match the live fleet.
const A330_FLEET = ["XTC", "XTN", "XTS", "XTP", "XTH", "XTR", "XTO", "XTQ"].map((s) => "HS-" + s);

const num = (v) => { const n = parseFloat(String(v == null ? "" : v).replace(/,/g, "")); return isNaN(n) ? 0 : n; };
const fullReg = (r) => { r = String(r == null ? "" : r).trim(); return r ? (r.startsWith("HS-") ? r : "HS-" + r) : r; };
const hhmm = (t) => (t ? String(t).slice(0, 5) : "");

// Normalize the sheet's many date spellings to the app's "DD-MMM-YY".
function normDate(s) {
  if (!s) return s || "—";
  const m = /(\d{1,2})[\s-]([A-Za-z]{3,})[\s-](\d{2,4})/.exec(String(s).trim());
  if (!m) return String(s).trim();
  const dd = String(parseInt(m[1], 10)).padStart(2, "0");
  const mon = m[2].slice(0, 3);
  const Mon = mon.charAt(0).toUpperCase() + mon.slice(1).toLowerCase();
  const yy = m[3].length > 2 ? m[3].slice(-2) : m[3].padStart(2, "0");
  return `${dd}-${Mon}-${yy}`;
}
const normCat = (c) => { const v = String(c || "").trim().toUpperCase(); return v.indexOf("NTC") >= 0 ? "NTC" : (v || "NIL"); };

async function getSheet(name) {
  const j = await apiGet(`${API.proxyBase}/${name}`);
  if (j && j.error) throw new Error(`${name}: ${j.error}`);
  return (j && j.values) || [];
}
function dueMap(rows) {
  const m = {};
  (rows || []).slice(1).forEach((r) => { if (r[0]) m[r[0]] = { perf: normDate(r[1]), due: normDate(r[2]) }; });
  return m;
}

// ── flight-board row (taaplus /api/flights) → app flight shape ───────────────
function mapFlight(f, acByReg) {
  const reg = fullReg(f.REG);
  const ac = acByReg[reg] || {};
  const pax = f.ActualPAX != null ? f.ActualPAX : (f.ExpectPAX != null ? f.ExpectPAX : 0);
  let op = "Scheduled";
  if (f.Landing) op = "Landed";
  else if (f.TakeOff || f.OFFChock) op = "Departed";
  else if (f.DoorClose) op = "Boarding closed";
  else if (f.Gate && f.Gate !== "-") op = "Boarding";
  // Fuel is already stored in litres (orders are round: 8800, 9000, 11600…).
  // Show the actual order (FuelOrder) when placed, else the planned OFP figure.
  // NOTE: do NOT divide by density — the values are litres, not kg.
  const order = num(f.FuelOrder);   // ordered from the bowser (round litres)
  const ofp = num(f.FuelOFP);       // planned fuel from the OFP (precise)
  const main = order || ofp;
  const tkof = hhmm(f.TakeOff) || hhmm(f.OFFChock);   // actual departure
  const land = hhmm(f.Landing);                        // actual arrival
  return {
    id: "FD" + f.FLT, reg, from: f.DEP || "—", to: f.ARR || "—",
    schedIn: hhmm(f.STA), actualIn: hhmm(f.ETA) || hhmm(f.Landing) || hhmm(f.STA),
    gate: f.Gate || "-", stand: f.Bay || "-", bay: f.Bay || "-", tsat: hhmm(f.TSAT) || "—",
    std: hhmm(f.STD), sta: hhmm(f.STA), status: f.FuelType || op,
    // departure/arrival times for the Google-style status card
    depSched: hhmm(f.STD), depEst: hhmm(f.ETD), depAct: tkof,
    arrSched: hhmm(f.STA), arrEst: hhmm(f.ETA), arrAct: land,
    fuelOrder: order, fuelOFP: ofp, fuelUplift: order, fuelUnit: "L", fuelDensity: 0.79, blockFuel: main,
    fuelFinal: main, fuelStatus: order ? (f.FuelType || "Final") : "OFP",
    refuelTruck: f.FuelBy || "—", nextOut: hhmm(f.ETD) || hhmm(f.STD), crew: "—",
    type: ac.type || "A320", pax, paxCap: ac.paxCap || 180, opStatus: op,
  };
}

// ── SOURCE: full real OPS (taaplus flights + aircraft, merged with the sheet) ─
async function fetchFromOps() {
  const [flightsRaw, acRaw, acRows, melRows, alRows, fumRows] = await Promise.all([
    apiGet(`${API.taaplusBase}/flights`),
    apiGet(`${API.taaplusBase}/aircraft`),
    getSheet("aircraftData"), getSheet("mel"), getSheet("autoland"), getSheet("fumigation"),
  ]);
  if (flightsRaw && flightsRaw.error) throw new Error("flights: " + flightsRaw.error);
  if (acRaw && acRaw.error) throw new Error("aircraft: " + acRaw.error);

  // fleet + per-reg lookup (type, fuel cap, pax cap) from taaplus aircraft
  const acByReg = {};
  const fleet = [];
  (acRaw || []).forEach((a) => {
    const reg = fullReg(a.reg);
    if (!reg) return;
    const zones = num(a["MAX ZONE A"]) + num(a["MAX ZONE B"]) + num(a["MAX ZONE C"]);
    acByReg[reg] = { reg, type: a.type || "A320", config: a.config, fuelCap: num(a["MAX Tank"]), paxCap: zones || 180 };
    fleet.push({ reg, type: a.type || "A320", eng: "CFM56-5B", apu: "—" }); // sheet/API have no engine — A320 fleet
  });

  // Supplement the A330 fleet (Thai AirAsia X — not on the FD board)
  A330_FLEET.forEach((reg) => {
    if (acByReg[reg]) return;
    acByReg[reg] = { reg, type: "A330-300", config: "377Y", fuelCap: 0, paxCap: 377 };
    fleet.push({ reg, type: "A330-300", eng: "Trent 700", apu: "GTCP331-350" });
  });

  // acInfo: MSN/SELCAL/W&B (AircraftData) + autoland + fumigation, keyed by HS-reg
  const autoland = dueMap(alRows), fum = dueMap(fumRows);
  const acInfo = {};
  (acRows || []).slice(1).forEach((r) => {
    const reg = r[0];
    if (!reg) return;
    acInfo[reg] = {
      config: (acByReg[reg] && acByReg[reg].config) || "180Y", msn: r[1] || "—", selcal: r[2] || "—",
      pf: r[16] || "—", iff: r[15] || "—",
      mtow: num(r[3]), mlw: num(r[4]), mzfw: num(r[5]),
      dow: num(r[7]), index: num(r[11]), mac: r[12] || "—", // 2-crew (2CP) set
      autolandPerf: (autoland[reg] || {}).perf || "—", autolandDue: (autoland[reg] || {}).due || "—",
      fumPerf: (fum[reg] || {}).perf || "—", fumDue: (fum[reg] || {}).due || "—",
    };
  });

  // mr2 from MEL (no header; positional A:H)
  const mr2 = {};
  (melRows || []).forEach((r) => {
    const reg = r[0];
    if (!reg) return;
    (mr2[reg] = mr2[reg] || []).push({
      date: normDate(r[1]), item: r[2] || "—", mel: r[3] || "NIL",
      cat: normCat(r[4]), status: r[5] || "OPEN",
      expire: normDate(r[6]), ext: r[7] && String(r[7]).trim() ? String(r[7]).trim() : "—",
    });
  });

  // flights: map + sort by STD
  let flights = (flightsRaw || []).map((f) => mapFlight(f, acByReg))
    .sort((a, b) => (a.std || "").localeCompare(b.std || ""));
  const total = flights.length;
  if (API.flightLimit > 0) flights = flights.slice(0, API.flightLimit);

  return { fleet, flights, acInfo, mr2, _meta: { source: "ops", flightsTotal: total, flights: flights.length, aircraft: fleet.length } };
}

// ── SOURCE: local mock JSON ──────────────────────────────────────────────────
async function fetchFromMock() {
  const [fleet, flights, acInfo, mr2] = await Promise.all([
    apiGet(`${API.mockBase}/fleet.json`),
    apiGet(`${API.mockBase}/flights.json`),
    apiGet(`${API.mockBase}/aircraft-info.json`),
    apiGet(`${API.mockBase}/mr2.json`),
  ]);
  return { fleet, flights, acInfo, mr2, _meta: { source: "mock" } };
}

// Pull everything in parallel. Rejects on failure so the caller can fall back to
// the bundled cache (offline-first).
async function fetchOpsData() {
  return API.source === "mock" ? fetchFromMock() : fetchFromOps();
}

// Tool Inventory (ALL TOOLS export) — static, same-origin. Each tool carries its
// store location + remaining qty so the tech can check the tool room from the log.
async function fetchTools() {
  const t = await apiGet(`${API.mockBase}/tools.json`);
  return Array.isArray(t) ? t : [];
}

// C/B reset guide — TSM (A320 Computer Reset matrix, approved) merged with the
// MOC field-note list (real engineer notes, mixed TH). Each item is tagged with
// `source` ("TSM" | "MOC") so the UI can flag approved vs field reference.
async function fetchCBGuide() {
  const [tsm, moc] = await Promise.all([
    apiGet(`${API.mockBase}/cb-reset.json`).catch(() => []),
    apiGet(`${API.mockBase}/cb-moc.json`).catch(() => []),
  ]);
  const a = (Array.isArray(tsm) ? tsm : []).map((x) => ({ ...x, source: x.source || "TSM" }));
  const b = (Array.isArray(moc) ? moc : []).map((x) => ({ ...x, source: x.source || "MOC" }));
  return a.concat(b);
}

// C/B Tip — per-computer quick reference (A320 CB Tip): pull/in wait time and the
// C/B positions per system. Each: { computer, out, in, sys:[...] }.
async function fetchCBTip() {
  const t = await apiGet(`${API.mockBase}/cb-tip.json`).catch(() => []);
  return Array.isArray(t) ? t : [];
}

// Aviation weather (METAR/TAF) via serve.py's /wx proxy (aviationweather.gov has
// no CORS). ids = comma-separated ICAO (e.g. "VTBD,VTBS"). Returns decoded JSON.
async function fetchMetar(ids) { const j = await apiGet(`wx/metar?ids=${encodeURIComponent(ids)}`); return Array.isArray(j) ? j : []; }
async function fetchTaf(ids) { const j = await apiGet(`wx/taf?ids=${encodeURIComponent(ids)}`); return Array.isArray(j) ? j : []; }

Object.assign(window, { API, apiGet, fetchOpsData, fetchFromOps, fetchFromMock, fetchTools, fetchCBGuide, fetchCBTip, fetchMetar, fetchTaf, normDate, normCat, fullReg });
