"use client";

import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import { useState } from "react";
import clsx from "clsx";
import { sendAppLog } from "@/components/app-runtime";
import { DeviceImportDraftManager } from "@/components/device-import-draft-manager";
import {
  clearNativeSessionSnapshot,
  currentAppLocation,
  isNativeBossApp,
  persistNativeSessionSnapshot,
  popAppHistoryEntry,
  resolveAppBackAction,
} from "@/lib/boss-app-client";
import {
  extractApprovedTargetProjectIds,
  summarizeDispatchPlan,
} from "@/lib/dispatch-plan-ui";
import type {
  Device,
  DeviceEnrollment,
  GoalItem,
  MasterIdentitySummary,
  Message,
  OtaUpdate,
  OtaUpdateLog,
  OpsRepairTicket,
  OpsRepairVerification,
  ThreadContextSnapshot,
  UserProfile,
  UserSettings,
} from "@/lib/boss-data";
import type { ConversationItem } from "@/lib/boss-projections";
import { formatTimestampLabel } from "@/lib/boss-projections";

function formatClock(value: string) {
  return formatTimestampLabel(value);
}

function formatBytes(value?: number) {
  if (!value || value <= 0) return "未知大小";
  if (value >= 1024 * 1024) return `${(value / 1024 / 1024).toFixed(2)} MB`;
  if (value >= 1024) return `${(value / 1024).toFixed(1)} KB`;
  return `${value} B`;
}

function boundDeviceIdFromDom() {
  return document.body.dataset.boundDeviceId || "mac-studio";
}

async function waitForLoginSessionReady(nativeClient: boolean) {
  for (let attempt = 0; attempt < 5; attempt += 1) {
    const response = await fetch("/api/auth/session", {
      cache: "no-store",
      headers: nativeClient ? { "x-boss-native-app": "1" } : undefined,
    }).catch(() => null);
    if (response?.ok) {
      return true;
    }
    await new Promise((resolve) => window.setTimeout(resolve, 120));
  }
  return false;
}

function navigateToConversations(router: ReturnType<typeof useRouter>) {
  router.replace("/conversations", { scroll: false });
  router.refresh();
  window.setTimeout(() => {
    if (window.location.pathname !== "/conversations") {
      window.location.replace("/conversations");
    }
  }, 180);
}

export function AppShell({
  children,
  bottomNav = true,
}: {
  children: React.ReactNode;
  bottomNav?: boolean;
}) {
  return (
    <main className="min-h-[100dvh] md:px-6 md:py-6">
      <div
        className={clsx(
          "flex w-full flex-col bg-[rgba(247,249,245,0.90)] backdrop-blur-[18px]",
          bottomNav ? "min-h-[100dvh] overflow-hidden md:h-[calc(100dvh-48px)]" : "min-h-[100dvh]",
          "md:mx-auto md:max-w-[430px] md:overflow-hidden md:rounded-[32px] md:border md:border-white/70 md:shadow-[0_20px_48px_rgba(22,31,44,0.14)]",
        )}
      >
        <div
          className={clsx(
            "flex min-h-0 flex-1 flex-col",
            bottomNav ? "overflow-y-auto overscroll-y-contain boss-scrollbar" : "",
          )}
        >
          {children}
        </div>
        {bottomNav ? <BottomNav /> : null}
      </div>
    </main>
  );
}

export function StatusBar() {
  return <div className="shrink-0" style={{ height: "max(12px, env(safe-area-inset-top))" }} />;
}

export function PageNav({
  title,
  backHref,
  rightLabel,
  rightHref,
  rightNode,
}: {
  title: string;
  backHref?: string;
  rightLabel?: string;
  rightHref?: string;
  rightNode?: React.ReactNode;
}) {
  const router = useRouter();

  async function handleBack() {
    const currentPath = currentAppLocation();
    const action = resolveAppBackAction(currentPath, backHref);
    if (action.mode === "history") {
      popAppHistoryEntry(currentPath);
      router.back();
      return;
    }
    if (action.mode === "replace") {
      popAppHistoryEntry(currentPath);
      router.replace(action.target, { scroll: false });
    }
  }

  return (
    <div className="flex items-center justify-between px-[18px] py-2">
      {backHref ? (
        <button type="button" onClick={() => void handleBack()} className="text-[15px] text-[#57606A]">
          {"< 返回"}
        </button>
      ) : (
        <span className="w-12" />
      )}
      <h1 className="text-[17px] font-semibold text-[#111111]">{title}</h1>
      {rightNode ? (
        rightNode
      ) : rightLabel ? (
        <Link href={rightHref ?? "#"} className="text-[15px] font-semibold text-[#07C160]">
          {rightLabel}
        </Link>
      ) : (
        <span className="w-12" />
      )}
    </div>
  );
}

export function BottomNav() {
  const pathname = usePathname();
  const items = [
    { href: "/conversations", label: "会话", icon: "◌" },
    { href: "/devices", label: "设备", icon: "◫" },
    { href: "/me", label: "我的", icon: "◍" },
  ];

  return (
    <div className="mt-auto shrink-0 border-t border-[#E5E5EA] bg-[rgba(255,255,255,0.94)] backdrop-blur-xl">
      <div
        className="flex items-center justify-between px-[18px] pt-[8px]"
        style={{ minHeight: "64px", paddingBottom: "max(12px, env(safe-area-inset-bottom))" }}
      >
        {items.map((item) => {
          const active = pathname.startsWith(item.href);
          return (
            <Link
              key={item.href}
              href={item.href}
              className="flex w-full flex-col items-center gap-1 text-xs"
            >
              <span className={clsx("text-lg", active ? "text-[#07C160]" : "text-[#9A9A9A]")}>
                {item.icon}
              </span>
              <span className={clsx(active ? "text-[#07C160]" : "text-[#9A9A9A]")}>
                {item.label}
              </span>
            </Link>
          );
        })}
      </div>
    </div>
  );
}

export function HeaderTitle({
  title,
  extra,
}: {
  title: string;
  extra?: React.ReactNode;
}) {
  return (
    <div className="flex items-center justify-between px-[18px] py-2">
      <h1 className="text-[22px] font-semibold text-[#111111]">{title}</h1>
      {extra}
    </div>
  );
}

export function LogoutButton({
  label = "退出登录",
  compact = false,
}: {
  label?: string;
  compact?: boolean;
}) {
  const router = useRouter();
  const [loading, setLoading] = useState(false);
  const [message, setMessage] = useState("");

  async function handleLogout() {
    setLoading(true);
    const response = await fetch("/api/auth/logout", {
      method: "POST",
    });
    const result = (await response.json()) as { ok: boolean; message?: string };
    setLoading(false);
    setMessage(result.message ?? (result.ok ? "已退出登录。" : "退出失败。"));
    if (result.ok) {
      await clearNativeSessionSnapshot();
      router.push("/auth/login");
      router.refresh();
    }
  }

  if (compact) {
    return (
      <button
        type="button"
        onClick={() => void handleLogout()}
        disabled={loading}
        className="text-[15px] font-semibold text-[#07C160]"
      >
        {loading ? "退出中" : label}
      </button>
    );
  }

  return (
    <div className="rounded-2xl border border-[#E5E5EA] bg-white px-4 py-4">
      <div className="flex items-center justify-between gap-3">
        <div>
          <div className="text-[16px] font-medium text-[#111111]">{label}</div>
          <div className="mt-1 text-[12px] text-[#8C8C8C]">
            清除当前会话，回到登录页重新选择账号或验证码方式。
          </div>
        </div>
        <button
          type="button"
          onClick={() => void handleLogout()}
          disabled={loading}
          className="rounded-full bg-[#111111] px-4 py-2 text-[13px] font-semibold text-white"
        >
          {loading ? "退出中" : "立即退出"}
        </button>
      </div>
      {message ? (
        <div className="mt-3 rounded-2xl bg-[#F7F8FA] px-4 py-3 text-[12px] text-[#57606A]">
          {message}
        </div>
      ) : null}
    </div>
  );
}

