<script lang="ts">
  import { onMount } from "svelte";
  import {
    getStatus,
    toggleLayout,
    startLayoutDrag,
    endLayoutDrag,
    startLayoutResize,
    endLayoutResize,
    type Status,
  } from "$lib/api";
  import {
    loadManifest,
    childrenOf,
    imageUrl,
    isLegend,
    rotationBlocked,
    type LayoutManifest,
  } from "$lib/layouts";

  let manifest = $state<LayoutManifest>({});
  let status = $state<Status | null>(null);

  // Decision-tree cursor + per-zone view state (reset whenever the zone changes).
  let path = $state("");
  let excluded = $state<string[]>([]);
  let rot = $state(0); // degrees, multiples of 90
  let flipH = $state(false);
  let flipV = $state(false);

  let lastArea = "";
  function syncArea(s: Status | null) {
    const id = s?.area_id ?? "";
    if (id !== lastArea) {
      lastArea = id;
      path = "";
      excluded = [];
      rot = 0;
      flipH = false;
      flipV = false;
    }
  }

  const areaId = $derived(status?.area_id ?? "");
  const areaName = $derived(status?.area_name || areaId || "—");
  const paths = $derived(manifest[areaId] ?? []);
  const hasZone = $derived(paths.length > 0);
  // Real layout candidates at the current branch (legends are pulled out below).
  const candidates = $derived(
    childrenOf(paths, path).filter((p) => !excluded.includes(p) && !isLegend(p)),
  );
  // The zone's legend pages ("x", "x_x", …), ordered by depth. A legend at depth
  // d documents the layout candidates at hierarchy level d (verified: e.g. g1_8
  // ~x explains the level-1 symbols, ~x_x the level-2 ones).
  const legendPaths = $derived(
    paths
      .filter((p) => isLegend(p))
      .sort((a, b) => a.split("_").length - b.split("_").length),
  );
  // The single legend to show for the current level: the i-th legend at level i,
  // clamped to the last one (so it carries forward when a level has none of its
  // own), and the last legend once the layout is identified.
  const activeLegend = $derived.by(() => {
    if (legendPaths.length === 0) return null;
    const pathDepth = path === "" ? 0 : path.split("_").length;
    const idx = resolved
      ? legendPaths.length - 1
      : Math.min(pathDepth, legendPaths.length - 1);
    return legendPaths[idx];
  });
  const resolved = $derived(path !== "" && childrenOf(paths, path).length === 0);
  const blocked = $derived(rotationBlocked(areaId));
  const transform = $derived(
    `rotate(${rot}deg) scaleX(${flipH ? -1 : 1}) scaleY(${flipV ? -1 : 1})`,
  );
  const crumbs = $derived(path === "" ? [] : path.split("_"));

  function pick(p: string) {
    path = p;
  }
  function back() {
    const seg = path.split("_");
    seg.pop();
    path = seg.join("_");
  }
  function exclude(p: string) {
    if (!excluded.includes(p)) excluded = [...excluded, p];
  }
  function rotate(dir: number) {
    rot = (((rot + dir * 90) % 360) + 360) % 360;
  }
  function resetOrientation() {
    rot = 0;
    flipH = false;
    flipV = false;
  }

  onMount(() => {
    let alive = true;
    // The layout window is created hidden, and its `listen` event subscription
    // never resolves in that state (unlike the always-visible overlay), so live
    // status events don't arrive here. The `get_status` command does work, so we
    // poll it and only react when the area actually changes (cheap; no flicker).
    (async () => {
      manifest = await loadManifest();
      while (alive) {
        const s = await getStatus();
        if (!status || s.area_id !== status.area_id) {
          status = s;
          syncArea(s);
        }
        await new Promise((r) => setTimeout(r, 500));
      }
    })();
    return () => {
      alive = false;
    };
  });

  // Drag the window by its title bar. WebKitGTK's `data-tauri-drag-region` and
  // Tauri's `setPosition` don't move this window under KWin, so the Rust side
  // follows the mouse with xdotool between start/end (driven by pointerdown/up,
  // which fire reliably — only pointermove is flaky here).
  function startDrag(e: PointerEvent) {
    if (e.button !== 0 || (e.target as HTMLElement).closest("button")) return;
    e.preventDefault();
    startLayoutDrag();
    window.addEventListener("pointerup", endDrag, { once: true });
    window.addEventListener("blur", endDrag, { once: true });
  }
  function endDrag() {
    window.removeEventListener("pointerup", endDrag);
    window.removeEventListener("blur", endDrag);
    endLayoutDrag();
  }

  // Resize the window via the corner grip. Same approach as the drag: the Rust
  // side follows the mouse (pointermove is flaky in this WebKitGTK window) and
  // resizes via set_size; pointerdown/up drive start/end.
  function startResize(e: PointerEvent) {
    if (e.button !== 0) return;
    e.preventDefault();
    e.stopPropagation();
    startLayoutResize();
    window.addEventListener("pointerup", endResize, { once: true });
    window.addEventListener("blur", endResize, { once: true });
  }
  function endResize() {
    window.removeEventListener("pointerup", endResize);
    window.removeEventListener("blur", endResize);
    endLayoutResize();
  }
