/* The Whole Table — shared components.
   Loaded as Babel JSX. Exports to window at the end. */

const { useState, useRef, useEffect } = React;

/* ---------- tiny primitives ---------- */

// $ price scale, e.g. price=3 -> "$$$" active, "$" muted remainder
function PriceTag({ price, className = "" }) {
  if (price == null) return null;
  return (
    <span className={"price-tag " + className} aria-label={`Price level ${price} of 4`}>
      {[1, 2, 3, 4].map((n) => (
        <span key={n} className={n <= price ? "on" : "off"}>$</span>
      ))}
    </span>
  );
}

// 5-segment rating bar (rectangles only — no decorative iconography)
function RatingBar({ value, max = 5 }) {
  return (
    <span className="rating-bar" role="img" aria-label={`${value} out of ${max}`}>
      {Array.from({ length: max }).map((_, i) => (
        <span key={i} className={"seg" + (i < value ? " fill" : "")} />
      ))}
    </span>
  );
}

// yes / no / partial access marker
function AccessMark({ state }) {
  // state: true | false | "partial"
  const map = {
    true: { c: "yes", g: "\u2713", t: "Yes" },
    false: { c: "no", g: "\u2715", t: "No" },
    partial: { c: "partial", g: "\u2013", t: "Partial" },
  };
  const m = map[String(state)] || map["partial"];
  return (
    <span className={"access-mark " + m.c} aria-label={m.t}>
      <span aria-hidden="true">{m.g}</span>
    </span>
  );
}

/* ---------- image slot ---------- */

function Slot({ id, ratio, className = "", placeholder = "Drop a photo", radius = 2, src }) {
  // wrapper keeps aspect ratio; <image-slot> fills it.
  // `src` is the real, deployed photo (a file under /images/food/) — the
  // drag-drop persistence only works inside the design tool, not on the site.
  return (
    <div className={"slot-wrap " + className} style={{ aspectRatio: ratio || "4 / 3" }}>
      <image-slot
        id={id}
        shape="rounded"
        radius={String(radius)}
        placeholder={placeholder}
        src={src || undefined}
        style={{ width: "100%", height: "100%", display: "block" }}
      ></image-slot>
    </div>
  );
}

/* ---------- dining profile ---------- */

function DiningProfile({ post, variant = "bars" }) {
  const meta = window.TWT_DATA.dimensionMeta;
  if (!post.dimensions) return null;
  return (
    <section className="profile" aria-label="Dining profile">
      <div className="profile-head">
        <span className="kicker">Dining Profile</span>
        <span className="profile-sub">Six dimensions, one honest read</span>
      </div>
      <dl className="profile-grid">
        {meta.map((d) => {
          const v = post.dimensions[d.key];
          return (
            <div className="profile-row" key={d.key}>
              <dt>{d.label}</dt>
              <dd>
                {variant === "dots" ? (
                  <span className="dots" role="img" aria-label={`${v} of 5`}>
                    {Array.from({ length: 5 }).map((_, i) => (
                      <span key={i} className={"dot" + (i < v ? " fill" : "")} />
                    ))}
                  </span>
                ) : variant === "number" ? (
                  <span className="prof-num">
                    <b>{v}</b>
                    <span className="den">/5</span>
                  </span>
                ) : (
                  <RatingBar value={v} />
                )}
              </dd>
            </div>
          );
        })}
      </dl>
    </section>
  );
}

/* ---------- access panel (detailed, plain language) ---------- */

function AccessPanel({ post }) {
  const a = post.access;
  if (!a) return null;
  const rows = [
    { label: "Step-free entry", state: a.stepFree },
    { label: "Accessible restroom", state: a.accessibleRestroom },
    { label: "Wheelchair seating", state: a.wheelchairSeating },
  ];
  return (
    <section className="access-panel" aria-label="Accessibility">
      <div className="access-head">
        <span className="kicker">Access</span>
        <span className="profile-sub">What to expect at the door and inside</span>
      </div>
      <ul className="access-checks">
        {rows.map((r) => (
          <li key={r.label}>
            <AccessMark state={r.state} />
            <span className="ac-label">{r.label}</span>
          </li>
        ))}
      </ul>
      <dl className="access-notes">
        <div>
          <dt>Getting there</dt>
          <dd>{a.transit}</dd>
        </div>
        {a.parking ? (
          <div>
            <dt>Parking</dt>
            <dd>{a.parking}</dd>
          </div>
        ) : null}
        <div>
          <dt>In the room</dt>
          <dd>{a.notes}</dd>
        </div>
        {a.sensory ? (
          <div>
            <dt>Sensory load</dt>
            <dd>{a.sensory}</dd>
          </div>
        ) : null}
      </dl>
    </section>
  );
}