export function AvatarStack({
  primary,
  secondary,
  overflowCount,
}: {
  primary: string;
  secondary?: string;
  overflowCount?: number;
}) {
  if (!secondary) {
    return (
      <div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-[#DFF4E8] text-lg font-semibold text-[#215B39]">
        {primary}
      </div>
    );
  }

  return (
    <div className="relative h-12 w-12">
      <div className="absolute left-0 top-1 flex h-8 w-8 items-center justify-center rounded-xl bg-[#DFF4E8] text-sm font-semibold text-[#215B39]">
        {primary}
      </div>
      <div className="absolute bottom-0 right-0 flex h-8 w-8 items-center justify-center rounded-xl border-2 border-white bg-[#EAF0FF] text-sm font-semibold text-[#3B5CCC]">
        {overflowCount ? `+${overflowCount}` : secondary}
      </div>
    </div>
  );
}

export function ContextRing({
  value,
  label,
  level,
}: {
  value: number;
  label: string;
  level?: "safe" | "watch" | "urgent" | "critical";
}) {
  const color =
    level === "critical"
      ? "#FF4D4F"
      : level === "urgent"
        ? "#FF8A00"
        : level === "watch"
          ? "#1C7ED6"
          : "#07C160";
  const background = `conic-gradient(${color} ${value * 3.6}deg, #D9D9D9 0deg)`;

  return (
    <div className="flex items-center gap-2 text-[11px] text-[#8C8C8C]">
      <span className="relative block h-[14px] w-[14px] rounded-full" style={{ background }}>
        <span className="absolute inset-[2px] rounded-full bg-white" />
      </span>
      <span>{label}</span>
    </div>
  );
}

function riskBadgeColor(level: ConversationItem["riskLevel"]) {
  switch (level) {
    case "high":
      return "bg-[#FFF1F0] text-[#CF1322]";
    case "medium":
      return "bg-[#FFF7E6] text-[#D46B08]";
    default:
      return "bg-[#F6FFED] text-[#389E0D]";
  }
}

function conversationActionsPath(projectId: string) {
  return `/api/v1/conversations/${projectId}/actions`;
}

function ConversationActionButtons({
  conversation,
}: {
  conversation: ConversationItem;
}) {
  const router = useRouter();
  const [loading, setLoading] = useState<"toggle_pin" | "mark_read" | null>(null);

  if (conversation.conversationType === "folder_archive") {
    return <div className="min-h-[24px]" />;
  }

  async function runAction(action: "toggle_pin" | "mark_read") {
    setLoading(action);
    await fetch(conversationActionsPath(conversation.projectId), {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ action }),
    });
    setLoading(null);
    router.refresh();
  }

  return (
    <div className="flex items-center gap-2">
      {conversation.projectId !== "master-agent" ? (
        <button
          type="button"
          onClick={() => void runAction("toggle_pin")}
          disabled={loading === "toggle_pin"}
          className="rounded-full border border-[#D9D9D9] px-3 py-1 text-[11px] text-[#57606A]"
        >
          {conversation.manualPinned ? "取消置顶" : "置顶"}
        </button>
      ) : null}
      {conversation.unreadCount > 0 ? (
        <button
          type="button"
          onClick={() => void runAction("mark_read")}
          disabled={loading === "mark_read"}
          className="rounded-full border border-[#E5E5EA] px-3 py-1 text-[11px] text-[#57606A]"
        >
          已读
        </button>
      ) : null}
    </div>
  );
}

export function ConversationList({
  conversations,
}: {
  conversations: ConversationItem[];
}) {
  return (
    <div className="flex flex-col gap-1 px-[18px] pb-5">
      {conversations.map((conversation) => (
        <div
          key={conversation.conversationId}
          className="rounded-2xl px-1 py-3 transition hover:bg-white/70"
        >
          <div className="mb-2 flex justify-end">
            <ConversationActionButtons conversation={conversation} />
          </div>
          <Link
            href={
              conversation.conversationType === "folder_archive" && conversation.folderKey
                ? `/conversations/folders/${encodeURIComponent(conversation.folderKey)}`
                : `/conversations/${conversation.projectId}`
            }
            className="flex items-start gap-3"
          >
            <AvatarStack
              primary={conversation.avatar.primary}
              secondary={conversation.avatar.secondary}
              overflowCount={conversation.avatar.overflowCount}
            />
            <div className="min-w-0 flex-1 border-b border-[#EFEFF4] pb-3">
              <div className="flex items-start justify-between gap-3">
                <div className="min-w-0">
                  <div className="flex items-center gap-2">
                    <div className="truncate text-[17px] font-medium text-[#111111]">
                      {conversation.conversationType === "folder_archive"
                        ? conversation.threadTitle
                        : conversation.projectTitle}
                    </div>
                    {conversation.conversationType === "folder_archive" ? (
                      <span className="rounded-full bg-[#F4F5F7] px-2 py-0.5 text-[10px] font-semibold text-[#57606A]">
                        {conversation.threadCount ?? 0} 个线程
                      </span>
                    ) : (
                      <span
                        className={clsx(
                          "rounded-full px-2 py-0.5 text-[10px] font-semibold",
                          riskBadgeColor(conversation.riskLevel),
                        )}
                      >
                        {conversation.riskLevel === "high"
                          ? "高风险"
                          : conversation.riskLevel === "medium"
                            ? "关注"
                            : "稳定"}
                      </span>
                    )}
                    {conversation.unreadCount > 0 ? (
                      <span className="rounded-full bg-[#FF4D4F] px-2 py-0.5 text-[10px] font-semibold text-white">
                        {conversation.unreadCount}
                      </span>
                    ) : null}
                  </div>
                  <div className="mt-1 text-[13px] text-[#8C8C8C]">
                    {conversation.conversationType === "folder_archive"
                      ? conversation.folderLabel
                      : conversation.deviceNamesPreview.join(" / ")}
                  </div>
                  <div className="mt-1 truncate text-[14px] text-[#57606A]">
                    {conversation.preview}
                  </div>
                </div>
                <div className="flex min-w-[98px] flex-col items-end gap-1">
                  <div className="min-h-[18px] text-[11px] text-[#07C160]">
                    {conversation.projectId === "master-agent"
                      ? "置顶"
                      : conversation.manualPinned
                        ? "置顶"
                        : ""}
                  </div>
                  <div className="text-[12px] text-[#8C8C8C]">
                    {conversation.latestReplyLabel}
                  </div>
                  {conversation.contextBudgetIndicator.visible &&
                  conversation.contextBudgetIndicator.percent !== undefined ? (
                    <ContextRing
                      value={conversation.contextBudgetIndicator.percent}
                      label={`${conversation.contextBudgetIndicator.percent}%`}
                      level={conversation.contextBudgetIndicator.level}
                    />
                  ) : (
                    <div className="min-h-[16px] text-[11px] text-[#8C8C8C]">
                      {conversation.activeDeviceCount > 1
                        ? `${conversation.activeDeviceCount} 台协作`
                        : ""}
                    </div>
                  )}
                </div>
              </div>
            </div>
          </Link>
        </div>
      ))}
    </div>
  );
}

