"use client";

import { useCallback, useEffect, useMemo, useState } from "react";
import {
  ChevronDown,
  ChevronUp,
  Copy,
  Loader2,
  MoreHorizontal,
  Pencil,
  Plus,
  Trash2,
} from "lucide-react";

import { McpServersSection } from "@/components/tools/McpServersSection";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { DataList, type DataListColumn } from "@/components/ui/data-list";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { FilterPills } from "@/components/ui/filter-pills";
import { ListToolbar } from "@/components/ui/list-toolbar";
import { PageHeader } from "@/components/ui/page-header";
import { SearchInput } from "@/components/ui/search-input";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
import {
  toolsApi,
  type HttpToolDefinition,
  type Tool,
  type ToolParameter,
  type ToolStatus,
  type ToolUpsert,
} from "@/lib/api";

type ToolKind = "end_call" | "http" | "mcp";
type HttpMethod = HttpToolDefinition["config"]["method"];
type ToolFilter = "全部" | "End Call" | "HTTP" | "MCP";
type SortOrder = "newest" | "oldest";

const toolFilters: readonly ToolFilter[] = ["全部", "End Call", "HTTP", "MCP"];

type ToolForm = {
  name: string;
  functionName: string;
  type: ToolKind;
  description: string;
  status: ToolStatus;
  messageType: "none" | "custom";
  customMessage: string;
  captureReason: boolean;
  method: HttpMethod;
  url: string;
  timeoutSeconds: string;
  headers: string;
  secretHeaders: string;
  parameters: string;
  body: string;
  dynamicVariableAssignments: string;
  secretDynamicVariables: string;
  mcpServerId: string;
  remoteToolName: string;
  inputSchema: string;
  schemaHash: string;
};

const EMPTY_OBJECT = "{}";
const EMPTY_ARRAY = "[]";

function blankForm(): ToolForm {
  return {
    name: "",
    functionName: "",
    type: "end_call",
    description: "",
    status: "active",
    messageType: "none",
    customMessage: "",
    captureReason: true,
    method: "GET",
    url: "",
    timeoutSeconds: "15",
    headers: EMPTY_OBJECT,
    secretHeaders: EMPTY_OBJECT,
    parameters: EMPTY_ARRAY,
    body: EMPTY_OBJECT,
    dynamicVariableAssignments: EMPTY_OBJECT,
    secretDynamicVariables: EMPTY_OBJECT,
    mcpServerId: "",
    remoteToolName: "",
    inputSchema: EMPTY_OBJECT,
    schemaHash: "",
  };
}

function pretty(value: unknown, fallback: string): string {
  return JSON.stringify(value ?? JSON.parse(fallback), null, 2);
}

function formFromTool(tool: Tool): ToolForm {
  const base = { ...blankForm(), name: tool.name, functionName: tool.functionName };
  base.type = tool.type;
  base.description = tool.description;
  base.status = tool.status;
  if (tool.definition.type === "end_call") {
    base.messageType = tool.definition.config.messageType;
    base.customMessage = tool.definition.config.customMessage;
    base.captureReason = tool.definition.config.captureReason;
    return base;
  }
  if (tool.definition.type === "mcp") {
    base.mcpServerId = tool.mcpServerId ?? "";
    base.remoteToolName = tool.definition.config.remoteToolName;
    base.inputSchema = pretty(tool.definition.config.inputSchema, EMPTY_OBJECT);
    base.schemaHash = tool.definition.config.schemaHash;
    base.dynamicVariableAssignments = pretty(
      tool.definition.config.dynamicVariableAssignments ?? {},
      EMPTY_OBJECT,
    );
    return base;
  }
  base.method = tool.definition.config.method;
  base.url = tool.definition.config.url;
  base.timeoutSeconds = String(tool.definition.config.timeoutSeconds);
  base.headers = pretty(tool.definition.config.headers, EMPTY_OBJECT);
  base.parameters = pretty(tool.definition.config.parameters, EMPTY_ARRAY);
  base.body = pretty(tool.definition.config.body, EMPTY_OBJECT);
  base.dynamicVariableAssignments = pretty(
    tool.definition.config.dynamicVariableAssignments ?? {},
    EMPTY_OBJECT,
  );
  const secrets = tool.secrets as {
    headers?: Record<string, string>;
    dynamic_variables?: Record<string, string>;
  };
  base.secretHeaders = pretty(secrets.headers ?? {}, EMPTY_OBJECT);
  base.secretDynamicVariables = pretty(
    secrets.dynamic_variables ?? {},
    EMPTY_OBJECT,
  );
  return base;
}

