// SPEC: PJ2026-0106050514 Workbench实时运行面 draft-2026-06-30-p0-1297-spec-first.
// Responsibility: Workbench bottom-follow plus session/tab scroll persistence.
// Mechanical source references:
// - OpenCode context/layout-scroll.ts:16-21 persistence state.
// - OpenCode context/layout-scroll.ts:35-51 seed/read.
// - OpenCode context/layout-scroll.ts:53-81 debounce flush.
// - OpenCode context/layout-scroll.ts:93-110 drop cleanup.

import { computed, nextTick, onBeforeUnmount, watch, type ComputedRef, type Ref } from "vue";
import { useBottomFollowScroll, type BottomFollowScrollOptions } from "@/composables/useBottomFollowScroll";
import { readWorkbenchJson, writeWorkbenchJson } from "@/utils/workbench-storage-runtime";

export interface SessionScroll {
  x: number;
  y: number;
}

export type WorkbenchScrollMap = Record<string, SessionScroll>;

export interface WorkbenchScrollPersistenceOptions {
  debounceMs?: number;
  getSnapshot: (sessionKey: string) => WorkbenchScrollMap | undefined;
  onFlush: (sessionKey: string, scroll: WorkbenchScrollMap) => void;
}

export interface WorkbenchBottomFollowScrollOptions extends BottomFollowScrollOptions {
  sessionKey?: string | Ref<string | null | undefined> | ComputedRef<string | null | undefined> | (() => string | null | undefined);
  tabKey?: string;
  persist?: boolean;
}

const WORKBENCH_SCROLL_STORAGE_KEY = "hwlab.workbench.scrollPersistence.v1";

export function createWorkbenchScrollPersistenceRuntime(opts: WorkbenchScrollPersistenceOptions) {
  const wait = Math.max(0, Math.trunc(opts.debounceMs ?? 200));
  const cache: Record<string, WorkbenchScrollMap> = {};
  const dirty = new Set<string>();
  const timers = new Map<string, ReturnType<typeof setTimeout>>();

  function clone(input?: WorkbenchScrollMap): WorkbenchScrollMap {
    const out: WorkbenchScrollMap = {};
    if (!input) return out;
    for (const key of Object.keys(input)) {
      const pos = input[key];
      if (!pos) continue;
      out[key] = { x: pos.x, y: pos.y };
    }
    return out;
  }

  function seed(sessionKey: string): void {
    const next = clone(opts.getSnapshot(sessionKey));
    const current = cache[sessionKey];
    if (!current) {
      cache[sessionKey] = next;
      return;
    }
    if (Object.keys(current).length > 0) return;
    if (Object.keys(next).length === 0) return;
    cache[sessionKey] = next;
  }

  function scroll(sessionKey: string, tab: string): SessionScroll | undefined {
    seed(sessionKey);
    return cache[sessionKey]?.[tab] ?? opts.getSnapshot(sessionKey)?.[tab];
  }

  function schedule(sessionKey: string): void {
    const prev = timers.get(sessionKey);
    if (prev) clearTimeout(prev);
    timers.set(sessionKey, setTimeout(() => flush(sessionKey), wait));
  }

  function setScroll(sessionKey: string, tab: string, pos: SessionScroll): void {
    seed(sessionKey);
    const prev = cache[sessionKey]?.[tab];
    if (prev?.x === pos.x && prev?.y === pos.y) return;
    cache[sessionKey] = { ...(cache[sessionKey] ?? {}), [tab]: { x: pos.x, y: pos.y } };
    dirty.add(sessionKey);
    schedule(sessionKey);
  }

  function flush(sessionKey: string): void {
    const timer = timers.get(sessionKey);
    if (timer) clearTimeout(timer);
    timers.delete(sessionKey);
    if (!dirty.has(sessionKey)) return;
    dirty.delete(sessionKey);
    opts.onFlush(sessionKey, clone(cache[sessionKey]));
  }

  function flushAll(): void {
    for (const key of Array.from(dirty)) flush(key);
  }

  function drop(keys: string[]): void {
    if (keys.length === 0) return;
    for (const key of keys) {
      const timer = timers.get(key);
      if (timer) clearTimeout(timer);
      timers.delete(key);
      dirty.delete(key);
      delete cache[key];
    }
  }

  function dispose(): void {
    flushAll();
    drop(Array.from(timers.keys()));
  }

  return { cache, drop, flush, flushAll, scroll, seed, setScroll, dispose };
}

const scrollPersistence = createWorkbenchScrollPersistenceRuntime({
  getSnapshot: (sessionKey) => readWorkbenchJson<Record<string, WorkbenchScrollMap>>(WORKBENCH_SCROLL_STORAGE_KEY, {}).value[sessionKey],
  onFlush: (sessionKey, scroll) => {
    const current = readWorkbenchJson<Record<string, WorkbenchScrollMap>>(WORKBENCH_SCROLL_STORAGE_KEY, {}).value;
    writeWorkbenchJson(WORKBENCH_SCROLL_STORAGE_KEY, { ...current, [sessionKey]: scroll });
  }
});

export function useWorkbenchBottomFollowScroll<T extends HTMLElement>(containerRef: Ref<T | null>, options: WorkbenchBottomFollowScrollOptions = {}) {
  const bottom = useBottomFollowScroll(containerRef, options);
  const tabKey = options.tabKey ?? "main";
  const persist = options.persist !== false;
  const sessionKey = computed(() => normalizeSessionKey(resolveSessionKey(options.sessionKey)));

  watch([containerRef, sessionKey], async () => {
    if (!persist) return;
    await nextTick();
    restoreScroll();
  }, { flush: "post", immediate: true });

  onBeforeUnmount(() => {
    persistScroll();
    scrollPersistence.flushAll();
  });

  function onScroll(): void {
    bottom.onScroll();
    persistScroll();
  }

  async function keepBottomAfterUpdate(): Promise<void> {
    await bottom.keepBottomAfterUpdate();
    if (!bottom.following.value) restoreScroll();
  }

  function scrollToBottom(): void {
    bottom.scrollToBottom();
    void nextTick().then(persistScroll);
  }

  function restoreScroll(): void {
    const element = containerRef.value;
    if (!element) return;
    const pos = scrollPersistence.scroll(sessionKey.value, tabKey);
    if (!pos) return;
    element.scrollTo({ left: pos.x, top: pos.y, behavior: "auto" });
    bottom.syncFollowingFromPosition();
  }

  function persistScroll(): void {
    if (!persist) return;
    const element = containerRef.value;
    if (!element) return;
    scrollPersistence.setScroll(sessionKey.value, tabKey, { x: element.scrollLeft, y: element.scrollTop });
  }

  return { ...bottom, keepBottomAfterUpdate, onScroll, restoreScroll, scrollToBottom };
}

function resolveSessionKey(value: WorkbenchBottomFollowScrollOptions["sessionKey"]): string | null | undefined {
  if (typeof value === "function") return value();
  if (value && typeof value === "object" && "value" in value) return value.value;
  return value;
}

function normalizeSessionKey(value: string | null | undefined): string {
  const text = String(value ?? "").trim();
  return text || "workbench";
}