// compact access chips for cards/listings
function AccessChips({ post }) {
  const a = post.access;
  if (!a) return null;
  const chips = [
    { t: "Step-free", on: a.stepFree },
    { t: "Accessible WC", on: a.accessibleRestroom },
    { t: "WC seating", on: a.wheelchairSeating },
  ];
  return (
    <div className="access-chips" aria-label="Accessibility at a glance">
      {chips.map((c) => (
        <span key={c.t} className={"chip" + (c.on ? " on" : " off")}>
          <span aria-hidden="true" className="chip-mark">{c.on ? "\u2713" : "\u2715"}</span>
          {c.t}
        </span>
      ))}
    </div>
  );
}

/* ---------- post card (listing) ---------- */

function PostCard({ post, onOpen, layout = "row" }) {
  return (
    <article className={"card card-" + layout} onClick={() => onOpen(post.id)} tabIndex={0}
      onKeyDown={(e) => { if (e.key === "Enter") onOpen(post.id); }} role="link"
      aria-label={`Open ${post.title}`}>
      <div className="card-media">
        <Slot id={post.slot + "-card"} ratio={layout === "row" ? "5 / 4" : "3 / 2"}
          placeholder={`${post.city} photo`} src={post.photo} />
        {post.price != null ? <span className="card-price"><PriceTag price={post.price} /></span> : null}
      </div>
      <div className="card-body">
        <div className="card-meta">
          <span className="loc">{post.restaurant ? post.restaurant + " \u00b7 " : ""}{post.city}{post.country ? ", " + post.country : ""}</span>
          <span className="dot-sep">{"\u00b7"}</span>
          <span>{post.cuisine}</span>
        </div>
        <h3 className="card-title">{post.title}</h3>
        <p className="card-excerpt">{post.excerpt}</p>
        <div className="card-foot">
          <AccessChips post={post} />
          <span className="readmore">Read {"\u2192"}</span>
        </div>
      </div>
    </article>
  );
}

/* ---------- small UI ---------- */

function Wordmark({ onClick, size = "md" }) {
  return (
    <button className={"wordmark wm-" + size} onClick={onClick} aria-label="The Whole Table — home">
      <span className="wm-the">The</span>
      <span className="wm-whole">Whole</span>
      <span className="wm-table">Table</span>
    </button>
  );
}

function Pill({ children, onClick, active, as = "button", ...rest }) {
  const Tag = as;
  return (
    <Tag className={"pill" + (active ? " active" : "")} onClick={onClick} {...rest}>
      {children}
    </Tag>
  );
}