function parseObject(value: string, label: string): Record<string, unknown> {
  let parsed: unknown;
  try {
    parsed = JSON.parse(value || EMPTY_OBJECT);
  } catch {
    throw new Error(`${label}不是有效的 JSON`);
  }
  if (!parsed || Array.isArray(parsed) || typeof parsed !== "object") {
    throw new Error(`${label}必须是 JSON 对象`);
  }
  return parsed as Record<string, unknown>;
}

function parseParameters(value: string): ToolParameter[] {
  let parsed: unknown;
  try {
    parsed = JSON.parse(value || EMPTY_ARRAY);
  } catch {
    throw new Error("参数定义不是有效的 JSON");
  }
  if (!Array.isArray(parsed)) throw new Error("参数定义必须是 JSON 数组");
  return parsed as ToolParameter[];
}

function functionNameFrom(value: string): string {
  return value
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "_")
    .replace(/^_+|_+$/g, "")
    .slice(0, 64);
}

function payloadFromForm(form: ToolForm): ToolUpsert {
  if (!form.name.trim()) throw new Error("请输入工具名称");
  if (!/^[a-z][a-z0-9_]{0,63}$/.test(form.functionName)) {
    throw new Error("函数名必须以小写字母开头，且只能包含小写字母、数字和下划线");
  }

  if (form.type === "end_call") {
    return {
      name: form.name.trim(),
      functionName: form.functionName,
      description: form.description.trim(),
      status: form.status,
      secrets: {},
      definition: {
        schemaVersion: 1,
        type: "end_call",
        config: {
          messageType: form.messageType,
          customMessage: form.messageType === "custom" ? form.customMessage : "",
          captureReason: form.captureReason,
        },
      },
    };
  }
  if (form.type === "mcp") {
    return {
      name: form.name.trim(),
      functionName: form.functionName,
      description: form.description.trim(),
      status: form.status,
      mcpServerId: form.mcpServerId,
      secrets: {},
      definition: {
        schemaVersion: 1,
        type: "mcp",
        config: {
          remoteToolName: form.remoteToolName,
          inputSchema: parseObject(form.inputSchema, "MCP Input Schema"),
          schemaHash: form.schemaHash,
          dynamicVariableAssignments: parseObject(
            form.dynamicVariableAssignments,
            "变量赋值",
          ) as Record<string, string>,
        },
      },
    };
  }

  const timeoutSeconds = Number(form.timeoutSeconds);
  if (!Number.isInteger(timeoutSeconds) || timeoutSeconds < 1 || timeoutSeconds > 120) {
    throw new Error("超时时间必须是 1 到 120 秒之间的整数");
  }
  const headers = parseObject(form.headers, "Header");
  const secretHeaders = parseObject(form.secretHeaders, "敏感 Header");
  const body = parseObject(form.body, "固定 Body");
  const dynamicVariableAssignments = parseObject(
    form.dynamicVariableAssignments,
    "变量赋值",
  );
  const secretDynamicVariables = parseObject(
    form.secretDynamicVariables,
    "密钥变量",
  );
  const parameters = parseParameters(form.parameters);
  return {
    name: form.name.trim(),
    functionName: form.functionName,
    description: form.description.trim(),
    status: form.status,
    definition: {
      schemaVersion: 1,
      type: "http",
      config: {
        method: form.method,
        url: form.url.trim(),
        timeoutSeconds,
        headers: headers as Record<string, string>,
        parameters,
        body,
        dynamicVariableAssignments: dynamicVariableAssignments as Record<string, string>,
      },
    },
    secrets: {
      headers: secretHeaders,
      dynamic_variables: secretDynamicVariables,
    },
  };
}

