// talon-scene.jsx — MapScene, parallax layers, POI markers, camera math.
const { useState, useRef, useEffect, useCallback } = React;

const PANEL_W = 420;
const prefersReduced = () =>
  window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;

// ── camera math ────────────────────────────────────────────────
// view = { cx, cy, zoom }  (cx,cy = world-center fraction 0..1)
// anchor = screen point the world-center should sit at
function layerTranslate(region, view, anchor, depth) {
  const W = region.size.width, H = region.size.height;
  return {
    tx: anchor.x - view.cx * W * view.zoom * depth,
    ty: anchor.y - view.cy * H * view.zoom * depth,
  };
}
function layerTransform(region, view, anchor, depth) {
  const { tx, ty } = layerTranslate(region, view, anchor, depth);
  return `translate(${tx}px, ${ty}px) scale(${view.zoom})`;
}
function getAnchor(vw, vh, leftInset, rightInset) {
  const l = leftInset || 0, r = rightInset || 0;
  return { x: l + (vw - l - r) / 2, y: vh / 2 };
}
const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
const easeInOutCubic = (t) => (t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2);

// ── tween hook ─────────────────────────────────────────────────
function useCamera(region) {
  const [view, setView] = useState(region.initialView ? {
    cx: region.initialView.x, cy: region.initialView.y, zoom: region.initialView.zoom,
  } : { cx: 0.5, cy: 0.5, zoom: 1 });
  const raf = useRef(0);

  // keep a live ref so drag/wheel read latest without stale closures
  const viewRef = useRef(view);
  useEffect(() => { viewRef.current = view; }, [view]);

  const stop = () => { if (raf.current) cancelAnimationFrame(raf.current); raf.current = 0; };

  const tweenTo = useCallback((target, duration = 700) => {
    stop();
    const from = { ...viewRef.current };
    const to = {
      cx: clamp(target.cx ?? from.cx, 0, 1),
      cy: clamp(target.cy ?? from.cy, 0, 1),
      zoom: clamp(target.zoom ?? from.zoom, region.minZoom, region.maxZoom),
    };
    if (prefersReduced() || duration === 0) { setView(to); viewRef.current = to; return; }
    const t0 = performance.now();
    const step = (now) => {
      const p = Math.min(1, (now - t0) / duration);
      const e = easeInOutCubic(p);
      const v = {
        cx: from.cx + (to.cx - from.cx) * e,
        cy: from.cy + (to.cy - from.cy) * e,
        zoom: from.zoom + (to.zoom - from.zoom) * e,
      };
      viewRef.current = v; setView(v);
      if (p < 1) raf.current = requestAnimationFrame(step); else raf.current = 0;
    };
    raf.current = requestAnimationFrame(step);
  }, [region]);

  // reset on region change
  useEffect(() => {
    stop();
    const iv = region.initialView
      ? { cx: region.initialView.x, cy: region.initialView.y, zoom: region.initialView.zoom }
      : { cx: 0.5, cy: 0.5, zoom: 1 };
    setView(iv); viewRef.current = iv;
    return stop;
  }, [region.id]);

  return { view, setView, viewRef, tweenTo, stop };
}