export function DeviceList({ devices }: { devices: Device[] }) {
  const router = useRouter();
  return (
    <div className="flex flex-col gap-3 px-[18px] pb-5">
      {devices.map((device) => (
        <button
          key={device.id}
          type="button"
          onClick={() => router.push(`/devices?device=${device.id}`)}
          className="flex w-full items-start gap-3 rounded-2xl border border-[#E5E5EA] bg-white px-4 py-4 text-left"
        >
          <div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-[#DFF4E8] text-lg font-semibold text-[#215B39]">
            {device.avatar}
          </div>
          <div className="min-w-0 flex-1">
            <div className="flex items-center gap-2">
              <div className="text-[16px] font-medium text-[#111111]">{device.name}</div>
              <span
                className={clsx(
                  "h-2.5 w-2.5 rounded-full",
                  device.status === "online"
                    ? "bg-[#07C160]"
                    : device.status === "abnormal"
                      ? "bg-[#FF4D4F]"
                      : "bg-[#B8B8B8]",
                )}
              />
            </div>
            <div className="mt-1 text-[13px] text-[#8C8C8C]">{device.account}</div>
            <div className="mt-2 text-[13px] text-[#57606A]">
              项目：{device.projects.join(" · ")}
            </div>
            <div className="mt-2 flex flex-wrap gap-4 text-[12px] text-[#57606A]">
              <span>5h：{device.quota5h}%</span>
              <span>7d：{device.quota7d}%</span>
              <span>最近：{formatClock(device.lastSeenAt)}</span>
            </div>
          </div>
        </button>
      ))}
    </div>
  );
}

export function DeviceEditorCard({
  device,
  relatedThreads,
  activeEnrollment,
}: {
  device: Device;
  relatedThreads: ThreadContextSnapshot[];
  activeEnrollment?: DeviceEnrollment;
}) {
  const router = useRouter();
  const [name, setName] = useState(device.name);
  const [avatar, setAvatar] = useState(device.avatar);
  const [account, setAccount] = useState(device.account);
  const [status, setStatus] = useState<Device["status"]>(device.status);
  const [endpoint, setEndpoint] = useState(device.endpoint ?? "");
  const [note, setNote] = useState(device.note ?? "");
  const [projects, setProjects] = useState(device.projects.join(", "));
  const [message, setMessage] = useState("");

  async function save() {
    const response = await fetch(`/api/v1/devices/${device.id}`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        name,
        avatar,
        account,
        status,
        endpoint,
        note,
        projects: projects
          .split(",")
          .map((item) => item.trim())
          .filter(Boolean),
      }),
    });
    const result = (await response.json()) as { ok: boolean; message?: string };
    setMessage(result.ok ? "设备信息已更新。" : result.message ?? "更新失败");
    if (result.ok) router.refresh();
  }

  return (
    <div className="space-y-3 rounded-2xl border border-[#E5E5EA] bg-white px-4 py-4">
      <div className="flex items-center justify-between">
        <div className="text-[16px] font-semibold text-[#111111]">设备详情</div>
        <div className="text-[12px] text-[#8C8C8C]">ID: {device.id}</div>
      </div>
      <div className="grid grid-cols-2 gap-3">
        <Field label="设备名称" value={name} onChange={setName} />
        <Field label="头像缩写" value={avatar} onChange={setAvatar} />
      </div>
      <Field label="账号" value={account} onChange={setAccount} />
      <Field label="Endpoint" value={endpoint} onChange={setEndpoint} />
      <Field label="备注" value={note} onChange={setNote} />
      <Field label="项目列表（逗号分隔）" value={projects} onChange={setProjects} />
      <div className="space-y-1">
        <div className="text-[12px] text-[#8C8C8C]">当前状态</div>
        <div className="flex gap-2">
          {(["online", "abnormal", "offline"] as const).map((item) => (
            <button
              key={item}
              type="button"
              onClick={() => setStatus(item)}
              className={clsx(
                "rounded-full px-3 py-2 text-[12px] font-semibold",
                status === item ? "bg-[#07C160] text-white" : "bg-[#F5F5F7] text-[#57606A]",
              )}
            >
              {item}
            </button>
          ))}
        </div>
      </div>
      <button
        type="button"
        onClick={() => void save()}
        className="rounded-full bg-[#07C160] px-4 py-2 text-[13px] font-semibold text-white"
      >
        保存设备信息
      </button>
      {message ? (
        <div className="rounded-2xl bg-[#EAF7F0] px-4 py-3 text-[12px] leading-6 text-[#215B39]">
          {message}
        </div>
      ) : null}
      {activeEnrollment ? (
        <div className="rounded-2xl bg-[#F7F8FA] px-4 py-3 text-[12px] leading-6 text-[#57606A]">
          Pairing code: <span className="font-semibold text-[#111111]">{activeEnrollment.pairingCode}</span>
          <br />
          Token: <span className="font-semibold text-[#111111]">{activeEnrollment.token}</span>
          <br />
          过期时间：{formatClock(activeEnrollment.expiresAt)}
        </div>
      ) : null}
      <div className="rounded-2xl bg-[#F7F8FA] px-4 py-3 text-[12px] leading-6 text-[#57606A]">
        活跃线程：{relatedThreads.length}
        <br />
        {relatedThreads.map((thread) => `${thread.title} (${thread.contextBudgetRemainingPct}%)`).join("；") ||
          "当前无活跃线程。"}
      </div>
    </div>
  );
}

export function ProfileHero({ user }: { user: UserProfile }) {
  return (
    <div className="rounded-2xl bg-white px-4 py-4">
      <div className="flex items-center justify-between gap-3">
        <div className="flex items-center gap-3">
          <div className="flex h-14 w-14 items-center justify-center rounded-2xl bg-[#DFF4E8] text-xl font-semibold text-[#215B39]">
            {user.avatar}
          </div>
          <div>
            <div className="text-[18px] font-semibold">{user.name}</div>
            <div className="mt-1 text-[13px] text-[#8C8C8C]">{user.accountType}</div>
            <div className="mt-1 text-[12px] text-[#57606A]">账号：{user.account}</div>
            <div className="mt-1 text-[12px] text-[#57606A]">
              绑定节点：{user.boundCodexNodeLabel ?? "未绑定"}
            </div>
            <div className="mt-1 text-[12px] text-[#57606A]">二维码：{user.qrCodeValue}</div>
          </div>
        </div>
        <div className="space-y-2 text-right">
          <div className="rounded-full bg-[#FFF5E8] px-3 py-1 text-[12px] font-semibold text-[#B54708]">
            {user.roleLabel}
          </div>
          <div className="rounded-xl border border-[#E5E5EA] px-3 py-2 text-lg text-[#57606A]">⌘</div>
        </div>
      </div>
    </div>
  );
}

export function MenuRow({
  href,
  title,
  description,
  badge,
}: {
  href: string;
  title: string;
  description: string;
  badge?: React.ReactNode;
}) {
  return (
    <Link
      href={href}
      className="flex items-center justify-between rounded-2xl border border-[#E5E5EA] bg-white px-4 py-4"
    >
      <div>
        <div className="text-[16px] font-medium text-[#111111]">{title}</div>
        <div className="mt-1 text-[12px] text-[#8C8C8C]">{description}</div>
      </div>
      <div className="flex items-center gap-2 text-[#8C8C8C]">
        {badge}
        <span>&gt;</span>
      </div>
    </Link>
  );
}

function kindLabel(kind?: Message["kind"]) {
  switch (kind) {
    case "voice_intent":
      return "语音";
    case "image_intent":
      return "图片";
    case "video_intent":
      return "视频";
    case "forward_notice":
      return "转发";
    default:
      return null;
  }
}

export function ChatBubble({ message }: { message: Message }) {
  const mine = message.sender === "user";
  const green = message.sender === "master";
  const tag = kindLabel(message.kind);

  return (
    <div className={clsx("flex", mine ? "justify-end" : "justify-start")}>
      <div className="max-w-[82%]">
        <div className="mb-1 px-1 text-[11px] text-[#8C8C8C]">
          {message.senderLabel} · {formatClock(message.sentAt)}
        </div>
        <div
          className={clsx(
            "rounded-[18px] px-4 py-3 text-[15px] leading-6",
            mine
              ? "bg-[#07C160] text-white"
              : green
                ? "bg-[#EAF7F0] text-[#215B39]"
                : "bg-white text-[#111111]",
          )}
        >
          {tag ? (
            <div className="mb-2 text-[11px] font-semibold opacity-80">{tag}</div>
          ) : null}
          {message.body}
        </div>
      </div>
    </div>
  );
}