function formatTimestamp(value?: string | null): string {
  if (!value) return "—";
  return new Intl.DateTimeFormat("zh-CN", {
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
  }).format(new Date(value));
}

function updatedAtValue(value?: string | null): number {
  const time = value ? new Date(value).getTime() : NaN;
  return Number.isNaN(time) ? 0 : time;
}

export function ComponentsToolsPage() {
  const [tools, setTools] = useState<Tool[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [search, setSearch] = useState("");
  const [filter, setFilter] = useState<ToolFilter>("全部");
  const [sortOrder, setSortOrder] = useState<SortOrder>("newest");
  const [currentPage, setCurrentPage] = useState(1);
  const [dialogOpen, setDialogOpen] = useState(false);
  const [editing, setEditing] = useState<Tool | null>(null);
  const [form, setForm] = useState<ToolForm>(blankForm);
  const [saving, setSaving] = useState(false);
  const [formError, setFormError] = useState<string | null>(null);
  const [functionNameTouched, setFunctionNameTouched] = useState(false);
  const [deletingId, setDeletingId] = useState<string | null>(null);
  const [duplicatingId, setDuplicatingId] = useState<string | null>(null);

  const loadTools = useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      setTools(await toolsApi.list());
    } catch (loadError) {
      setError(loadError instanceof Error ? loadError.message : "加载工具失败");
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    // eslint-disable-next-line react-hooks/set-state-in-effect
    void loadTools();
  }, [loadTools]);

  const filteredTools = useMemo(() => {
    const query = search.trim().toLowerCase();
    return tools.filter((tool) =>
      (filter === "全部" ||
        (filter === "End Call" && tool.type === "end_call") ||
        (filter === "HTTP" && tool.type === "http") ||
        (filter === "MCP" && tool.type === "mcp")) &&
      (!query ||
        [tool.name, tool.functionName, tool.description].some((value) =>
          value.toLowerCase().includes(query),
        )),
    );
  }, [filter, search, tools]);

  const sortedTools = useMemo(() => {
    return [...filteredTools].sort((a, b) => {
      const diff = updatedAtValue(b.updatedAt) - updatedAtValue(a.updatedAt);
      if (diff !== 0) return sortOrder === "newest" ? diff : -diff;
      return a.id.localeCompare(b.id);
    });
  }, [filteredTools, sortOrder]);

  const pageSize = 5;
  const totalPages = Math.max(1, Math.ceil(sortedTools.length / pageSize));
  const safeCurrentPage = Math.min(currentPage, totalPages);
  const pageStart = (safeCurrentPage - 1) * pageSize;
  const pageEnd = pageStart + pageSize;
  const paginatedTools = sortedTools.slice(pageStart, pageEnd);

  function changeFilter(value: ToolFilter) {
    setFilter(value);
    setCurrentPage(1);
  }

  function changeSearch(value: string) {
    setSearch(value);
    setCurrentPage(1);
  }

  function openCreate() {
    setEditing(null);
    setForm(blankForm());
    setFunctionNameTouched(false);
    setFormError(null);
    setDialogOpen(true);
  }

  function openEdit(tool: Tool) {
    setEditing(tool);
    setForm(formFromTool(tool));
    setFunctionNameTouched(true);
    setFormError(null);
    setDialogOpen(true);
  }

  async function saveTool() {
    if (saving) return;
    setSaving(true);
    setFormError(null);
    try {
      const payload = payloadFromForm(form);
      if (editing) await toolsApi.update(editing.id, payload);
      else await toolsApi.create(payload);
      setDialogOpen(false);
      await loadTools();
    } catch (saveError) {
      setFormError(saveError instanceof Error ? saveError.message : "保存失败");
    } finally {
      setSaving(false);
    }
  }

  async function removeTool(tool: Tool) {
    if (!window.confirm(`确认删除工具“${tool.name}”？`)) return;
    setDeletingId(tool.id);
    try {
      await toolsApi.remove(tool.id);
      await loadTools();
    } catch (removeError) {
      setError(removeError instanceof Error ? removeError.message : "删除失败");
    } finally {
      setDeletingId(null);
    }
  }

  async function duplicateTool(id: string) {
    setDuplicatingId(id);
    try {
      await toolsApi.duplicate(id);
      await loadTools();
    } catch (duplicateError) {
      setError(duplicateError instanceof Error ? duplicateError.message : "复制失败");
    } finally {
      setDuplicatingId(null);
    }
  }

  const columns: DataListColumn<Tool>[] = [
    {
      key: "name",
      header: "工具名称",
      width: "md:w-[360px]",
      cell: (tool) => (
        <>
          <div className="flex min-w-0 items-center gap-2">
            <span className="truncate font-medium text-foreground">{tool.name}</span>
          </div>
          <div className="mt-1 truncate font-mono text-xs text-muted-soft">
            {tool.functionName}
          </div>
        </>
      ),
    },
    {
      key: "type",
      header: "类型",
      width: "md:w-[128px]",
      cell: (tool) => (
        <Badge
          variant="secondary"
          className="h-6 bg-surface-strong px-3 text-muted-foreground"
        >
          {tool.type === "end_call"
            ? "End Call"
            : tool.type === "mcp"
              ? "MCP"
              : "HTTP"}
        </Badge>
      ),
    },
    {
      key: "status",
      header: "状态",
      width: "md:w-[156px]",
      cell: (tool) => (
        <Badge variant="outline" className="h-6 px-3">
          {tool.status === "active" ? "启用" : tool.status === "draft" ? "草稿" : "已归档"}
        </Badge>
      ),
    },
    {
      key: "updated",
      width: "md:w-[176px]",
      header: (
        <button
          type="button"
          onClick={() => {
            setSortOrder((current) => (current === "newest" ? "oldest" : "newest"));
            setCurrentPage(1);
          }}
          className="caption-label -mx-2 inline-flex items-center gap-1 rounded-md px-2 py-1 text-muted-soft transition-colors hover:bg-surface-strong hover:text-foreground"
          aria-label={sortOrder === "newest" ? "当前按最近更新排序" : "当前按最早更新排序"}
        >
          更新时间
          {sortOrder === "newest" ? <ChevronDown size={13} /> : <ChevronUp size={13} />}
        </button>
      ),
      cellClassName: "whitespace-nowrap tabular-nums text-muted-foreground",
      cell: (tool) => formatTimestamp(tool.updatedAt),
    },
    {
      key: "actions",
      header: "操作",
      align: "right",
      cellClassName: "flex justify-end gap-2",
      cell: (tool) => (
        <>
          <Button
            variant="outline"
            size="sm"
            className="gap-1.5 border-hairline-strong text-xs text-muted-foreground hover:text-foreground"
            onClick={() => openEdit(tool)}
          >
            <Pencil size={14} />
            编辑
          </Button>
          <DropdownMenu>
            <DropdownMenuTrigger asChild>
              <Button
                variant="outline"
                size="icon-sm"
                className="border-hairline-strong text-muted-foreground hover:text-foreground"
                aria-label={`${tool.name} 更多操作`}
              >
                <MoreHorizontal size={15} />
              </Button>
            </DropdownMenuTrigger>
            <DropdownMenuContent
              align="end"
              className="w-32 min-w-32 rounded-xl border border-hairline bg-popover p-1"
            >
              <DropdownMenuItem
                className="rounded-lg"
                disabled={duplicatingId === tool.id || tool.type === "mcp"}
                onSelect={(event) => {
                  event.preventDefault();
                  void duplicateTool(tool.id);
                }}
              >
                {duplicatingId === tool.id ? (
                  <Loader2 size={14} className="animate-spin" />
                ) : (
                  <Copy size={14} />
                )}
                复制
              </DropdownMenuItem>
              <DropdownMenuItem
                variant="destructive"
                className="rounded-lg"
                disabled={deletingId === tool.id}
                onSelect={(event) => {
                  event.preventDefault();
                  void removeTool(tool);
                }}
              >
                {deletingId === tool.id ? (
                  <Loader2 size={14} className="animate-spin" />
                ) : (
                  <Trash2 size={14} />
                )}
                删除
              </DropdownMenuItem>
            </DropdownMenuContent>
          </DropdownMenu>
        </>
      ),
    },
  ];

  return (
    <div className="mx-auto flex w-full max-w-[1440px] flex-col gap-8">
      <PageHeader
        title="工具资源"
        description="管理可复用的助手工具，并将启用的工具绑定到提示词助手。"
        action={
          <Button size="lg" className="w-full gap-2 sm:w-auto" onClick={openCreate}>
            <Plus size={16} />
            添加工具
          </Button>
        }
      />

      <McpServersSection onToolsChanged={loadTools} />

      <section className="rounded-2xl border border-hairline bg-card p-6 shadow-sm">
        <ListToolbar
          filters={
            <FilterPills options={toolFilters} value={filter} onChange={changeFilter} />
          }
          search={
            <SearchInput
              value={search}
              onChange={changeSearch}
              placeholder="搜索名称、函数名或描述"
              className="lg:w-[320px]"
            />
          }
        />
        <DataList<Tool>
          columns={columns}
          rows={paginatedTools}
          rowKey={(tool) => tool.id}
          loading={loading}
          loadingText="正在加载工具…"
          error={error}
          onRetry={() => void loadTools()}
          empty={{
            title: tools.length === 0 ? "暂无工具资源" : "未找到匹配的工具资源",
            description:
              tools.length === 0
                ? "点击右上角「添加工具」开始。"
                : "请调整关键词或筛选条件后再试。",
          }}
          pagination={{
            page: safeCurrentPage,
            totalPages,
            onPageChange: setCurrentPage,
            summary:
              filteredTools.length === 0
                ? "没有数据"
                : `显示 ${pageStart + 1}-${Math.min(pageEnd, filteredTools.length)} / 共 ${filteredTools.length} 个工具资源`,
          }}
        />
      </section>

      <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
        <DialogContent className="max-h-[calc(100vh-3rem)] overflow-y-auto sm:max-w-6xl lg:overflow-hidden">
          <DialogHeader>
            <DialogTitle>{editing ? "编辑工具资源" : "添加工具资源"}</DialogTitle>
            <DialogDescription>
              左侧配置工具的固定信息，右侧参数会随工具类型更新。
            </DialogDescription>
          </DialogHeader>

          <div className="grid gap-5 lg:grid-cols-2">
            <FieldSection title="基本信息" tall>
              <Field label="工具名称" required>
                <Input
                  value={form.name}
                  onChange={(event) => {
                    const name = event.target.value;
                    setForm((current) => ({
                      ...current,
                      name,
                      functionName: functionNameTouched
                        ? current.functionName
                        : functionNameFrom(name),
                    }));
                  }}
                />
              </Field>
              <Field label="函数名" required>
                <Input
                  value={form.functionName}
                  onChange={(event) => {
                    setFunctionNameTouched(true);
                    setForm((current) => ({ ...current, functionName: event.target.value }));
                  }}
                  placeholder="query_order"
                />
              </Field>
              <Field label="工具类型" required>
                <Select
                  value={form.type}
                  disabled={form.type === "mcp"}
                  onValueChange={(type: ToolKind) =>
                    setForm((current) => ({ ...current, type }))
                  }
                >
                  <SelectTrigger className="w-full"><SelectValue /></SelectTrigger>
                  <SelectContent>
                    <SelectItem value="end_call">End Call</SelectItem>
                    <SelectItem value="http">HTTP</SelectItem>
                    <SelectItem value="mcp" disabled={!editing || form.type !== "mcp"}>
                      MCP（由 Server 同步）
                    </SelectItem>
                  </SelectContent>
                </Select>
              </Field>
              <Field label="状态">
                <Select
                  value={form.status}
                  onValueChange={(status: ToolStatus) =>
                    setForm((current) => ({ ...current, status }))
                  }
                >
                  <SelectTrigger className="w-full"><SelectValue /></SelectTrigger>
                  <SelectContent>
                    <SelectItem value="active">启用</SelectItem>
                    <SelectItem value="draft">草稿</SelectItem>
                    <SelectItem value="archived">归档</SelectItem>
                  </SelectContent>
                </Select>
              </Field>
              <Field label="描述">
                <Textarea
                  value={form.description}
                  onChange={(event) =>
                    setForm((current) => ({ ...current, description: event.target.value }))
                  }
                  rows={5}
                />
              </Field>
            </FieldSection>

            <FieldSection title="参数配置" scrollable tall>
              {form.type === "end_call" ? (
                <EndCallFields form={form} setForm={setForm} />
              ) : form.type === "mcp" ? (
                <McpFields form={form} setForm={setForm} />
              ) : (
                <HttpFields form={form} setForm={setForm} />
              )}
            </FieldSection>
          </div>

          {formError && <div className="text-sm text-destructive">{formError}</div>}

          <DialogFooter>
            <div className="flex items-center gap-2">
              <Button variant="outline" onClick={() => setDialogOpen(false)}>取消</Button>
              <Button onClick={() => void saveTool()} disabled={saving}>
                {saving && <Loader2 size={15} className="animate-spin" />}
                保存
              </Button>
            </div>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </div>
  );
}