/* ---------- City globe: wireframe orthographic globe, searchable cities ---------- */
function CityGlobe({ posts, onOpen }) {
  const coordsMap = (window.TWT_DATA && window.TWT_DATA.cityCoords) || {};
  const cities = React.useMemo(() => {
    const m = new Map();
    posts
      .filter((p) => !p.isEssay && p.city && coordsMap[p.city])
      .forEach((p) => {
        if (!m.has(p.city)) m.set(p.city, { name: p.city, lat: coordsMap[p.city].lat, lon: coordsMap[p.city].lon, posts: [] });
        m.get(p.city).posts.push(p);
      });
    return [...m.values()];
  }, [posts]);

  const [selected, setSelected] = React.useState(null);
  const [query, setQuery] = React.useState("");
  const [noMatch, setNoMatch] = React.useState(null);

  const canvasRef = React.useRef(null);
  const rot = React.useRef({ lam: -123, phi: 35 });
  const target = React.useRef(null);
  const dragRef = React.useRef(null);
  const dotsRef = React.useRef([]);
  const selRef = React.useRef(null);
  React.useEffect(() => { selRef.current = selected; }, [selected]);

  const reduced = () =>
    document.documentElement.classList.contains("reduce") ||
    window.matchMedia("(prefers-reduced-motion: reduce)").matches;

  function selectCity(name) {
    const c = cities.find((x) => x.name.toLowerCase() === String(name).toLowerCase().trim());
    if (!c) { setNoMatch(String(name).trim()); return; }
    setNoMatch(null);
    setSelected(c.name);
    setQuery("");
    if (reduced()) { rot.current = { lam: c.lon, phi: c.lat }; target.current = null; }
    else target.current = { lam: c.lon, phi: c.lat };
  }

  React.useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    const D = Math.PI / 180;
    let raf = 0, w = 0, h = 0, R = 0, dpr = 1;

    function resize() {
      const box = canvas.parentElement.getBoundingClientRect();
      const size = Math.max(240, Math.min(box.width, 420));
      dpr = window.devicePixelRatio || 1;
      canvas.style.width = size + "px";
      canvas.style.height = size + "px";
      canvas.width = size * dpr;
      canvas.height = size * dpr;
      w = h = size;
      R = size / 2 - 16;
    }
    resize();
    window.addEventListener("resize", resize);

    function project(lat, lon) {
      const phi = lat * D, lam = (lon - rot.current.lam) * D, phi0 = rot.current.phi * D;
      const cosc = Math.sin(phi0) * Math.sin(phi) + Math.cos(phi0) * Math.cos(phi) * Math.cos(lam);
      const x = Math.cos(phi) * Math.sin(lam);
      const y = Math.cos(phi0) * Math.sin(phi) - Math.sin(phi0) * Math.cos(phi) * Math.cos(lam);
      return { x: w / 2 + R * x, y: h / 2 - R * y, visible: cosc > 0.015 };
    }

    function arc(fromLat, fromLon, toLat, toLon, step) {
      ctx.beginPath();
      let pen = false;
      const n = Math.ceil(Math.max(Math.abs(toLat - fromLat), Math.abs(toLon - fromLon)) / step);
      for (let i = 0; i <= n; i++) {
        const lat = fromLat + ((toLat - fromLat) * i) / n;
        const lon = fromLon + ((toLon - fromLon) * i) / n;
        const p = project(lat, lon);
        if (p.visible) { if (pen) ctx.lineTo(p.x, p.y); else ctx.moveTo(p.x, p.y); pen = true; }
        else pen = false;
      }
      ctx.stroke();
    }

    function draw() {
      const cs = getComputedStyle(document.documentElement);
      const fg = cs.getPropertyValue("--fg").trim();
      const line = cs.getPropertyValue("--line").trim();
      const lineStrong = cs.getPropertyValue("--line-strong").trim();
      const muted = cs.getPropertyValue("--muted").trim();
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      ctx.clearRect(0, 0, w, h);

      ctx.beginPath();
      ctx.arc(w / 2, h / 2, R, 0, Math.PI * 2);
      ctx.strokeStyle = lineStrong;
      ctx.lineWidth = 1.2;
      ctx.stroke();

      ctx.lineWidth = 0.7;
      ctx.strokeStyle = line;
      for (let lon = -180; lon < 180; lon += 20) arc(-88, lon, 88, lon, 4);
      for (let lat = -60; lat <= 60; lat += 20) arc(lat, -180, lat, 180, 4);

      dotsRef.current = [];
      ctx.font = "10px " + (cs.getPropertyValue("--mono").trim() || "monospace");
      const visible = [];
      cities.forEach((c) => {
        const p = project(c.lat, c.lon);
        if (!p.visible) return;
        const sel = selRef.current === c.name;
        visible.push({ c, p, sel });
        dotsRef.current.push({ x: p.x, y: p.y, name: c.name });
        ctx.beginPath();
        ctx.arc(p.x, p.y, sel ? 5 : 3.5, 0, Math.PI * 2);
        ctx.fillStyle = fg;
        ctx.fill();
        if (sel) {
          ctx.beginPath();
          ctx.arc(p.x, p.y, 10, 0, Math.PI * 2);
          ctx.strokeStyle = fg;
          ctx.lineWidth = 1;
          ctx.stroke();
          ctx.lineWidth = 0.7;
        }
      });
      // labels: selected city first, then others if they don't collide
      const placed = [];
      visible
        .sort((a, b) => (b.sel ? 1 : 0) - (a.sel ? 1 : 0))
        .forEach(({ c, p, sel }) => {
          const collides = placed.some((q) => Math.abs(q.x - p.x) < 110 && Math.abs(q.y - p.y) < 16);
          if (collides && !sel) return;
          placed.push({ x: p.x, y: p.y });
          ctx.fillStyle = sel ? fg : muted;
          ctx.fillText(c.name.toUpperCase(), p.x + 13, p.y + 3);
        });
    }

    function tick() {
      const t = target.current;
      if (t) {
        const dl = ((t.lam - rot.current.lam + 540) % 360) - 180;
        const dp = t.phi - rot.current.phi;
        rot.current.lam += dl * 0.07;
        rot.current.phi += dp * 0.07;
        if (Math.abs(dl) < 0.15 && Math.abs(dp) < 0.15) target.current = null;
      } else if (!dragRef.current && !selRef.current && !reduced()) {
        rot.current.lam += 0.06; // idle drift until a city is chosen
      }
      draw();
      raf = requestAnimationFrame(tick);
    }
    raf = requestAnimationFrame(tick);

    function onDown(e) {
      canvas.setPointerCapture(e.pointerId);
      dragRef.current = { x: e.clientX, y: e.clientY, lam: rot.current.lam, phi: rot.current.phi, moved: false };
    }
    function onMove(e) {
      const d = dragRef.current;
      if (!d) return;
      const dx = e.clientX - d.x, dy = e.clientY - d.y;
      if (Math.abs(dx) + Math.abs(dy) > 4) d.moved = true;
      if (d.moved) {
        target.current = null;
        rot.current.lam = d.lam - dx * 0.45;
        rot.current.phi = Math.max(-85, Math.min(85, d.phi + dy * 0.45));
      }
    }
    function onUp(e) {
      const d = dragRef.current;
      dragRef.current = null;
      if (d && !d.moved) {
        const r = canvas.getBoundingClientRect();
        const x = e.clientX - r.left, y = e.clientY - r.top;
        const hit = dotsRef.current.find((pt) => Math.hypot(pt.x - x, pt.y - y) < 16);
        if (hit) selectCity(hit.name);
      }
    }
    canvas.addEventListener("pointerdown", onDown);
    canvas.addEventListener("pointermove", onMove);
    canvas.addEventListener("pointerup", onUp);
    canvas.addEventListener("pointercancel", onUp);

    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener("resize", resize);
      canvas.removeEventListener("pointerdown", onDown);
      canvas.removeEventListener("pointermove", onMove);
      canvas.removeEventListener("pointerup", onUp);
      canvas.removeEventListener("pointercancel", onUp);
    };
  }, [cities]);

  const sel = cities.find((c) => c.name === selected);

  return (
    <div className="globe-wrap">
      <div className="globe-stage">
        <canvas
          ref={canvasRef}
          role="img"
          aria-label="Interactive globe of cities with journal entries. Drag to spin; use the city list to navigate."
        />
      </div>
      <div className="globe-side">
        <form
          className="globe-search"
          onSubmit={(e) => { e.preventDefault(); if (query.trim()) selectCity(query); }}
        >
          <input
            value={query}
            onChange={(e) => { setQuery(e.target.value); setNoMatch(null); }}
            placeholder="Search a city…"
            aria-label="Search a city"
          />
        </form>
        <div className="globe-cities" role="list">
          {cities.map((c) => (
            <button
              key={c.name}
              role="listitem"
              className={"city-chip" + (selected === c.name ? " on" : "")}
              onClick={() => selectCity(c.name)}
            >
              {c.name} <span>{"(" + c.posts.length + ")"}</span>
            </button>
          ))}
        </div>
        {noMatch && (
          <p className="globe-empty">
            No entries from {"“"}{noMatch}{"”"} yet {"—"} the table hasn{"’"}t traveled there. Yet.
          </p>
        )}
        {sel ? (
          <div className="globe-results">
            {sel.posts.map((p) => (
              <button key={p.id} className="globe-post" onClick={() => onOpen(p.id)}>
                <span className="gp-title">{p.title}</span>
                <span className="gp-meta">{p.restaurant} {"·"} {p.cuisine}</span>
              </button>
            ))}
          </div>
        ) : (
          !noMatch && <p className="globe-hint">Spin the globe, tap a dot, or pick a city.</p>
        )}
      </div>
    </div>
  );
}

Object.assign(window, {
  PriceTag, RatingBar, AccessMark, Slot, DiningProfile,
  AccessPanel, AccessChips, PostCard, Wordmark, Pill, CityGlobe,
});