export function ProjectHeaderActions({ projectId }: { projectId: string }) {
  return (
    <div className="grid grid-cols-3 gap-3">
      <Link
        href={`/conversations/${projectId}/goals`}
        className="flex h-11 items-center justify-center rounded-2xl bg-[#EAF7F0] text-[14px] font-semibold text-[#215B39]"
      >
        项目目标
      </Link>
      <Link
        href={`/conversations/${projectId}/versions`}
        className="flex h-11 items-center justify-center rounded-2xl bg-white text-[14px] font-semibold text-[#111111]"
      >
        版本记录
      </Link>
      <Link
        href={`/conversations/${projectId}/forward`}
        className="flex h-11 items-center justify-center rounded-2xl bg-white text-[14px] font-semibold text-[#111111]"
      >
        转发
      </Link>
    </div>
  );
}

function masterIdentityPillClasses(role: MasterIdentitySummary["role"]) {
  switch (role) {
    case "primary":
      return "border-[#BBD6FF] bg-[#EEF5FF] text-[#2457C5]";
    case "backup":
      return "border-[#FFD9B8] bg-[#FFF5E8] text-[#B54708]";
    case "api_fallback":
      return "border-[#D8DDE6] bg-[#F3F4F6] text-[#4B5563]";
    default:
      return "border-[#D8DDE6] bg-[#F3F4F6] text-[#4B5563]";
  }
}

export function MasterIdentityPill({ identity }: { identity: MasterIdentitySummary }) {
  return (
    <Link
      href="/me/ai-accounts"
      className={clsx(
        "rounded-full border px-3 py-1 text-right",
        masterIdentityPillClasses(identity.role),
      )}
    >
      <div className="text-[11px] font-semibold leading-4">{identity.roleLabel}</div>
      <div className="text-[10px] leading-4 opacity-80">
        {(identity.nodeLabel || identity.displayName).slice(0, 18)}
      </div>
    </Link>
  );
}

type PendingDispatchPlanState = {
  planId: string;
  summary?: string;
  targets: Array<{ projectId: string; threadDisplayName: string }>;
};

export function ChatComposer({
  projectId,
  initialPendingDispatchPlan,
}: {
  projectId: string;
  initialPendingDispatchPlan?: PendingDispatchPlanState | null;
}) {
  const router = useRouter();
  const [value, setValue] = useState("");
  const [message, setMessage] = useState("");
  const [messageTone, setMessageTone] = useState<"success" | "error">("success");
  const [loading, setLoading] = useState(false);
  const [localPendingDispatchPlan, setLocalPendingDispatchPlan] =
    useState<PendingDispatchPlanState | null>(null);
  const [dismissedPendingPlanId, setDismissedPendingPlanId] = useState<string | null>(null);
  const pendingDispatchPlan =
    localPendingDispatchPlan ??
    (initialPendingDispatchPlan && initialPendingDispatchPlan.planId !== dismissedPendingPlanId
      ? initialPendingDispatchPlan
      : null);

  async function confirmDispatchPlan() {
    if (!pendingDispatchPlan) return;
    setLoading(true);
    const response = await fetch(
      `/api/v1/projects/${projectId}/dispatch-plans/${pendingDispatchPlan.planId}/confirm`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          approvedTargetProjectIds: extractApprovedTargetProjectIds(pendingDispatchPlan),
        }),
      },
    );
    const result = (await response.json()) as {
      ok: boolean;
      executions?: Array<unknown>;
      message?: string;
    };
    setLoading(false);
    if (!result.ok) {
      setMessageTone("error");
      setMessage(result.message ?? "确认下发失败，请重试。");
      return;
    }
    const executionCount = result.executions?.length ?? extractApprovedTargetProjectIds(pendingDispatchPlan).length;
    setLocalPendingDispatchPlan(null);
    setDismissedPendingPlanId(pendingDispatchPlan.planId);
    setMessageTone("success");
    setMessage(`已确认下发到 ${executionCount} 个线程。`);
    router.refresh();
  }

  async function send(kind: "text" | "voice_intent" | "image_intent" | "video_intent") {
    setLoading(true);
    const response = await fetch(`/api/v1/projects/${projectId}/messages`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ body: kind === "text" ? value : undefined, kind }),
    });
    const result = (await response.json()) as {
      ok: boolean;
      message?: { body: string };
      dispatchPlan?: {
        planId: string;
        summary?: string;
        targets?: Array<{ projectId: string; threadDisplayName: string }>;
      } | null;
      collaborationGate?: {
        requiresMasterAgentApproval?: boolean;
      };
      messageText?: string;
    };
    setLoading(false);
    if (result.ok) {
      void sendAppLog({
        deviceId: boundDeviceIdFromDom(),
        projectId,
        level: "info",
        category: "chat.message_sent",
        message:
          kind === "text" ? `已发送文本消息：${value.trim() || "空文本"}` : `已发送 ${kind} 意图消息。`,
        mirrorToMaster: false,
      });
      setValue("");
      if (result.dispatchPlan) {
        setLocalPendingDispatchPlan({
          planId: result.dispatchPlan.planId,
          summary: result.dispatchPlan.summary,
          targets: result.dispatchPlan.targets ?? [],
        });
        setDismissedPendingPlanId(null);
        setMessage(
          result.collaborationGate?.requiresMasterAgentApproval
            ? "消息已发送，等待你批准主 Agent 下发。"
            : "消息已发送，主 Agent 已给出推荐线程。",
        );
        setMessageTone("success");
      } else {
        setLocalPendingDispatchPlan(null);
        setMessage("");
      }
      router.refresh();
      return;
    }
    void sendAppLog({
      deviceId: boundDeviceIdFromDom(),
      projectId,
      level: "error",
      category: "chat.message_failed",
      message: `发送 ${kind} 消息失败。`,
      detail: "Boss 会话消息接口返回失败。",
      mirrorToMaster: true,
    });
    setMessageTone("error");
    setMessage("消息发送失败，请重试。");
  }

  return (
    <div
      className="mt-auto border-t border-[#E5E5EA] bg-white px-[18px] pt-3"
      style={{ paddingBottom: "max(12px, env(safe-area-inset-bottom))" }}
    >
      <div className="flex items-center gap-2">
        <button
          type="button"
          disabled={loading}
          onClick={() => void send("voice_intent")}
          className="text-xl text-[#57606A]"
        >
          🎙
        </button>
        <input
          value={value}
          onChange={(event) => setValue(event.target.value)}
          placeholder="向主 Agent 或项目发送消息"
          className="flex-1 rounded-full bg-[#F5F5F7] px-4 py-3 text-[14px] text-[#111111] outline-none"
        />
        <button
          type="button"
          disabled={loading}
          onClick={() => void send("text")}
          className="rounded-full bg-[#07C160] px-4 py-2 text-[13px] font-semibold text-white"
        >
          发送
        </button>
      </div>
      <div className="mt-3 flex items-center gap-4 text-[12px] text-[#8C8C8C]">
        <button type="button" onClick={() => void send("image_intent")}>
          图片
        </button>
        <button type="button" onClick={() => void send("video_intent")}>
          视频
        </button>
        <Link href={`/conversations/${projectId}/forward`}>转发</Link>
      </div>
      {message ? (
        <div
          className={clsx(
            "mt-3 rounded-2xl px-4 py-3 text-[12px]",
            messageTone === "success"
              ? "bg-[#EAF7F0] text-[#215B39]"
              : "bg-[#FFF1F0] text-[#CF1322]",
          )}
        >
          {message}
        </div>
      ) : null}
      {pendingDispatchPlan ? (
        <div className="mt-3 rounded-2xl border border-[#E5E5EA] bg-[#F7F8FA] px-4 py-4 text-[12px] leading-6 text-[#57606A]">
          <div className="text-[14px] font-semibold text-[#111111]">等待你确认主 Agent 推荐</div>
          <div className="mt-2 whitespace-pre-line">{summarizeDispatchPlan(pendingDispatchPlan)}</div>
          <div className="mt-3 flex flex-wrap gap-2">
            <button
              type="button"
              disabled={loading}
              onClick={() => void confirmDispatchPlan()}
              className="rounded-full bg-[#07C160] px-4 py-2 text-[13px] font-semibold text-white disabled:bg-[#B7E6C9]"
            >
              确认下发
            </button>
            <button
              type="button"
              disabled={loading}
              onClick={() => {
                setLocalPendingDispatchPlan(null);
                setDismissedPendingPlanId(pendingDispatchPlan.planId);
              }}
              className="rounded-full border border-[#D9D9D9] px-4 py-2 text-[13px] font-semibold text-[#57606A]"
            >
              稍后处理
            </button>
          </div>
        </div>
      ) : null}
    </div>
  );
}

