import {
  ArrowRight,
  Clock3,
  CloudUpload,
  FileCode2,
  Package,
  Play,
  RadioTower,
  RefreshCw,
  ShieldCheck,
  SquareTerminal,
  X,
} from "lucide-solid";
import { createMemo, createSignal, onCleanup, onMount, Show, type JSXElement } from "solid-js";
import { Button, IconButton } from "../components/ui/button";
import { Field, FieldLabel } from "../components/ui/field";
import { Segment, SegmentedControl } from "../components/ui/segmented-control";
import { Textarea } from "../components/ui/textarea";
import type { InvokeResult } from "../lib/api";
import {
  formatInvocationOutput,
  invocationInputSize,
  parseInvocationInput,
  type Service,
} from "../lib/model";

export type DialogState =
  | { type: "register" }
  | { type: "wit-publish" }
  | { type: "invoke"; service: Service }
  | { type: "activate"; service: Service; currentRevision: string | null };

export function DialogLayer(props: {
  dialog: DialogState;
  busy: boolean;
  onClose: () => void;
  onRegister: (file: File) => Promise<void>;
  onPublishWit: (file: File) => Promise<void>;
  onInvoke: (service: Service, input: Uint8Array) => Promise<InvokeResult>;
  onActivate: (service: Service) => Promise<void>;
}) {
  return (
    <>
      <Show when={props.dialog.type === "register"}>
        <UploadDialog
          kind="component"
          busy={props.busy}
          onClose={props.onClose}
          onSubmit={props.onRegister}
        />
      </Show>
      <Show when={props.dialog.type === "wit-publish"}>
        <UploadDialog
          kind="wit"
          busy={props.busy}
          onClose={props.onClose}
          onSubmit={props.onPublishWit}
        />
      </Show>
      <Show when={props.dialog.type === "invoke" && "service" in props.dialog}>
        <InvokeDialog
          service={(props.dialog as Extract<DialogState, { type: "invoke" }>).service}
          onClose={props.onClose}
          onInvoke={props.onInvoke}
        />
      </Show>
      <Show when={props.dialog.type === "activate" && "service" in props.dialog}>
        <ActivateDialog
          dialog={props.dialog as Extract<DialogState, { type: "activate" }>}
          busy={props.busy}
          onClose={props.onClose}
          onActivate={props.onActivate}
        />
      </Show>
    </>
  );
}

function DialogFrame(props: {
  title: string;
  description: string;
  icon: JSXElement;
  closeDisabled?: boolean;
  onClose: () => void;
  children: JSXElement;
  footer: JSXElement;
}) {
  const onKeyDown = (event: KeyboardEvent) => {
    if (event.key === "Escape" && !props.closeDisabled) props.onClose();
  };
  onMount(() => window.addEventListener("keydown", onKeyDown));
  onCleanup(() => window.removeEventListener("keydown", onKeyDown));

  return (
    <div
      class="fixed inset-0 z-50 flex items-center justify-center bg-[#0d1715]/55 p-3 backdrop-blur-[1px]"
      role="presentation"
    >
      <dialog
        open
        class="relative m-0 max-h-[calc(100dvh-24px)] w-full max-w-3xl overflow-auto rounded-md border border-line bg-white p-0 shadow-2xl"
        aria-label={props.title}
      >
        <header class="flex items-start justify-between gap-4 border-b border-line px-5 py-4 sm:px-7 sm:py-5">
          <div class="flex min-w-0 items-start gap-3">
            <span class="flex size-10 shrink-0 items-center justify-center rounded-md bg-brand-soft text-brand">
              {props.icon}
            </span>
            <div>
              <h2 class="text-lg font-bold sm:text-xl">{props.title}</h2>
              <p class="mt-1 text-sm text-muted">{props.description}</p>
            </div>
          </div>
          <IconButton
            aria-label="关闭"
            title="关闭"
            disabled={props.closeDisabled}
            onClick={props.onClose}
          >
            <X size={18} />
          </IconButton>
        </header>
        <div class="px-5 py-5 sm:px-7">{props.children}</div>
        <footer class="flex items-center justify-end gap-2 border-t border-line bg-[#fafbfb] px-5 py-4 sm:px-7">
          {props.footer}
        </footer>
      </dialog>
    </div>
  );
}

