/* global React, window */
const { useState, useRef, useEffect } = React;

/* =========================================================
   CONTOURS — hairline cartographic field.
   Deterministic topographic loops + a route + survey ticks.
   Editorial, not illustrative: 1px strokes, current color.
   ========================================================= */
function seeded(seed) {
  let s = seed % 2147483647; if (s <= 0) s += 2147483646;
  return () => (s = (s * 16807) % 2147483647) / 2147483647;
}
function contourPath(cx, cy, rx, ry, wobble, pts, rnd) {
  const offs = Array.from({ length: pts }, () => 1 + (rnd() - 0.5) * 2 * wobble);
  let d = "";
  const coords = [];
  for (let i = 0; i < pts; i++) {
    const a = (i / pts) * Math.PI * 2;
    coords.push([cx + Math.cos(a) * rx * offs[i], cy + Math.sin(a) * ry * offs[i]]);
  }
  for (let i = 0; i <= pts; i++) {
    const p0 = coords[(i - 1 + pts) % pts];
    const p1 = coords[i % pts];
    const p2 = coords[(i + 1) % pts];
    const p3 = coords[(i + 2) % pts];
    if (i === 0) { d += `M ${p1[0].toFixed(1)} ${p1[1].toFixed(1)} `; continue; }
    const c1x = p0[0] + (p1[0] - p0[0]) ; // fallback
    // Catmull-Rom -> bezier
    const b1x = p1[0] + (p2[0] - p0[0]) / 6, b1y = p1[1] + (p2[1] - p0[1]) / 6;
    const b2x = p2[0] - (p3[0] - p1[0]) / 6, b2y = p2[1] - (p3[1] - p1[1]) / 6;
    d += `C ${b1x.toFixed(1)} ${b1y.toFixed(1)} ${b2x.toFixed(1)} ${b2y.toFixed(1)} ${p2[0].toFixed(1)} ${p2[1].toFixed(1)} `;
  }
  return d + "Z";
}
function Contours({ density = "faint", seed = 7, route = true }) {
  if (density === "off") return <div className="cartofield cartofield--off" />;
  const W = 1280, H = 800;
  const rnd = seeded(seed * 131 + 17);
  const cx = W * (0.5 + rnd() * 0.4), cy = H * (0.42 + rnd() * 0.4);
  const rings = density === "present" ? 7 : 5;
  const loops = [];
  for (let i = 1; i <= rings; i++) {
    const t = i / rings;
    loops.push(
      <path key={i} d={contourPath(cx, cy, 60 + t * 520, 40 + t * 360, 0.06 + t * 0.05, 14, seeded(seed * 91 + i * 7))}
        fill="none" stroke="currentColor" strokeWidth="1" vectorEffect="non-scaling-stroke" />
    );
  }
  // a second, smaller basin
  const cx2 = W * (0.62 + rnd() * 0.28), cy2 = H * (0.5 + rnd() * 0.35);
  for (let i = 1; i <= Math.round(rings * 0.6); i++) {
    const t = i / (rings * 0.6);
    loops.push(
      <path key={"b" + i} d={contourPath(cx2, cy2, 30 + t * 180, 24 + t * 150, 0.07, 12, seeded(seed * 53 + i * 13))}
        fill="none" stroke="currentColor" strokeWidth="1" vectorEffect="non-scaling-stroke" />
    );
  }
  // route polyline across the field
  const routeD = `M ${(-20)} ${H * 0.7} C ${W * 0.2} ${H * 0.55}, ${W * 0.3} ${H * 0.3}, ${cx} ${cy} S ${W * 0.8} ${H * 0.2}, ${W + 20} ${H * 0.35}`;
  return (
    <div className={"cartofield cartofield--" + density} aria-hidden="true">
      <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="xMidYMid slice">
        {loops}
        {route && <path d={routeD} fill="none" stroke="currentColor" strokeWidth="1"
          strokeDasharray="2 6" vectorEffect="non-scaling-stroke" opacity="0.9" />}
        {route && <circle cx={cx} cy={cy} r="4" fill="currentColor" />}
        {/* edge survey ticks */}
        {Array.from({ length: 24 }).map((_, i) => (
          <line key={"t" + i} x1={(i / 24) * W} y1="0" x2={(i / 24) * W} y2={i % 4 === 0 ? 10 : 5}
            stroke="currentColor" strokeWidth="1" vectorEffect="non-scaling-stroke" />
        ))}
      </svg>
    </div>
  );
}

/* =========================================================
   TRAIL — fixed bottom expedition path + narrative progress.
   ========================================================= */