export function GoalChecklist({
  projectId,
  goals,
}: {
  projectId: string;
  goals: GoalItem[];
}) {
  const router = useRouter();
  const [workingId, setWorkingId] = useState<string | null>(null);
  const [editingId, setEditingId] = useState<string | null>(null);
  const [draft, setDraft] = useState("");
  const [newGoal, setNewGoal] = useState("");

  async function handleToggle(goalId: string) {
    setWorkingId(goalId);
    await fetch(`/api/projects/${projectId}/goals/${goalId}/toggle`, { method: "POST" });
    setWorkingId(null);
    router.refresh();
  }

  async function handleSave(goalId: string) {
    await fetch(`/api/projects/${projectId}/goals/update`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ goalId, text: draft, action: "update" }),
    });
    setEditingId(null);
    setDraft("");
    router.refresh();
  }

  async function handleCreate() {
    if (!newGoal.trim()) return;
    await fetch(`/api/projects/${projectId}/goals/update`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ text: newGoal, action: "create" }),
    });
    setNewGoal("");
    router.refresh();
  }

  return (
    <div className="flex flex-col gap-3">
      <div className="rounded-2xl border border-dashed border-[#B7EAC7] bg-[#F5FFF8] px-4 py-4">
        <div className="text-[13px] font-semibold text-[#215B39]">新增项目目标</div>
        <textarea
          value={newGoal}
          onChange={(event) => setNewGoal(event.target.value)}
          placeholder="写入新的项目目标，主 Agent 会据此调整优先级。"
          className="mt-3 min-h-24 w-full rounded-xl border border-[#DFF4E8] bg-white p-3 text-[14px] leading-6 outline-none"
        />
        <button
          type="button"
          onClick={() => void handleCreate()}
          className="mt-3 rounded-full bg-[#07C160] px-4 py-2 text-[13px] font-semibold text-white"
        >
          添加目标
        </button>
      </div>
      {goals.map((goal) => {
        const completed = goal.state === "completed";
        const editing = editingId === goal.id;

        return (
          <div key={goal.id} className="rounded-2xl border border-[#E5E5EA] bg-white px-4 py-4">
            <div className="flex items-start gap-3">
              <button
                type="button"
                onClick={() => void handleToggle(goal.id)}
                disabled={workingId === goal.id}
                className={clsx(
                  "mt-0.5 flex h-[22px] w-[22px] shrink-0 items-center justify-center rounded-full border-2",
                  completed
                    ? "border-[#07C160] bg-[#07C160] text-white"
                    : "border-[#07C160] bg-white text-transparent",
                )}
              >
                ✓
              </button>
              <div className="min-w-0 flex-1">
                {editing ? (
                  <div className="space-y-2">
                    <textarea
                      value={draft}
                      onChange={(event) => setDraft(event.target.value)}
                      className="min-h-24 w-full rounded-xl border border-[#E5E5EA] bg-[#F9F9FA] p-3 text-[14px] leading-6 outline-none"
                    />
                    <div className="flex gap-2">
                      <button
                        type="button"
                        onClick={() => void handleSave(goal.id)}
                        className="rounded-full bg-[#07C160] px-4 py-2 text-[13px] font-semibold text-white"
                      >
                        保存
                      </button>
                      <button
                        type="button"
                        onClick={() => {
                          setEditingId(null);
                          setDraft("");
                        }}
                        className="rounded-full border border-[#E5E5EA] px-4 py-2 text-[13px] text-[#57606A]"
                      >
                        取消
                      </button>
                    </div>
                  </div>
                ) : (
                  <>
                    <div
                      className={clsx(
                        "text-[15px] leading-6",
                        completed ? "text-[#8C8C8C] line-through" : "text-[#111111]",
                      )}
                    >
                      {goal.text}
                    </div>
                    <div className="mt-2 text-[12px] text-[#8C8C8C]">
                      {goal.note}
                      {goal.completedAt ? ` · ${formatClock(goal.completedAt)}` : ""}
                    </div>
                  </>
                )}
              </div>
              {!editing ? (
                <button
                  type="button"
                  onClick={() => {
                    setEditingId(goal.id);
                    setDraft(goal.text);
                  }}
                  className="text-[13px] font-semibold text-[#07C160]"
                >
                  编辑
                </button>
              ) : null}
            </div>
          </div>
        );
      })}
    </div>
  );
}

