<script lang="ts">
  import { onMount } from "svelte";
  import {
    listDistros,
    saveWorkspace,
    loadWorkspace,
    writeToPane,
  } from "./ipc";
  import Pane from "./lib/layout/Pane.svelte";
  import Notifications from "./components/Notifications.svelte";
  import Palette from "./components/Palette.svelte";
  import {
    provideOrchestration,
    type TreeOps,
  } from "./lib/layout/orchestration.svelte";
  import {
    type TreeNode,
    type NodeId,
    type Orientation,
    type LeafNode,
    newLeaf,
    splitLeaf,
    closeLeaf,
    findLeaf,
    leafCount,
    walkLeaves,
    changeDistro,
    changeLabel,
    toggleBroadcast as toggleBroadcastInTree,
    serialize,
    deserialize,
    presetSingle,
    presetTwoColumns,
    presetThreeColumns,
    presetTwoRows,
    presetTwoByTwo,
  } from "./lib/layout/tree";

  const LEGACY_STORAGE_KEY = "tiletopia.tree.v1";

  let defaultDistro = $state<string | undefined>(undefined);
  let ready = $state(false);
  let tree = $state<TreeNode>(newLeaf());
  let paletteOpen = $state(false);

  // ---- tree mutation handlers (closures over tree $state) -----------------
  function handleSplit(leafId: NodeId, orientation: Orientation) {
    const parent = findLeaf(tree, leafId);
    const inherit = parent
      ? { distro: parent.distro ?? defaultDistro, cwd: parent.cwd }
      : { distro: defaultDistro };
    tree = splitLeaf(tree, leafId, orientation, inherit);
  }

  function handleClose(leafId: NodeId) {
    const next = closeLeaf(tree, leafId);
    tree = next ?? newLeaf({ distro: defaultDistro });
    orch.clearActiveIf(leafId);
  }

  function handleSetDistro(leafId: NodeId, distro: string) {
    tree = changeDistro(tree, leafId, distro);
  }

  function handleSetLabel(leafId: NodeId, label: string | undefined) {
    tree = changeLabel(tree, leafId, label);
  }

  function handleToggleBroadcast(leafId: NodeId) {
    tree = toggleBroadcastInTree(tree, leafId);
  }

  function handleBroadcastFrom(originLeafId: NodeId, dataB64: string) {
    let peers = 0;
    for (const leaf of walkLeaves(tree)) {
      if (leaf.id === originLeafId) continue;
      if (!leaf.broadcast) continue;
      const paneId = orch.paneIdByLeaf.get(leaf.id);
      if (paneId == null) {
        console.warn("[tiletopia] broadcast peer has no paneId yet:", leaf.id);
        continue;
      }
      peers++;
      writeToPane(paneId, dataB64).catch((e) =>
        console.warn("[tiletopia] broadcast write failed:", e),
      );
    }
    console.log("[tiletopia] broadcastFrom", originLeafId, "→", peers, "peer(s)");
  }

  const treeOps: TreeOps = {
    split: handleSplit,
    close: handleClose,
    setDistro: handleSetDistro,
    setLabel: handleSetLabel,
    toggleBroadcast: handleToggleBroadcast,
    broadcastFrom: handleBroadcastFrom,
  };

  // Provide the orchestration store. All Pane / SplitNode / LeafPane
  // descendants consume it via `useOrchestration()` — no prop drilling.
  const orch = provideOrchestration(treeOps);

  function isInteractiveDistro(name: string): boolean {
    return !name.toLowerCase().startsWith("docker-desktop");
  }

  onMount(async () => {
    // 1. Try APPDATA persistence.
    let loaded: TreeNode | null = null;
    try {
      const json = await loadWorkspace();
      if (json) loaded = deserialize(json);
    } catch (e) {
      console.warn("loadWorkspace failed:", e);
    }

    // 2. Migrate from M2 localStorage if APPDATA is empty.
    if (!loaded) {
      try {
        const legacy = localStorage.getItem(LEGACY_STORAGE_KEY);
        if (legacy) {
          loaded = deserialize(legacy);
          if (loaded) void saveWorkspace(legacy);
          localStorage.removeItem(LEGACY_STORAGE_KEY);
        }
      } catch (e) {
        console.warn("legacy localStorage migration failed:", e);
      }
    }

    if (loaded) tree = loaded;

    // 3. Resolve default distro.
    try {
      const ds = await listDistros();
      orch.distros = ds;
      defaultDistro = ds.find(isInteractiveDistro) ?? ds[0];
    } catch (e) {
      console.warn("list_distros failed:", e);
    }

    if (defaultDistro) backfillDistro(tree, defaultDistro);
    ready = true;
  });

  function backfillDistro(node: TreeNode, fallback: string) {
    if (node.kind === "leaf") {
      if (!node.distro) node.distro = fallback;
    } else {
      backfillDistro(node.a, fallback);
      backfillDistro(node.b, fallback);
    }
  }

  // ---- debounced auto-save -------------------------------------------------
  let saveTimer: number | null = null;
  const SAVE_DEBOUNCE_MS = 500;

  $effect(() => {
    if (!ready) return;
    const json = serialize(tree);
    if (saveTimer != null) clearTimeout(saveTimer);
    saveTimer = window.setTimeout(() => {
      saveTimer = null;
      saveWorkspace(json).catch((e) =>
        console.warn("saveWorkspace failed:", e),
      );
    }, SAVE_DEBOUNCE_MS);
  });

  // ---- Ctrl+K palette toggle ----------------------------------------------
  // Capture phase so we win over xterm.js's keystroke capture inside terminals.
  $effect(() => {
    function onKey(e: KeyboardEvent) {
      if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") {
        console.log("[tiletopia] Ctrl+K caught, paletteOpen ->", !paletteOpen);
        e.preventDefault();
        e.stopPropagation();
        paletteOpen = !paletteOpen;
      }
    }
    window.addEventListener("keydown", onKey, true);
    return () => window.removeEventListener("keydown", onKey, true);
  });

  // ---- Document-level active-pane detector --------------------------------
  // xterm.js calls `stopPropagation` on pointerdown inside terminals, so a
  // per-leaf `onpointerdown` never fires for body clicks. A document-level
  // CAPTURE-phase listener fires before xterm.js can intercept, then finds
  // the nearest `data-leaf-id` ancestor to know which pane was clicked.
  // Toolbar buttons also pass through (they're outside the xterm container,
  // their own onclick still fires in the bubble phase afterwards).
  $effect(() => {
    function onAnyPointerDown(e: PointerEvent) {
      const t = e.target as Element | null;
      if (!t) return;
      const leafEl = t.closest("[data-leaf-id]");
      if (!leafEl) return;
      const id = leafEl.getAttribute("data-leaf-id");
      if (id) orch.setActive(id);
    }
    document.addEventListener("pointerdown", onAnyPointerDown, true);
    return () => document.removeEventListener("pointerdown", onAnyPointerDown, true);
  });

  // ---- preset layouts ------------------------------------------------------
  function applyPreset(make: (d: { distro?: string }) => TreeNode) {
    const count = leafCount(tree);
    if (count > 1 && !confirm(`Replace current layout (${count} panes)? This kills all open shells.`)) {
      return;
    }
    tree = make({ distro: defaultDistro });
  }

  // ---- palette feed --------------------------------------------------------
  const paletteLeaves = $derived.by<LeafNode[]>(() => {
    if (!paletteOpen) return [];
    return Array.from(walkLeaves(tree));
  });

  function onPalettePick(leafId: string) {
    orch.setActive(leafId);
    paletteOpen = false;
  }
