/**
 * Orchestration store — all shared reactive state + operations the
 * pane tree needs. Lives in a class with `$state` fields so Svelte 5
 * reactivity tracks per-property access; provided via context so any
 * descendant component can `useOrchestration()` without prop drilling.
 *
 * (File must be `.svelte.ts` because `$state` can only be used in
 * Svelte components or files with the `.svelte.{js,ts}` extension.)
 */

import { setContext, getContext } from "svelte";
import type { NodeId, Orientation } from "./tree";
import type { PaneId } from "../../ipc";

export interface Toast {
  id: number;
  message: string;
}

/**
 * Callbacks App provides at construction time. These do tree mutations
 * (which require access to App's `tree = $state(...)`) plus broadcast
 * routing (which also needs the tree). Kept as an injection seam rather
 * than living inside the store so the store doesn't need to own the tree.
 */
export interface TreeOps {
  split: (leafId: NodeId, orientation: Orientation) => void;
  close: (leafId: NodeId) => void;
  setDistro: (leafId: NodeId, distro: string) => void;
  setLabel: (leafId: NodeId, label: string | undefined) => void;
  toggleBroadcast: (leafId: NodeId) => void;
  broadcastFrom: (originLeafId: NodeId, dataB64: string) => void;
}

export class Orchestration {
  // ---- shared reactive state ----------------------------------------------
  activeLeafId = $state<NodeId | null>(null);
  notifications = $state<Toast[]>([]);
  distros = $state<string[]>([]);

  // ---- non-reactive lookups -----------------------------------------------
  // Plain Map: broadcast routing reads this from an event handler, not
  // from reactive context. No need for $state.
  paneIdByLeaf = new Map<NodeId, PaneId>();

  // ---- internal -----------------------------------------------------------
  #nextNotifId = 1;
  #dismissTimers = new Map<number, ReturnType<typeof setTimeout>>();
  #ops: TreeOps;

  constructor(ops: TreeOps) {
    this.#ops = ops;
  }

  // ---- active pane --------------------------------------------------------
  setActive(leafId: NodeId): void {
    console.log("[orch] setActive", leafId, "was", this.activeLeafId);
    this.activeLeafId = leafId;
  }

  clearActiveIf(leafId: NodeId): void {
    if (this.activeLeafId === leafId) this.activeLeafId = null;
  }

  // ---- notifications ------------------------------------------------------
  notify(message: string): void {
    const id = this.#nextNotifId++;
    console.log("[orch] notify", message);
    this.notifications.push({ id, message });
    const timer = setTimeout(() => {
      this.notifications = this.notifications.filter((n) => n.id !== id);
      this.#dismissTimers.delete(id);
    }, 5000);
    this.#dismissTimers.set(id, timer);
  }

  dismiss(id: number): void {
    const t = this.#dismissTimers.get(id);
    if (t) {
      clearTimeout(t);
      this.#dismissTimers.delete(id);
    }
    this.notifications = this.notifications.filter((n) => n.id !== id);
  }

  // ---- pane id registry ---------------------------------------------------
  registerPaneId(leafId: NodeId, paneId: PaneId | null): void {
    if (paneId == null) this.paneIdByLeaf.delete(leafId);
    else this.paneIdByLeaf.set(leafId, paneId);
  }

  // ---- delegated tree ops -------------------------------------------------
  // Thin pass-through so consumers only need one object.
  split(leafId: NodeId, orientation: Orientation): void {
    console.log("[orch] split", leafId, orientation);
    this.#ops.split(leafId, orientation);
  }

  close(leafId: NodeId): void {
    console.log("[orch] close", leafId);
    this.#ops.close(leafId);
  }

  setDistro(leafId: NodeId, distro: string): void {
    this.#ops.setDistro(leafId, distro);
  }

  setLabel(leafId: NodeId, label: string | undefined): void {
    this.#ops.setLabel(leafId, label);
  }

  toggleBroadcast(leafId: NodeId): void {
    console.log("[orch] toggleBroadcast", leafId);
    this.#ops.toggleBroadcast(leafId);
  }

  broadcastFrom(originLeafId: NodeId, dataB64: string): void {
    this.#ops.broadcastFrom(originLeafId, dataB64);
  }
}

const KEY = Symbol("tiletopia.orchestration");

export function provideOrchestration(ops: TreeOps): Orchestration {
  const o = new Orchestration(ops);
  setContext(KEY, o);
  return o;
}

export function useOrchestration(): Orchestration {
  const o = getContext<Orchestration | undefined>(KEY);
  if (!o) {
    throw new Error(
      "useOrchestration() called outside a provideOrchestration() ancestor",
    );
  }
  return o;
}