function EndCallFields({
  form,
  setForm,
}: {
  form: ToolForm;
  setForm: React.Dispatch<React.SetStateAction<ToolForm>>;
}) {
  return (
    <div className="space-y-4">
      <Field label="结束语">
        <Select
          value={form.messageType}
          onValueChange={(messageType: "none" | "custom") =>
            setForm((current) => ({ ...current, messageType }))
          }
        >
          <SelectTrigger className="w-full border-hairline-strong bg-background"><SelectValue /></SelectTrigger>
          <SelectContent>
            <SelectItem value="none">不播放固定结束语</SelectItem>
            <SelectItem value="custom">自定义结束语</SelectItem>
          </SelectContent>
        </Select>
      </Field>
      {form.messageType === "custom" && (
        <Field label="自定义结束语">
          <Textarea
            value={form.customMessage}
            onChange={(event) =>
              setForm((current) => ({ ...current, customMessage: event.target.value }))
            }
            rows={3}
          />
        </Field>
      )}
      <div className="flex items-center justify-between gap-4 rounded-lg border border-hairline-strong px-4 py-3">
        <div>
          <div className="font-medium text-foreground">记录结束原因</div>
          <div className="mt-0.5 text-xs text-muted-foreground">要求模型在调用时提供 reason 参数</div>
        </div>
        <Switch
          checked={form.captureReason}
          onCheckedChange={(captureReason) =>
            setForm((current) => ({ ...current, captureReason }))
          }
        />
      </div>
    </div>
  );
}