</script>

<div class="app">
  <header class="titlebar">
    <span class="label">tiletopia</span>

    <span class="distros">
      {#if orch.distros.length === 0}
        <span class="muted">no distros enumerated</span>
      {:else}
        <span class="muted">default:</span>
        {#each orch.distros as d}
          <button
            class="distro-btn"
            class:active={d === defaultDistro}
            onclick={() => (defaultDistro = d)}
            title="Set default distro for new panes"
          >{d}</button>
        {/each}
      {/if}
    </span>

    <span class="presets">
      <span class="muted">layout:</span>
      <button class="preset-btn" title="Single pane" onclick={() => applyPreset(presetSingle)}>1</button>
      <button class="preset-btn" title="Two columns" onclick={() => applyPreset(presetTwoColumns)}>2H</button>
      <button class="preset-btn" title="Three columns" onclick={() => applyPreset(presetThreeColumns)}>3H</button>
      <button class="preset-btn" title="Two rows" onclick={() => applyPreset(presetTwoRows)}>2V</button>
      <button class="preset-btn" title="2 × 2 grid" onclick={() => applyPreset(presetTwoByTwo)}>2×2</button>
    </span>

    <button class="palette-btn" onclick={() => (paletteOpen = true)} title="Jump to pane (Ctrl+K)">
      ⌘K
    </button>
    <button class="palette-btn" onclick={() => orch.notify("test toast at " + new Date().toLocaleTimeString())} title="Fire a test toast">
      🔔
    </button>

    <span class="layout-info">
      {leafCount(tree)} pane{leafCount(tree) === 1 ? "" : "s"}
    </span>
  </header>

  <div class="pane-wrap">
    {#if ready}
      <Pane node={tree} />
    {/if}
  </div>

  <Notifications
    notifications={orch.notifications}
    onDismiss={(id) => orch.dismiss(id)}
  />

  {#if paletteOpen}
    <Palette
      leaves={paletteLeaves}
      onPick={onPalettePick}
      onClose={() => (paletteOpen = false)}
    />
  {/if}
</div>

<style>
  .distros, .presets {
    display: flex;
    gap: 4px;
    align-items: center;
  }
  .distro-btn, .preset-btn, .palette-btn {
    font: inherit;
    font-family: "Cascadia Mono", "JetBrains Mono", "Consolas", monospace;
    font-size: 11px;
    background: #222;
    color: #aaa;
    border: 1px solid #333;
    border-radius: 3px;
    padding: 2px 8px;
    cursor: pointer;
  }
  .distro-btn:hover, .preset-btn:hover, .palette-btn:hover {
    background: #2a2a2a;
    color: #ddd;
  }
  .distro-btn.active {
    background: #1a3a5c;
    color: #cce6ff;
    border-color: #2a5a8c;
  }
  .preset-btn {
    min-width: 28px;
    text-align: center;
  }
  .muted {
    color: #666;
    font-style: italic;
  }
  .layout-info {
    margin-left: auto;
    font-family: "Cascadia Mono", "JetBrains Mono", "Consolas", monospace;
    color: #777;
    font-size: 11px;
  }
</style>