const NARR = [
  "The expedition is about to set out.",
  "We have made the first observations.",
  "The challenge has begun to take shape.",
  "We have looked toward the horizon.",
  "Hidden beliefs have surfaced.",
  "We have reached the river.",
  "We are climbing toward the pass.",
  "The sky has opened. The pattern is visible.",
];
function Trail({ locations, currentIndex, maxReached, onJump }) {
  const covered = Math.min(maxReached, locations.length - 1);
  return (
    <nav className="trail" aria-label="Expedition trail">
      <div className="trail__inner">
        <div className="trail__narr">
          <span className="muted">Progress&nbsp;&nbsp;·&nbsp;&nbsp;</span>
          {currentIndex < 0
            ? "The expedition is about to set out."
            : NARR[currentIndex + 1] || NARR[NARR.length - 1]}
        </div>
        <div className="trail__path">
          <div className="trail__line" />
          {locations.map((loc, i) => {
            const state = i === currentIndex ? "current" : i <= maxReached ? "done" : "future";
            return (
              <button key={loc.id}
                className={"trail__node " + state}
                onClick={() => onJump(i)}
                title={loc.name}>
                <span className="trail__tick" />
                <span className="trail__label">{loc.roman}<span className="ph"> · {loc.phase}</span></span>
              </button>
            );
          })}
        </div>
      </div>
    </nav>
  );
}

/* =========================================================
   ARTIFACT — evidence object. Pick up (select), highlight a
   phrase, see provenance. No shadow; selection = offset rule.
   ========================================================= */
function Artifact({ note, selected, onToggle, highlighted, onHighlight, grab = true, showProv = true, extraProv }) {
  const isQuote = note.kind === "quote";
  return (
    <article
      className={"artifact" + (note.kind === "stat" ? " artifact--bone" : "") + (grab ? " is-grab" : "") + (selected ? " is-selected" : "")}
      onClick={onToggle}>
      <span className="artifact__pin" />
      <div className="artifact__meta">
        <span className="src">{note.src}</span>
        <span>{note.coord}</span>
      </div>
      <div className={"artifact__body" + (isQuote ? " is-quote" : "")}>
        {onHighlight ? (
          <span className={"hl" + (highlighted ? " on" : "")}
            onClick={(e) => { e.stopPropagation(); onHighlight(); }}>
            {isQuote ? <>“{note.body}”</> : note.body}
          </span>
        ) : (
          isQuote ? <>“{note.body}”</> : note.body
        )}
      </div>
      {showProv && (
        <div className="artifact__prov">
          {(extraProv || note.prov || []).map((p, i) => (
            <span key={i} className={p.includes("here") || p.includes("HERE") ? "here" : ""}>
              {i > 0 && "→ "}{p}
            </span>
          ))}
          {selected && <span className="here">→ on the wall</span>}
        </div>
      )}
    </article>
  );
}

/* =========================================================
   FRICTION GATE — required, sober choice before a location.
   ========================================================= */
function FrictionGate({ gate, onResolve }) {
  const [chosen, setChosen] = useState(-1);
  return (
    <div className="gate" role="dialog" aria-modal="true">
      <div className="gate__inner">
        <div className="gate__eyebrow">{gate.eyebrow}</div>
        <h2 className="gate__q">{gate.q}</h2>
        <div className="gate__opts">
          {gate.opts.map((o, i) => (
            <button key={i} className={"gate__opt" + (chosen === i ? " chosen" : "")}
              onClick={() => setChosen(i)}>
              <span className="mk">{chosen === i ? "●" : String(i + 1).padStart(2, "0")}</span>
              <span className="tx">{o}</span>
            </button>
          ))}
        </div>
        <div className="row" style={{ marginTop: "var(--sp-6)", justifyContent: "space-between" }}>
          <span className="coord">{chosen < 0 ? "A choice is required to continue." : "Recorded. This commitment travels with you."}</span>
          <button className="btn btn--primary btn--lg" disabled={chosen < 0}
            onClick={() => onResolve(gate.opts[chosen])}>
            Continue <span className="arrow">→</span>
          </button>
        </div>
      </div>
    </div>
  );
}

/* =========================================================
   Small shared bits
   ========================================================= */
function LocationHead({ loc, children }) {
  return (
    <header className="loc-head fade-rise">
      <div className="loc-head__index">
        <span className="signal">{loc.roman}</span>
        <span>{loc.coord}</span>
      </div>
      <h1 className="loc-head__title">{loc.name}</h1>
      <div className="loc-head__phase">{loc.phase}</div>
      <p className="loc-head__prompt">{loc.prompt}</p>
      {children}
    </header>
  );
}

function LayerTag({ n, children }) {
  return <div className="layer-tag"><span className="n">{n}</span><span>{children}</span></div>;
}

function NextButton({ label = "Continue the expedition", onClick, variant = "primary" }) {
  return (
    <button className={"btn btn--" + variant + " btn--lg"} onClick={onClick}>
      {label} <span className="arrow">→</span>
    </button>
  );
}

Object.assign(window, { Contours, Trail, Artifact, FrictionGate, LocationHead, LayerTag, NextButton });