export function AuthForm({
  title,
  description,
  mode,
}: {
  title: string;
  description: string;
  mode: "login" | "register" | "forgot-password";
}) {
  const router = useRouter();
  const [account, setAccount] = useState("");
  const [loginMethod, setLoginMethod] = useState<"password" | "code">("password");
  const [password, setPassword] = useState("");
  const [confirmPassword, setConfirmPassword] = useState("");
  const [code, setCode] = useState("");
  const [message, setMessage] = useState("");
  const [submitting, setSubmitting] = useState(false);

  async function sendCode() {
    if (!account.trim()) {
      setMessage("请先输入账号，再发送验证码。");
      return;
    }
    const response = await fetch("/api/auth/send-code", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ account, purpose: mode }),
    });
    const result = (await response.json()) as { message: string };
    setMessage(result.message);
  }

  async function submit() {
    setSubmitting(true);
    const route =
      mode === "login"
        ? "/api/auth/login"
        : mode === "register"
          ? "/api/auth/register"
          : "/api/auth/forgot-password";

    const nativeClient = await isNativeBossApp();
    try {
      const response = await fetch(route, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          ...(nativeClient ? { "x-boss-native-app": "1" } : {}),
        },
        body: JSON.stringify({
          account,
          password,
          confirmPassword,
          code,
          method: mode === "login" ? loginMethod : undefined,
        }),
      });

      const result = (await response.json()) as {
        ok: boolean;
        message: string;
        account?: string;
        displayName?: string;
        sessionExpiresAt?: string;
        restoreToken?: string;
      };
      setMessage(result.message);
      if (result.ok && mode === "login") {
        if (
          nativeClient &&
          result.restoreToken &&
          result.account &&
          result.displayName &&
          result.sessionExpiresAt
        ) {
          try {
            await persistNativeSessionSnapshot({
              restoreToken: result.restoreToken,
              account: result.account,
              displayName: result.displayName,
              expiresAt: result.sessionExpiresAt,
              lastSyncedAt: new Date().toISOString(),
            });
          } catch (error) {
            void sendAppLog({
              deviceId: boundDeviceIdFromDom(),
              level: "warn",
              category: "auth.native_restore_snapshot_failed",
              message: "登录成功，但原生 restore token 快照写入失败。",
              detail: error instanceof Error ? error.message : String(error),
              mirrorToMaster: false,
            });
          }
        }

        await waitForLoginSessionReady(nativeClient);
        navigateToConversations(router);
        return;
      }
      if (result.ok && mode === "register") {
        router.push("/auth/login");
      }
    } catch (error) {
      setMessage("登录链路发生异常，请重试。");
      void sendAppLog({
        deviceId: boundDeviceIdFromDom(),
        level: "error",
        category: "auth.submit_failed",
        message: "登录或注册提交失败。",
        detail: error instanceof Error ? error.message : String(error),
        mirrorToMaster: true,
      });
    } finally {
      setSubmitting(false);
    }
  }

  return (
    <div className="space-y-4 px-[18px] pb-8">
      <div className="space-y-2 px-0 py-2">
        <div className="flex h-16 w-16 items-center justify-center rounded-[18px] bg-[#07C160] text-[30px] font-semibold text-white">
          C
        </div>
        <h1 className="pt-2 text-[34px] font-semibold tracking-[-0.02em] text-[#111111]">
          {title}
        </h1>
        <p className="text-[14px] leading-6 text-[#8C8C8C]">{description}</p>
      </div>

      <div className="overflow-hidden rounded-2xl border border-[#E5E5EA] bg-white">
        {mode === "login" ? (
          <>
            <div className="grid grid-cols-2 gap-2 bg-[#F7F8FA] px-4 py-3">
              <button
                type="button"
                onClick={() => {
                  setLoginMethod("password");
                  setMessage("");
                }}
                className={clsx(
                  "rounded-xl px-3 py-2 text-[13px] font-semibold",
                  loginMethod === "password" ? "bg-[#07C160] text-white" : "bg-white text-[#57606A]",
                )}
              >
                账号密码登录
              </button>
              <button
                type="button"
                onClick={() => {
                  setLoginMethod("code");
                  setMessage("");
                }}
                className={clsx(
                  "rounded-xl px-3 py-2 text-[13px] font-semibold",
                  loginMethod === "code" ? "bg-[#07C160] text-white" : "bg-white text-[#57606A]",
                )}
              >
                验证码登录
              </button>
            </div>
            <div className="h-px w-full bg-[#E5E5EA]" />
          </>
        ) : null}
        <AuthField label="账号" value={account} onChange={setAccount} placeholder="输入手机号 / 邮箱" />
        {mode === "login" && loginMethod === "code" ? (
          <>
            <AuthCodeField label="登录验证码" value={code} onChange={setCode} onSend={sendCode} />
            <div className="border-t border-[#E5E5EA] px-4 py-3 text-[12px] leading-5 text-[#57606A]">
              当前固定验证码模式下，可直接输入 <span className="font-semibold text-[#111111]">000000</span>{" "}
              登录；如需确认账号状态，也可以先点“发送验证码”。
            </div>
          </>
        ) : null}
        {mode !== "login" ? (
          <AuthCodeField
            label={mode === "register" ? "注册验证码" : "重置验证码"}
            value={code}
            onChange={setCode}
            onSend={sendCode}
          />
        ) : null}
        {(mode !== "login" || loginMethod === "password") ? (
          <AuthField
            label={mode === "forgot-password" ? "新密码" : mode === "register" ? "登录密码" : "密码"}
            value={password}
            onChange={setPassword}
            placeholder={mode === "register" ? "设置 8-20 位密码" : "输入密码"}
            type="password"
          />
        ) : null}
        {(mode === "register" || mode === "forgot-password") && (
          <AuthField
            label={mode === "register" ? "确认密码" : "确认新密码"}
            value={confirmPassword}
            onChange={setConfirmPassword}
            placeholder="再次输入密码"
            type="password"
          />
        )}
      </div>

      <button
        type="button"
        onClick={() => void submit()}
        disabled={submitting}
        className="flex h-[52px] w-full items-center justify-center rounded-[14px] bg-[#07C160] text-[17px] font-semibold text-white"
      >
        {submitting ? "处理中..." : mode === "login" ? "登录" : mode === "register" ? "注册并继续" : "确认重置"}
      </button>

      {message ? (
        <div className="rounded-2xl bg-[#EAF7F0] px-4 py-3 text-[13px] leading-6 text-[#215B39]">
          {message}
        </div>
      ) : null}

      <div className="flex items-center justify-between text-[14px]">
        {mode === "login" ? (
          <>
            <Link href="/auth/register" className="text-[#07C160]">
              注册账号
            </Link>
            <Link href="/auth/forgot-password" className="text-[#57606A]">
              忘记密码
            </Link>
          </>
        ) : mode === "register" ? (
          <Link href="/auth/login" className="mx-auto text-[#07C160]">
            已有账号，去登录
          </Link>
        ) : (
          <Link href="/auth/login" className="mx-auto text-[#57606A]">
            密码重置成功后，回到登录页继续验证。
          </Link>
        )}
      </div>
    </div>
  );
}

function AuthField({
  label,
  value,
  onChange,
  placeholder,
  type = "text",
}: {
  label: string;
  value: string;
  onChange: (value: string) => void;
  placeholder: string;
  type?: "text" | "password";
}) {
  return (
    <>
      <div className="space-y-1 px-4 py-3">
        <div className="text-[12px] text-[#8C8C8C]">{label}</div>
        <input
          type={type}
          value={value}
          onChange={(event) => onChange(event.target.value)}
          placeholder={placeholder}
          className="w-full border-0 bg-transparent p-0 text-[16px] text-[#111111] outline-none"
        />
      </div>
      <div className="h-px w-full bg-[#E5E5EA]" />
    </>
  );
}

function AuthCodeField({
  label,
  value,
  onChange,
  onSend,
}: {
  label: string;
  value: string;
  onChange: (value: string) => void;
  onSend: () => void;
}) {
  return (
    <>
      <div className="flex items-center gap-3 px-4 py-3">
        <div className="min-w-0 flex-1 space-y-1">
          <div className="text-[12px] text-[#8C8C8C]">{label}</div>
          <input
            value={value}
            onChange={(event) => onChange(event.target.value)}
            placeholder="输入 6 位验证码"
            inputMode="numeric"
            className="w-full border-0 bg-transparent p-0 text-[16px] text-[#111111] outline-none"
          />
        </div>
        <button
          type="button"
          onClick={onSend}
          className="rounded-xl border border-[#07C160] bg-[#F5FFF8] px-3 py-2 text-[13px] font-semibold text-[#07C160]"
        >
          发送验证码
        </button>
      </div>
      <div className="h-px w-full bg-[#E5E5EA]" />
    </>
  );
}

function Field({
  label,
  value,
  onChange,
}: {
  label: string;
  value: string;
  onChange: (value: string) => void;
}) {
  return (
    <label className="block space-y-1">
      <span className="text-[12px] text-[#8C8C8C]">{label}</span>
      <input
        value={value}
        onChange={(event) => onChange(event.target.value)}
        className="w-full rounded-xl border border-[#E5E5EA] bg-[#F9F9FA] px-3 py-3 text-[14px] outline-none"
      />
    </label>
  );
}