// ── POI marker ─────────────────────────────────────────────────
function PoiMarker({ poi, screen, state, farZoom, accent, admin, onHover, onSelect, onMarkerDown }) {
  const Icon = iconFor(poi.category);
  const selected = state === "selected";
  const hovered = state === "hovered";
  const baseScale = farZoom ? 0.8 : 1;
  const scale = selected ? baseScale * 1.25 : hovered ? baseScale * 1.15 : baseScale;
  const showLabel = (hovered || selected) && !farZoom;

  return (
    <button
      className={"poi-marker" + (admin ? " is-admin" : "")}
      style={{
        left: screen.x, top: screen.y,
        transform: `translate(-50%, -100%) scale(${scale})`,
        ["--accent"]: accent.base,
        ["--accent-bright"]: accent.bright,
        ["--accent-glow"]: accent.glow,
        zIndex: selected ? 12 : hovered ? 11 : 4,
        cursor: admin ? "grab" : "pointer",
      }}
      onMouseEnter={() => onHover(poi.id)}
      onMouseLeave={() => onHover(null)}
      onMouseDown={admin ? (e) => { e.stopPropagation(); e.preventDefault(); onMarkerDown(poi, e); } : undefined}
      onClick={(e) => { e.stopPropagation(); if (!admin) onSelect(poi); }}
      aria-label={poi.title}
      data-poi={poi.id}
    >
      {showLabel && <span className="poi-label">{poi.title}</span>}
      {selected && <span className="poi-ring" aria-hidden="true" />}
      <span className={"poi-badge" + (selected ? " is-selected" : "")}>
        <Icon size={16} />
      </span>
    </button>
  );
}

// ── parallax atmosphere layers (CSS-painted) ───────────────────
function AtmosphereDeep({ style }) {
  return <div className="layer-deep" style={style} aria-hidden="true" />;
}
function AtmosphereFog({ style }) {
  return <div className="layer-fog" style={style} aria-hidden="true" />;
}

