import { Boxes, Search, ShieldCheck, WifiOff } from "lucide-solid";
import {
  batch,
  createEffect,
  createMemo,
  createSignal,
  For,
  on,
  onCleanup,
  onMount,
  Show,
} from "solid-js";
import {
  activateDeployment,
  changeRuntimeState,
  changeServiceState,
  fetchSnapshot,
  getApiBase,
  invokeComponent,
  publishWitPackage,
  registerComponent,
  saveApiBase,
  type BackendSnapshot,
  type InvokeResult,
} from "../lib/api";
import { serviceKey, type ConnectionState, type Service } from "../lib/model";
import { servicesFrom } from "../lib/view-state";
import { isView, PAGE_DEFINITIONS, pageDefinition, type View } from "../pages/registry";
import { Input } from "../components/ui/input";
import { pageUrl, sendLifecycle, sendState } from "../sdk/host";
import {
  isPageMessage,
  type HostGlobalState,
  type HostSharedState,
  type PageCommand,
  type RuntimeAction,
} from "../sdk/protocol";
import { DialogLayer, type DialogState } from "./dialogs";
import { FrameCache, type FrameEntry } from "./frame-cache";
import { createBrowserHostTabSync, type HostTabSync } from "./tab-sync";

const KEEP_ALIVE_INACTIVE_TTL_MS = 30 * 60 * 1000;