function McpFields({
  form,
  setForm,
}: {
  form: ToolForm;
  setForm: React.Dispatch<React.SetStateAction<ToolForm>>;
}) {
  return (
    <div className="space-y-4">
      <div className="rounded-xl border border-hairline bg-canvas-soft px-4 py-3 text-sm text-muted-foreground">
        连接和参数 Schema 由 MCP Server 同步维护；这里仅配置本地名称、状态和结果变量赋值。
      </div>
      <Field label="远端工具名称">
        <Input value={form.remoteToolName} disabled className="font-mono" />
      </Field>
      <Field label="MCP Server ID">
        <Input value={form.mcpServerId} disabled className="font-mono" />
      </Field>
      <JsonField
        label="Input Schema（只读）"
        value={form.inputSchema}
        onChange={() => undefined}
        rows={10}
        disabled
      />
      <JsonField
        label="响应变量赋值"
        value={form.dynamicVariableAssignments}
        onChange={(dynamicVariableAssignments) =>
          setForm((current) => ({ ...current, dynamicVariableAssignments }))
        }
      />
    </div>
  );
}

function HttpFields({
  form,
  setForm,
}: {
  form: ToolForm;
  setForm: React.Dispatch<React.SetStateAction<ToolForm>>;
}) {
  return (
    <div className="space-y-4">
      <div className="grid gap-4 sm:grid-cols-[140px_1fr]">
        <Field label="方法">
          <Select
            value={form.method}
            onValueChange={(method: HttpMethod) =>
              setForm((current) => ({ ...current, method }))
            }
          >
            <SelectTrigger className="w-full border-hairline-strong bg-background"><SelectValue /></SelectTrigger>
            <SelectContent>
              {(["GET", "POST", "PUT", "PATCH", "DELETE"] as HttpMethod[]).map((method) => (
                <SelectItem key={method} value={method}>{method}</SelectItem>
              ))}
            </SelectContent>
          </Select>
        </Field>
        <Field label="URL">
          <Input
            value={form.url}
            onChange={(event) => setForm((current) => ({ ...current, url: event.target.value }))}
            placeholder="https://api.example.com/orders/{order_id}"
          />
        </Field>
      </div>
      <Field label="超时时间（秒）">
        <Input
          type="number"
          min={1}
          max={120}
          value={form.timeoutSeconds}
          onChange={(event) =>
            setForm((current) => ({ ...current, timeoutSeconds: event.target.value }))
          }
        />
      </Field>
      <JsonField label="Header" value={form.headers} onChange={(headers) => setForm((current) => ({ ...current, headers }))} />
      <JsonField label="敏感 Header" value={form.secretHeaders} onChange={(secretHeaders) => setForm((current) => ({ ...current, secretHeaders }))} />
      <JsonField label="参数定义" value={form.parameters} onChange={(parameters) => setForm((current) => ({ ...current, parameters }))} rows={6} />
      <JsonField label="固定 Body" value={form.body} onChange={(body) => setForm((current) => ({ ...current, body }))} />
      <JsonField
        label="响应变量赋值"
        value={form.dynamicVariableAssignments}
        onChange={(dynamicVariableAssignments) =>
          setForm((current) => ({ ...current, dynamicVariableAssignments }))
        }
      />
      <JsonField
        label="密钥变量"
        value={form.secretDynamicVariables}
        onChange={(secretDynamicVariables) =>
          setForm((current) => ({ ...current, secretDynamicVariables }))
        }
      />
    </div>
  );
}

