// app.jsx — root App: router, tweaks, sheets, toast. Mounts to #root.
const { useState: useApp, useEffect: useFx } = React;

const THEME_MAP = { "AirAsia": "airasia", "Apron": "apron", "Cockpit": "cockpit", "Hi-Vis": "hivis" };
const ATA_MAP = { "Search": "search", "System grid": "grid", "Recent+fav": "recent" };
const FC_MAP = { "Data row": "row", "Boarding pass": "boarding", "Fuel gauge": "gauge" };
const LAY_MAP = { "Long form": "form", "Stepper": "stepper", "Cards": "cards" };

// ── local persistence ────────────────────────────────────────────────────────
// Work Logs (incl. fuel/towing/MEL/C/B entries and on-site photos) are saved to
// localStorage so they survive a reload / app restart. First run seeds the demo.
const LOGS_KEY = "aerolog.logs.v2";
const loadLogs = () => {
  try { const raw = localStorage.getItem(LOGS_KEY); if (raw) { const a = JSON.parse(raw); if (Array.isArray(a)) return a; } } catch (e) {}
  return [];   // real use: start empty (no demo logs)
};
const saveLogsLS = (logs) => {
  try { localStorage.setItem(LOGS_KEY, JSON.stringify(logs)); return true; }
  catch (e) { return false; }  // quota (too many/large photos) → keep in memory only
};