export default function HostApp() {
  // All backend ownership stays in this persistent document. Page iframes are
  // disposable renderers and can only request typed commands through the SDK.
  const initial = initialView();
  const frameCache = new FrameCache(initial, {
    inactiveTtlMs: KEEP_ALIVE_INACTIVE_TTL_MS,
    shouldKeepAlive: (view) => pageDefinition(view).keepAlive,
  });
  const [view, setView] = createSignal(initial);
  const [frames, setFrames] = createSignal(frameCache.snapshot());
  const [readyViews, setReadyViews] = createSignal<ReadonlySet<View>>(new Set<View>());
  const [snapshot, setSnapshot] = createSignal<BackendSnapshot | null>(null);
  const [connection, setConnection] = createSignal<ConnectionState>("connecting");
  const [apiBase, setApiBase] = createSignal(getApiBase());
  const [query, setQuery] = createSignal("");
  const [runtimeAction, setRuntimeAction] = createSignal<RuntimeAction | null>(null);
  const [serviceAction, setServiceAction] = createSignal<string | null>(null);
  const [deploymentAction, setDeploymentAction] = createSignal<string | null>(null);
  const [dialog, setDialog] = createSignal<DialogState | null>(null);
  const [dialogBusy, setDialogBusy] = createSignal(false);
  const [toast, setToast] = createSignal<string | null>(null);
  const frameElements = new Map<View, HTMLIFrameElement>();
  let tabSync: HostTabSync | null = null;
  let applyingSharedHostState = false;
  let toastTimer: number | undefined;
  let frameExpirationTimer: number | undefined;
  let refreshGeneration = 0;

  const globalState = createMemo<HostGlobalState>(() => ({
    snapshot: snapshot(),
    connection: connection(),
    apiBase: apiBase(),
    runtimeAction: runtimeAction(),
    serviceAction: serviceAction(),
    deploymentAction: deploymentAction(),
  }));
  const state = createMemo<HostSharedState>(() => ({
    ...globalState(),
    view: view(),
    query: query(),
  }));
  const activeServices = createMemo(
    () => servicesFrom(state()).filter((service) => service.status === "running").length,
  );
  const frameReady = createMemo(() => readyViews().has(view()));
  const title = createMemo(() => pageDefinition(view()).label);

  function stateFor(targetView: View): HostSharedState {
    return { ...state(), view: targetView };
  }

  function applySharedHostState(next: HostGlobalState) {
    applyingSharedHostState = true;
    refreshGeneration += 1;
    batch(() => {
      setSnapshot(next.snapshot);
      setConnection(next.connection);
      setApiBase(next.apiBase);
      setRuntimeAction(next.runtimeAction);
      setServiceAction(next.serviceAction);
      setDeploymentAction(next.deploymentAction);
    });
    applyingSharedHostState = false;
  }

  async function refresh() {
    const generation = ++refreshGeneration;
    try {
      const next = await fetchSnapshot(apiBase());
      if (generation !== refreshGeneration) return;
      batch(() => {
        setSnapshot(next);
        setConnection("online");
      });
    } catch {
      if (generation !== refreshGeneration) return;
      batch(() => {
        setSnapshot(null);
        setConnection("offline");
      });
    }
  }

  function notify(message: string) {
    if (toastTimer !== undefined) window.clearTimeout(toastTimer);
    setToast(message);
    toastTimer = window.setTimeout(() => setToast(null), 2800);
  }

  function navigate(next: View, pushHistory = true) {
    if (next === view()) return;

    const previous = view();
    const transition = frameCache.activate(next);
    const removedViews = new Set(transition.removed.map((entry) => entry.view));

    // Retained frames are paused before being hidden. Transient and expired
    // frames receive dispose before Solid removes their document from the DOM.
    if (!removedViews.has(previous)) sendFrameLifecycle(previous, "deactivate");
    for (const entry of transition.removed) sendFrameLifecycle(entry.view, "dispose");

    setReadyViews((current) => {
      const remaining = new Set(current);
      for (const removed of removedViews) remaining.delete(removed);
      return remaining;
    });
    setFrames(transition.entries);
    setView(next);
    setQuery("");

    if (readyViews().has(next)) {
      sendFrameState(next);
      sendFrameLifecycle(next, "activate");
    }
    if (pushHistory) {
      const url = new URL(window.location.href);
      url.searchParams.set("view", next);
      window.history.pushState({ view: next }, "", url);
    }
    scheduleFrameExpiration();
  }

  function findService(key: string): Service | null {
    return servicesFrom(state()).find((service) => serviceKey(service) === key) ?? null;
  }

  async function performRuntimeAction(action: RuntimeAction) {
    setRuntimeAction(action);
    try {
      await changeRuntimeState(apiBase(), action);
      await refresh();
      notify(
        action === "start"
          ? "Runtime 已启动"
          : action === "stop"
            ? "Runtime 已停止"
            : "Runtime 已重启",
      );
    } catch (error) {
      notify(error instanceof Error ? error.message : "Runtime 操作失败");
    } finally {
      setRuntimeAction(null);
    }
  }

  async function performServiceAction(key: string, action: "start" | "stop" | "restart") {
    const service = findService(key);
    if (!service) return;
    setServiceAction(key);
    try {
      await changeServiceState(apiBase(), service, action);
      await refresh();
      notify(
        `${service.id} ${action === "stop" ? "已停止" : action === "restart" ? "已重启" : "已启动"}`,
      );
    } catch (error) {
      notify(error instanceof Error ? error.message : "Actor 操作失败");
    } finally {
      setServiceAction(null);
    }
  }

  async function handleCommand(command: PageCommand) {
    switch (command.type) {
      case "refresh":
        await refresh();
        return;
      case "navigate":
        navigate(command.view);
        return;
      case "open-register":
        setDialog({ type: "register" });
        return;
      case "open-wit-publish":
        setDialog({ type: "wit-publish" });
        return;
      case "open-invoke": {
        const service = findService(command.serviceKey);
        if (service) setDialog({ type: "invoke", service });
        return;
      }
      case "open-activate": {
        const service = findService(command.serviceKey);
        if (!service) return;
        const currentRevision =
          snapshot()?.deployments.find((item) => item.service_id === service.id)?.active_revision ??
          null;
        setDialog({ type: "activate", service, currentRevision });
        return;
      }
      case "runtime-action":
        await performRuntimeAction(command.action);
        return;
      case "service-action":
        await performServiceAction(command.serviceKey, command.action);
        return;
      case "save-api-base":
        try {
          const next = saveApiBase(command.value);
          setApiBase(next);
          setSnapshot(null);
          setConnection("connecting");
          await refresh();
          notify("API 地址已保存");
        } catch (error) {
          notify(error instanceof Error ? error.message : "API 地址无效");
        }
    }
  }

  const receive = (event: MessageEvent<unknown>) => {
    if (event.origin !== window.location.origin || !isPageMessage(event.data)) return;
    const sourceView = viewForSource(event.source);
    if (!sourceView || event.data.view !== sourceView) return;

    if (event.data.type === "ready") {
      setReadyViews((current) => new Set(current).add(sourceView));
      sendFrameState(sourceView);
      sendFrameLifecycle(sourceView, sourceView === view() ? "activate" : "deactivate");
      return;
    }
    // Hidden documents cannot initiate management operations. This matters for
    // page-owned timers or libraries that may finish work after deactivation.
    if (sourceView !== view()) return;
    void handleCommand(event.data.command);
  };

  function viewForSource(source: MessageEventSource | null): View | null {
    for (const [frameView, element] of frameElements) {
      if (source === element.contentWindow) return frameView;
    }
    return null;
  }

  function sendFrameState(targetView: View) {
    const target = frameElements.get(targetView)?.contentWindow;
    if (target) sendState(target, stateFor(targetView));
  }

  function sendFrameLifecycle(targetView: View, phase: "activate" | "deactivate" | "dispose") {
    const target = frameElements.get(targetView)?.contentWindow;
    if (target) sendLifecycle(target, phase);
  }

  function scheduleFrameExpiration() {
    if (frameExpirationTimer !== undefined) {
      window.clearTimeout(frameExpirationTimer);
      frameExpirationTimer = undefined;
    }
    const delay = frameCache.timeUntilExpiration();
    if (delay === null) return;
    frameExpirationTimer = window.setTimeout(expireInactiveFrames, delay);
  }

  function expireInactiveFrames() {
    frameExpirationTimer = undefined;
    const expiration = frameCache.expireInactive();
    if (expiration.removed.length > 0) {
      const removedViews = new Set(expiration.removed.map((entry) => entry.view));
      for (const entry of expiration.removed) sendFrameLifecycle(entry.view, "dispose");
      setReadyViews((current) => {
        const remaining = new Set(current);
        for (const removed of removedViews) remaining.delete(removed);
        return remaining;
      });
      setFrames(expiration.entries);
    }
    scheduleFrameExpiration();
  }

  const popState = () => {
    const requested = new URLSearchParams(window.location.search).get("view");
    navigate(isView(requested) ? requested : "overview", false);
  };

  onMount(() => {
    window.addEventListener("message", receive);
    window.addEventListener("popstate", popState);
    tabSync = createBrowserHostTabSync({
      getState: globalState,
      applyState: applySharedHostState,
    });
    void refresh();
    const interval = window.setInterval(() => void refresh(), 5000);
    onCleanup(() => window.clearInterval(interval));
  });

  onCleanup(() => {
    window.removeEventListener("message", receive);
    window.removeEventListener("popstate", popState);
    tabSync?.close();
    if (toastTimer !== undefined) window.clearTimeout(toastTimer);
    if (frameExpirationTimer !== undefined) window.clearTimeout(frameExpirationTimer);
    for (const frameView of frameElements.keys()) sendFrameLifecycle(frameView, "dispose");
  });

  createEffect(() => {
    state();
    const ready = readyViews();
    for (const frame of frames()) {
      if (ready.has(frame.view)) sendFrameState(frame.view);
    }
  });

  createEffect(
    on(
      globalState,
      (next) => {
        if (!applyingSharedHostState) tabSync?.publish(next);
      },
      { defer: true },
    ),
  );

  async function register(file: File) {
    setDialogBusy(true);
    try {
      const registered = await registerComponent(apiBase(), file);
      await refresh();
      setDialog(null);
      notify(`${registered.id}@${registered.revision} 已注册`);
    } finally {
      setDialogBusy(false);
    }
  }

  async function publishWit(file: File) {
    setDialogBusy(true);
    try {
      const published = await publishWitPackage(apiBase(), file);
      await refresh();
      setDialog(null);
      notify(`${published.name}@${published.version} 已发布`);
    } finally {
      setDialogBusy(false);
    }
  }

  async function activate(service: Service) {
    setDialogBusy(true);
    setDeploymentAction(serviceKey(service));
    try {
      await activateDeployment(apiBase(), service);
      await refresh();
      setDialog(null);
      notify(`${service.id}@${service.revision} 已设为对外版本`);
    } finally {
      setDialogBusy(false);
      setDeploymentAction(null);
    }
  }

  async function invoke(service: Service, input: Uint8Array): Promise<InvokeResult> {
    const result = await invokeComponent(apiBase(), service, input);
    await refresh();
    return result;
  }

  return (
    <div class="grid h-dvh min-w-0 grid-cols-[minmax(0,1fr)] grid-rows-[auto_56px_minmax(0,1fr)_30px] overflow-hidden bg-canvas lg:grid-cols-[232px_minmax(0,1fr)] lg:grid-rows-[64px_minmax(0,1fr)_30px]">
      <aside class="border-line min-w-0 bg-[#16221f] text-white lg:row-span-3 lg:flex lg:min-h-0 lg:flex-col lg:border-r">
        <div class="flex h-16 shrink-0 items-center gap-3 px-4 lg:px-5">
          <span class="flex size-9 items-center justify-center rounded-md bg-[#e5f4ef] text-brand-strong">
            <Boxes size={19} />
          </span>
          <div>
            <strong class="block text-[15px]">Wasmeld</strong>
            <span class="block text-[10px] text-[#9ab0aa]">COMPONENT RUNTIME</span>
          </div>
        </div>
        <nav class="flex gap-1 overflow-x-auto px-3 pb-3 lg:block lg:min-h-0 lg:flex-1 lg:space-y-1 lg:overflow-y-auto lg:pb-0">
          <For each={PAGE_DEFINITIONS}>
            {(item) => {
              const Icon = item.icon;
              return (
                <button
                  class="flex h-10 shrink-0 items-center gap-3 rounded-md px-3 text-sm text-[#b8c7c3] transition-colors hover:bg-white/7 hover:text-white lg:w-full"
                  classList={{ "bg-white/10 text-white": view() === item.id }}
                  type="button"
                  onClick={() => navigate(item.id)}
                >
                  <Icon size={16} />
                  {item.label}
                </button>
              );
            }}
          </For>
        </nav>
        <div class="hidden border-t border-white/10 p-4 lg:block">
          <div class="flex items-center justify-between text-xs">
            <span class="text-[#9ab0aa]">连接状态</span>
            <span
              classList={{
                "font-semibold text-[#76d5b4]": connection() === "online",
                "font-semibold text-[#ffac93]": connection() === "offline",
                "font-semibold text-[#f5cf7a]": connection() === "connecting",
              }}
            >
              {connection() === "online" ? "在线" : connection() === "offline" ? "离线" : "连接中"}
            </span>
          </div>
          <div class="mt-3 flex items-center justify-between text-xs">
            <span class="text-[#9ab0aa]">活跃 Actor</span>
            <strong>{activeServices()}</strong>
          </div>
        </div>
      </aside>

      <header class="border-line col-start-1 flex min-w-0 items-center justify-between gap-3 border-b bg-white px-4 lg:col-start-2 lg:px-6">
        <div class="min-w-0">
          <span class="hidden text-xs text-muted sm:inline">运行平台 / </span>
          <strong class="text-sm">{title()}</strong>
        </div>
        <label class="relative w-full max-w-80">
          <Search
            class="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-muted"
            size={15}
          />
          <Input
            density="compact"
            class="pl-9"
            value={query()}
            aria-label="搜索"
            placeholder="搜索服务、制品或事件"
            onInput={(event) => setQuery(event.currentTarget.value)}
          />
        </label>
      </header>

      <section class="relative col-start-1 min-h-0 min-w-0 overflow-hidden lg:col-start-2">
        <For each={frames()}>
          {(frame) => (
            <PageFrame
              frame={frame}
              active={view() === frame.view}
              title={pageDefinition(frame.view).label}
              register={(frameView, element) => frameElements.set(frameView, element)}
              unregister={(frameView, element) => {
                if (frameElements.get(frameView) === element) frameElements.delete(frameView);
              }}
            />
          )}
        </For>
        <Show when={!frameReady()}>
          <div class="pointer-events-none absolute inset-0 flex items-center justify-center bg-canvas">
            <div class="flex flex-col items-center gap-3 text-sm text-muted">
              <div class="flex items-center gap-2">
                <span class="size-2 animate-pulse rounded-full bg-brand" />
                正在加载 {title()}
              </div>
              <div
                class="h-1 w-40 overflow-hidden rounded-sm bg-line"
                role="progressbar"
                aria-label={`${title()}加载进度`}
              >
                <span class="block h-full w-[36%] animate-[loading-progress-swing_1s_ease-in-out_infinite_alternate] rounded-sm bg-brand motion-reduce:translate-x-[89%] motion-reduce:animate-none" />
              </div>
            </div>
          </div>
        </Show>
        <Show when={connection() === "offline"}>
          <div class="absolute bottom-4 left-1/2 flex -translate-x-1/2 items-center gap-2 rounded-md border border-coral-strong/20 bg-coral-soft px-4 py-2 text-xs font-semibold text-coral-strong shadow-lg">
            <WifiOff size={14} />
            管理 API 不可用
          </div>
        </Show>
      </section>

      <footer class="border-line col-start-1 flex items-center justify-between border-t bg-white px-4 text-[10px] text-muted lg:col-start-2 lg:px-6">
        <span class="inline-flex items-center gap-1.5">
          <ShieldCheck size={12} />
          {snapshot()?.runtime.status === "running" ? "Sandbox 正常" : "Runtime 已停止"}
        </span>
        <span>Component Model · WASI P2</span>
      </footer>

      <Show when={dialog()}>
        {(activeDialog) => (
          <DialogLayer
            dialog={activeDialog()}
            busy={dialogBusy()}
            onClose={() => !dialogBusy() && setDialog(null)}
            onRegister={register}
            onPublishWit={publishWit}
            onActivate={activate}
            onInvoke={invoke}
          />
        )}
      </Show>

      <Show when={toast()}>
        {(message) => (
          <div class="fixed bottom-12 right-5 z-70 max-w-sm rounded-md bg-[#16221f] px-4 py-3 text-sm text-white shadow-xl">
            {message()}
          </div>
        )}
      </Show>
    </div>
  );
}

function initialView(): View {
  const requested = new URLSearchParams(window.location.search).get("view");
  return isView(requested) ? requested : "overview";
}

function PageFrame(props: {
  frame: FrameEntry;
  active: boolean;
  title: string;
  register: (view: View, element: HTMLIFrameElement) => void;
  unregister: (view: View, element: HTMLIFrameElement) => void;
}) {
  let element: HTMLIFrameElement | undefined;

  onCleanup(() => {
    if (element) props.unregister(props.frame.view, element);
  });

  return (
    <iframe
      ref={(next) => {
        element = next;
        props.register(props.frame.view, next);
      }}
      class="size-full border-0 bg-canvas"
      classList={{ hidden: !props.active }}
      src={pageUrl(props.frame.view, props.frame.instance)}
      title={props.title}
      aria-hidden={!props.active}
      tabIndex={props.active ? 0 : -1}
    />
  );
}