</script>

<div class="viewer">
  <div class="bar" role="toolbar" tabindex="-1" onpointerdown={startDrag}>
    <span class="title">🗺 {areaName}</span>
    <button class="close" title="Fermer (hotkey)" onclick={() => toggleLayout()}>✕</button>
  </div>

  {#if !hasZone}
    <div class="empty">Pas de layout répertorié pour cette zone.</div>
  {:else}
    <!-- Main image: the deepest committed pick, with the zone's orientation.
         The stage flex-grows to fill the window, so the image scales with it. -->
    <div class="stage">
      {#if path !== ""}
        <img src={imageUrl(areaId, path)} alt={path} style="transform:{transform};" />
      {:else}
        <div class="hint">Clique le layout qui correspond à ce que tu vois en jeu.</div>
      {/if}
    </div>

    <!-- Orientation controls (the whole zone is placed at a random rotation). -->
    {#if !blocked}
      <div class="ctrls">
        <button title="Pivoter à gauche" onclick={() => rotate(-1)}>⟲</button>
        <button title="Pivoter à droite" onclick={() => rotate(1)}>⟳</button>
        <button class:on={flipH} title="Miroir horizontal" onclick={() => (flipH = !flipH)}>⇋</button>
        <button class:on={flipV} title="Miroir vertical" onclick={() => (flipV = !flipV)}>⇅</button>
        <button title="Réinitialiser l'orientation" onclick={resetOrientation}>↺</button>
        <span class="deg">{rot}°</span>
      </div>
    {/if}

    <!-- Breadcrumb / back navigation. -->
    {#if path !== ""}
      <div class="crumbs">
        <button class="link" onclick={() => (path = "")}>début</button>
        {#each crumbs as seg, i}
          <span class="sep">›</span>
          <button
            class="link"
            onclick={() => (path = crumbs.slice(0, i + 1).join("_"))}
          >{seg}</button>
        {/each}
        <button class="back" onclick={back}>◀ retour</button>
      </div>
    {/if}

    <!-- Zone legend for the current level: always visible while navigating (not a
         layout candidate). Rendered without the orientation transform — it's a
         symbol key, not a map. -->
    {#if activeLegend}
      <div class="label">Légende :</div>
      <div class="legend">
        <img class="legend-img" src={imageUrl(areaId, activeLegend)} alt="légende" />
      </div>
    {/if}

    <!-- Candidate thumbnails at the current branch. -->
    {#if candidates.length}
      <div class="label">{resolved ? "" : path === "" ? "Layouts possibles :" : "Affine :"}</div>
      <div class="thumbs">
        {#each candidates as c}
          <button
            class="thumb"
            title={c}
            onclick={() => pick(c)}
            oncontextmenu={(e) => {
              e.preventDefault();
              exclude(c);
            }}
          >
            <img src={imageUrl(areaId, c)} alt={c} style="transform:{transform};" />
            <span class="cap">{c.split("_").slice(-1)[0]}</span>
          </button>
        {/each}
      </div>
      <div class="tip">clic = choisir · clic droit = exclure</div>
    {:else if resolved}
      <div class="label done">Layout identifié.</div>
    {/if}
  {/if}

  <!-- Corner resize grip (the window has no WM resize borders under KWin). -->
  <div
    class="grip"
    role="button"
    tabindex="-1"
    title="Redimensionner"
    onpointerdown={startResize}
  ></div>
</div>

<style>
  .viewer {
    position: fixed;
    inset: 0;
    display: flex;
    flex-direction: column;
    background: #0f0c08;
    border: 1px solid var(--border);
    box-sizing: border-box;
    overflow: hidden;
  }
  .bar {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 3px 6px 3px 10px;
    background: var(--accent);
    color: #14110d;
    cursor: move;
    user-select: none;
  }
  .bar .title {
    font-weight: 700;
    font-size: 0.9em;
    text-transform: capitalize;
    flex: 1;
  }
  .bar .close {
    background: transparent;
    border: none;
    color: #14110d;
    cursor: pointer;
    font-weight: 700;
    padding: 0 4px;
  }
  .empty {
    padding: 18px;
    color: var(--muted);
    font-size: 0.9em;
    text-align: center;
  }
  .stage {
    flex: 1 1 auto;
    min-height: 80px;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 8px 8px 4px;
    background: #000;
    border: 1px solid var(--border);
    overflow: hidden;
  }
  .stage img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    transition: transform 0.12s ease;
  }
  .stage .hint {
    color: var(--muted);
    font-size: 0.85em;
    text-align: center;
    padding: 12px;
  }
  .ctrls {
    display: flex;
    gap: 4px;
    align-items: center;
    justify-content: center;
    padding: 2px 6px;
  }
  .ctrls button {
    background: #2a2218;
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 2px 8px;
    cursor: pointer;
    font-size: 1em;
    line-height: 1.2;
  }
  .ctrls button:hover {
    border-color: var(--accent);
  }
  .ctrls button.on {
    border-color: var(--accent);
    background: rgba(200, 162, 74, 0.18);
  }
  .ctrls .deg {
    color: var(--muted);
    font-size: 0.8em;
    margin-left: 4px;
  }
  .crumbs {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    padding: 2px 8px;
    font-size: 0.8em;
  }
  .crumbs .sep {
    color: var(--muted);
  }
  .crumbs .link {
    background: transparent;
    border: none;
    color: var(--accent2);
    cursor: pointer;
    padding: 0 2px;
  }
  .crumbs .back {
    margin-left: auto;
    background: #2a2218;
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 1px 8px;
    cursor: pointer;
  }
  .label {
    padding: 2px 10px 0;
    font-size: 0.82em;
    color: var(--muted);
  }
  .label.done {
    color: #7cfc00;
    padding-bottom: 8px;
  }
  .thumbs {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    padding: 4px 8px;
    justify-content: center;
    /* Without this, items stretch on the cross axis, which overrides the
       thumbs' aspect-ratio and collapses their height to 0. */
    align-items: flex-start;
  }
  .thumb {
    position: relative;
    /* Scale with the window width (the viewer fills the viewport), staying
       square via aspect-ratio, clamped so it never gets tiny or huge. */
    width: clamp(72px, 24vw, 200px);
    aspect-ratio: 1;
    padding: 0;
    background: #000;
    border: 1px solid var(--border);
    border-radius: 4px;
    cursor: pointer;
    overflow: hidden;
  }
  .thumb:hover {
    border-color: var(--accent);
  }
  .legend {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    padding: 4px 8px;
    justify-content: center;
    align-items: flex-start;
  }
  .legend-img {
    /* Symbol key: shown whole (contain, never rotated), scaling with the window. */
    width: clamp(90px, 28vw, 240px);
    aspect-ratio: 1;
    object-fit: contain;
    background: #000;
    border: 1px solid var(--border);
    border-radius: 4px;
  }
  .thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform 0.12s ease;
  }
  .thumb .cap {
    position: absolute;
    bottom: 0;
    right: 0;
    background: rgba(0, 0, 0, 0.7);
    color: var(--accent);
    font-size: 0.75em;
    padding: 0 4px;
    border-top-left-radius: 4px;
  }
  .tip {
    padding: 0 8px 8px;
    font-size: 0.72em;
    color: var(--muted);
    text-align: center;
  }
  .grip {
    position: absolute;
    right: 0;
    bottom: 0;
    width: 16px;
    height: 16px;
    cursor: nwse-resize;
    background: linear-gradient(
      135deg,
      transparent 0 55%,
      var(--border) 55% 70%,
      transparent 70% 80%,
      var(--accent) 80% 100%
    );
    z-index: 10;
  }
</style>