// Shift roster overrides (call-in / OT / swap / leave), keyed by ISO date.
const SHIFTS_KEY = "aerolog.shifts.v1";
const loadShifts = () => {
  try { const raw = localStorage.getItem(SHIFTS_KEY); if (raw) { const o = JSON.parse(raw); if (o && typeof o === "object") return o; } } catch (e) {}
  return {};
};
// The technician's assigned aircraft (shown on Today) — user-chosen, persisted.
const ASSIGNED_KEY = "aerolog.assigned";
const loadAssigned = () => { try { return localStorage.getItem(ASSIGNED_KEY) || null; } catch (e) { return null; } };
// Flights the tech is watching for arrival (transit pickup alerts), persisted.
const WATCH_KEY = "aerolog.watch";
const loadWatch = () => { try { const r = localStorage.getItem(WATCH_KEY); if (r) { const a = JSON.parse(r); if (Array.isArray(a)) return a; } } catch (e) {} return []; };

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "theme": "AirAsia",
  "ataVariant": "Recent+fav",
  "flightCard": "Boarding pass",
  "entryLayout": "Stepper"
}/*EDITMODE-END*/;

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const theme = THEME_MAP[t.theme] || "apron";
  const ataVariant = ATA_MAP[t.ataVariant] || "search";
  const flightCard = FC_MAP[t.flightCard] || "row";
  const entryLayout = LAY_MAP[t.entryLayout] || "form";
  const auth = useAuth();

  const [tab, setTab] = useApp("today");
  const [view, setView] = useApp("tab");       // tab | detail | new
  const [sel, setSel] = useApp(null);
  const [logs, setLogs] = useApp(loadLogs);
  const [attached, setAttached] = useApp(null);
  const [fuelPreset, setFuelPreset] = useApp(null);
  const [melPreset, setMelPreset] = useApp(null);
  const [selReg, setSelReg] = useApp(null);
  const [seed, setSeed] = useApp(null);
  const [editLog, setEditLog] = useApp(null);
  const [cbOpen, setCbOpen] = useApp(false);
  const [photoOpen, setPhotoOpen] = useApp(false);
  const [photoBuf, setPhotoBuf] = useApp([]);
  const [toolOpen, setToolOpen] = useApp(false);
  const [wxOpen, setWxOpen] = useApp(false);
  const [convOpen, setConvOpen] = useApp(false);
  const [statusFlt, setStatusFlt] = useApp(null);
  const [assignedReg, setAssignedReg] = useApp(loadAssigned);
  const [assignOpen, setAssignOpen] = useApp(false);
  const [watch, setWatch] = useApp(loadWatch);
  useFx(() => { try { if (assignedReg) localStorage.setItem(ASSIGNED_KEY, assignedReg); } catch (e) {} }, [assignedReg]);
  useFx(() => { try { localStorage.setItem(WATCH_KEY, JSON.stringify(watch)); } catch (e) {} }, [watch]);
  const [toast, setToast] = useApp(null);
  const [sync, setSync] = useApp({ state: "loading", at: null, error: null });
  const [shifts, setShifts] = useApp(loadShifts);

  // persist logs + shift overrides whenever they change (survives reload/restart)
  useFx(() => { saveLogsLS(logs); }, [logs]);
  useFx(() => { try { localStorage.setItem(SHIFTS_KEY, JSON.stringify(shifts)); } catch (e) {} }, [shifts]);
  const addShift = (o) => setShifts((p) => ({ ...p, [o.date]: o }));
  const removeShift = (iso) => setShifts((p) => { const n = { ...p }; delete n[iso]; return n; });

  // Pull OPS data from the API. opts.tools also refreshes the tool inventory;
  // opts.silent skips the loading banner (used by the auto-refresh poll so it
  // doesn't flicker every cycle). Tools are static → fetched once at startup only.
  const REFRESH_MS = 60000; // auto-refresh OPS data every 60s (real-time-ish)
  const loadOps = (opts = {}) => {
    if (!opts.silent) setSync((s) => ({ ...s, state: "loading" }));
    const jobs = [fetchOpsData()];
    if (opts.tools) { jobs.push(fetchTools().catch(() => null), fetchCBGuide().catch(() => null), fetchCBTip().catch(() => null)); }
    return Promise.all(jobs)
      .then((res) => {
        const d = res[0], tools = res[1], cbg = res[2], cbt = res[3];
        Object.assign(window, { FLEET: d.fleet, FLIGHTS: d.flights, AC_INFO: d.acInfo, MR2: d.mr2 });
        if (tools && tools.length) window.TOOL_LIB = tools;   // real tool inventory
        if (cbg && cbg.length) window.CB_GUIDE = cbg;          // real C/B reset guide (TSM + MOC)
        if (cbt && cbt.length) window.CB_TIP = cbt;            // per-computer C/B tip
        checkWatches(d.flights);                               // transit arrival alerts
        const n = new Date();
        const at = String(n.getHours()).padStart(2, "0") + ":" + String(n.getMinutes()).padStart(2, "0");
        setSync({ state: "live", at, error: null });
      })
      // A failed background poll keeps the last good data on screen (frozen
      // timestamp signals staleness); only an explicit/initial load shows offline.
      .catch((e) => { if (!opts.silent) setSync({ state: "offline", at: null, error: String((e && e.message) || e) }); });
  };
  useFx(() => {
    loadOps({ tools: true });
    const id = setInterval(() => loadOps({ silent: true }), REFRESH_MS);
    return () => clearInterval(id);
  }, []);

  const flash = (msg) => { setToast(msg); setTimeout(() => setToast(null), 2200); };

  // ── transit arrival alerts ──────────────────────────────────────────────────
  const notify = (title, body) => {
    flash(title);
    try {
      if (("Notification" in window) && Notification.permission === "granted") {
        if (navigator.serviceWorker && navigator.serviceWorker.ready) {
          navigator.serviceWorker.ready
            .then((reg) => reg.showNotification(title, { body, icon: "icons/icon-192.png", badge: "icons/icon-192.png", tag: "aerolog-arr", renotify: true, requireInteraction: true, silent: false, vibrate: [200, 90, 200] }))
            .catch(() => { try { new Notification(title, { body }); } catch (e) {} });
        } else { try { new Notification(title, { body }); } catch (e) {} }
      }
    } catch (e) {}
  };
  const toggleWatch = (f) => {
    if (!f) return;
    try { if (("Notification" in window) && Notification.permission === "default") Notification.requestPermission().catch(() => {}); } catch (e) {}
    setWatch((p) => p.some((w) => w.id === f.id)
      ? p.filter((w) => w.id !== f.id)
      : [{ id: f.id, reg: f.reg, lead: 15, soon: false, landed: false }, ...p]);
  };
  const removeWatch = (id) => setWatch((p) => p.filter((w) => w.id !== id));
  // Called after each OPS refresh — fire alerts when a watched flight nears arrival / lands.
  const checkWatches = (flights) => {
    setWatch((prev) => {
      const fires = [];
      const next = prev.map((w) => {
        const f = (flights || []).find((x) => x.id === w.id);
        if (!f) return w;
        const a = flightArrival(f);
        if (a.landed && !w.landed) { fires.push(["✈️ " + w.id + " ลงแล้ว", `${f.reg || ""} ${f.from}→${f.to} · ไปรับเครื่องได้`]); return { ...w, landed: true, soon: true }; }
        if (!a.landed && a.mins != null && a.mins <= w.lead && a.mins > -5 && !w.soon) { fires.push(["🔔 " + w.id + " ใกล้ลง ~" + Math.max(0, a.mins) + " นาที", `${f.reg || ""} ${f.from}→${f.to} · เตรียมไปรับเครื่อง`]); return { ...w, soon: true }; }
        return w;
      });
      if (fires.length) setTimeout(() => fires.forEach(([t, b]) => notify(t, b)), 0);
      return fires.length ? next : prev;
    });
  };
  const fmt = (n) => Math.round(n).toLocaleString();
  const startFuel = (preset) => { setFuelPreset(preset || null); setView("fuel"); };
  const startAircraft = (reg) => { setSelReg(reg); setView("aircraft"); };
  const fuelFromFlight = (f) => {
    const tp = /A33/.test(f.type || FLEET.find((a) => a.reg === f.reg)?.type || "") ? "A330" : "A320";
    const base = tp === "A320" ? 3100 : 14000;
    return { type: tp, actual: base, target: base + f.fuelUplift, density: f.fuelDensity };
  };
  const saveFuel = (r) => {
    const log = { id: "LOG-" + r.id, reg: "—", ata: "28", flight: null,
      title: `Fuel ${r.type} — uplift ${fmt(r.calcUplift)} kg`, th: "Fuel uplift",
      defect: `Refuel to planned ${fmt(r.planned)} kg`,
      action: `Remaining ${fmt(r.remaining)} kg + uplift ${fmt(r.calcUplift)} kg (${fmt(r.litres)} L @ ${r.density} kg/L) = departure ${fmt(r.departureCalc)} kg · panel ${fmt(r.departureGauge)} kg · discrepancy ${r.disc >= 0 ? "+" : ""}${fmt(r.disc)} kg${r.mli ? " — MLI CHECK REQ" : ""}.`,
      amm: ["28-00-00"], start: nowHM(), stop: nowHM(), dur: "—", parts: [], tools: [], cb: [], photos: [], status: "draft", tech: "You" };
    setLogs((p) => [log, ...p]); setView("tab"); setTab("today");
    flash(r.mli ? "Saved · ⚠ MLI Check required" : "Fuel uplift saved");
  };

  const openLog = (l) => { setSel(l); setView("detail"); };
  const startNew = (fl) => { setSeed(null); setEditLog(null); setAttached(fl || null); setView("new"); };
  const startEditLog = (l) => { setEditLog(l); setSeed(null); setAttached(null); setView("new"); };
  const startTowing = () => setView("towing");
  const saveTowing = (r) => {
    const reg = (FLEET[0] && FLEET[0].reg) || "—";
    const log = { id: "LOG-TOW" + Math.floor(Math.random() * 900), reg, ata: "09", flight: null,
      title: `Towing ${r.type} — checklist ${r.done}/${r.total}`, th: "Towing",
      defect: "Aircraft towing", action: `Towing checklist ${r.done}/${r.total} items done (${r.type}, ${r.type === "A330" ? "B" : "Y"} HYD).`,
      amm: "09-00-00", start: nowHM(), stop: nowHM(), dur: "—", parts: [], tools: [], cb: [], photos: [], status: "draft", tech: "You" };
    setLogs((p) => [log, ...p]); setView("tab"); setTab("today"); flash("Towing saved");
  };
  const startMel = (preset) => { setMelPreset(preset || null); setView("mel"); };
  const saveMel = (r) => {
    const reg = r.reg || (FLEET[0] && FLEET[0].reg) || "—";
    const log = { id: "LOG-MEL" + Math.floor(Math.random() * 900), reg, ata: "—", flight: null,
      title: `MEL Cat ${r.cat} — expires ${r.expiry}`, th: "MEL deferral",
      defect: `${r.item ? r.item + "\n" : ""}MEL Cat ${r.cat} (${r.days} days)${r.mel ? " · MEL " + r.mel : ""} deferred ${r.disc}`,
      action: `MEL Cat ${r.cat} · ${r.days} calendar days (UTC). Expires ${r.expiry}. ${r.expired ? "Expired" : r.remaining + " days remaining"}.`,
      amm: "—", start: nowHM(), stop: nowHM(), dur: "—", parts: [], tools: [], cb: [], photos: [], status: "draft", tech: "You" };
    setLogs((p) => [log, ...p]); setMelPreset(null); setView("tab"); setTab("today"); flash("MEL saved");
  };
  const startLogFromPart = (p) => { setSeed({ ata: p.ata, pos: p.name + " — " + p.pos }); setAttached(null); setView("new"); };
  const saveLog = (log) => {
    setLogs((p) => { const i = p.findIndex((l) => l.id === log.id); if (i >= 0) { const n = p.slice(); n[i] = log; return n; } return [log, ...p]; });
    const editing = !!editLog; setEditLog(null); setView("tab"); setTab("today");
    flash(editing ? "Saved changes to " + log.id : "Saved " + log.id + " · pending sync");
  };
  const saveCB = (f, note) => {
    if (f) {
      const reg = (FLEET[0] && FLEET[0].reg) || "—";
      const action = (note ? note + "\n\n" : "") + (f.procedure || f.note || "Reset, ops check normal");
      const log = { id: "LOG-CB" + Math.floor(Math.random() * 900), reg, ata: f.ata || "—", flight: null,
        title: "C/B reset: " + (f.computer || f.ecam || "").slice(0, 40),
        th: "C/B reset" + (f.ecam ? " · ECAM: " + f.ecam : ""), defect: f.ecam ? "ECAM: " + f.ecam : "C/B reset",
        action, amm: "—", start: nowHM(), stop: nowHM(), dur: "5m", parts: [], tools: [],
        cb: f.computer ? [f.computer] : (f.id ? [f.id] : []), photos: [], status: "draft", tech: "You" };
      setLogs((p) => [log, ...p]);
      flash("C/B reset saved");
    }
    setCbOpen(false);
  };

  // On-site photos → save as a standalone photo Work Log (real images attached)
  const savePhotos = () => {
    if (!photoBuf.length) { setPhotoOpen(false); return; }
    const reg = (FLEET[0] && FLEET[0].reg) || "—";
    const log = { id: "LOG-PH" + Math.floor(Math.random() * 900), reg, ata: "—", flight: null,
      title: `On-site photos (${photoBuf.length})`, th: "Photo note", defect: "On-site photo documentation.",
      action: `${photoBuf.length} photo(s) captured on-site.`, amm: "—", start: nowHM(), stop: nowHM(), dur: "—",
      parts: [], tools: [], cb: [], photos: photoBuf, status: "draft", tech: "You" };
    setLogs((p) => [log, ...p]); setPhotoBuf([]); setPhotoOpen(false); setView("tab"); setTab("today");
    flash(`Saved ${photoBuf.length} photo(s) to Work Log`);
  };
  const resetLocal = () => {
    try { localStorage.removeItem(LOGS_KEY); } catch (e) {}
    setLogs([]); flash("Local logs cleared");
  };

  // reset to tab when changing tab
  const go = (k) => { setTab(k); setView("tab"); };

  // Auth gate — first run shows setup, otherwise the PIN lock, before the app.
  if (auth.stage !== "in") {
    return (
      <PhoneFrame theme={theme}>
        <StatusBar />
        <div className="app-body">
          {auth.stage === "setup"
            ? <SetupScreen defaults={DEFAULT_PROFILE} onDone={auth.setup} />
            : <LockScreen profile={auth.profile} onUnlock={auth.unlock} onForgot={auth.signOut} />}
        </div>
      </PhoneFrame>
    );
  }

  let body;
  if (view === "new") {
    body = <NewLogFlow layout={entryLayout} ataVariant={ataVariant} attachedFlight={attached} seed={seed} editLog={editLog} onClose={() => setView("tab")} onSave={saveLog} />;
  } else if (view === "towing") {
    body = <TowingScreen onBack={() => setView("tab")} onSave={saveTowing} />;
  } else if (view === "mel") {
    body = <MelScreen preset={melPreset} onBack={() => setView("tab")} onSave={saveMel} />;
  } else if (view === "fuel") {
    body = <FuelCalc preset={fuelPreset} onBack={() => setView("tab")} onSave={saveFuel} />;
  } else if (view === "aircraft") {
    body = <AircraftScreen reg={selReg} onBack={() => setView("tab")} onFuel={(f) => startFuel(fuelFromFlight(f))} onAttach={(f) => startNew(f)} onMel={startMel} onStatus={(f) => setStatusFlt(f)} />;
  } else if (view === "detail") {
    body = <LogDetail log={sel} onBack={() => setView("tab")} onEdit={() => startEditLog(sel)} />;
  } else if (tab === "today") {
    body = <TodayScreen logs={logs} onOpenLog={openLog} onNew={() => startNew()} openCB={() => setCbOpen(true)} openPhoto={() => setPhotoOpen(true)} onFuel={() => startFuel()} onAircraft={startAircraft} onTowing={startTowing} onMel={startMel} onToolSearch={() => setToolOpen(true)} onWx={() => setWxOpen(true)} onConvert={() => setConvOpen(true)} onStatus={(f) => setStatusFlt(f)} go={go} sync={sync} onRetry={() => loadOps()} shifts={shifts} profile={auth.profile} assignedReg={assignedReg} onPickAssigned={() => setAssignOpen(true)} watch={watch} onRemoveWatch={removeWatch} />;
  } else if (tab === "logs") {
    body = <LogsScreen logs={logs} onOpenLog={openLog} />;
  } else if (tab === "flights") {
    body = <FlightsScreen variant={flightCard} onAttach={(f) => startNew(f)} onFuel={(f) => startFuel(fuelFromFlight(f))} onAircraft={startAircraft} onStatus={(f) => setStatusFlt(f)} sync={sync} onRetry={() => loadOps()} />;
  } else {
    body = <ProfileScreen logCount={logs.length} onReset={resetLocal} shifts={shifts} onAddShift={addShift} onRemoveShift={removeShift} profile={auth.profile} onLock={auth.lock} onSignOut={auth.signOut} />;
  }

  const showNav = view === "tab";
  return (
    <PhoneFrame theme={theme}>
      <StatusBar />
      <div className="app-body">{body}</div>
      {showNav && <BottomNav active={tab} onChange={go} onNew={() => startNew()} />}

      <CBSheet open={cbOpen} onClose={() => setCbOpen(false)} onSave={saveCB} />
      <ToolSearchSheet open={toolOpen} onClose={() => setToolOpen(false)} />
      <WeatherSheet open={wxOpen} onClose={() => setWxOpen(false)} />
      <ConverterSheet open={convOpen} onClose={() => setConvOpen(false)} />
      <FlightStatusSheet flight={statusFlt} onClose={() => setStatusFlt(null)} watching={!!statusFlt && watch.some((w) => w.id === statusFlt.id)} onToggleWatch={() => toggleWatch(statusFlt)} />
      <AssignSheet open={assignOpen} onClose={() => setAssignOpen(false)} onPick={setAssignedReg} current={assignedReg} />
      <Sheet open={photoOpen} onClose={() => setPhotoOpen(false)} title="On-site Photo">
        <p className="sheet-lead">ถ่ายรูปหน้างาน — รูปจะถูกบันทึกเป็น Work Log (เก็บในเครื่อง เปิดใหม่ยังอยู่)</p>
        <PhotoPick photos={photoBuf} set={setPhotoBuf} />
        <div className="photo-count mono">{photoBuf.length ? `${photoBuf.length} photo(s) ready` : "No photo yet — tap Add photo to open the camera"}</div>
        <Btn full kind="primary" onClick={savePhotos} icon={<Ic.check width="18" height="18" />}>{photoBuf.length ? `Save ${photoBuf.length} to Work Log` : "Done"}</Btn>
      </Sheet>

      {toast && <div className="toast"><Ic.check width="16" height="16" />{toast}</div>}

      <TweaksPanel title="Tweaks">
        <TweakSection label="Visual / Theme" />
        <TweakRadio label="Theme" value={t.theme} options={["AirAsia", "Apron", "Cockpit", "Hi-Vis"]} onChange={(v) => setTweak("theme", v)} />
        <TweakSection label="UX / Flow" />
        <TweakRadio label="ATA selector" value={t.ataVariant} options={["Search", "System grid", "Recent+fav"]} onChange={(v) => setTweak("ataVariant", v)} />
        <TweakRadio label="Flight card" value={t.flightCard} options={["Data row", "Boarding pass", "Fuel gauge"]} onChange={(v) => setTweak("flightCard", v)} />
        <TweakRadio label="Entry layout" value={t.entryLayout} options={["Long form", "Stepper", "Cards"]} onChange={(v) => setTweak("entryLayout", v)} />
      </TweaksPanel>
    </PhoneFrame>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