function UploadDialog(props: {
  kind: "component" | "wit";
  busy: boolean;
  onClose: () => void;
  onSubmit: (file: File) => Promise<void>;
}) {
  const [file, setFile] = createSignal<File | null>(null);
  const [error, setError] = createSignal("");
  const isWit = () => props.kind === "wit";
  const title = () => (isWit() ? "发布 WIT 包" : "注册组件包");

  async function submit() {
    const selected = file();
    if (!selected || props.busy) return;
    const extension = isWit() ? ".wasm" : ".wasmpkg";
    const limit = isWit() ? 4 * 1024 * 1024 : 64 * 1024 * 1024;
    if (!selected.name.toLowerCase().endsWith(extension)) {
      setError(`请选择 ${extension} 文件`);
      return;
    }
    if (selected.size > limit) {
      setError(`文件不能超过 ${isWit() ? "4 MB" : "64 MB"}`);
      return;
    }
    setError("");
    await props.onSubmit(selected).catch((submitError: unknown) => {
      setError(submitError instanceof Error ? submitError.message : `${title()}失败`);
    });
  }

  return (
    <DialogFrame
      title={title()}
      description={
        isWit()
          ? "包名、版本和直接依赖从二进制 WIT Package 读取。"
          : "组件身份、WIT World 和运行边界由平台自动校验。"
      }
      icon={isWit() ? <FileCode2 size={19} /> : <CloudUpload size={19} />}
      closeDisabled={props.busy}
      onClose={props.onClose}
      footer={
        <>
          <Button disabled={props.busy} onClick={props.onClose}>
            取消
          </Button>
          <Button variant="primary" disabled={props.busy || !file()} onClick={() => void submit()}>
            {props.busy ? <RefreshCw class="animate-spin" size={16} /> : <ShieldCheck size={16} />}
            {props.busy ? "正在校验" : isWit() ? "校验并发布" : "校验并注册"}
          </Button>
        </>
      }
    >
      <label class="flex min-h-48 cursor-pointer flex-col items-center justify-center gap-2 rounded-md border border-dashed border-line bg-[#fbfcfc] px-5 text-center text-muted transition-colors hover:border-brand hover:bg-brand-soft">
        <input
          class="sr-only"
          type="file"
          accept={isWit() ? ".wasm,application/wasm" : ".wasmpkg,application/zip"}
          onChange={(event) => {
            setFile(event.currentTarget.files?.[0] ?? null);
            setError("");
          }}
        />
        {file() ? (
          isWit() ? (
            <FileCode2 size={28} />
          ) : (
            <Package size={28} />
          )
        ) : (
          <CloudUpload size={28} />
        )}
        <strong class="text-sm text-ink">
          {file()?.name ?? `选择 ${isWit() ? "WIT Package" : "组件包"}`}
        </strong>
        <span class="text-xs">
          {file()
            ? `${(file()!.size / 1024).toFixed(1)} KB · 等待校验`
            : `${isWit() ? ".wasm · 最大 4 MB" : ".wasmpkg · 最大 64 MB"}`}
        </span>
      </label>
      <Show when={error()}>
        <div class="mt-3 rounded-md border border-coral-strong/30 bg-coral-soft px-3 py-2 text-sm text-coral-strong">
          {error()}
        </div>
      </Show>
    </DialogFrame>
  );
}

function ActivateDialog(props: {
  dialog: Extract<DialogState, { type: "activate" }>;
  busy: boolean;
  onClose: () => void;
  onActivate: (service: Service) => Promise<void>;
}) {
  return (
    <DialogFrame
      title="切换对外版本"
      description="更新该服务在 Gateway 上接收请求的活动版本。"
      icon={<RadioTower size={19} />}
      closeDisabled={props.busy}
      onClose={props.onClose}
      footer={
        <>
          <Button disabled={props.busy} onClick={props.onClose}>
            取消
          </Button>
          <Button
            variant="primary"
            disabled={props.busy}
            onClick={() => void props.onActivate(props.dialog.service)}
          >
            {props.busy ? <RefreshCw class="animate-spin" size={16} /> : <RadioTower size={16} />}
            {props.busy ? "正在切换" : "确认切换"}
          </Button>
        </>
      }
    >
      <div class="grid grid-cols-[1fr_auto_1fr] items-center gap-3 rounded-md border border-line bg-canvas p-4">
        <div>
          <span class="text-xs text-muted">当前版本</span>
          <strong class="mt-1 block font-mono text-xs">
            {props.dialog.currentRevision ?? "尚未部署"}
          </strong>
        </div>
        <ArrowRight size={18} class="text-muted" />
        <div>
          <span class="text-xs text-muted">目标版本</span>
          <strong class="mt-1 block font-mono text-xs text-brand">
            {props.dialog.service.revision}
          </strong>
        </div>
      </div>
      <p class="mt-4 text-sm leading-6 text-muted">
        Wasmeld 将先确认 {props.dialog.service.id}@{props.dialog.service.revision} 的 Actor
        可用，再更新对外路由。
      </p>
    </DialogFrame>
  );
}