// ── MapScene ───────────────────────────────────────────────────
function MapScene({ region, camera, vw, vh, leftInset, rightInset, selectedId, hoveredId,
                    setHovered, onSelect, onBackground, admin, placing, onPlace,
                    onMarkerDrag, onMarkerEdit, tweaks }) {
  const { view, setView, viewRef } = camera;
  const anchor = getAnchor(vw, vh, leftInset, rightInset);
  const drag = useRef(null);
  const mdrag = useRef(null);
  const [grabbing, setGrabbing] = useState(false);
  const moved = useRef(false);

  const startMarkerDrag = (poi, e) => {
    mdrag.current = { id: poi.id, sx: e.clientX, sy: e.clientY, moved: false };
  };

  const parallax = tweaks.parallax; // 0..1 intensity
  const dDeep = 1 - 0.12 * parallax;
  const dFog = 1 + 0.14 * parallax;

  const W = region.size.width, H = region.size.height;
  const terrain = layerTranslate(region, view, anchor, 1);

  // world fraction under a screen point
  const fracUnder = (sx, sy) => ({
    fx: view.cx + (sx - anchor.x) / (W * view.zoom),
    fy: view.cy + (sy - anchor.y) / (H * view.zoom),
  });

  const onDown = (e) => {
    if (e.button !== 0) return;
    drag.current = { sx: e.clientX, sy: e.clientY, cx: viewRef.current.cx, cy: viewRef.current.cy };
    moved.current = false;
    setGrabbing(true);
  };
  const onMove = (e) => {
    if (mdrag.current) {
      const dx = e.clientX - mdrag.current.sx, dy = e.clientY - mdrag.current.sy;
      if (Math.abs(dx) + Math.abs(dy) > 3) mdrag.current.moved = true;
      const { fx, fy } = fracUnder(e.clientX, e.clientY);
      onMarkerDrag(mdrag.current.id, { x: clamp(+fx.toFixed(4), 0, 1), y: clamp(+fy.toFixed(4), 0, 1) });
      return;
    }
    if (!drag.current) return;
    const dx = e.clientX - drag.current.sx, dy = e.clientY - drag.current.sy;
    if (Math.abs(dx) + Math.abs(dy) > 3) moved.current = true;
    const z = viewRef.current.zoom;
    const ncx = clamp(drag.current.cx - dx / (W * z), 0, 1);
    const ncy = clamp(drag.current.cy - dy / (H * z), 0, 1);
    const v = { cx: ncx, cy: ncy, zoom: z };
    viewRef.current = v; setView(v);
  };
  const endDrag = () => {
    if (mdrag.current) {
      const md = mdrag.current; mdrag.current = null;
      if (!md.moved && onMarkerEdit) onMarkerEdit(md.id);
      return;
    }
    drag.current = null; setGrabbing(false);
  };

  const onWheel = (e) => {
    e.preventDefault();
    const v = viewRef.current;
    const factor = Math.exp(-e.deltaY * 0.0012);
    const nz = clamp(v.zoom * factor, region.minZoom, region.maxZoom);
    if (nz === v.zoom) return;
    const { fx, fy } = fracUnder(e.clientX, e.clientY);
    const ncx = clamp(fx - (e.clientX - anchor.x) / (W * nz), 0, 1);
    const ncy = clamp(fy - (e.clientY - anchor.y) / (H * nz), 0, 1);
    const nv = { cx: ncx, cy: ncy, zoom: nz };
    viewRef.current = nv; setView(nv);
  };

  useEffect(() => {
    window.addEventListener("mousemove", onMove);
    window.addEventListener("mouseup", endDrag);
    return () => { window.removeEventListener("mousemove", onMove); window.removeEventListener("mouseup", endDrag); };
  });

  const farZoom = view.zoom < 0.7;
  const accent = tweaks._accent;
  const showPainted = region.treatment === "painted" && tweaks.treatment !== "placeholder";

  return (
    <div
      className={"map-scene" + (grabbing ? " is-grabbing" : "") + (placing ? " is-placing" : "")}
      style={{ background: tweaks._ink }}
      onMouseDown={onDown}
      onWheel={onWheel}
      onClick={(e) => {
        if (moved.current) return;
        if (admin && placing) {
          const { fx, fy } = fracUnder(e.clientX, e.clientY);
          onPlace({ x: clamp(+fx.toFixed(4), 0, 1), y: clamp(+fy.toFixed(4), 0, 1) });
        } else { onBackground(); }
      }}
    >
      {/* deep atmosphere (parallax slow) */}
      <AtmosphereDeep style={{
        width: W, height: H, transformOrigin: "0 0",
        transform: layerTransform(region, view, anchor, dDeep),
      }} />

      {/* terrain layer */}
      <div className="layer-terrain" style={{
        width: W, height: H, transformOrigin: "0 0",
        transform: layerTransform(region, view, anchor, 1),
      }}>
        {showPainted ? (
          <img src={region.image} className="terrain-img" alt={region.name + " map"}
               draggable="false" onError={(e) => { e.target.style.display = "none"; }} />
        ) : (
          <div className="terrain-placeholder">
            <span className="ph-label">painted&nbsp;map&nbsp;layer</span>
            <span className="ph-sub">drop {region.name} art here · {W}×{H}</span>
          </div>
        )}
      </div>

      {/* foreground fog (parallax fast) */}
      <AtmosphereFog style={{
        width: W, height: H, transformOrigin: "0 0",
        transform: layerTransform(region, view, anchor, dFog),
        opacity: tweaks.atmosphere,
      }} />

      {/* POI markers — screen-space so they never scale with zoom */}
      <div className="poi-layer">
        {region.pois.map((poi) => {
          const sx = terrain.tx + poi.coords.x * W * view.zoom;
          const sy = terrain.ty + poi.coords.y * H * view.zoom;
          if (sx < -80 || sx > vw + 80 || sy < -80 || sy > vh + 80) return null;
          const st = selectedId === poi.id ? "selected" : hoveredId === poi.id ? "hovered" : "idle";
          return (
            <PoiMarker key={poi.id} poi={poi} screen={{ x: sx, y: sy }} state={st}
                       farZoom={farZoom} accent={accent} admin={admin}
                       onHover={setHovered} onSelect={onSelect}
                       onMarkerDown={startMarkerDrag} />
          );
        })}
      </div>
    </div>
  );
}

Object.assign(window, { MapScene, useCamera, getAnchor, PANEL_W, clamp, prefersReduced });
