// talon-app.jsx — App root: state, region switching, tweaks, admin, mount.
const { useState: uS, useRef: uR, useEffect: uE, useMemo: uM, useCallback: uCB } = React;

const INK = "#0a0c12";
const ADMIN_W = 360;
const ACCENTS = {
  "#fcd34d": { base: "#fcd34d", bright: "#fde68a", glow: "rgba(252,211,77,0.55)", name: "Amber" },
  "#6ee7b7": { base: "#6ee7b7", bright: "#a7f3d0", glow: "rgba(110,231,183,0.5)", name: "Jade" },
  "#7dd3fc": { base: "#7dd3fc", bright: "#bae6fd", glow: "rgba(125,211,252,0.5)", name: "Azure" },
  "#fca5a5": { base: "#fca5a5", bright: "#fecaca", glow: "rgba(252,165,165,0.5)", name: "Ember" },
  "#c4b5fd": { base: "#c4b5fd", bright: "#ddd6fe", glow: "rgba(196,181,253,0.5)", name: "Violet" },
};
const FONTS = {
  Cinzel: "'Cinzel', serif",
  "Cormorant Garamond": "'Cormorant Garamond', serif",
  "EB Garamond": "'EB Garamond', serif",
};

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "#fcd34d",
  "treatment": "painted",
  "titleFont": "Cinzel",
  "parallax": 1,
  "atmosphere": 0.5
}/*EDITMODE-END*/;

const EMPTY_REGION = {
  id: '', name: '', pois: [],
  treatment: 'placeholder', image: null,
  size: { width: 1920, height: 1080 },
  initialView: { x: 0.5, y: 0.5, zoom: 1 },
  minZoom: 0.5, maxZoom: 2.5,
};