export function DeviceEnrollmentBuilder() {
  const [name, setName] = useState("Mac Mini");
  const [avatar, setAvatar] = useState("M");
  const [account, setAccount] = useState("17600003315");
  const [projects, setProjects] = useState("Boss 移动控制台");
  const [endpoint, setEndpoint] = useState("mac://new-device.local");
  const [note, setNote] = useState("新设备待绑定");
  const [result, setResult] = useState<{
    enrollment?: DeviceEnrollment;
    device?: Device;
    message?: string;
  }>({});

  async function submit() {
    const response = await fetch("/api/v1/devices/enrollments", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        name,
        avatar,
        account,
        endpoint,
        note,
        projects: projects
          .split(",")
          .map((item) => item.trim())
          .filter(Boolean),
      }),
    });
    const data = (await response.json()) as {
      ok: boolean;
      enrollment?: DeviceEnrollment;
      device?: Device;
      message?: string;
    };
    setResult(data);
  }

  const configSnippet = result.enrollment
    ? JSON.stringify(
        {
          bindHost: "127.0.0.1",
          port: 4317,
          heartbeatIntervalMs: 60000,
          controlPlaneUrl: "http://127.0.0.1:3000",
          deviceId: result.device?.id,
          pairingCode: result.enrollment.pairingCode,
          token: result.enrollment.token,
          name,
          avatar,
          account,
          status: "online",
          quota5h: 100,
          quota7d: 100,
          projects: projects
            .split(",")
            .map((item) => item.trim())
            .filter(Boolean),
          endpoint,
        },
        null,
        2,
      )
    : "";

  return (
    <div className="space-y-3 rounded-2xl border border-[#E5E5EA] bg-white px-4 py-4">
      <div className="text-[16px] font-semibold text-[#111111]">生成设备绑定草稿</div>
      <Field label="设备名称" value={name} onChange={setName} />
      <div className="grid grid-cols-2 gap-3">
        <Field label="头像缩写" value={avatar} onChange={setAvatar} />
        <Field label="账号" value={account} onChange={setAccount} />
      </div>
      <Field label="项目列表（逗号分隔）" value={projects} onChange={setProjects} />
      <Field label="Endpoint" value={endpoint} onChange={setEndpoint} />
      <Field label="备注" value={note} onChange={setNote} />
      <button
        type="button"
        onClick={() => void submit()}
        className="rounded-full bg-[#07C160] px-4 py-2 text-[13px] font-semibold text-white"
      >
        生成 pairing code / token
      </button>
      {result.message ? (
        <div className="rounded-2xl bg-[#FFF1F0] px-4 py-3 text-[12px] text-[#CF1322]">
          {result.message}
        </div>
      ) : null}
      {result.enrollment ? (
        <div className="rounded-2xl bg-[#F7F8FA] px-4 py-3 text-[12px] leading-6 text-[#57606A]">
          Pairing code: <span className="font-semibold text-[#111111]">{result.enrollment.pairingCode}</span>
          <br />
          Token: <span className="font-semibold text-[#111111]">{result.enrollment.token}</span>
          <br />
          设备 ID: {result.device?.id}
          <br />
          过期时间：{formatClock(result.enrollment.expiresAt)}
        </div>
      ) : null}
      {configSnippet ? (
        <pre className="overflow-x-auto rounded-2xl bg-[#111111] p-4 text-[11px] leading-6 text-[#E8E8E8]">
          {configSnippet}
        </pre>
      ) : null}
      {result.device?.id ? (
        <DeviceImportDraftManager deviceId={result.device.id} deviceName={result.device.name} />
      ) : null}
    </div>
  );
}

export function OtaActionButton({ canApply = true }: { canApply?: boolean }) {
  const router = useRouter();
  const [message, setMessage] = useState("");

  async function run() {
    if (!canApply) {
      setMessage("当前账号没有 OTA 执行权限。");
      void sendAppLog({
        deviceId: boundDeviceIdFromDom(),
        level: "warn",
        category: "ota.apply_blocked",
        message: "当前账号没有 OTA 执行权限。",
        mirrorToMaster: true,
      });
      return;
    }
    const response = await fetch("/api/v1/user/ota", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ action: "apply" }),
    });
    const result = (await response.json()) as { ok: boolean; version?: string; message?: string };
    setMessage(result.ok ? `已升级到 ${result.version}` : result.message ?? "OTA 失败");
    void sendAppLog({
      deviceId: boundDeviceIdFromDom(),
      level: result.ok ? "info" : "error",
      category: result.ok ? "ota.apply_succeeded" : "ota.apply_failed",
      message: result.ok ? `已执行 OTA 升级到 ${result.version}` : result.message ?? "OTA 失败",
      mirrorToMaster: true,
    });
    if (result.ok) router.refresh();
  }

  return (
    <>
      <button
        type="button"
        onClick={() => void run()}
        disabled={!canApply}
        className={clsx(
          "rounded-full px-4 py-2 text-[13px] font-semibold",
          canApply ? "bg-[#07C160] text-white" : "bg-[#E5E5EA] text-[#8C8C8C]",
        )}
      >
        立即 OTA
      </button>
      {message ? (
        <div className="mt-3 rounded-2xl bg-[#EAF7F0] px-4 py-3 text-[12px] text-[#215B39]">
          {message}
        </div>
      ) : null}
    </>
  );
}

export function OtaCenterCard({
  currentVersion,
  availableRelease,
  logs,
  boundCodexNodeLabel,
  roleLabel,
  canApply,
  compact = false,
}: {
  currentVersion: string;
  availableRelease?: OtaUpdate | null;
  logs: OtaUpdateLog[];
  boundCodexNodeLabel?: string;
  roleLabel: string;
  canApply: boolean;
  compact?: boolean;
}) {
  const router = useRouter();
  const [message, setMessage] = useState("");

  async function checkUpdate() {
    const response = await fetch("/api/v1/user/ota", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ action: "check" }),
    });
    const result = (await response.json()) as { ok: boolean; message?: string };
    setMessage(result.message ?? (result.ok ? "检查完成。" : "检查失败"));
    void sendAppLog({
      deviceId: boundDeviceIdFromDom(),
      level: result.ok ? "info" : "error",
      category: result.ok ? "ota.check_succeeded" : "ota.check_failed",
      message: result.message ?? (result.ok ? "检查完成。" : "检查失败"),
      mirrorToMaster: !result.ok,
    });
    if (result.ok) router.refresh();
  }

  return (
    <div className="rounded-2xl border border-[#E5E5EA] bg-white px-4 py-4">
      <div className="flex items-start justify-between gap-3">
        <div>
          <div className="text-[16px] font-semibold text-[#111111]">关于 / OTA</div>
          <div className="mt-1 text-[13px] leading-6 text-[#57606A]">
            当前版本 {currentVersion}
            <br />
            管理员角色：{roleLabel}
            <br />
            绑定节点：{boundCodexNodeLabel ?? "未绑定"}
          </div>
        </div>
        {availableRelease ? (
          <span className="rounded-full bg-[#FFF1F0] px-3 py-1 text-[12px] font-semibold text-[#CF1322]">
            OTA
          </span>
        ) : null}
      </div>

      <div className="mt-4 rounded-2xl bg-[#F7F8FA] px-4 py-4">
        <div className="text-[14px] font-semibold text-[#111111]">
          {availableRelease ? `可用版本 ${availableRelease.version}` : "当前没有待升级版本"}
        </div>
        <div className="mt-2 text-[13px] leading-6 text-[#57606A]">
          {availableRelease ? (
            <>
              来源版本：{availableRelease.currentVersion}
              <br />
              发布范围：{availableRelease.targetScope}
              <br />
              {(availableRelease.summary ?? []).join("；")}
              {availableRelease.packageFileName ? (
                <>
                  <br />
                  安装包：{availableRelease.packageFileName}
                  {availableRelease.packageSizeBytes ? ` · ${formatBytes(availableRelease.packageSizeBytes)}` : ""}
                </>
              ) : null}
              {availableRelease.assetUpdatedAt ? (
                <>
                  <br />
                  包更新时间：{formatClock(availableRelease.assetUpdatedAt)}
                </>
              ) : null}
            </>
          ) : (
            "点击“检查更新”可重新拉取 OTA 状态。"
          )}
        </div>
      </div>

      <div className="mt-4 flex flex-wrap gap-2">
        <button
          type="button"
          onClick={() => void checkUpdate()}
          className="rounded-full border border-[#07C160] bg-[#F5FFF8] px-4 py-2 text-[13px] font-semibold text-[#07C160]"
        >
          检查更新
        </button>
        {availableRelease?.downloadUrl ? (
          <a
            href={availableRelease.downloadUrl}
            target="_blank"
            rel="noreferrer"
            className="rounded-full border border-[#111111] bg-white px-4 py-2 text-[13px] font-semibold text-[#111111]"
          >
            下载 APK
          </a>
        ) : null}
        {availableRelease ? <OtaActionButton canApply={canApply} /> : null}
      </div>

      {message ? (
        <div className="mt-3 rounded-2xl bg-[#EAF7F0] px-4 py-3 text-[12px] leading-6 text-[#215B39]">
          {message}
        </div>
      ) : null}

      {availableRelease?.packageSha256 ? (
        <div className="mt-3 rounded-2xl bg-[#F7F8FA] px-4 py-3 text-[12px] leading-6 text-[#57606A]">
          SHA256：{availableRelease.packageSha256}
        </div>
      ) : null}

      {!compact ? (
        <div className="mt-4 rounded-2xl bg-[#F7F8FA] px-4 py-4">
          <div className="text-[14px] font-semibold text-[#111111]">OTA 记录</div>
          <div className="mt-2 space-y-3 text-[12px] leading-6 text-[#57606A]">
            {logs.length ? (
              logs.slice(0, 4).map((log) => (
                <div key={log.logId}>
                  <div className="font-semibold text-[#111111]">
                    {log.version} · {log.status}
                  </div>
                  <div>
                    {formatClock(log.triggeredAt)} · {log.note}
                  </div>
                </div>
              ))
            ) : (
              <div>当前还没有 OTA 记录。</div>
            )}
          </div>
        </div>
      ) : null}
    </div>
  );
}