function InvokeDialog(props: {
  service: Service;
  onClose: () => void;
  onInvoke: (service: Service, input: Uint8Array) => Promise<InvokeResult>;
}) {
  const [input, setInput] = createSignal(props.service.id === "echo" ? "hello wasm" : "");
  const [format, setFormat] = createSignal<"utf8" | "hex">("utf8");
  const [output, setOutput] = createSignal("");
  const [outputMeta, setOutputMeta] = createSignal("");
  const [running, setRunning] = createSignal(false);
  const [failed, setFailed] = createSignal(false);
  const inputBytes = createMemo(() => invocationInputSize(input(), format()));

  async function invoke() {
    setRunning(true);
    setOutput("");
    setOutputMeta("");
    setFailed(false);
    try {
      const result = await props.onInvoke(props.service, parseInvocationInput(input(), format()));
      const formatted = formatInvocationOutput(props.service, result.output, format());
      setOutput(formatted.text);
      setOutputMeta(
        `${result.outputBytes} B · ${formatted.format}${formatted.automatic ? " · 自动" : ""} · ${result.latencyMs} ms`,
      );
    } catch (error) {
      setFailed(true);
      setOutput(error instanceof Error ? error.message : "组件调用失败");
    } finally {
      setRunning(false);
    }
  }

  return (
    <DialogFrame
      title={`调用 ${props.service.id}@${props.service.revision}`}
      description="请求将进入该实例的串行 mailbox。"
      icon={<SquareTerminal size={19} />}
      closeDisabled={running()}
      onClose={props.onClose}
      footer={
        <>
          <Button disabled={running()} onClick={props.onClose}>
            关闭
          </Button>
          <Button variant="primary" disabled={running()} onClick={() => void invoke()}>
            {running() ? <RefreshCw class="animate-spin" size={16} /> : <Play size={16} />}
            执行调用
          </Button>
        </>
      }
    >
      <div class="mb-3 flex items-center justify-between gap-3">
        <SegmentedControl aria-label="输入格式">
          <Segment active={format() === "utf8"} onClick={() => setFormat("utf8")}>
            UTF-8
          </Segment>
          <Segment active={format() === "hex"} onClick={() => setFormat("hex")}>
            HEX
          </Segment>
        </SegmentedControl>
        <span class="font-mono text-xs text-muted">{inputBytes()} B</span>
      </div>
      <Field>
        <FieldLabel>输入</FieldLabel>
        <Textarea
          value={input()}
          spellcheck={false}
          placeholder={format() === "utf8" ? "输入请求内容" : "00 ff a1"}
          onInput={(event) => setInput(event.currentTarget.value)}
        />
      </Field>
      <div class="mt-4">
        <div class="mb-1.5 flex items-center justify-between">
          <span class="text-xs font-semibold text-muted">输出</span>
          <Show when={outputMeta()}>
            <span class="inline-flex items-center gap-1 font-mono text-[11px] text-brand">
              <Clock3 size={12} />
              {outputMeta()}
            </span>
          </Show>
        </div>
        <pre
          classList={{
            "min-h-32 overflow-auto rounded-md border p-4 font-mono text-sm whitespace-pre-wrap": true,
            "border-coral-strong/30 bg-coral-soft text-coral-strong": failed(),
            "border-[#263532] bg-[#16221f] text-[#c8eee1]": !failed(),
          }}
        >
          {running() ? "invoking..." : output() || "等待调用"}
        </pre>
      </div>
    </DialogFrame>
  );
}