function Field({
  label,
  required,
  children,
}: {
  label: string;
  required?: boolean;
  children: React.ReactNode;
}) {
  return (
    <label className="block space-y-2">
      <span className="text-sm font-medium text-foreground">
        {label}
        {required && <span className="ml-1 text-destructive">*</span>}
      </span>
      {children}
    </label>
  );
}

function FieldSection({
  title,
  scrollable,
  tall,
  children,
}: {
  title: string;
  scrollable?: boolean;
  tall?: boolean;
  children: React.ReactNode;
}) {
  return (
    <section
      className={[
        "rounded-xl border border-hairline bg-surface-strong/20",
        tall ? "lg:flex lg:h-[38rem] lg:flex-col" : "",
      ].join(" ")}
    >
      <div className="border-b border-hairline px-4 py-3 text-sm font-medium">
        {title}
      </div>
      <div
        className={[
          "space-y-4 p-4",
          scrollable ? "max-h-72 overflow-y-auto" : "",
          tall ? "lg:max-h-none lg:flex-1" : "",
        ].join(" ")}
      >
        {children}
      </div>
    </section>
  );
}

function JsonField({
  label,
  value,
  onChange,
  rows = 4,
  disabled = false,
}: {
  label: string;
  value: string;
  onChange: (value: string) => void;
  rows?: number;
  disabled?: boolean;
}) {
  return (
    <Field label={label}>
      <Textarea
        value={value}
        onChange={(event) => onChange(event.target.value)}
        rows={rows}
        disabled={disabled}
        className="font-mono text-xs"
        spellCheck={false}
      />
    </Field>
  );
}