export function SettingsForm({ settings }: { settings: UserSettings }) {
  const router = useRouter();
  const [draft, setDraft] = useState(settings);
  const [message, setMessage] = useState("");

  async function save() {
    const response = await fetch("/api/v1/settings", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(draft),
    });
    const result = (await response.json()) as { ok: boolean; message?: string };
    setMessage(result.ok ? "设置已保存。" : result.message ?? "保存失败");
    if (result.ok) router.refresh();
  }

  return (
    <div className="space-y-3 rounded-2xl border border-[#E5E5EA] bg-white px-4 py-4">
      <SwitchRow
        label="启用实时刷新提示"
        value={draft.liveUpdates}
        onChange={(value) => setDraft((current) => ({ ...current, liveUpdates: value }))}
      />
      <SwitchRow
        label="显示风险徽标"
        value={draft.showRiskBadges}
        onChange={(value) => setDraft((current) => ({ ...current, showRiskBadges: value }))}
      />
      <SwitchRow
        label="危险操作前二次确认"
        value={draft.confirmDangerousActions}
        onChange={(value) =>
          setDraft((current) => ({ ...current, confirmDangerousActions: value }))
        }
      />
      <div className="space-y-1">
        <div className="text-[12px] text-[#8C8C8C]">默认首页</div>
        <div className="flex gap-2">
          {(["conversations", "devices", "me"] as const).map((item) => (
            <button
              key={item}
              type="button"
              onClick={() => setDraft((current) => ({ ...current, preferredEntryPoint: item }))}
              className={clsx(
                "rounded-full px-3 py-2 text-[12px] font-semibold",
                draft.preferredEntryPoint === item
                  ? "bg-[#07C160] text-white"
                  : "bg-[#F5F5F7] text-[#57606A]",
              )}
            >
              {item}
            </button>
          ))}
        </div>
      </div>
      <button
        type="button"
        onClick={() => void save()}
        className="rounded-full bg-[#07C160] px-4 py-2 text-[13px] font-semibold text-white"
      >
        保存设置
      </button>
      {message ? (
        <div className="rounded-2xl bg-[#EAF7F0] px-4 py-3 text-[12px] text-[#215B39]">
          {message}
        </div>
      ) : null}
    </div>
  );
}

function SwitchRow({
  label,
  value,
  onChange,
}: {
  label: string;
  value: boolean;
  onChange: (value: boolean) => void;
}) {
  return (
    <button
      type="button"
      onClick={() => onChange(!value)}
      className="flex w-full items-center justify-between rounded-2xl bg-[#F7F8FA] px-4 py-3"
    >
      <span className="text-[14px] text-[#111111]">{label}</span>
      <span
        className={clsx(
          "rounded-full px-3 py-1 text-[12px] font-semibold",
          value ? "bg-[#07C160] text-white" : "bg-white text-[#57606A]",
        )}
      >
        {value ? "开" : "关"}
      </span>
    </button>
  );
}

export function RepairTicketActions({
  ticket,
  verification,
}: {
  ticket: OpsRepairTicket;
  verification?: OpsRepairVerification;
}) {
  const router = useRouter();
  const [message, setMessage] = useState("");

  async function run(action: "approve" | "verify") {
    const response = await fetch(`/api/v1/ops/repair-tickets/${ticket.ticketId}/${action}`, {
      method: "POST",
    });
    const result = (await response.json()) as { ok: boolean; message?: string };
    setMessage(result.ok ? "工单状态已更新。" : result.message ?? "操作失败");
    if (result.ok) router.refresh();
  }

  return (
    <div className="mt-3 flex flex-wrap items-center gap-2">
      {ticket.approvalStatus !== "approved" ? (
        <button
          type="button"
          onClick={() => void run("approve")}
          className="rounded-full bg-[#07C160] px-3 py-2 text-[12px] font-semibold text-white"
        >
          主 Agent 批准
        </button>
      ) : null}
      {ticket.executionStatus !== "verified" ? (
        <button
          type="button"
          onClick={() => void run("verify")}
          className="rounded-full border border-[#07C160] px-3 py-2 text-[12px] font-semibold text-[#07C160]"
        >
          运维审计复验
        </button>
      ) : null}
      {verification ? (
        <span className="rounded-full bg-[#F5F5F7] px-3 py-2 text-[12px] text-[#57606A]">
          复验：{verification.status}
        </span>
      ) : null}
      {message ? (
        <span className="text-[12px] text-[#57606A]">{message}</span>
      ) : null}
    </div>
  );
}

export function ForwardComposer({
  sourceProjectId,
  targets,
}: {
  sourceProjectId: string;
  targets: Array<{ id: string; name: string }>;
}) {
  const router = useRouter();
  const [targetProjectId, setTargetProjectId] = useState(targets[0]?.id ?? "");
  const [note, setNote] = useState("");
  const [message, setMessage] = useState("");

  async function submit() {
    const response = await fetch(`/api/v1/projects/${sourceProjectId}/forwards`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ targetProjectId, note }),
    });
    const result = (await response.json()) as { ok: boolean; message?: string };
    if (result.ok) {
      router.push(`/conversations/${targetProjectId}`);
      router.refresh();
      return;
    }
    setMessage(result.message ?? "转发失败");
  }

  return (
    <div className="space-y-3 rounded-2xl border border-[#E5E5EA] bg-white px-4 py-4">
      <div className="text-[16px] font-semibold text-[#111111]">转发到项目</div>
      <label className="block space-y-1">
        <span className="text-[12px] text-[#8C8C8C]">目标项目</span>
        <select
          value={targetProjectId}
          onChange={(event) => setTargetProjectId(event.target.value)}
          className="w-full rounded-xl border border-[#E5E5EA] bg-[#F9F9FA] px-3 py-3 text-[14px] outline-none"
        >
          {targets.map((target) => (
            <option key={target.id} value={target.id}>
              {target.name}
            </option>
          ))}
        </select>
      </label>
      <label className="block space-y-1">
        <span className="text-[12px] text-[#8C8C8C]">转发说明</span>
        <textarea
          value={note}
          onChange={(event) => setNote(event.target.value)}
          className="min-h-28 w-full rounded-xl border border-[#E5E5EA] bg-[#F9F9FA] p-3 text-[14px] leading-6 outline-none"
        />
      </label>
      <button
        type="button"
        onClick={() => void submit()}
        className="rounded-full bg-[#07C160] px-4 py-2 text-[13px] font-semibold text-white"
      >
        执行转发
      </button>
      {message ? (
        <div className="rounded-2xl bg-[#FFF1F0] px-4 py-3 text-[12px] text-[#CF1322]">
          {message}
        </div>
      ) : null}
    </div>
  );
}