function App() {
  const [apiRegions, setApiRegions] = uS(() => window.TalonDB.readRegions());
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [activeId, setActiveId] = uS(() => window.TalonDB.readRegions()[0]?.id || 'talon-mainland');

  const regions = uM(
    () => apiRegions.map(r => ({ ...r, pois: (window.TALON_BASE_POIS || {})[r.id] || [] })),
    [apiRegions]
  );
  const region = uM(() => regions.find((r) => r.id === activeId) || regions[0] || EMPTY_REGION, [activeId, regions]);

  const [overrides, setOverrides] = uS({ regions: {} });
  const [dbMode, setDbMode] = uS("loading");
  const [dbReady, setDbReady] = uS(false);
  const livePois = uM(() => computePois(region, overrides), [region, overrides]);
  const liveRegion = uM(() => ({ ...region, pois: livePois }), [region, livePois]);

  const camera = useCamera(region);
  const [selected, setSelected] = uS(null);
  const [hovered, setHovered] = uS(null);
  const [vw, setVw] = uS(window.innerWidth);
  const [vh, setVh] = uS(window.innerHeight);
  const [ready, setReady] = uS(false);
  const [fade, setFade] = uS(1);
  const lastMarker = uR(null);

  // admin
  const [adminMode, setAdminMode] = uS(false);
  const [editingId, setEditingId] = uS(null);
  const [placing, setPlacing] = uS(null);       // null | {mode:'new'} | {mode:'move'}
  const [showData, setShowData] = uS(false);
  const [showAuthModal, setShowAuthModal] = uS(false);
  const [setupMode, setSetupMode] = uS(false);

  // init the SQLite engine, then load saved overrides from it
  uE(() => {
    let alive = true;
    window.TalonDB.init().then(({ mode }) => {
      if (!alive) return;
      setDbMode(mode);
      const regions = window.TalonDB.readRegions();
      setApiRegions(regions);
      setOverrides(window.TalonDB.readOverrides());
      setDbReady(true);
      if (mode !== 'fallback' && regions.length === 0) setSetupMode(true);
    });
    return () => { alive = false; };
  }, []);

  const accent = ACCENTS[t.accent] || ACCENTS["#fcd34d"];
  const articleOpen = !!selected && !adminMode;
  const leftInset = adminMode ? ADMIN_W : 0;
  const rightInset = articleOpen ? 420 : 0;

  // resize
  uE(() => {
    const h = () => { setVw(window.innerWidth); setVh(window.innerHeight); };
    window.addEventListener("resize", h);
    return () => window.removeEventListener("resize", h);
  }, []);

  // display font + accent on :root
  uE(() => {
    document.documentElement.style.setProperty("--font-display", FONTS[t.titleFont] || FONTS.Cinzel);
    document.documentElement.style.setProperty("--accent", accent.base);
    document.documentElement.style.setProperty("--accent-bright", accent.bright);
    document.documentElement.style.setProperty("--accent-glow", accent.glow);
  }, [t.titleFont, t.accent]);

  // preload active region image
  uE(() => {
    setReady(false);
    const placeholder = region.treatment !== "painted" || t.treatment === "placeholder";
    if (placeholder || !region.image) { const x = setTimeout(() => setReady(true), 120); return () => clearTimeout(x); }
    const img = new Image(); let done = false;
    img.onload = () => { done = true; setReady(true); };
    img.onerror = () => { done = true; setReady(true); };
    img.src = region.image;
    const safety = setTimeout(() => { if (!done) setReady(true); }, 4000);
    return () => clearTimeout(safety);
  }, [region.id, t.treatment]);

  // ── reader handlers ──
  const selectPoi = (poi) => {
    if (!poi) return;
    lastMarker.current = poi.id;
    setSelected(poi);
    const wasOpen = !!selected;
    camera.tweenTo({ cx: poi.coords.x, cy: poi.coords.y, zoom: Math.max(camera.view.zoom, 1.35) }, wasOpen ? 500 : 700);
  };
  const closePanel = () => {
    if (!selected) return;
    const v = camera.viewRef.current, W = region.size.width;
    camera.tweenTo({ cx: clamp(v.cx + (420 / 2) / (W * v.zoom), 0, 1) }, 300);
    setSelected(null);
    const id = lastMarker.current;
    setTimeout(() => { const el = document.querySelector(`.poi-marker[data-poi="${id}"]`); if (el) el.focus(); }, 60);
  };
  const goHome = () => {
    setSelected(null);
    camera.tweenTo({ cx: region.initialView.x, cy: region.initialView.y, zoom: region.initialView.zoom }, 600);
  };
  const zoomBtn = (factor) => {
    const v = camera.viewRef.current;
    camera.tweenTo({ zoom: clamp(v.zoom * factor, region.minZoom, region.maxZoom) }, 220);
  };
  const resetView = () => camera.tweenTo({ cx: region.initialView.x, cy: region.initialView.y, zoom: region.initialView.zoom }, 600);

  const pickRegion = (id) => {
    if (id === activeId) return;
    setSelected(null); setEditingId(null); setPlacing(null);
    if (prefersReduced()) { setActiveId(id); return; }
    setFade(0);
    setTimeout(() => { setActiveId(id); setReady(false); }, 200);
    setTimeout(() => setFade(1), 420);
  };

  // ── admin handlers ──
  const toggleAdmin = uCB(() => {
    setAdminMode((m) => {
      const next = !m;
      setSelected(null); setEditingId(null); setPlacing(null);
      return next;
    });
  }, []);

  const requestAdminToggle = uCB(() => {
    if (!adminMode && window.TalonDB.authRequired && !window.TalonDB._token) {
      setShowAuthModal(true);
    } else {
      toggleAdmin();
    }
  }, [adminMode, toggleAdmin]);

  const handleRegionCreate = uCB(async (data) => {
    try {
      await window.TalonDB.createRegion(data);
      setApiRegions(window.TalonDB.readRegions());
    } catch (e) {
      console.warn('[App] createRegion failed:', e.message);
    }
  }, []);

  const handleRegionUpdate = uCB(async (id, patch) => {
    try {
      await window.TalonDB.updateRegion(id, patch);
      setApiRegions(window.TalonDB.readRegions());
    } catch (e) {
      console.warn('[App] updateRegion failed:', e.message);
    }
  }, []);

  const handleRegionDelete = uCB(async (id) => {
    try {
      await window.TalonDB.deleteRegion(id);
      setOverrides((o) => clearRegion(o, id));
      const remaining = window.TalonDB.readRegions();
      setApiRegions(remaining);
      if (activeId === id) {
        setActiveId(remaining[0]?.id || '');
        setSelected(null);
        setEditingId(null);
        setPlacing(null);
      }
    } catch (e) {
      console.warn('[App] deleteRegion failed:', e.message);
    }
  }, [activeId]);

  // keyboard shortcut: Shift+A (ignored while typing)
  uE(() => {
    const h = (e) => {
      const el = e.target;
      const typing = el && (el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.tagName === "SELECT" || el.isContentEditable);
      if (typing) return;
      if (e.key === "A" && e.shiftKey) { e.preventDefault(); requestAdminToggle(); }
    };
    window.addEventListener("keydown", h);
    return () => window.removeEventListener("keydown", h);
  }, [requestAdminToggle]);

  const startNew = () => { setEditingId(null); setPlacing({ mode: "new" }); };
  const pickOnMap = () => { if (editingId) setPlacing({ mode: "move" }); };

  const handlePlace = (frac) => {
    if (!placing) return;
    if (placing.mode === "new") {
      const p = blankPoi(frac);
      setOverrides((o) => withUpsert(o, region.id, p));
      setEditingId(p.id); setPlacing(null);
      camera.tweenTo({ cx: frac.x, cy: frac.y, zoom: Math.max(camera.view.zoom, 1.2) }, 450);
    } else if (placing.mode === "move" && editingId) {
      const target = editingId;
      setOverrides((o) => {
        const poi = computePois(region, o).find((p) => p.id === target);
        return poi ? withUpsert(o, region.id, { ...poi, coords: frac }) : o;
      });
      setPlacing(null);
    }
  };
  const handleMarkerDrag = (id, frac) => {
    setOverrides((o) => {
      const poi = computePois(region, o).find((p) => p.id === id);
      return poi ? withUpsert(o, region.id, { ...poi, coords: frac }) : o;
    });
  };
  const handleMarkerEdit = (id) => {
    setEditingId(id); setPlacing(null);
    const poi = livePois.find((p) => p.id === id);
    if (poi) camera.tweenTo({ cx: poi.coords.x, cy: poi.coords.y }, 400);
  };
  const changePoi = (updated) => setOverrides((o) => withUpsert(o, region.id, updated));
  const deletePoi = (poi) => {
    const base = isBasePoi(region, poi.id);
    setOverrides((o) => withDelete(o, region.id, poi.id, base));
    if (editingId === poi.id) { setEditingId(null); setPlacing(null); }
  };
  const revertPoi = (poi) => { setOverrides((o) => withRevert(o, region.id, poi.id)); };

  const handleImport = (newOverrides) => {
    setOverrides(newOverrides);
    // imported snapshot may have changed the active region's POIs; clear edit focus
    setEditingId(null); setPlacing(null); setSelected(null);
  };

  const sceneTweaks = { parallax: t.parallax, atmosphere: t.atmosphere, treatment: t.treatment, _accent: accent, _ink: INK };

  if (vw < 768) return <DesktopGuard />;

  if (setupMode) return (
    <SetupScreen
      authRequired={window.TalonDB.isAuthRequired()}
      onComplete={(region) => {
        setApiRegions([region]);
        setActiveId(region.id);
        setSetupMode(false);
      }}
    />
  );

  return (
    <div className={"talon-root" + (adminMode ? " is-admin" : "")}>
      <div className="scene-wrap" style={{ opacity: fade, transition: "opacity .2s linear" }}>
        {(ready && dbReady) ? (
          <MapScene
            region={liveRegion} camera={camera} vw={vw} vh={vh}
            leftInset={leftInset} rightInset={rightInset}
            selectedId={adminMode ? editingId : selected?.id} hoveredId={hovered}
            setHovered={setHovered} onSelect={selectPoi} onBackground={closePanel}
            admin={adminMode} placing={!!placing} onPlace={handlePlace}
            onMarkerDrag={handleMarkerDrag} onMarkerEdit={handleMarkerEdit}
            tweaks={sceneTweaks}
          />
        ) : <div className="map-scene" style={{ background: INK }} />}
      </div>

      <div className="vignette" aria-hidden="true" />
      {(!ready || !dbReady) && <LoadingMark />}

      <TopBar regions={regions} activeId={activeId} onPick={pickRegion} onHome={goHome} />

      <ZoomControls zoom={camera.view.zoom} min={region.minZoom} max={region.maxZoom} onZoom={zoomBtn} onReset={resetView} />

      {!adminMode && (
        <ArticlePanel poi={selected} region={liveRegion} regionName={region.name} accent={accent}
                      onClose={closePanel} onSelectRelated={selectPoi} />
      )}

      {adminMode && (
        <AdminPanel region={liveRegion} pois={livePois} regions={regions}
                    editingId={editingId} placing={!!placing}
                    isBaseFn={(id) => isBasePoi(region, id)}
                    editedFn={(id) => isEdited(region, overrides, id)}
                    onNew={startNew} onEdit={setEditingId} onChangePoi={changePoi}
                    onDelete={deletePoi} onRevert={revertPoi} onPickOnMap={pickOnMap}
                    onData={() => setShowData(true)} dbMode={dbMode} onClose={toggleAdmin}
                    onRegionCreate={handleRegionCreate}
                    onRegionUpdate={handleRegionUpdate}
                    onRegionDelete={handleRegionDelete} />
      )}

      <AdminLock active={adminMode} onToggle={requestAdminToggle} />

      {showData && (
        <DataModal regions={regions} overrides={overrides} activeRegion={region}
                   dbMode={dbMode} onImport={handleImport} onClose={() => setShowData(false)} />
      )}

      {showAuthModal && (
        <AuthModal
          onSuccess={(token) => { window.TalonDB.setToken(token); setShowAuthModal(false); toggleAdmin(); }}
          onClose={() => setShowAuthModal(false)}
        />
      )}

      <TweaksPanel>
        <TweakSection label="Atlas" />
        <TweakColor label="Accent glow" value={t.accent} options={Object.keys(ACCENTS)} onChange={(v) => setTweak("accent", v)} />
        <TweakRadio label="Mainland art" value={t.treatment} options={["painted", "placeholder"]} onChange={(v) => setTweak("treatment", v)} />
        <TweakSelect label="Title font" value={t.titleFont} options={Object.keys(FONTS)} onChange={(v) => setTweak("titleFont", v)} />
        <TweakSection label="Atmosphere" />
        <TweakSlider label="Parallax depth" value={t.parallax} min={0} max={1.5} step={0.1} onChange={(v) => setTweak("parallax", v)} />
        <TweakSlider label="Fog" value={t.atmosphere} min={0} max={1} step={0.05} onChange={(v) => setTweak("atmosphere", v)} />
      </TweaksPanel>
    </div>
  );
}

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