import { type ReactNode, type RefObject, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { invoke, isTauri } from "@tauri-apps/api/core";
import { open } from "@tauri-apps/plugin-dialog";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "@/shared/ui/dialog";
import { AgentsSettingsPanel } from "@/modules/settings-center/ui/agents-settings-panel";
import { GatewaySettingsPanel } from "@/modules/settings-center/ui/gateway-settings-panel";
import { PageHeading, SettingsSection, SettingsRow, ChoiceGroup, SectionDivider } from "@/modules/settings-center/ui/settings-shared";
import { ProfileAgentAccess } from "@/modules/settings-center/ui/profile-agent-access";
import { useT } from "@/i18n";
import type { TranslationKey } from "@/i18n/locales/zh-CN";
import { getEffectiveModelCapabilities, inferModelCapabilities } from "@/modules/settings-center/model/model-capabilities";
import type { WebSearchSettingsPatch } from "@/modules/settings-center/model/web-search-settings";
import {
  ArrowLeft,
  Bot,
  Brain,
  Check,
  ChevronDown,
  CircleUserRound,
  Copy,
  Database,
  Download,
  Eye,
  EyeOff,
  FolderOpen,
  FolderPlus,
  GitBranch,
  Image,
  Info,
  MessageSquare,
  Monitor,
  MousePointerClick,
  Pencil,
  Plus,
  RefreshCw,
  Search,
  Server,
  Settings2,
  ShieldCheck,
  Shuffle,
  Star,
  TerminalSquare,
  Trash2,
  UserCog,
  Wrench,
  X,
  Zap,
} from "lucide-react";
import { openPath, openUrl } from "@tauri-apps/plugin-opener";
import type { LanguagePreference } from "@/app/providers/language-provider";
import type { ThemePreference } from "@/app/providers/theme-provider";
import { getInvokeErrorMessage } from "@/shared/lib/invoke-error";
import { sortAgentProfilesByName } from "@/modules/settings-center/model/profile-utils";
import { matchProviderIcon } from "@/shared/lib/llm-brand-matcher";
import type { ProviderModelConnectionTestResultDto } from "@/shared/types/api";
import type { ConfigDiagnostic } from "@/shared/types/extensions";
import type { SystemMetadata } from "@/shared/types/system";
import { cn } from "@/shared/lib/utils";
import { Button } from "@/shared/ui/button";
import { Input } from "@/shared/ui/input";
import { LocalLlmIcon } from "@/shared/ui/local-llm-icon";
import { ModelBrandIcon } from "@/shared/ui/model-brand-icon";
import { Switch } from "@/shared/ui/switch";
import { Textarea } from "@/shared/ui/textarea";
import type {
  AgentProfile,
  ApprovalPolicy,
  CommandEntry,
  CommandSettings,
  CustomProviderType,
  CustomSubagent,
  GeneralPreferences,
  PatternEntry,
  PolicySettings,
  PromptResponseStyle,
  ThinkingLevel,
  ProviderCatalogEntry,
  ProviderEntry,
  ProviderModel,
  ProviderModelCapabilities,
  SettingsCategory,
  TerminalCursorStyle,
  TerminalSettings,
  WebSearchEngine,
  WebSearchSettings,
  WorkspaceEntry,
  WritableRootEntry,
} from "@/modules/settings-center/model/types";

function validatePolicyPatternInput(
  value: string,
  t: ReturnType<typeof useT>,
): string | null {
  const trimmed = value.trim();
  if (!trimmed) {
    return t("settings.permissions.patternRequired");
  }

  if (trimmed.startsWith("shell:")) {
    return trimmed.slice("shell:".length).trim()
      ? null
      : t("settings.permissions.shellPatternRequired");
  }

  if (trimmed.startsWith("any:")) {
    return trimmed.slice("any:".length).trim()
      ? null
      : t("settings.permissions.anyPatternRequired");
  }

  if (trimmed.startsWith("tool:")) {
    const remainder = trimmed.slice("tool:".length).trimStart();
    if (!remainder) {
      return t("settings.permissions.toolPatternFormat");
    }

    const separatorIndex = remainder.search(/\s/);
    if (separatorIndex < 0) {
      return t("settings.permissions.toolPatternFormat");
    }

    const toolName = remainder.slice(0, separatorIndex).trim();
    const pattern = remainder.slice(separatorIndex).trim();
    return toolName && pattern
      ? null
      : t("settings.permissions.toolPatternFormat");
  }

  return null;
}

type SettingsCenterOverlayProps = {
  activeCategory: SettingsCategory;
  agentProfiles: Array<AgentProfile>;
  activeAgentProfileId: string;
  contentRef: RefObject<HTMLDivElement | null>;
  configDiagnostics: ConfigDiagnostic[];
  customSubagents: Array<CustomSubagent>;
  generalPreferences: GeneralPreferences;
  webSearch: WebSearchSettings;
  isCheckingUpdates: boolean;
  language: LanguagePreference;
  commands: CommandSettings;
  policy: PolicySettings;
  terminal: TerminalSettings;
  availableShells: Array<{ path: string; name: string }>;
  providerCatalog: Array<ProviderCatalogEntry>;
  providers: Array<ProviderEntry>;
  systemMetadata: SystemMetadata | null;
  theme: ThemePreference;
  updateStatus: string | null;
  workspaces: Array<WorkspaceEntry>;
  onAddAgentProfile: (entry: Omit<AgentProfile, "id">) => void;
  onAddAllowEntry: (entry: Omit<PatternEntry, "id">) => void;
  onAddCommand: (entry: Omit<CommandEntry, "id">) => void;
  onCommitNewCommand: (id: string) => void;
  onAddDenyEntry: (entry: Omit<PatternEntry, "id">) => void;
  onAddProvider: (entry: Omit<ProviderEntry, "id">) => void;
  onAddWorkspace: (entry: Omit<WorkspaceEntry, "id">) => void;
  onAddWritableRoot: (entry: Omit<WritableRootEntry, "id">) => void;
  onCheckUpdates: () => void;
  onClose: () => void;
  onDuplicateAgentProfile: (id: string) => void;
  onRemoveAgentProfile: (id: string) => void;
  onRemoveAllowEntry: (id: string) => void;
  onRemoveCommand: (id: string) => void;
  onRemoveDenyEntry: (id: string) => void;
  onRemoveProvider: (id: string) => void;
  onRemoveWorkspace: (id: string) => void;
  onRemoveWritableRoot: (id: string) => void;
  onSelectCategory: (category: SettingsCategory) => void;
  onSelectLanguage: (language: LanguagePreference) => void;
  onSelectTheme: (theme: ThemePreference) => void;
  onSetActiveAgentProfile: (id: string) => void;
  onSetDefaultWorkspace: (id: string) => void;
  onUpdateAgentProfile: (id: string, patch: Partial<Omit<AgentProfile, "id">>) => void;
  onUpdateAllowEntry: (id: string, patch: Partial<Omit<PatternEntry, "id">>) => void;
  onUpdateCommand: (id: string, patch: Partial<Omit<CommandEntry, "id">>) => void;
  onUpdateDenyEntry: (id: string, patch: Partial<Omit<PatternEntry, "id">>) => void;
  onUpdateGeneralPreference: <Key extends keyof GeneralPreferences>(key: Key, value: GeneralPreferences[Key]) => void;
  onUpdateWebSearchSettings: (patch: WebSearchSettingsPatch) => Promise<WebSearchSettings>;
  onUpdatePolicySetting: <Key extends keyof PolicySettings>(key: Key, value: PolicySettings[Key]) => void;
  onFetchProviderModels: (id: string) => Promise<void>;
  onTestProviderModelConnection: (
    providerId: string,
    modelId: string,
  ) => Promise<ProviderModelConnectionTestResultDto>;
  onUpdateProvider: (id: string, patch: Partial<Omit<ProviderEntry, "id">>) => void;
  onUpdateTerminalSetting: <Key extends keyof TerminalSettings>(key: Key, value: TerminalSettings[Key]) => void;
  onUpdateWritableRoot: (id: string, patch: Partial<Omit<WritableRootEntry, "id">>) => void;
};

type TFunc = (key: TranslationKey, params?: Record<string, string | number>) => string;

function getCategoryMeta(t: TFunc) {
  return [
    {
      key: "account" as SettingsCategory,
      title: t("settings.category.account"),
      description: t("settings.category.accountDesc"),
      icon: CircleUserRound,
    },
    {
      key: "general" as SettingsCategory,
      title: t("settings.category.general"),
      description: t("settings.category.generalDesc"),
      icon: Monitor,
    },
    {
      key: "providers" as SettingsCategory,
      title: t("settings.category.providers"),
      description: t("settings.category.providersDesc"),
      icon: Server,
    },
    {
      key: "profiles" as SettingsCategory,
      title: t("settings.category.profiles"),
      description: t("settings.category.profilesDesc"),
      icon: UserCog,
    },
    {
      key: "gateway" as SettingsCategory,
      title: t("settings.category.gateway"),
      description: t("settings.category.gatewayDesc"),
      icon: MessageSquare,
    },
    {
      key: "agents" as SettingsCategory,
      title: t("settings.category.agents"),
      description: t("settings.category.agentsDesc"),
      icon: Bot,
    },
    {
      key: "commands" as SettingsCategory,
      title: t("settings.category.commands"),
      description: t("settings.category.commandsDesc"),
      icon: Zap,
    },
    {
      key: "policy" as SettingsCategory,
      title: t("settings.category.permissions"),
      description: t("settings.category.permissionsDesc"),
      icon: ShieldCheck,
    },
    {
      key: "workspace" as SettingsCategory,
      title: t("settings.category.workspace"),
      description: t("settings.category.workspaceDesc"),
      icon: FolderOpen,
    },
    {
      key: "terminal" as SettingsCategory,
      title: t("settings.category.terminal"),
      description: t("settings.category.terminalDesc"),
      icon: TerminalSquare,
    },
    {
      key: "about" as SettingsCategory,
      title: t("settings.category.about"),
      description: t("settings.category.aboutDesc"),
      icon: Info,
    },
  ];
}

const THEME_OPTIONS: ReadonlyArray<{ label: string; value: ThemePreference }> = [
  { label: "System", value: "system" },
  { label: "Light", value: "light" },
  { label: "Dark", value: "dark" },
];

const LANGUAGE_OPTIONS: ReadonlyArray<{ label: string; value: LanguagePreference }> = [
  { label: "English", value: "en" },
  { label: "简体中文", value: "zh-CN" },
];

function getResponseStyleOptions(t: TFunc) {
  return [
    { value: "concise" as PromptResponseStyle, label: t("settings.responseStyle.concise"), description: t("settings.responseStyle.conciseDesc") },
    { value: "balanced" as PromptResponseStyle, label: t("settings.responseStyle.balanced"), description: t("settings.responseStyle.balancedDesc") },
    { value: "guide" as PromptResponseStyle, label: t("settings.responseStyle.guided"), description: t("settings.responseStyle.guidedDesc") },
  ];
}

function getThinkingLevelOptions(t: TFunc) {
  return [
    { value: "off" as ThinkingLevel, label: t("settings.thinkingLevel.off"), description: t("settings.thinkingLevel.offDesc") },
    { value: "minimal" as ThinkingLevel, label: t("settings.thinkingLevel.minimal"), description: t("settings.thinkingLevel.minimalDesc") },
    { value: "low" as ThinkingLevel, label: t("settings.thinkingLevel.low"), description: t("settings.thinkingLevel.lowDesc") },
    { value: "medium" as ThinkingLevel, label: t("settings.thinkingLevel.medium"), description: t("settings.thinkingLevel.mediumDesc") },
    { value: "high" as ThinkingLevel, label: t("settings.thinkingLevel.high"), description: t("settings.thinkingLevel.highDesc") },
    { value: "xhigh" as ThinkingLevel, label: t("settings.thinkingLevel.xhigh"), description: t("settings.thinkingLevel.xhighDesc") },
  ];
}

function getApprovalPolicyOptions(t: TFunc) {
  return [
    { value: "untrusted" as ApprovalPolicy, label: t("settings.policy.untrusted"), description: t("settings.policy.untrustedDesc") },
    { value: "on-request" as ApprovalPolicy, label: t("settings.policy.onRequest"), description: t("settings.policy.onRequestDesc") },
    { value: "never" as ApprovalPolicy, label: t("settings.policy.never"), description: t("settings.policy.neverDesc") },
  ];
}

const MINIMAX_BASE_URL_OPTIONS = [
  "https://api.minimax.io/anthropic/v1",
  "https://api.minimaxi.com/anthropic/v1",
] as const;

function deriveWorkspaceNameFromPath(path: string) {
  const segments = path.split(/[\\/]/).filter(Boolean);
  return segments.length > 0 ? segments[segments.length - 1] : "New Workspace";
}

export function SettingsCenterOverlay({
  activeCategory,
  agentProfiles,
  activeAgentProfileId,
  contentRef,
  configDiagnostics,
  customSubagents,
  generalPreferences,
  webSearch,
  isCheckingUpdates,
  language,
  commands,
  policy,
  terminal,
  availableShells,
  providerCatalog,
  providers,
  systemMetadata,
  theme,
  updateStatus,
  workspaces,
  onAddAgentProfile,
  onAddAllowEntry,
  onAddCommand,
  onCommitNewCommand,
  onAddDenyEntry,
  onAddProvider,
  onAddWorkspace,
  onAddWritableRoot,
  onCheckUpdates,
  onClose,
  onDuplicateAgentProfile,
  onRemoveAgentProfile,
  onRemoveAllowEntry,
  onRemoveCommand,
  onRemoveDenyEntry,
  onRemoveProvider,
  onRemoveWorkspace,
  onRemoveWritableRoot,
  onSelectCategory,
  onSelectLanguage,
  onSelectTheme,
  onSetActiveAgentProfile,
  onSetDefaultWorkspace,
  onUpdateAgentProfile,
  onUpdateAllowEntry,
  onUpdateCommand,
  onUpdateDenyEntry,
  onUpdateGeneralPreference,
  onUpdateWebSearchSettings,
  onUpdatePolicySetting,
  onFetchProviderModels,
  onTestProviderModelConnection,
  onUpdateProvider,
  onUpdateTerminalSetting,
  onUpdateWritableRoot,
}: SettingsCenterOverlayProps) {
  const t = useT();
  const CATEGORY_META = useMemo(() => getCategoryMeta(t), [t]);
  const VISIBLE_CATEGORY_META = useMemo(() => CATEGORY_META.filter((category) => category.key !== "account"), [CATEGORY_META]);
  const activeMeta = CATEGORY_META.find((category) => category.key === activeCategory) ?? CATEGORY_META[0];
  const isAboutCategory = activeCategory === "about";
  const [agentsUnsavedChanges, setAgentsUnsavedChanges] = useState(false);
  const [pendingCategory, setPendingCategory] = useState<SettingsCategory | null>(null);
  const [pendingClose, setPendingClose] = useState(false);

  const handleSelectCategory = (category: SettingsCategory) => {
    if (agentsUnsavedChanges && activeCategory === "agents" && category !== activeCategory) {
      setPendingCategory(category);
      return;
    }
    onSelectCategory(category);
  };

  const handleCloseWithGuard = () => {
    if (agentsUnsavedChanges && activeCategory === "agents") {
      setPendingClose(true);
    } else {
      onClose();
    }
  };

  const handleDiscardAgentsChanges = () => {
    const wasPendingClose = pendingClose;
    const nextCategory = pendingCategory;
    setPendingCategory(null);
    setPendingClose(false);
    setAgentsUnsavedChanges(false);
    if (wasPendingClose) {
      onClose();
    } else if (nextCategory) {
      onSelectCategory(nextCategory);
    }
  };

  return (
    <div className="fixed inset-x-0 bottom-0 top-9 z-[60] bg-app-canvas text-app-foreground">
      <div className="flex h-full min-h-0">
        <aside className="hidden w-[320px] shrink-0 overflow-hidden border-r border-app-border bg-app-sidebar md:flex md:flex-col">
          <div className="flex h-full min-h-0 flex-col px-3 pb-3 pt-4">
            <button
              type="button"
              className="inline-flex items-center gap-2 px-3 py-1 text-[12px] text-app-muted transition-colors hover:text-app-foreground"
              onClick={handleCloseWithGuard}
            >
              <ArrowLeft className="size-3.5" />
              <span>{t("settings.backToApp")}</span>
            </button>

            <div className="mt-4 space-y-1">
              {VISIBLE_CATEGORY_META.map((category) => {
                const Icon = category.icon;
                const isActive = category.key === activeCategory;

                return (
                  <button
                    key={category.key}
                    type="button"
                    className={cn(
                      "group flex w-full items-center gap-2.5 rounded-xl border px-3 py-2.5 text-left transition-[transform,box-shadow,background-color,border-color,color] duration-200 active:scale-[0.99]",
                      isActive
                        ? "border-app-border-strong bg-app-surface-active text-app-foreground shadow-[0_4px_14px_rgba(15,23,42,0.08)]"
                        : "border-transparent bg-transparent text-app-muted hover:border-app-border hover:bg-app-surface-hover hover:text-app-foreground hover:shadow-[0_4px_14px_rgba(15,23,42,0.08)]",
                    )}
                    onClick={() => handleSelectCategory(category.key)}
                  >
                    <Icon
                      className={cn(
                        "size-4 shrink-0 transition-colors duration-200",
                        isActive ? "text-app-foreground" : "text-app-subtle group-hover:text-app-foreground",
                      )}
                    />
                    <span className="truncate text-sm font-medium">{category.title}</span>
                  </button>
                );
              })}
            </div>
          </div>
        </aside>

        <section className="min-w-0 flex-1 min-h-0 select-text bg-app-canvas">
          <div className="flex h-full min-h-0 flex-col">
          <div className="flex items-center justify-between border-b border-app-border px-4 py-3 md:hidden">
            <button
              type="button"
              className="inline-flex items-center gap-2 rounded-lg px-2 py-1.5 text-[12px] text-app-muted transition-colors hover:bg-app-surface-hover hover:text-app-foreground"
              onClick={handleCloseWithGuard}
            >
              <ArrowLeft className="size-3.5" />
              <span>{t("settings.backToApp")}</span>
            </button>
            <p className="text-sm font-medium text-app-foreground">{activeMeta.title}</p>
          </div>

          <div className="overflow-x-auto border-b border-app-border px-4 py-2 md:hidden [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
            <div className="flex w-max items-center gap-4">
              {VISIBLE_CATEGORY_META.map((category) => {
                const isActive = category.key === activeCategory;

                return (
                  <button
                    key={category.key}
                    type="button"
                    className={cn(
                      "border-b px-0.5 py-1 text-[13px] transition-colors",
                      isActive
                        ? "border-app-border-strong text-app-foreground"
                        : "border-transparent text-app-muted hover:text-app-foreground",
                    )}
                    onClick={() => handleSelectCategory(category.key)}
                  >
                    {category.title}
                  </button>
                );
              })}
            </div>
          </div>

          <div
            ref={contentRef}
            className="relative min-h-0 flex-1"
          >
            <div className="h-full overflow-y-auto overscroll-contain [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
              <div
                className={cn(
                  "mx-auto px-6",
                  isAboutCategory
                    ? "flex min-h-full w-full max-w-4xl items-center justify-center py-8"
                    : "flex max-w-4xl flex-col gap-6 pb-28 pt-6",
                )}
              >
                {configDiagnostics.length > 0 ? (
                  <div className="rounded-2xl border border-app-warning/30 bg-app-warning/8 px-4 py-3 text-sm text-app-warning">
                    <p className="font-medium text-app-foreground">{t("extensions.configIssuesDetected")}</p>
                    <div className="mt-2 space-y-2 text-[12px] leading-5">
                      {configDiagnostics.map((diagnostic) => (
                        <div key={diagnostic.id} className="rounded-xl border border-app-warning/20 bg-app-canvas/40 px-3 py-2">
                          <p className="font-medium text-app-foreground">{diagnostic.summary}</p>
                          <p className="mt-1 break-all">{diagnostic.filePath}</p>
                          <p className="mt-1 text-app-subtle">{diagnostic.suggestion}</p>
                        </div>
                      ))}
                    </div>
                  </div>
                ) : null}

                {activeCategory === "account" ? (
                  <AccountSettingsPanel
                    description={activeMeta.description}
                  />
                ) : null}

                {activeCategory === "general" ? (
                  <GeneralSettingsPanel
                    description={activeMeta.description}
                    generalPreferences={generalPreferences}
                    webSearch={webSearch}
                    language={language}
                    theme={theme}
                    onSelectLanguage={onSelectLanguage}
                    onSelectTheme={onSelectTheme}
                    onUpdateGeneralPreference={onUpdateGeneralPreference}
                    onUpdateWebSearchSettings={onUpdateWebSearchSettings}
                  />
                ) : null}

                {activeCategory === "profiles" ? (
                  <ProfileSettingsPanel
                    agentProfiles={agentProfiles}
                    activeAgentProfileId={activeAgentProfileId}
                    customSubagents={customSubagents}
                    description={activeMeta.description}
                    providers={providers}
                    onAddAgentProfile={onAddAgentProfile}
                    onDuplicateAgentProfile={onDuplicateAgentProfile}
                    onRemoveAgentProfile={onRemoveAgentProfile}
                    onSetActiveAgentProfile={onSetActiveAgentProfile}
                    onUpdateAgentProfile={onUpdateAgentProfile}
                  />
                ) : null}

                {activeCategory === "workspace" ? (
                  <WorkspaceSettingsPanel
                    description={activeMeta.description}
                    systemMetadata={systemMetadata}
                    workspaces={workspaces}
                    onAddWorkspace={onAddWorkspace}
                    onRemoveWorkspace={onRemoveWorkspace}
                    onSetDefaultWorkspace={onSetDefaultWorkspace}
                  />
                ) : null}

                {activeCategory === "providers" ? (
                  <ProviderSettingsPanel
                    description={activeMeta.description}
                    providers={providers}
                    providerCatalog={providerCatalog}
                    onAddProvider={onAddProvider}
                    onFetchProviderModels={onFetchProviderModels}
                    onTestProviderModelConnection={onTestProviderModelConnection}
                    onRemoveProvider={onRemoveProvider}
                    onUpdateProvider={onUpdateProvider}
                  />
                ) : null}

                {activeCategory === "commands" ? (
                  <CommandSettingsPanel
                    description={activeMeta.description}
                    commands={commands}
                    onAddCommand={onAddCommand}
                    onCommitNewCommand={onCommitNewCommand}
                    onRemoveCommand={onRemoveCommand}
                    onUpdateCommand={onUpdateCommand}
                  />
                ) : null}

                {activeCategory === "terminal" ? (
                  <TerminalSettingsPanel
                    description={activeMeta.description}
                    terminal={terminal}
                    availableShells={availableShells}
                    onUpdateTerminalSetting={onUpdateTerminalSetting}
                  />
                ) : null}

                {activeCategory === "policy" ? (
                  <PolicySettingsPanel
                    description={activeMeta.description}
                    policy={policy}
                    onAddAllowEntry={onAddAllowEntry}
                    onAddDenyEntry={onAddDenyEntry}
                    onAddWritableRoot={onAddWritableRoot}
                    onRemoveAllowEntry={onRemoveAllowEntry}
                    onRemoveDenyEntry={onRemoveDenyEntry}
                    onRemoveWritableRoot={onRemoveWritableRoot}
                    onUpdateAllowEntry={onUpdateAllowEntry}
                    onUpdateDenyEntry={onUpdateDenyEntry}
                    onUpdatePolicySetting={onUpdatePolicySetting}
                    onUpdateWritableRoot={onUpdateWritableRoot}
                  />
                ) : null}

                {activeCategory === "agents" ? (
                  <AgentsSettingsPanel
                    customSubagents={customSubagents}
                    description={activeMeta.description}
                    onUnsavedChangesChange={setAgentsUnsavedChanges}
                  />
                ) : null}

                {activeCategory === "gateway" ? (
                  <GatewaySettingsPanel description={activeMeta.description} />
                ) : null}

                {activeCategory === "about" ? (
                  <AboutSettingsPanel
                    isCheckingUpdates={isCheckingUpdates}
                    runtime={systemMetadata}
                    updateStatus={updateStatus}
                    onCheckUpdates={onCheckUpdates}
                  />
                ) : null}
              </div>
            </div>
            <div className="pointer-events-none absolute inset-x-0 bottom-0 h-14 bg-gradient-to-b from-transparent via-app-overlay via-55% to-app-canvas" />
          </div>
        </div>
        </section>
      </div>

      <Dialog open={pendingCategory !== null || pendingClose} onOpenChange={(open) => { if (!open) { setPendingCategory(null); setPendingClose(false); } }}>
        <DialogContent className="max-w-md border-app-border bg-app-canvas">
          <DialogHeader>
            <DialogTitle>{t("settings.agents.unsavedChangesTitle")}</DialogTitle>
            <DialogDescription>{t("settings.agents.unsavedChangesLeaveDesc")}</DialogDescription>
          </DialogHeader>
          <DialogFooter className="gap-2">
            <Button type="button" variant="outline" onClick={() => setPendingCategory(null)}>
              {t("settings.agents.continueEditing")}
            </Button>
            <Button type="button" variant="destructive" onClick={handleDiscardAgentsChanges}>
              {t("settings.agents.discardChanges")}
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </div>
  );
}

type PlanName = "Free" | "Lite" | "Pro" | "Max";

type PlanDefinition = {
  description: string;
  monthlyCredits: number;
  name: PlanName;
  priceLabel: string;
  summary: string;
};

const PLAN_DEFINITIONS: ReadonlyArray<PlanDefinition> = [
  {
    name: "Free",
    priceLabel: "",
    summary: "A one-time trial with 1,000 credits for your first 31 days after signup.",
    description: "Best for evaluating the core experience, testing a few real workflows, and deciding whether you need an ongoing plan.",
    monthlyCredits: 1_000,
  },
  {
    name: "Lite",
    priceLabel: "$9.9/mo",
    summary: "For occasional personal use, quick chats, and lightweight agent workflows.",
    description: "Includes 2,000 credits each month for individuals who use the product regularly but do not need heavy daily volume.",
    monthlyCredits: 2_000,
  },
  {
    name: "Pro",
    priceLabel: "$19.9/mo",
    summary: "For consistent day-to-day work across coding, research, and repeated agent runs.",
    description: "Includes 5,000 monthly credits and is the best fit for active individual users who want reliable room for deeper sessions.",
    monthlyCredits: 5_000,
  },
  {
    name: "Max",
    priceLabel: "$199.9/mo",
    summary: "For intensive usage, long sessions, and high-volume multi-step workflows.",
    description: "Includes 80,000 monthly credits for advanced users who need substantial capacity for sustained, heavy workloads.",
    monthlyCredits: 80_000,
  },
] as const;

const PLAN_VISUAL_STYLES: Partial<
  Record<
    PlanName,
    {
      activeClass: string;
      cardClass: string;
      chipClass: string;
      currentBadgeClass: string;
    }
  >
> = {
  Lite: {
    cardClass:
      "border-emerald-400/25 bg-[radial-gradient(circle_at_top_left,rgba(52,211,153,0.16),transparent_42%),linear-gradient(180deg,rgba(255,255,255,0.82),rgba(255,255,255,0.52))] shadow-[0_18px_40px_-32px_rgba(16,185,129,0.55)] dark:border-emerald-400/18 dark:bg-[radial-gradient(circle_at_top_left,rgba(52,211,153,0.2),transparent_38%),linear-gradient(180deg,rgba(20,30,28,0.88),rgba(20,30,28,0.56))] dark:shadow-[0_18px_42px_-30px_rgba(16,185,129,0.38)]",
    activeClass:
      "border-emerald-500/35 shadow-[0_22px_48px_-30px_rgba(16,185,129,0.62)] dark:border-emerald-300/28 dark:shadow-[0_20px_46px_-28px_rgba(16,185,129,0.44)]",
    chipClass:
      "border-emerald-400/24 bg-white/72 text-emerald-700 dark:border-emerald-400/18 dark:bg-emerald-400/10 dark:text-emerald-100",
    currentBadgeClass:
      "border-emerald-500/18 bg-white/70 text-emerald-700 dark:border-emerald-400/18 dark:bg-emerald-400/10 dark:text-emerald-100",
  },
  Pro: {
    cardClass:
      "border-sky-400/28 bg-[radial-gradient(circle_at_top_right,rgba(56,189,248,0.16),transparent_42%),linear-gradient(180deg,rgba(255,255,255,0.84),rgba(255,255,255,0.54))] shadow-[0_18px_40px_-32px_rgba(59,130,246,0.5)] dark:border-sky-400/18 dark:bg-[radial-gradient(circle_at_top_right,rgba(56,189,248,0.2),transparent_38%),linear-gradient(180deg,rgba(18,24,34,0.88),rgba(18,24,34,0.56))] dark:shadow-[0_18px_42px_-30px_rgba(56,189,248,0.34)]",
    activeClass:
      "border-sky-500/38 shadow-[0_22px_48px_-30px_rgba(59,130,246,0.58)] dark:border-sky-300/28 dark:shadow-[0_20px_46px_-28px_rgba(56,189,248,0.42)]",
    chipClass:
      "border-sky-400/24 bg-white/72 text-sky-700 dark:border-sky-400/18 dark:bg-sky-400/10 dark:text-sky-100",
    currentBadgeClass:
      "border-sky-500/18 bg-white/70 text-sky-700 dark:border-sky-400/18 dark:bg-sky-400/10 dark:text-sky-100",
  },
  Max: {
    cardClass:
      "border-lime-400/25 bg-[radial-gradient(circle_at_top_center,rgba(163,230,53,0.16),transparent_42%),linear-gradient(180deg,rgba(255,255,255,0.84),rgba(255,255,255,0.54))] shadow-[0_18px_40px_-32px_rgba(132,204,22,0.45)] dark:border-lime-400/18 dark:bg-[radial-gradient(circle_at_top_center,rgba(163,230,53,0.22),transparent_38%),linear-gradient(180deg,rgba(30,32,20,0.88),rgba(30,32,20,0.56))] dark:shadow-[0_18px_42px_-30px_rgba(163,230,53,0.34)]",
    activeClass:
      "border-lime-500/34 shadow-[0_22px_48px_-30px_rgba(132,204,22,0.52)] dark:border-lime-300/24 dark:shadow-[0_20px_46px_-28px_rgba(163,230,53,0.4)]",
    chipClass:
      "border-lime-400/24 bg-white/72 text-lime-700 dark:border-lime-400/18 dark:bg-lime-400/10 dark:text-lime-100",
    currentBadgeClass:
      "border-lime-500/18 bg-white/70 text-lime-700 dark:border-lime-400/18 dark:bg-lime-400/10 dark:text-lime-100",
  },
};

function formatCreditCount(count: number): string {
  if (count >= 1_000_000) {
    return `${(count / 1_000_000).toFixed(1)}M`;
  }

  if (count >= 1_000) {
    return `${(count / 1_000).toFixed(count % 1_000 === 0 ? 0 : 1)}K`;
  }

  return count.toString();
}

function SessionIdentityCard() {
  const t = useT();
  return (
    <div className="flex flex-col gap-4 px-4 py-4 sm:flex-row sm:items-center sm:justify-between">
      <div className="flex min-w-0 items-center gap-3">
        <div className="flex size-12 shrink-0 items-center justify-center rounded-full border border-app-border bg-app-surface-muted text-sm font-semibold text-app-foreground">
          <CircleUserRound className="size-5 text-app-subtle" />
        </div>
        <div className="min-w-0">
          <p className="truncate text-[14px] font-semibold text-app-foreground">
            {t("settings.account.guest")}
          </p>
          <p className="mt-1 truncate text-[12px] text-app-muted">
            {t("settings.account.signInHint")}
          </p>
        </div>
      </div>

      <div className="rounded-full border border-dashed border-app-border px-3 py-1 text-[12px] text-app-subtle">
        {t("settings.account.guestAccess")}
      </div>
    </div>
  );
}

function PlanCatalogCard({
  isActive,
  plan,
}: {
  isActive: boolean;
  plan: PlanDefinition;
}) {
  const t = useT();
  const planStyle = PLAN_VISUAL_STYLES[plan.name];

  return (
    <article
      className={cn(
        "rounded-2xl border px-4 py-4 transition-colors",
        planStyle?.cardClass ?? "border-app-border bg-app-surface",
        isActive
          ? planStyle?.activeClass ?? "border-app-foreground/20 bg-app-surface-muted"
          : null,
      )}
    >
      <div className="flex items-start justify-between gap-3">
        <div>
          <div className="flex items-center gap-2">
            <h3 className="text-[15px] font-semibold text-app-foreground">{plan.name}</h3>
            {isActive ? (
              <span
                className={cn(
                  "inline-flex items-center rounded-full border bg-app-surface px-2 py-0.5 text-[10px] font-medium uppercase tracking-[0.1em] text-app-foreground",
                  planStyle?.currentBadgeClass ?? "border-app-foreground/15",
                )}
              >
                {t("settings.account.currentBadge")}
              </span>
            ) : null}
          </div>
          <div className="mt-2 flex min-h-[20px] items-center">
            {plan.priceLabel ? (
              <p className="text-[20px] font-semibold leading-none text-app-foreground">{plan.priceLabel}</p>
            ) : null}
          </div>
          <p className="mt-2 text-[12px] leading-5 text-app-muted">{plan.summary}</p>
        </div>

        <div
          className={cn(
            "inline-flex min-h-8 shrink-0 items-center gap-1.5 rounded-full border bg-app-surface px-3 text-[12px] font-medium text-app-foreground",
            planStyle?.chipClass ?? "border-app-border",
          )}
        >
          <span>{formatCreditCount(plan.monthlyCredits)} credits</span>
          {plan.name === "Free" ? (
            <span className="inline-flex items-center rounded-full bg-app-surface-muted px-1.5 py-0.5 text-[10px] font-medium leading-none text-app-subtle">
              {t("settings.account.trialBadge")}
            </span>
          ) : null}
        </div>
      </div>

      <p className="mt-4 text-[12px] leading-5 text-app-muted">{plan.description}</p>
    </article>
  );
}

function AccountSettingsPanel({
  description,
}: {
  description: string;
}) {
  const t = useT();

  return (
    <div className="flex flex-col gap-6">
      <PageHeading title={t("settings.category.account")} description={description} />

      <SettingsSection title="Session">
        <SessionIdentityCard />
      </SettingsSection>

      <section>
        <div className="mb-2 flex items-center justify-between px-1">
          <h2 className="text-[11px] font-medium uppercase tracking-[0.12em] text-app-subtle">{t("settings.account.currentPlan")}</h2>
        </div>
        <div className="rounded-2xl border border-dashed border-app-border bg-app-surface-muted px-4 py-4">
          <p className="text-[13px] font-medium text-app-foreground">{t("settings.account.guestAccess")}</p>
          <p className="mt-1 text-[12px] leading-5 text-app-muted">
            {t("settings.account.signInForPlan")}
          </p>
        </div>
      </section>

      <section>
        <div className="mb-2 flex items-center justify-between px-1">
          <h2 className="text-[11px] font-medium uppercase tracking-[0.12em] text-app-subtle">{t("settings.account.planComparison")}</h2>
        </div>
        <div className="grid gap-3 sm:grid-cols-2">
          {PLAN_DEFINITIONS.map((plan) => (
            <PlanCatalogCard
              key={plan.name}
              plan={plan}
              isActive={false}
            />
          ))}
        </div>
      </section>

      <section>
        <div className="mb-2 flex items-center justify-between px-1">
          <h2 className="text-[11px] font-medium uppercase tracking-[0.12em] text-app-subtle">{t("settings.account.billingHistory")}</h2>
        </div>
        <div className="rounded-2xl border border-dashed border-app-border bg-app-surface-muted px-4 py-4">
          <p className="text-[13px] font-medium text-app-foreground">{t("settings.account.billingAfterSignIn")}</p>
          <p className="mt-1 text-[12px] leading-5 text-app-muted">
            {t("settings.account.signInForBilling")}
          </p>
        </div>
      </section>
    </div>
  );
}

function ProfileLibraryActions({
  onAddProfile,
}: {
  onAddProfile: () => void;
}) {
  const t = useT();

  return (
    <button
      type="button"
      className="inline-flex shrink-0 items-center gap-1.5 rounded-lg border border-app-border bg-app-surface px-3 py-1.5 text-[12px] font-medium text-app-foreground transition-colors hover:bg-app-surface-hover"
      onClick={onAddProfile}
    >
      <Plus className="size-3.5" />
      <span>{t("settings.profiles.addProfile")}</span>
    </button>
  );
}

type ProfilePrimaryModelSummary = {
  label: string;
  iconDisplayName?: string;
  iconModelId?: string;
};

function getThinkingLevelLabel(value: ThinkingLevel, t: TFunc) {
  return getThinkingLevelOptions(t).find((option) => option.value === value)?.label ?? t("settings.thinkingLevel.off");
}

function ProfileLibraryCard({
  profile,
  isActive,
  canDelete,
  primaryModel,
  isEditing,
  editingName,
  onSelect,
  onStartRename,
  onEditingNameChange,
  onCommitRename,
  onCancelRename,
  onDuplicate,
  onDelete,
}: {
  profile: AgentProfile;
  isActive: boolean;
  canDelete: boolean;
  primaryModel: ProfilePrimaryModelSummary;
  isEditing: boolean;
  editingName: string;
  onSelect: () => void;
  onStartRename: () => void;
  onEditingNameChange: (name: string) => void;
  onCommitRename: () => void;
  onCancelRename: () => void;
  onDuplicate: () => void;
  onDelete: () => void;
}) {
  const t = useT();
  const [showActions, setShowActions] = useState(false);
  const thinkingLevelLabel = getThinkingLevelLabel(profile.thinkingLevel, t);
  const modelSummaryTitle = `${primaryModel.label} · ${thinkingLevelLabel}`;

  return (
    <div
      role="button"
      tabIndex={0}
      aria-pressed={isActive}
      className={cn(
        "group relative flex min-h-[76px] cursor-pointer items-center gap-3 rounded-2xl border px-4 py-3 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-app-accent/40",
        isActive
          ? "border-app-info/45 bg-app-info/10 shadow-sm"
          : "border-transparent bg-app-surface-muted hover:border-app-border hover:bg-app-surface-hover",
      )}
      onClick={onSelect}
      onMouseEnter={() => setShowActions(true)}
      onMouseLeave={(event) => {
        if (!event.currentTarget.contains(document.activeElement)) {
          setShowActions(false);
        }
      }}
      onFocus={() => setShowActions(true)}
      onBlur={(event) => {
        if (!event.currentTarget.contains(event.relatedTarget)) {
          setShowActions(false);
        }
      }}
      onKeyDown={(event) => {
        if (event.target !== event.currentTarget) return;
        if (event.key === "Enter" || event.key === " ") {
          event.preventDefault();
          onSelect();
        }
      }}
    >
      <span
        aria-hidden="true"
        className={cn(
          "flex size-11 shrink-0 items-center justify-center rounded-2xl border",
          isActive
            ? "border-app-info/30 bg-app-info/10 text-app-info"
            : "border-app-border bg-app-surface text-app-subtle",
        )}
      >
        {primaryModel.iconModelId ? (
          <ModelBrandIcon
            className="size-5 text-[15px]"
            displayName={primaryModel.iconDisplayName ?? primaryModel.label}
            modelId={primaryModel.iconModelId}
          />
        ) : (
          <Bot className="size-5" />
        )}
      </span>

      <div
        className={cn(
          "min-w-0 flex-1",
          !isEditing && showActions && "pr-24",
          !isEditing && isActive && !showActions && "pr-10",
        )}
      >
        {isEditing ? (
          <input
            autoFocus
            className="h-7 w-full min-w-0 rounded-lg border border-app-accent bg-app-surface px-2 text-[13px] font-medium text-app-foreground outline-none"
            value={editingName}
            onClick={(event) => event.stopPropagation()}
            onChange={(event) => onEditingNameChange(event.target.value)}
            onBlur={onCommitRename}
            onKeyDown={(event) => {
              if (event.key === "Enter") onCommitRename();
              if (event.key === "Escape") onCancelRename();
            }}
          />
        ) : (
          <p className="truncate text-[13px] font-medium leading-5 text-app-foreground">{profile.name}</p>
        )}
        <div
          className="mt-1 flex min-w-0 items-center gap-1 text-[12px] leading-5 text-app-muted"
          title={modelSummaryTitle}
        >
          <span className="min-w-0 truncate">{primaryModel.label}</span>
          <span aria-hidden="true" className="shrink-0 text-app-subtle">·</span>
          <span className="min-w-0 max-w-[88px] truncate">{thinkingLevelLabel}</span>
        </div>
      </div>

      {isActive && !isEditing && !showActions ? (
        <span
          className="absolute right-3 top-3 flex size-6 items-center justify-center rounded-full border border-app-info/30 bg-app-info/15 text-app-info shadow-sm"
          title={t("settings.profiles.currentProfileLabel")}
          aria-label={t("settings.profiles.currentProfileLabel")}
        >
          <Check className="size-3.5" />
        </span>
      ) : null}

      {!isEditing && showActions ? (
        <div className="absolute right-3 top-3 flex items-center gap-0.5 rounded-lg bg-app-surface-muted/90 p-0.5 shadow-sm backdrop-blur">
          <button
            type="button"
            className="flex size-6 items-center justify-center rounded text-app-subtle hover:bg-app-canvas hover:text-app-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-app-accent/40"
            title={t("settings.profile.rename")}
            onClick={(event) => {
              event.stopPropagation();
              onStartRename();
            }}
          >
            <Pencil className="size-3" />
          </button>
          <button
            type="button"
            className="flex size-6 items-center justify-center rounded text-app-subtle hover:bg-app-canvas hover:text-app-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-app-accent/40"
            title={t("settings.profile.duplicate")}
            onClick={(event) => {
              event.stopPropagation();
              onDuplicate();
            }}
          >
            <Copy className="size-3" />
          </button>
          <button
            type="button"
            className={cn(
              "flex size-6 items-center justify-center rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-app-accent/40",
              canDelete
                ? "text-app-subtle hover:bg-app-canvas hover:text-red-500"
                : "cursor-not-allowed text-app-subtle/40",
            )}
            title={t("settings.profile.delete")}
            disabled={!canDelete}
            onClick={(event) => {
              event.stopPropagation();
              if (canDelete) onDelete();
            }}
          >
            <Trash2 className="size-3" />
          </button>
        </div>
      ) : null}
    </div>
  );
}

function GeneralSettingsPanel({
  description,
  generalPreferences,
  webSearch,
  language,
  theme,
  onSelectLanguage,
  onSelectTheme,
  onUpdateGeneralPreference,
  onUpdateWebSearchSettings,
}: {
  description: string;
  generalPreferences: GeneralPreferences;
  webSearch: WebSearchSettings;
  language: LanguagePreference;
  theme: ThemePreference;
  onSelectLanguage: (language: LanguagePreference) => void;
  onSelectTheme: (theme: ThemePreference) => void;
  onUpdateGeneralPreference: <Key extends keyof GeneralPreferences>(key: Key, value: GeneralPreferences[Key]) => void;
  onUpdateWebSearchSettings: (patch: WebSearchSettingsPatch) => Promise<WebSearchSettings>;
}) {
  const t = useT();
  const [webSearchApiKey, setWebSearchApiKey] = useState("");
  const [webSearchBaseUrl, setWebSearchBaseUrl] = useState(webSearch.baseUrl ?? "");
  const [webSearchMaxResults, setWebSearchMaxResults] = useState<number | "">(webSearch.maxResults);
  const [isSavingWebSearchKey, setIsSavingWebSearchKey] = useState(false);
  const [webSearchApiKeyError, setWebSearchApiKeyError] = useState<string | null>(null);
  const webSearchEngineOptions = useMemo(
    () => [
      { label: "Tavily", value: "tavily" as WebSearchEngine },
      { label: "Brave", value: "brave" as WebSearchEngine },
      { label: "Exa", value: "exa" as WebSearchEngine },
      { label: "Firecrawl", value: "firecrawl" as WebSearchEngine },
    ],
    [],
  );

  const handleSaveWebSearchApiKey = async () => {
    setWebSearchApiKeyError(null);
    setIsSavingWebSearchKey(true);
    try {
      await onUpdateWebSearchSettings({ apiKey: webSearchApiKey });
      setWebSearchApiKey("");
    } catch (error) {
      setWebSearchApiKeyError(
        getInvokeErrorMessage(error, t("settings.general.webSearchApiKeySaveError")),
      );
    } finally {
      setIsSavingWebSearchKey(false);
    }
  };

  const isClearingWebSearchApiKey = webSearch.hasApiKey && !webSearchApiKey.trim();

  useEffect(() => {
    setWebSearchApiKey("");
    setWebSearchApiKeyError(null);
  }, [webSearch.engine]);

  useEffect(() => {
    setWebSearchBaseUrl(webSearch.baseUrl ?? "");
  }, [webSearch.baseUrl]);

  useEffect(() => {
    setWebSearchMaxResults(webSearch.maxResults);
  }, [webSearch.maxResults]);

  const clampWebSearchMaxResults = (value: number) => Math.min(20, Math.max(1, Math.round(value)));

  const [cliInstalled, setCliInstalled] = useState(false);
  const [cliLoading, setCliLoading] = useState(false);
  const [cliError, setCliError] = useState<string | null>(null);

  useEffect(() => {
    if (isTauri()) {
      invoke<boolean>("is_cli_installed").then(setCliInstalled).catch(() => {});
    }
  }, []);

  const handleToggleCli = async () => {
    if (!isTauri()) return;
    setCliLoading(true);
    setCliError(null);
    try {
      if (cliInstalled) {
        await invoke<string>("uninstall_cli_from_path");
        setCliInstalled(false);
      } else {
        await invoke<string>("install_cli_in_path");
        setCliInstalled(true);
      }
    } catch (error) {
      // User-cancelled operations get a friendly message from the backend;
      // other errors are surfaced so the user knows something went wrong.
      const msg = typeof error === "string" ? error : String(error);
      if (!msg.includes("cancelled by user") && !msg.includes("User canceled") && !msg.includes("(-128)")) {
        console.error("CLI toggle failed:", error);
        setCliError(msg);
      }
    } finally {
      setCliLoading(false);
    }
  };

  return (
    <div className="flex flex-col gap-6">
      <PageHeading title={t("settings.category.general")} description={description} />

      <SettingsSection title={t("settings.general.preferences")}>
        <SettingsRow
          label={t("settings.general.themeLabel")}
          description={t("settings.general.themeDesc")}
          control={
            <ChoiceGroup
              options={THEME_OPTIONS}
              value={theme}
              onValueChange={(value) => onSelectTheme(value as ThemePreference)}
            />
          }
        />
        <SectionDivider />
        <SettingsRow
          label={t("settings.general.languageLabel")}
          description={t("settings.general.languageDesc")}
          control={
            <ChoiceGroup
              options={LANGUAGE_OPTIONS}
              value={language}
              onValueChange={(value) => onSelectLanguage(value as LanguagePreference)}
            />
          }
        />
        <SectionDivider />
        <SettingsRow
          label={t("settings.general.startupLabel")}
          description={t("settings.general.startupDesc")}
          control={
            <Switch
              size="sm"
              checked={generalPreferences.launchAtLogin}
              onCheckedChange={(checked) => onUpdateGeneralPreference("launchAtLogin", checked)}
            />
          }
        />
        <SectionDivider />
        <SettingsRow
          label={t("settings.general.preventSleepLabel")}
          description={t("settings.general.preventSleepDesc")}
          control={
            <Switch
              size="sm"
              checked={generalPreferences.preventSleepWhileRunning}
              onCheckedChange={(checked) => onUpdateGeneralPreference("preventSleepWhileRunning", checked)}
            />
          }
        />
        <SectionDivider />
        <SettingsRow
          label={t("settings.general.closeToTrayLabel")}
          description={t("settings.general.closeToTrayDesc")}
          control={
            <Switch
              size="sm"
              checked={generalPreferences.minimizeToTray}
              onCheckedChange={(checked) => onUpdateGeneralPreference("minimizeToTray", checked)}
            />
          }
        />
        <SectionDivider />
        <SettingsRow
          label={t("settings.general.defaultAppendKindLabel")}
          description={t("settings.general.defaultAppendKindDesc")}
          control={
            <ChoiceGroup
              options={[
                { label: t("composer.queueMode.steer"), value: "steer" },
                { label: t("composer.queueMode.followUp"), value: "follow_up" },
              ]}
              value={generalPreferences.defaultAppendMessageKind}
              onValueChange={(value) => onUpdateGeneralPreference("defaultAppendMessageKind", value)}
            />
          }
        />
      </SettingsSection>

      <SettingsSection title={t("settings.general.webSearchTitle")}>
        <SettingsRow
          label={t("settings.general.webSearchEnabledLabel")}
          description={t("settings.general.webSearchEnabledDesc")}
          control={
            <Switch
              size="sm"
              checked={webSearch.enabled}
              onCheckedChange={(checked) => void onUpdateWebSearchSettings({ enabled: checked })}
            />
          }
        />
        <SectionDivider />
        <SettingsRow
          label={t("settings.general.webSearchEngineLabel")}
          description={t("settings.general.webSearchEngineDesc")}
          control={
            <ChoiceGroup
              options={webSearchEngineOptions}
              value={webSearch.engine}
              onValueChange={(value) => void onUpdateWebSearchSettings({ engine: value })}
            />
          }
        />
        <SectionDivider />
        <SettingsRow
          label={t("settings.general.webSearchApiKeyLabel")}
          description={
            webSearch.hasApiKey
              ? t("settings.general.webSearchApiKeyConfiguredDesc")
              : t("settings.general.webSearchApiKeyDesc")
          }
          control={
            <div className="flex w-full flex-col gap-2 md:w-[360px]">
              <div className="flex gap-2">
                <Input
                  type="password"
                  value={webSearchApiKey}
                  placeholder={webSearch.hasApiKey ? "••••••••••••" : t("settings.general.webSearchApiKeyPlaceholder")}
                  onChange={(event) => {
                    setWebSearchApiKey(event.target.value);
                    setWebSearchApiKeyError(null);
                  }}
                />
                <Button
                  type="button"
                  variant="secondary"
                  size="sm"
                  disabled={(!webSearchApiKey.trim() && !webSearch.hasApiKey) || isSavingWebSearchKey}
                  onClick={() => void handleSaveWebSearchApiKey()}
                >
                  {isClearingWebSearchApiKey
                    ? t("settings.general.webSearchApiKeyClear")
                    : t("settings.general.webSearchApiKeySave")}
                </Button>
              </div>
              {webSearchApiKeyError ? (
                <p className="text-[11px] font-medium text-app-error">{webSearchApiKeyError}</p>
              ) : null}
            </div>
          }
        />
        <SectionDivider />
        <SettingsRow
          label={t("settings.general.webSearchBaseUrlLabel")}
          description={t("settings.general.webSearchBaseUrlDesc")}
          optional
          control={
            <Input
              value={webSearchBaseUrl}
              placeholder={t("settings.general.webSearchBaseUrlPlaceholder")}
              className="w-full md:w-[360px]"
              onChange={(event) => setWebSearchBaseUrl(event.target.value)}
              onBlur={() => {
                const trimmed = webSearchBaseUrl.trim();
                if (trimmed !== (webSearch.baseUrl ?? "")) {
                  void onUpdateWebSearchSettings({ baseUrl: trimmed });
                }
              }}
            />
          }
        />
        <SectionDivider />
        <SettingsRow
          label={t("settings.general.webSearchMaxResultsLabel")}
          description={t("settings.general.webSearchMaxResultsDesc")}
          control={
            <Input
              type="number"
              min={1}
              max={20}
              className="w-24"
              value={webSearchMaxResults}
              onChange={(event) => {
                const { value } = event.target;
                if (value === "") {
                  setWebSearchMaxResults("");
                  return;
                }
                const nextValue = Number(value);
                if (Number.isFinite(nextValue)) {
                  setWebSearchMaxResults(nextValue);
                }
              }}
              onBlur={() => {
                const draftValue = webSearchMaxResults === "" ? webSearch.maxResults : webSearchMaxResults;
                const clamped = clampWebSearchMaxResults(draftValue);
                setWebSearchMaxResults(clamped);
                if (clamped !== webSearch.maxResults) {
                  void onUpdateWebSearchSettings({ maxResults: clamped });
                }
              }}
            />
          }
        />
        <SectionDivider />
        <SettingsRow
          label={t("settings.general.webSearchRawContentLabel")}
          description={t("settings.general.webSearchRawContentDesc")}
          control={
            <Switch
              size="sm"
              checked={webSearch.includeRawContent}
              onCheckedChange={(checked) => void onUpdateWebSearchSettings({ includeRawContent: checked })}
            />
          }
        />
      </SettingsSection>

      <SettingsSection title={t("settings.general.acpServerTitle")}>
        <SettingsRow
          label={t("settings.general.acpServerCliLabel")}
          description={t("settings.general.acpServerCliDesc")}
          control={
            <Button
              type="button"
              variant="outline"
              className="h-8 rounded-lg border-app-border bg-app-surface-muted px-3 text-[12px] font-medium text-app-foreground shadow-none hover:border-app-border-strong hover:bg-app-surface-hover"
              disabled={cliLoading}
              onClick={handleToggleCli}
            >
              {cliInstalled ? (
                <>
                  <Check className="size-3.5" />
                  {t("settings.general.cliUninstall")}
                </>
              ) : (
                <>
                  <TerminalSquare className="size-3.5" />
                  {t("settings.general.cliInstall")}
                </>
              )}
            </Button>
          }
        />
        {cliError && (
          <p className="px-1 text-xs text-app-danger">{cliError}</p>
        )}
      </SettingsSection>
    </div>
  );
}

type AvailableProfileModel = {
  providerId: string;
  providerName: string;
  modelRecordId: string;
  modelId: string;
  displayName: string;
};

function collectAvailableProfileModels(providers: Array<ProviderEntry>): Array<AvailableProfileModel> {
  const models: Array<AvailableProfileModel> = [];
  for (const provider of providers) {
    if (!provider.enabled) continue;
    for (const model of provider.models) {
      if (!model.enabled) continue;
      models.push({
        providerId: provider.id,
        modelRecordId: model.id,
        modelId: model.modelId,
        displayName: model.displayName || model.modelId,
        providerName: provider.displayName,
      });
    }
  }
  return models.sort((a, b) => {
    const providerCompare = a.providerName.localeCompare(b.providerName, undefined, { sensitivity: "base" });
    if (providerCompare !== 0) return providerCompare;
    const modelCompare = a.displayName.localeCompare(b.displayName, undefined, { sensitivity: "base" });
    if (modelCompare !== 0) return modelCompare;
    return a.modelId.localeCompare(b.modelId, undefined, { sensitivity: "base" });
  });
}

function ProfileSettingsPanel({
  agentProfiles,
  activeAgentProfileId,
  customSubagents,
  description,
  providers,
  onAddAgentProfile,
  onDuplicateAgentProfile,
  onRemoveAgentProfile,
  onSetActiveAgentProfile,
  onUpdateAgentProfile,
}: {
  agentProfiles: Array<AgentProfile>;
  activeAgentProfileId: string;
  customSubagents: Array<CustomSubagent>;
  description: string;
  providers: Array<ProviderEntry>;
  onAddAgentProfile: (entry: Omit<AgentProfile, "id">) => void;
  onDuplicateAgentProfile: (id: string) => void;
  onRemoveAgentProfile: (id: string) => void;
  onSetActiveAgentProfile: (id: string) => void;
  onUpdateAgentProfile: (id: string, patch: Partial<Omit<AgentProfile, "id">>) => void;
}) {
  const t = useT();
  const RESPONSE_STYLE_OPTIONS = useMemo(() => getResponseStyleOptions(t), [t]);
  const THINKING_LEVEL_OPTIONS = useMemo(() => getThinkingLevelOptions(t), [t]);
  const availableModels = useMemo(() => collectAvailableProfileModels(providers), [providers]);

  const activeProfile = agentProfiles.find((p) => p.id === activeAgentProfileId) ?? agentProfiles[0];
  const sortedProfiles = useMemo(() => sortAgentProfilesByName(agentProfiles), [agentProfiles]);
  const selectedStyle = RESPONSE_STYLE_OPTIONS.find((option) => option.value === activeProfile.responseStyle) ?? RESPONSE_STYLE_OPTIONS[0];
  const selectedThinking = THINKING_LEVEL_OPTIONS.find((option) => option.value === activeProfile.thinkingLevel) ?? THINKING_LEVEL_OPTIONS[0];
  const [editingProfileId, setEditingProfileId] = useState<string | null>(null);
  const [editingProfileName, setEditingProfileName] = useState("");

  const getProfilePrimaryModelSummary = (profile: AgentProfile): ProfilePrimaryModelSummary => {
    const selectedModel = availableModels.find(
      (model) => model.providerId === profile.primaryProviderId && model.modelRecordId === profile.primaryModelId,
    );
    if (selectedModel) {
      return {
        label: selectedModel.displayName,
        iconDisplayName: selectedModel.displayName,
        iconModelId: selectedModel.modelId,
      };
    }
    if (profile.primaryModelId) {
      return {
        label: profile.primaryModelId,
        iconModelId: profile.primaryModelId,
      };
    }
    return { label: t("settings.general.notSet") };
  };

  const handleStartRenameProfile = (profile: AgentProfile) => {
    setEditingProfileId(profile.id);
    setEditingProfileName(profile.name);
  };

  const handleCommitRenameProfile = () => {
    if (editingProfileId && editingProfileName.trim()) {
      onUpdateAgentProfile(editingProfileId, { name: editingProfileName.trim() });
    }
    setEditingProfileId(null);
  };

  const handleCancelRenameProfile = () => {
    setEditingProfileId(null);
    setEditingProfileName("");
  };

  const handleAddProfile = () => {
    onAddAgentProfile({
      name: t("settings.profiles.newProfile"),
      customInstructions: "",
      commitMessagePrompt: activeProfile.commitMessagePrompt,
      responseStyle: "balanced",
      thinkingLevel: "off",
      responseLanguage: "English",
      commitMessageLanguage: "English",
      primaryProviderId: "",
      primaryModelId: "",
      assistantProviderId: "",
      assistantModelId: "",
      liteProviderId: "",
      liteModelId: "",
    });
  };

  return (
    <div className="flex flex-col gap-6">
      <PageHeading title={t("settings.category.profiles")} description={description} />

      <SettingsSection
        title={t("settings.profiles.profileLibrary")}
        headerClassName="flex-wrap gap-2"
        action={
          <ProfileLibraryActions
            onAddProfile={handleAddProfile}
          />
        }
      >
        <div className="space-y-4 bg-app-surface px-4 py-4">
          <p className="max-w-2xl text-[12px] leading-5 text-app-muted">
            {t("settings.profiles.profileLibraryDesc")}
          </p>
          <div className="max-h-[210px] overflow-y-auto pr-1 [scrollbar-width:thin]">
            <div className="grid gap-2.5 md:grid-cols-2">
              {sortedProfiles.map((profile) => (
                <ProfileLibraryCard
                  key={profile.id}
                  profile={profile}
                  isActive={profile.id === activeProfile.id}
                  canDelete={agentProfiles.length > 1}
                  primaryModel={getProfilePrimaryModelSummary(profile)}
                  isEditing={editingProfileId === profile.id}
                  editingName={editingProfileName}
                  onSelect={() => onSetActiveAgentProfile(profile.id)}
                  onStartRename={() => handleStartRenameProfile(profile)}
                  onEditingNameChange={setEditingProfileName}
                  onCommitRename={handleCommitRenameProfile}
                  onCancelRename={handleCancelRenameProfile}
                  onDuplicate={() => onDuplicateAgentProfile(profile.id)}
                  onDelete={() => {
                    setEditingProfileId(null);
                    onRemoveAgentProfile(profile.id);
                  }}
                />
              ))}
            </div>
          </div>
        </div>
      </SettingsSection>

      <SettingsSection title={t("settings.profiles.behaviorSection")}>
        <div className="bg-app-surface px-4 py-3">
          <p className="text-[12px] leading-5 text-app-muted">{t("settings.profiles.behaviorSectionDesc")}</p>
        </div>
        <SectionDivider />
        <SettingsRow
          label={t("settings.general.responseStyle")}
          description={selectedStyle.description}
          control={
            <ChoiceGroup
              options={RESPONSE_STYLE_OPTIONS.map(({ label, value }) => ({ label, value }))}
              value={activeProfile.responseStyle}
              onValueChange={(value) => onUpdateAgentProfile(activeProfile.id, { responseStyle: value as PromptResponseStyle })}
            />
          }
        />
        <SectionDivider />
        <SettingsRow
          label={t("settings.general.responseLanguage")}
          description={t("settings.general.responseLanguageDesc")}
          control={
            <Input
              value={activeProfile.responseLanguage}
              onChange={(event) => onUpdateAgentProfile(activeProfile.id, { responseLanguage: event.target.value })}
              className="w-40 text-[13px]"
              placeholder="English"
            />
          }
        />
      </SettingsSection>

      <SettingsSection title={t("settings.profiles.modelRoutingSection")}>
        <div className="bg-app-surface px-4 py-3">
          <p className="text-[12px] leading-5 text-app-muted">{t("settings.profiles.modelRoutingSectionDesc")}</p>
        </div>
        <SectionDivider />
        <SettingsRow
          label={t("settings.general.thinkingLevel")}
          description={selectedThinking.description}
          control={
            <ChoiceGroup
              options={THINKING_LEVEL_OPTIONS.map(({ label, value }) => ({ label, value }))}
              value={activeProfile.thinkingLevel}
              onValueChange={(value) => onUpdateAgentProfile(activeProfile.id, { thinkingLevel: value as ThinkingLevel })}
            />
          }
        />
        <SectionDivider />
        <ModelSelectRow
          label={t("settings.general.primaryModel")}
          description={t("settings.general.primaryModelDesc")}
          providerId={activeProfile.primaryProviderId}
          modelRecordId={activeProfile.primaryModelId}
          availableModels={availableModels}
          onValueChange={(providerId, modelRecordId) => onUpdateAgentProfile(activeProfile.id, {
            primaryProviderId: providerId,
            primaryModelId: modelRecordId,
          })}
        />
        <SectionDivider />
        <ModelSelectRow
          label={t("settings.general.auxiliaryModel")}
          description={t("settings.general.auxiliaryModelDesc")}
          providerId={activeProfile.assistantProviderId}
          modelRecordId={activeProfile.assistantModelId}
          availableModels={availableModels}
          onValueChange={(providerId, modelRecordId) => onUpdateAgentProfile(activeProfile.id, {
            assistantProviderId: providerId,
            assistantModelId: modelRecordId,
          })}
        />
        <SectionDivider />
        <ModelSelectRow
          label={t("settings.general.lightweightModel")}
          description={t("settings.general.lightweightModelDesc")}
          providerId={activeProfile.liteProviderId}
          modelRecordId={activeProfile.liteModelId}
          availableModels={availableModels}
          onValueChange={(providerId, modelRecordId) => onUpdateAgentProfile(activeProfile.id, {
            liteProviderId: providerId,
            liteModelId: modelRecordId,
          })}
        />
      </SettingsSection>

      <SettingsSection title={t("settings.profiles.agentAccessSection")}>
        <div className="bg-app-surface px-4 py-3">
          <p className="text-[12px] leading-5 text-app-muted">{t("settings.profiles.agentAccessSectionDesc")}</p>
        </div>
        <SectionDivider />
        <div className="px-4 py-3">
          <ProfileAgentAccess
            profileId={activeProfile.id}
            customSubagents={customSubagents}
          />
        </div>
      </SettingsSection>

      <SettingsSection title={t("settings.profiles.instructionsSection")}>
        <div className="bg-app-surface px-4 py-3">
          <p className="text-[12px] leading-5 text-app-muted">{t("settings.profiles.instructionsSectionDesc")}</p>
        </div>
        <SectionDivider />
        <div className="px-4 py-3">
          <div className="mb-1 text-[13px] font-medium leading-5 text-app-foreground">{t("settings.general.customInstructions")}</div>
          <p className="mb-3 text-[12px] leading-5 text-app-muted">
            {t("settings.general.customInstructionsDesc")}
          </p>
          <Textarea
            value={activeProfile.customInstructions}
            onChange={(event) => onUpdateAgentProfile(activeProfile.id, { customInstructions: event.target.value })}
            className="min-h-36"
          />
        </div>
        <SectionDivider />
        <SettingsRow
          label={t("settings.general.commitLanguage")}
          description={t("settings.general.commitLanguageDesc")}
          control={
            <Input
              value={activeProfile.commitMessageLanguage}
              onChange={(event) => onUpdateAgentProfile(activeProfile.id, {
                commitMessageLanguage: event.target.value,
              })}
              className="w-40 text-[13px]"
              placeholder="English"
            />
          }
        />
        <SectionDivider />
        <div className="px-4 py-3">
          <div className="mb-1 text-[13px] font-medium leading-5 text-app-foreground">{t("settings.general.commitPrompt")}</div>
          <p className="mb-3 text-[12px] leading-5 text-app-muted">
            {t("settings.general.commitPromptDesc")}
          </p>
          <Textarea
            value={activeProfile.commitMessagePrompt}
            onChange={(event) => onUpdateAgentProfile(activeProfile.id, { commitMessagePrompt: event.target.value })}
            className="h-48 min-h-48 overflow-y-auto [field-sizing:fixed]"
          />
        </div>
      </SettingsSection>
    </div>
  );
}

function ModelSelectRow({
  availableModels,
  description,
  label,
  providerId,
  modelRecordId,
  onValueChange,
}: {
  availableModels: Array<{
    providerId: string;
    providerName: string;
    modelRecordId: string;
    modelId: string;
    displayName: string;
  }>;
  description: string;
  label: string;
  providerId: string;
  modelRecordId: string;
  onValueChange: (providerId: string, modelRecordId: string) => void;
}) {
  const t = useT();
  const [isOpen, setIsOpen] = useState(false);
  const triggerRef = useRef<HTMLButtonElement>(null);
  const dropdownRef = useRef<HTMLDivElement>(null);
  const [dropdownStyle, setDropdownStyle] = useState<React.CSSProperties>({});

  useLayoutEffect(() => {
    if (!isOpen || !triggerRef.current) return;
    const rect = triggerRef.current.getBoundingClientRect();
    const dropdownMaxH = 256;
    const spaceBelow = window.innerHeight - rect.bottom - 8;
    const placeAbove = spaceBelow < dropdownMaxH && rect.top > spaceBelow;
    setDropdownStyle({
      position: "fixed",
      right: window.innerWidth - rect.right,
      minWidth: Math.max(rect.width, 280),
      maxHeight: dropdownMaxH,
      ...(placeAbove
        ? { bottom: window.innerHeight - rect.top + 4 }
        : { top: rect.bottom + 4 }),
    });
  }, [isOpen]);

  useEffect(() => {
    if (!isOpen) return;
    const handleClickOutside = (event: MouseEvent) => {
      const target = event.target as Node;
      if (triggerRef.current?.contains(target) || dropdownRef.current?.contains(target)) return;
      setIsOpen(false);
    };
    document.addEventListener("pointerdown", handleClickOutside);
    return () => document.removeEventListener("pointerdown", handleClickOutside);
  }, [isOpen]);

  const selectedModel = availableModels.find((m) => m.providerId === providerId && m.modelRecordId === modelRecordId);
  const displayValue = selectedModel
    ? `${selectedModel.displayName}`
    : modelRecordId
      ? modelRecordId
      : t("settings.general.notSet");

  const grouped = useMemo(() => {
    const map = new Map<string, Array<{
      providerId: string;
      providerName: string;
      modelRecordId: string;
      modelId: string;
      displayName: string;
    }>>();
    for (const model of availableModels) {
      const list = map.get(model.providerName) ?? [];
      list.push(model);
      map.set(model.providerName, list);
    }
    return map;
  }, [availableModels]);

  return (
    <div className="grid gap-3 bg-app-surface px-4 py-3 md:grid-cols-[minmax(0,1fr)_auto] md:items-center">
      <div className="min-w-0">
        <p className="text-[13px] font-medium text-app-foreground">{label}</p>
        <p className="mt-1 text-[12px] leading-5 text-app-muted">{description}</p>
      </div>
      <div className="min-w-0 md:justify-self-end">
        <button
          ref={triggerRef}
          type="button"
          className={cn(
            "inline-flex min-h-8 w-full items-center justify-between gap-2 rounded-lg border border-app-border bg-app-surface-muted px-3 text-[12px] transition-colors hover:bg-app-surface-hover md:w-auto md:min-w-[200px]",
            !selectedModel && !modelRecordId && "text-app-muted",
          )}
          onClick={() => setIsOpen(!isOpen)}
        >
          <span className="flex min-w-0 items-center gap-2">
            {selectedModel ? (
              <ModelBrandIcon
                className="size-4"
                displayName={selectedModel.displayName}
                modelId={selectedModel.modelId}
              />
            ) : null}
            <span className="truncate">{displayValue}</span>
          </span>
          <ChevronDown className={cn("size-3 shrink-0 text-app-subtle transition-transform", isOpen && "rotate-180")} />
        </button>
        {isOpen
          ? createPortal(
              <div
                ref={dropdownRef}
                style={dropdownStyle}
                className="z-[100] overflow-y-auto rounded-lg border border-app-border bg-app-surface shadow-lg"
              >
                <button
                  type="button"
                  className={cn(
                    "flex w-full items-center px-3 py-2 text-left text-[12px] transition-colors hover:bg-app-surface-hover",
                    !modelRecordId && "text-app-accent",
                  )}
                  onClick={() => { onValueChange("", ""); setIsOpen(false); }}
                >
                  <span className="italic text-app-muted">{t("settings.general.notSet")}</span>
                </button>
                {[...grouped.entries()].map(([providerName, models]) => (
                  <div key={providerName}>
                    <div className="sticky top-0 bg-app-surface px-3 py-1.5 text-[11px] font-semibold uppercase tracking-wider text-app-subtle">
                      {providerName}
                    </div>
                    {models.map((model) => (
                      <button
                        key={`${model.providerId}:${model.modelRecordId}`}
                        type="button"
                        className={cn(
                          "flex w-full items-center gap-2 px-3 py-2 text-left text-[12px] transition-colors hover:bg-app-surface-hover",
                          providerId === model.providerId && modelRecordId === model.modelRecordId && "text-app-accent",
                        )}
                        onClick={() => { onValueChange(model.providerId, model.modelRecordId); setIsOpen(false); }}
                      >
                        <ModelBrandIcon className="size-4" displayName={model.displayName} modelId={model.modelId} />
                        <span className="truncate">{model.displayName}</span>
                        <span className="ml-auto shrink-0 truncate font-mono text-[10px] text-app-subtle">{model.modelId.split("/").pop()}</span>
                      </button>
                    ))}
                  </div>
                ))}
                {availableModels.length === 0 ? (
                  <div className="px-3 py-4 text-center text-[12px] text-app-muted">
                    {t("settings.general.noModelsAvailable")}
                  </div>
                ) : null}
              </div>,
              document.body,
            )
          : null}
      </div>
    </div>
  );
}

function AboutSettingsPanel({
  isCheckingUpdates,
  runtime,
  updateStatus,
  onCheckUpdates,
}: {
  isCheckingUpdates: boolean;
  runtime: SystemMetadata | null;
  updateStatus: string | null;
  onCheckUpdates: () => void;
}) {
  const t = useT();
  const version = runtime?.version ?? "0.1.0";
  const appName = runtime?.appName ?? "TiyCode";
  const platformSummary = runtime?.platform ?? "Unknown platform";
  const architectureSummary = runtime?.arch ?? "Unknown architecture";

  const aboutActions = [
    { href: "https://tiy.ai", label: t("settings.about.officialWebsite") },
    { href: "https://github.com/tiylabs/tiycode/blob/master/LICENSE", label: t("settings.about.license") },
    { href: "https://github.com/tiylabs/tiycode/issues", label: t("settings.about.feedback") },
    { href: "mailto:contact@tiy.ai", label: t("settings.about.contactEmail") },
  ] as const;

  return (
    <div className="flex w-full items-center justify-center">
      <section className="flex w-full justify-center">
        <div className="flex flex-col items-center gap-5 px-5 py-7 text-center">
          <img src="/app-icon.png" alt={`${appName} logo`} className="size-20 object-contain" />

          <div className="max-w-[560px] space-y-2">
            <h2 className="text-[19px] font-semibold tracking-[-0.03em] text-app-foreground">{appName}</h2>
            <p className="text-[13px] leading-6 text-app-muted">
              {t("settings.about.description")}
            </p>
            <p className="text-[12px] leading-5 text-app-subtle">{`Version v${version} • ${platformSummary} • ${architectureSummary}`}</p>
          </div>

          <div className="flex max-w-[560px] flex-wrap items-center justify-center gap-2">
            {aboutActions.map((action) => (
              <Button
                key={action.label}
                type="button"
                variant="outline"
                className="h-9 rounded-xl border-app-border bg-app-surface-muted px-4 text-[13px] font-medium text-app-foreground shadow-none hover:border-app-border-strong hover:bg-app-surface-hover"
                onClick={() => {
                  void openUrl(action.href);
                }}
              >
                {action.label}
              </Button>
            ))}
          </div>

          <Button
            type="button"
            variant="outline"
            className="h-9 rounded-xl border-app-border bg-app-surface-muted px-4 text-[13px] font-medium text-app-foreground shadow-none hover:border-app-border-strong hover:bg-app-surface-hover"
            onClick={onCheckUpdates}
          >
            <RefreshCw data-icon="inline-start" className={cn("size-3.5", isCheckingUpdates && "animate-spin")} />
            {isCheckingUpdates ? t("settings.about.checkingUpdates") : t("settings.about.checkForUpdates")}
          </Button>

          <div className="flex min-h-10 items-center justify-center">
            <p className={cn("text-[12px] leading-5 text-app-subtle", !updateStatus && "invisible")} aria-live="polite">
              {updateStatus ?? "Update status placeholder"}
            </p>
          </div>

          <div className="flex w-full max-w-[560px] flex-col items-center gap-1.5 pt-1 text-[11px] leading-5 text-app-subtle">
            <p className="text-center">{t("settings.about.copyright")}</p>

            <div className="flex items-center justify-center gap-3">
              <button
                type="button"
                className="transition-colors hover:text-app-foreground"
                onClick={() => {
                  void openUrl("https://tiy.ai/terms");
                }}
              >
                {t("settings.about.termsOfService")}
              </button>
              <button
                type="button"
                className="transition-colors hover:text-app-foreground"
                onClick={() => {
                  void openUrl("https://tiy.ai/privacy");
                }}
              >
                {t("settings.about.privacyPolicy")}
              </button>
            </div>
          </div>
        </div>
      </section>
    </div>
  );
}

function CommandSettingsPanel({
  description,
  commands,
  onAddCommand,
  onCommitNewCommand,
  onRemoveCommand,
  onUpdateCommand,
}: {
  description: string;
  commands: CommandSettings;
  onAddCommand: (entry: Omit<CommandEntry, "id">) => void;
  onCommitNewCommand: (id: string) => void;
  onRemoveCommand: (id: string) => void;
  onUpdateCommand: (id: string, patch: Partial<Omit<CommandEntry, "id">>) => void;
}) {
  const t = useT();
  return (
    <div className="flex flex-col gap-6">
      <PageHeading title={t("settings.category.commands")} description={description} />

      <CommandsSection
        commands={commands.commands}
        onAddCommand={onAddCommand}
        onCommitNewCommand={onCommitNewCommand}
        onRemoveCommand={onRemoveCommand}
        onUpdateCommand={onUpdateCommand}
      />
    </div>
  );
}

function CommandsSection({
  commands,
  onAddCommand,
  onCommitNewCommand,
  onRemoveCommand,
  onUpdateCommand,
}: {
  commands: Array<CommandEntry>;
  onAddCommand: (entry: Omit<CommandEntry, "id">) => void;
  onCommitNewCommand: (id: string) => void;
  onRemoveCommand: (id: string) => void;
  onUpdateCommand: (id: string, patch: Partial<Omit<CommandEntry, "id">>) => void;
}) {
  const t = useT();
  const [editingId, setEditingId] = useState<string | null>(null);
  const pendingAddRef = useRef(false);

  useEffect(() => {
    if (pendingAddRef.current && commands.length > 0) {
      pendingAddRef.current = false;
      setEditingId(commands[commands.length - 1].id);
    }
  }, [commands]);

  const handleAddCommand = () => {
    pendingAddRef.current = true;
    onAddCommand({
      name: "",
      path: "",
      argumentHint: "",
      description: "",
      prompt: "",
    });
  };

  return (
    <SettingsSection
      title={t("settings.category.commands")}
      action={
        <button
          type="button"
          className="inline-flex shrink-0 items-center gap-1.5 rounded-lg border border-app-border bg-app-surface px-3 py-1.5 text-[12px] font-medium text-app-foreground transition-colors hover:bg-app-surface-hover"
          onClick={handleAddCommand}
        >
          <Plus className="size-3.5" />
          <span>{t("settings.commands.addPrompt")}</span>
        </button>
      }
    >
      <div className="px-4 py-3">
        <p className="text-[12px] leading-5 text-app-muted">
          {t("settings.commands.createHint")}
        </p>
      </div>
      {commands.length > 0 ? (
        <div className="flex flex-col">
          {commands.map((command) => (
            <CommandItem
              key={command.id}
              command={command}
              isEditing={editingId === command.id}
              onEdit={() => {
                if (command.pendingCreate) {
                  onCommitNewCommand(command.id);
                }
                setEditingId(editingId === command.id ? null : command.id);
              }}
              onCancelEdit={() => {
                if (command.pendingCreate) {
                  onRemoveCommand(command.id);
                }
                setEditingId(null);
              }}
              onRemove={() => onRemoveCommand(command.id)}
              onUpdate={(patch) => onUpdateCommand(command.id, patch)}
            />
          ))}
        </div>
      ) : null}
    </SettingsSection>
  );
}

function CommandItem({
  command,
  isEditing,
  onEdit,
  onCancelEdit,
  onRemove,
  onUpdate,
}: {
  command: CommandEntry;
  isEditing: boolean;
  onEdit: () => void;
  onCancelEdit: () => void;
  onRemove: () => void;
  onUpdate: (patch: Partial<Omit<CommandEntry, "id">>) => void;
}) {
  const t = useT();
  const commandPath = command.name ? `/prompts:${command.name}` : "/prompts:unnamed";

  return (
    <div className="border-t border-app-border">
      <div className="flex items-center gap-3 px-4 py-3.5">
        <div className="shrink-0 text-app-subtle">
          <Zap className="size-4" />
        </div>

        <div className="min-w-0 flex-1">
          <div className="flex items-center gap-2">
            <span className="text-[13px] font-semibold text-app-foreground">{command.name || t("settings.commands.untitled")}</span>
            <span className="text-[12px] text-app-subtle">{commandPath}</span>
          </div>
          <p className="mt-1 truncate text-[12px] leading-5 text-app-muted">
            {command.description || <span className="italic">{t("settings.commands.noDescription")}</span>}
          </p>
          <p className="mt-1 truncate text-[11px] leading-5 text-app-subtle">
            {command.argumentHint || <span className="italic">{t("settings.commands.noArgumentHint")}</span>}
          </p>
        </div>

        <div className="flex shrink-0 items-center gap-1">
          {isEditing ? (
            <>
              <button
                type="button"
                title={t("settings.commands.saveChanges")}
                aria-label={t("settings.commands.saveChanges")}
                className="flex size-7 items-center justify-center rounded-md text-green-500 transition-colors hover:bg-app-surface-hover hover:text-green-600"
                onClick={onEdit}
              >
                <Check className="size-3.5" />
              </button>
              <button
                type="button"
                title={t("settings.commands.cancelEditing")}
                aria-label={t("settings.commands.cancelEditing")}
                className="flex size-7 items-center justify-center rounded-md text-app-subtle transition-colors hover:bg-app-surface-hover hover:text-app-foreground"
                onClick={onCancelEdit}
              >
                <X className="size-3.5" />
              </button>
            </>
          ) : (
            <>
              <button
                type="button"
                title={t("settings.commands.editCommand")}
                aria-label={t("settings.commands.editCommand")}
                className="flex size-7 items-center justify-center rounded-md text-app-subtle transition-colors hover:bg-app-surface-hover hover:text-app-foreground"
                onClick={onEdit}
              >
                <Pencil className="size-3.5" />
              </button>
              {command.source === "user" ? (
                <button
                  type="button"
                  title={t("settings.commands.removeCommand")}
                  aria-label={t("settings.commands.removeCommand")}
                  className="flex size-7 items-center justify-center rounded-md text-app-subtle transition-colors hover:bg-app-surface-hover hover:text-red-500"
                  onClick={onRemove}
                >
                  <Trash2 className="size-3.5" />
                </button>
              ) : null}
            </>
          )}
        </div>
      </div>

      {isEditing ? (
        <div className="border-t border-dashed border-app-border bg-app-surface-muted px-4 py-3">
          <div>
            <label className="mb-1 block text-[11px] font-medium uppercase tracking-wider text-app-subtle">{t("settings.commands.nameLabel")}</label>
            <Input
              value={command.name}
              onChange={(event) => onUpdate({ name: event.target.value })}
              placeholder="review"
              className="text-[13px]"
            />
            <p className="mt-1 text-[11px] text-app-subtle">Command path: {command.name ? `/prompts:${command.name}` : "/prompts:..."}</p>
          </div>
          <div className="mt-3">
            <label className="mb-1 block text-[11px] font-medium uppercase tracking-wider text-app-subtle">{t("settings.commands.descriptionLabel")}</label>
            <Input
              value={command.description}
              onChange={(event) => onUpdate({ description: event.target.value })}
              placeholder={t("settings.commands.descriptionPlaceholder")}
              className="text-[13px]"
            />
          </div>
          <div className="mt-3">
            <label className="mb-1 block text-[11px] font-medium uppercase tracking-wider text-app-subtle">{t("settings.commands.argumentHintLabel")}</label>
            <Input
              value={command.argumentHint}
              onChange={(event) => onUpdate({ argumentHint: event.target.value })}
              placeholder="[file=path] [focus=topic]"
              className="text-[13px]"
            />
          </div>
          <div className="mt-3">
            <label className="mb-1 block text-[11px] font-medium uppercase tracking-wider text-app-subtle">{t("settings.commands.commandPromptLabel")}</label>
            <Textarea
              value={command.prompt}
              onChange={(event) => onUpdate({ prompt: event.target.value })}
              placeholder={t("settings.commands.commandPromptPlaceholder")}
              className="min-h-24 text-[13px]"
            />
          </div>
        </div>
      ) : null}
    </div>
  );
}

function PolicySettingsPanel({
  description,
  policy,
  onAddAllowEntry,
  onAddDenyEntry,
  onAddWritableRoot,
  onRemoveAllowEntry,
  onRemoveDenyEntry,
  onRemoveWritableRoot,
  onUpdateAllowEntry,
  onUpdateDenyEntry,
  onUpdatePolicySetting,
  onUpdateWritableRoot,
}: {
  description: string;
  policy: PolicySettings;
  onAddAllowEntry: (entry: Omit<PatternEntry, "id">) => void;
  onAddDenyEntry: (entry: Omit<PatternEntry, "id">) => void;
  onAddWritableRoot: (entry: Omit<WritableRootEntry, "id">) => void;
  onRemoveAllowEntry: (id: string) => void;
  onRemoveDenyEntry: (id: string) => void;
  onRemoveWritableRoot: (id: string) => void;
  onUpdateAllowEntry: (id: string, patch: Partial<Omit<PatternEntry, "id">>) => void;
  onUpdateDenyEntry: (id: string, patch: Partial<Omit<PatternEntry, "id">>) => void;
  onUpdatePolicySetting: <Key extends keyof PolicySettings>(key: Key, value: PolicySettings[Key]) => void;
  onUpdateWritableRoot: (id: string, patch: Partial<Omit<WritableRootEntry, "id">>) => void;
}) {
  const t = useT();
  const APPROVAL_POLICY_OPTIONS = useMemo(() => getApprovalPolicyOptions(t), [t]);
  return (
    <div className="flex flex-col gap-6">
      <PageHeading title={t("settings.category.permissions")} description={description} />

      <SettingsSection title={t("settings.permissions.execution")}>
        <SettingsRow
          label={t("settings.permissions.approvalPolicy")}
          description={APPROVAL_POLICY_OPTIONS.find((option) => option.value === policy.approvalPolicy)?.description ?? ""}
          control={
            <ChoiceGroup
              options={APPROVAL_POLICY_OPTIONS.map(({ label, value }) => ({ label, value }))}
              value={policy.approvalPolicy}
              onValueChange={(value) => onUpdatePolicySetting("approvalPolicy", value as ApprovalPolicy)}
            />
          }
        />
        <SectionDivider />
        <PatternSubsection
          title={t("settings.permissions.allowed")}
          description={t("settings.permissions.allowedDesc")}
          entries={policy.allowList}
          onAdd={onAddAllowEntry}
          onRemove={onRemoveAllowEntry}
          onUpdate={onUpdateAllowEntry}
        />
        <SectionDivider />
        <PatternSubsection
          title={t("settings.permissions.denied")}
          description={t("settings.permissions.deniedDesc")}
          entries={policy.denyList}
          onAdd={onAddDenyEntry}
          onRemove={onRemoveDenyEntry}
          onUpdate={onUpdateDenyEntry}
        />
        <SectionDivider />
        <WritableRootsSubsection
          entries={policy.writableRoots}
          onAdd={onAddWritableRoot}
          onRemove={onRemoveWritableRoot}
          onUpdate={onUpdateWritableRoot}
        />
      </SettingsSection>
    </div>
  );
}

function PatternSubsection({
  description,
  entries,
  onAdd,
  onRemove,
  onUpdate,
  title,
}: {
  description: string;
  entries: Array<PatternEntry>;
  onAdd: (entry: Omit<PatternEntry, "id">) => void;
  onRemove: (id: string) => void;
  onUpdate: (id: string, patch: Partial<Omit<PatternEntry, "id">>) => void;
  title: string;
}) {
  const t = useT();
  const [editingId, setEditingId] = useState<string | null>(null);
  const pendingAddRef = useRef(false);

  useEffect(() => {
    if (pendingAddRef.current && entries.length > 0) {
      pendingAddRef.current = false;
      setEditingId(entries[entries.length - 1].id);
    }
  }, [entries]);

  const handleAdd = () => {
    pendingAddRef.current = true;
    onAdd({ pattern: "" });
  };

  return (
    <div>
      <div className="flex items-center justify-between px-4 py-3">
        <div className="min-w-0">
          <p className="text-[13px] font-medium text-app-foreground">{title}</p>
          <p className="mt-1 text-[12px] leading-5 text-app-muted">{description}</p>
        </div>
        <button
          type="button"
          className="inline-flex shrink-0 items-center gap-1.5 rounded-lg border border-app-border bg-app-surface px-3 py-1.5 text-[12px] font-medium text-app-foreground transition-colors hover:bg-app-surface-hover"
          onClick={handleAdd}
        >
          <Plus className="size-3.5" />
          <span>{t("settings.permissions.add")}</span>
        </button>
      </div>
      {entries.length > 0 ? (
        <div className="mx-4 mb-3 mt-1 flex flex-col overflow-hidden rounded-lg border border-app-border bg-app-surface-muted">
          {entries.map((entry, index) => (
            <PatternItem
              key={entry.id}
              entry={entry}
              isEditing={editingId === entry.id}
              isFirst={index === 0}
              onCancelEdit={() => setEditingId(null)}
              onEdit={() => setEditingId(editingId === entry.id ? null : entry.id)}
              onRemove={() => onRemove(entry.id)}
              onUpdate={(patch) => onUpdate(entry.id, patch)}
            />
          ))}
        </div>
      ) : null}
    </div>
  );
}

function PatternItem({
  entry,
  isEditing,
  isFirst,
  onCancelEdit,
  onEdit,
  onRemove,
  onUpdate,
}: {
  entry: PatternEntry;
  isEditing: boolean;
  isFirst: boolean;
  onCancelEdit: () => void;
  onEdit: () => void;
  onRemove: () => void;
  onUpdate: (patch: Partial<Omit<PatternEntry, "id">>) => void;
}) {
  const t = useT();
  const validationError = isEditing ? validatePolicyPatternInput(entry.pattern, t) : null;
  const isValid = !validationError;

  return (
    <div className={cn("px-3 py-2", !isFirst && "border-t border-dashed border-app-border")}>
      <div className="flex items-center gap-3">
        <div className="min-w-0 flex-1">
        {isEditing ? (
          <Input
            autoFocus
            value={entry.pattern}
            onChange={(event) => onUpdate({ pattern: event.target.value })}
            onKeyDown={(event) => {
              if (event.key === "Enter" && isValid) onEdit();
              if (event.key === "Escape") onCancelEdit();
            }}
            aria-invalid={!isValid}
            placeholder={t("settings.permissions.patternPlaceholder")}
            className="h-8 text-[13px]"
          />
        ) : (
          <span className="text-[13px] font-medium text-app-foreground">
            {entry.pattern || <span className="italic text-app-muted">{t("settings.permissions.empty")}</span>}
          </span>
        )}
        </div>

        <div className="flex shrink-0 items-center gap-1">
        {isEditing ? (
          <>
            <button
              type="button"
              title={t("settings.permissions.confirm")}
              aria-label={t("settings.permissions.confirm")}
              disabled={!isValid}
              className={cn(
                "flex size-7 items-center justify-center rounded-md transition-colors",
                isValid
                  ? "text-green-500 hover:bg-app-surface-hover hover:text-green-600"
                  : "cursor-not-allowed text-app-subtle/50",
              )}
              onClick={() => {
                if (isValid) onEdit();
              }}
            >
              <Check className="size-3.5" />
            </button>
            <button
              type="button"
              title={t("settings.permissions.cancel")}
              aria-label={t("settings.permissions.cancel")}
              className="flex size-7 items-center justify-center rounded-md text-app-subtle transition-colors hover:bg-app-surface-hover hover:text-app-foreground"
              onClick={onCancelEdit}
            >
              <X className="size-3.5" />
            </button>
          </>
        ) : (
          <>
            <button
              type="button"
              title={t("settings.permissions.editPattern")}
              aria-label={t("settings.permissions.editPattern")}
              className="flex size-7 items-center justify-center rounded-md text-app-subtle transition-colors hover:bg-app-surface-hover hover:text-app-foreground"
              onClick={onEdit}
            >
              <Pencil className="size-3.5" />
            </button>
            <button
              type="button"
              title={t("settings.permissions.removePattern")}
              aria-label={t("settings.permissions.removePattern")}
              className="flex size-7 items-center justify-center rounded-md text-app-subtle transition-colors hover:bg-app-surface-hover hover:text-red-500"
              onClick={onRemove}
            >
              <Trash2 className="size-3.5" />
            </button>
          </>
        )}
        </div>
      </div>
      {isEditing && validationError ? (
        <p className="mt-2 text-[11px] text-app-danger">{validationError}</p>
      ) : null}
    </div>
  );
}

function WritableRootsSubsection({
  entries,
  onAdd,
  onRemove,
  onUpdate,
}: {
  entries: Array<WritableRootEntry>;
  onAdd: (entry: Omit<WritableRootEntry, "id">) => void;
  onRemove: (id: string) => void;
  onUpdate: (id: string, patch: Partial<Omit<WritableRootEntry, "id">>) => void;
}) {
  const t = useT();
  const [editingId, setEditingId] = useState<string | null>(null);
  const pendingAddRef = useRef(false);

  useEffect(() => {
    if (pendingAddRef.current && entries.length > 0) {
      pendingAddRef.current = false;
      setEditingId(entries[entries.length - 1].id);
    }
  }, [entries]);

  const handleAdd = () => {
    pendingAddRef.current = true;
    onAdd({ path: "" });
  };

  return (
    <div>
      <div className="flex items-center justify-between px-4 py-3">
        <div className="min-w-0">
          <p className="text-[13px] font-medium text-app-foreground">{t("settings.permissions.writableRoots")}</p>
          <p className="mt-1 text-[12px] leading-5 text-app-muted">{t("settings.permissions.writableRootsDesc")}</p>
        </div>
        <button
          type="button"
          className="inline-flex shrink-0 items-center gap-1.5 rounded-lg border border-app-border bg-app-surface px-3 py-1.5 text-[12px] font-medium text-app-foreground transition-colors hover:bg-app-surface-hover"
          onClick={handleAdd}
        >
          <Plus className="size-3.5" />
          <span>{t("settings.permissions.add")}</span>
        </button>
      </div>
      {entries.length > 0 ? (
        <div className="mx-4 mb-3 mt-1 flex flex-col overflow-hidden rounded-lg border border-app-border bg-app-surface-muted">
          {entries.map((entry, index) => (
            <WritableRootItem
              key={entry.id}
              entry={entry}
              isEditing={editingId === entry.id}
              isFirst={index === 0}
              onCancelEdit={() => setEditingId(null)}
              onEdit={() => setEditingId(editingId === entry.id ? null : entry.id)}
              onRemove={() => onRemove(entry.id)}
              onUpdate={(patch) => onUpdate(entry.id, patch)}
            />
          ))}
        </div>
      ) : null}
    </div>
  );
}

function WritableRootItem({
  entry,
  isEditing,
  isFirst,
  onCancelEdit,
  onEdit,
  onRemove,
  onUpdate,
}: {
  entry: WritableRootEntry;
  isEditing: boolean;
  isFirst: boolean;
  onCancelEdit: () => void;
  onEdit: () => void;
  onRemove: () => void;
  onUpdate: (patch: Partial<Omit<WritableRootEntry, "id">>) => void;
}) {
  const t = useT();
  return (
    <div className={cn("flex items-center gap-3 px-3 py-2", !isFirst && "border-t border-dashed border-app-border")}>
      <div className="shrink-0 text-app-subtle">
        <FolderOpen className="size-4" />
      </div>

      <div className="min-w-0 flex-1">
        {isEditing ? (
          <Input
            autoFocus
            value={entry.path}
            onChange={(event) => onUpdate({ path: event.target.value })}
            onKeyDown={(event) => {
              if (event.key === "Enter") onEdit();
              if (event.key === "Escape") onCancelEdit();
            }}
            placeholder="/path/to/directory"
            className="h-8 text-[13px]"
          />
        ) : (
          <span className="text-[13px] font-medium text-app-foreground">
            {entry.path || <span className="italic text-app-muted">{t("settings.permissions.emptyPath")}</span>}
          </span>
        )}
      </div>

      <div className="flex shrink-0 items-center gap-1">
        {isEditing ? (
          <>
            <button
              type="button"
              title={t("settings.permissions.confirm")}
              aria-label={t("settings.permissions.confirm")}
              className="flex size-7 items-center justify-center rounded-md text-green-500 transition-colors hover:bg-app-surface-hover hover:text-green-600"
              onClick={onEdit}
            >
              <Check className="size-3.5" />
            </button>
            <button
              type="button"
              title={t("settings.permissions.cancel")}
              aria-label={t("settings.permissions.cancel")}
              className="flex size-7 items-center justify-center rounded-md text-app-subtle transition-colors hover:bg-app-surface-hover hover:text-app-foreground"
              onClick={onCancelEdit}
            >
              <X className="size-3.5" />
            </button>
          </>
        ) : (
          <>
            <button
              type="button"
              title={t("settings.permissions.editPath")}
              aria-label={t("settings.permissions.editPath")}
              className="flex size-7 items-center justify-center rounded-md text-app-subtle transition-colors hover:bg-app-surface-hover hover:text-app-foreground"
              onClick={onEdit}
            >
              <Pencil className="size-3.5" />
            </button>
            <button
              type="button"
              title={t("settings.permissions.removePath")}
              aria-label={t("settings.permissions.removePath")}
              className="flex size-7 items-center justify-center rounded-md text-app-subtle transition-colors hover:bg-app-surface-hover hover:text-red-500"
              onClick={onRemove}
            >
              <Trash2 className="size-3.5" />
            </button>
          </>
        )}
      </div>
    </div>
  );
}

function getModelCapabilityMeta(t: TFunc) {
  return [
    { key: "vision" as keyof ProviderModelCapabilities, label: t("settings.capability.vision"), icon: Eye },
    { key: "imageOutput" as keyof ProviderModelCapabilities, label: t("settings.capability.imageOutput"), icon: Image },
    { key: "toolCalling" as keyof ProviderModelCapabilities, label: t("settings.capability.toolCalling"), icon: Wrench },
    { key: "reasoning" as keyof ProviderModelCapabilities, label: t("settings.capability.reasoning"), icon: Brain },
    { key: "embedding" as keyof ProviderModelCapabilities, label: t("settings.capability.embedding"), icon: Database },
  ];
}

function formatCustomHeaders(customHeaders: Record<string, string> | undefined) {
  return JSON.stringify(customHeaders ?? {}, null, 2);
}

function parseCustomHeadersInput(value: string): { customHeaders: Record<string, string> | null; error: string | null } {
  if (!value.trim()) {
    return { customHeaders: {}, error: null };
  }

  try {
    const parsed = JSON.parse(value) as unknown;

    if (!parsed || Array.isArray(parsed) || typeof parsed !== "object") {
      return {
        customHeaders: null,
        error: "Custom HTTP Headers must be a JSON object.",
      };
    }

    const invalidEntry = Object.entries(parsed).find(([, headerValue]) => typeof headerValue !== "string");
    if (invalidEntry) {
      return {
        customHeaders: null,
        error: `Header "${invalidEntry[0]}" must use a string value.`,
      };
    }

    return {
      customHeaders: parsed as Record<string, string>,
      error: null,
    };
  } catch {
    return {
      customHeaders: null,
      error: "Invalid JSON. Example: {\"HTTP-Referer\": \"https://example.com\"}",
    };
  }
}

function formatProviderOptions(providerOptions: Record<string, unknown> | undefined) {
  return JSON.stringify(providerOptions ?? {}, null, 2);
}

function parseProviderOptionsInput(value: string): { error: string | null; providerOptions: Record<string, unknown> | null } {
  if (!value.trim()) {
    return { providerOptions: {}, error: null };
  }

  try {
    const parsed = JSON.parse(value) as unknown;

    if (!parsed || Array.isArray(parsed) || typeof parsed !== "object") {
      return {
        providerOptions: null,
        error: "Provider Options must be a JSON object.",
      };
    }

    return {
      providerOptions: parsed as Record<string, unknown>,
      error: null,
    };
  } catch {
    return {
      providerOptions: null,
      error: "Invalid JSON. Example: {\"thinking\": {\"type\": \"disabled\"}}",
    };
  }
}

function ProviderSettingsPanel({
  description,
  providerCatalog,
  providers,
  onAddProvider,
  onFetchProviderModels,
  onTestProviderModelConnection,
  onRemoveProvider,
  onUpdateProvider,
}: {
  description: string;
  providerCatalog: Array<ProviderCatalogEntry>;
  providers: Array<ProviderEntry>;
  onAddProvider: (entry: Omit<ProviderEntry, "id">) => void;
  onFetchProviderModels: (id: string) => Promise<void>;
  onTestProviderModelConnection: (
    providerId: string,
    modelId: string,
  ) => Promise<ProviderModelConnectionTestResultDto>;
  onRemoveProvider: (id: string) => void;
  onUpdateProvider: (id: string, patch: Partial<Omit<ProviderEntry, "id">>) => void;
}) {
  const t = useT();
  const [selectedProviderId, setSelectedProviderId] = useState<string | null>(
    () => providers[0]?.id ?? null,
  );
  const [providerSearch, setProviderSearch] = useState("");
  const [showApiKey, setShowApiKey] = useState(false);
  const [apiKeyDraft, setApiKeyDraft] = useState("");
  const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
  const [customHeadersInput, setCustomHeadersInput] = useState("{}");
  const [customHeadersError, setCustomHeadersError] = useState<string | null>(null);
  const [expandedModelId, setExpandedModelId] = useState<string | null>(null);
  const [modelSearch, setModelSearch] = useState("");
  const [newModelId, setNewModelId] = useState("");
  const [newModelDisplayName, setNewModelDisplayName] = useState("");
  const [isFetchingModels, setIsFetchingModels] = useState(false);
  const [fetchFeedback, setFetchFeedback] = useState<{ kind: "success" | "error"; message: string } | null>(null);
  const [testingModelId, setTestingModelId] = useState<string | null>(null);
  const [modelTestFeedback, setModelTestFeedback] = useState<Record<string, ProviderModelConnectionTestResultDto>>({});
  const [isMiniMaxCustomBaseUrl, setIsMiniMaxCustomBaseUrl] = useState(false);
  const modelTestFeedbackTimeoutsRef = useRef<Record<string, ReturnType<typeof setTimeout>>>({});
  const pendingSelectNewProviderRef = useRef(false);
  const previousProviderIdsRef = useRef(new Set(providers.map((provider) => provider.id)));

  const selectedProvider = providers.find((provider) => provider.id === selectedProviderId) ?? null;
  const isMiniMaxProvider = selectedProvider?.providerKey === "minimax";
  const minimaxBaseUrlMode =
    isMiniMaxCustomBaseUrl
      ? "__custom__"
      : selectedProvider && MINIMAX_BASE_URL_OPTIONS.includes(selectedProvider.baseUrl as typeof MINIMAX_BASE_URL_OPTIONS[number])
      ? selectedProvider.baseUrl
      : "__custom__";

  const filteredProviders = useMemo(() => {
    if (!providerSearch.trim()) return providers;
    const query = providerSearch.toLowerCase();
    return providers.filter((provider) => provider.displayName.toLowerCase().includes(query));
  }, [providers, providerSearch]);

  const filteredModels = useMemo(() => {
    if (!selectedProvider) return [];
    if (!modelSearch.trim()) return selectedProvider.models;
    const query = modelSearch.toLowerCase();
    return selectedProvider.models.filter(
      (model) =>
        model.modelId.toLowerCase().includes(query) ||
        model.displayName.toLowerCase().includes(query),
    );
  }, [selectedProvider, modelSearch]);

  const customProviderTypeOptions = useMemo(
    () =>
      providerCatalog
        .filter((entry) => entry.supportsCustom)
        .map((entry) => ({
          value: entry.providerType as CustomProviderType,
          label: entry.displayName,
          defaultBaseUrl: entry.defaultBaseUrl,
        })),
    [providerCatalog],
  );

  useEffect(() => {
    const currentProviderIds = new Set(providers.map((provider) => provider.id));

    if (pendingSelectNewProviderRef.current) {
      for (const provider of providers) {
        if (!previousProviderIdsRef.current.has(provider.id)) {
          setSelectedProviderId(provider.id);
          pendingSelectNewProviderRef.current = false;
          break;
        }
      }
    }

    previousProviderIdsRef.current = currentProviderIds;
  }, [providers]);

  useEffect(() => {
    if (!providers.some((provider) => provider.id === selectedProviderId)) {
      setSelectedProviderId(providers[0]?.id ?? null);
    }
  }, [providers, selectedProviderId]);

  useEffect(() => {
    if (!selectedProvider) {
      setShowAdvancedSettings(false);
      setCustomHeadersInput("{}");
      setCustomHeadersError(null);
      setIsMiniMaxCustomBaseUrl(false);
      return;
    }

    const hasCustomHeaders = Object.keys(selectedProvider.customHeaders).length > 0;
    setApiKeyDraft(selectedProvider.apiKey);
    setShowAdvancedSettings(hasCustomHeaders);
    setCustomHeadersInput(formatCustomHeaders(selectedProvider.customHeaders));
    setCustomHeadersError(null);
    setIsMiniMaxCustomBaseUrl(
      selectedProvider.providerKey === "minimax"
      && !MINIMAX_BASE_URL_OPTIONS.includes(selectedProvider.baseUrl as typeof MINIMAX_BASE_URL_OPTIONS[number]),
    );
  }, [selectedProvider?.id]);

  useEffect(() => {
    if (!selectedProvider?.models.some((model) => model.id === expandedModelId)) {
      setExpandedModelId(null);
    }
  }, [expandedModelId, selectedProvider]);

  useEffect(() => {
    setFetchFeedback(null);
    setIsFetchingModels(false);
    setTestingModelId(null);
    Object.values(modelTestFeedbackTimeoutsRef.current).forEach((timeoutId) => clearTimeout(timeoutId));
    modelTestFeedbackTimeoutsRef.current = {};
    setModelTestFeedback({});
  }, [selectedProvider?.id]);

  useEffect(() => () => {
    Object.values(modelTestFeedbackTimeoutsRef.current).forEach((timeoutId) => clearTimeout(timeoutId));
  }, []);

  const handleAddCustomProvider = () => {
    const newProvider: Omit<ProviderEntry, "id"> = {
      kind: "custom",
      providerKey: crypto.randomUUID(),
      providerType: customProviderTypeOptions[0]?.value ?? "openai-compatible",
      displayName: t("settings.providers.customProvider"),
      baseUrl: customProviderTypeOptions[0]?.defaultBaseUrl ?? "https://api.example.com/v1",
      apiKey: "",
      hasApiKey: false,
      lockedMapping: false,
      customHeaders: {},
      enabled: false,
      models: [],
    };
    pendingSelectNewProviderRef.current = true;
    onAddProvider(newProvider);
  };

  const handleCustomHeadersChange = (value: string) => {
    setCustomHeadersInput(value);
    const { customHeaders, error } = parseCustomHeadersInput(value);
    setCustomHeadersError(error);
    if (!selectedProvider || !customHeaders) {
      return;
    }

    onUpdateProvider(selectedProvider.id, { customHeaders });
  };

  const handleApiKeySave = () => {
    if (!selectedProvider) {
      return;
    }

    const value = apiKeyDraft.trim();
    if (!value) {
      return;
    }

    onUpdateProvider(selectedProvider.id, {
      apiKey: value,
      enabled: true,
    });
  };

  const handleUpdateModel = (modelId: string, patch: Partial<ProviderModel>) => {
    if (!selectedProvider) return;
    onUpdateProvider(selectedProvider.id, {
      models: selectedProvider.models.map((model) =>
        model.id === modelId ? { ...model, ...patch } : model,
      ),
    });
  };

  const handleToggleModel = (modelId: string, enabled: boolean) => {
    handleUpdateModel(modelId, { enabled });
  };

  const handleRemoveModel = (modelId: string) => {
    if (!selectedProvider) return;
    if (expandedModelId === modelId) {
      setExpandedModelId(null);
    }
    onUpdateProvider(selectedProvider.id, {
      models: selectedProvider.models.filter((model) => model.id !== modelId),
    });
  };

  const handleAddModel = () => {
    if (!selectedProvider || !newModelId.trim()) return;
    const newModel: ProviderModel = {
      id: crypto.randomUUID(),
      modelId: newModelId.trim(),
      sortIndex: selectedProvider.models.reduce((max, model) => Math.max(max, model.sortIndex), -1) + 1,
      displayName: newModelDisplayName.trim(),
      enabled: true,
      capabilityOverrides: {},
      providerOptions: {},
      isManual: true,
    };
    onUpdateProvider(selectedProvider.id, {
      models: [newModel, ...selectedProvider.models],
    });
    setNewModelId("");
    setNewModelDisplayName("");
    setExpandedModelId(newModel.id);
  };

  const handleFetchModels = async () => {
    if (!selectedProvider || isFetchingModels) {
      return;
    }

    setIsFetchingModels(true);
    setFetchFeedback(null);

    try {
      await onFetchProviderModels(selectedProvider.id);
      setFetchFeedback({
        kind: "success",
        message: t("settings.providers.catalogUpdated"),
      });
    } catch (error) {
      const message = error instanceof Error ? error.message : t("settings.providers.fetchFailed");
      setFetchFeedback({
        kind: "error",
        message,
      });
    } finally {
      setIsFetchingModels(false);
    }
  };

  const handleTestModelConnection = async (modelId: string) => {
    if (!selectedProvider || testingModelId === modelId) {
      return;
    }

    setTestingModelId(modelId);

    try {
      const result = await onTestProviderModelConnection(selectedProvider.id, modelId);
      setModelTestFeedback((current) => ({
        ...current,
        [modelId]: result,
      }));
      if (modelTestFeedbackTimeoutsRef.current[modelId]) {
        clearTimeout(modelTestFeedbackTimeoutsRef.current[modelId]);
      }
      modelTestFeedbackTimeoutsRef.current[modelId] = setTimeout(() => {
        setModelTestFeedback((current) => {
          const next = { ...current };
          delete next[modelId];
          return next;
        });
        delete modelTestFeedbackTimeoutsRef.current[modelId];
      }, 5000);
    } catch (error) {
      const detail = error instanceof Error ? error.message : "Unknown error";
      setModelTestFeedback((current) => ({
        ...current,
        [modelId]: {
          success: false,
          unsupported: false,
          message: t("settings.providers.connectionTestFailed"),
          detail,
        },
      }));
      if (modelTestFeedbackTimeoutsRef.current[modelId]) {
        clearTimeout(modelTestFeedbackTimeoutsRef.current[modelId]);
      }
      modelTestFeedbackTimeoutsRef.current[modelId] = setTimeout(() => {
        setModelTestFeedback((current) => {
          const next = { ...current };
          delete next[modelId];
          return next;
        });
        delete modelTestFeedbackTimeoutsRef.current[modelId];
      }, 5000);
    } finally {
      setTestingModelId((current) => (current === modelId ? null : current));
    }
  };

  return (
    <div className="flex flex-col gap-6">
      <PageHeading title={t("settings.category.providers")} description={description} />

      <section>
        <div className="mb-2 flex items-center justify-between px-1">
          <h2 className="text-[11px] font-medium uppercase tracking-[0.12em] text-app-subtle">{t("settings.category.providers")}</h2>
          <button
            type="button"
            className="inline-flex shrink-0 items-center gap-1.5 rounded-lg border border-app-border bg-app-surface px-3 py-1.5 text-[12px] font-medium text-app-foreground transition-colors hover:bg-app-surface-hover"
            onClick={handleAddCustomProvider}
          >
            <Plus className="size-3.5" />
            <span>{t("settings.providers.addProvider")}</span>
          </button>
        </div>
        <div className="flex min-h-[520px] gap-4" style={{ height: "calc(100vh - 220px)" }}>
          {/* Provider sidebar list */}
          <div className="flex w-[220px] shrink-0 flex-col overflow-hidden rounded-2xl border border-app-border bg-app-surface">
            <div className="border-b border-app-border p-2">
              <div className="flex items-center gap-2 rounded-lg bg-app-surface-muted px-2.5 py-1.5">
                <Search className="size-3.5 shrink-0 text-app-subtle" />
                <input
                  type="text"
                  placeholder={t("settings.providers.searchProviders")}
                  value={providerSearch}
                  onChange={(event) => setProviderSearch(event.target.value)}
                  className="min-w-0 flex-1 bg-transparent text-[12px] text-app-foreground placeholder:text-app-subtle outline-none"
                />
              </div>
            </div>
            <div className="min-h-0 flex-1 overflow-y-auto overscroll-contain p-1.5 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
              <div className="space-y-0.5">
                {filteredProviders.map((provider) => {
                  const isSelected = provider.id === selectedProviderId;
                  return (
                    <button
                      key={provider.id}
                      type="button"
                      className={cn(
                        "flex w-full items-center gap-2.5 rounded-xl px-2.5 py-2 text-left transition-colors",
                        isSelected
                          ? "bg-app-surface-active text-app-foreground"
                          : "text-app-muted hover:bg-app-surface-hover hover:text-app-foreground",
                      )}
                      onClick={() => {
                        setSelectedProviderId(provider.id);
                        setShowApiKey(false);
                        setModelSearch("");
                      }}
                    >
                      <ProviderIcon name={provider.displayName} className="size-5 shrink-0" />
                      <span className="min-w-0 flex-1 truncate text-[13px] font-medium">{provider.displayName}</span>
                      {provider.kind === "custom" ? (
                        <span className="shrink-0 rounded px-1 py-0.5 text-[10px] font-medium uppercase tracking-wide text-app-subtle">
                          {t("settings.providers.customBadge")}
                        </span>
                      ) : null}
                      <div
                        className={cn(
                          "size-2 shrink-0 rounded-full",
                          provider.enabled ? "bg-app-success" : "bg-app-border",
                        )}
                      />
                    </button>
                  );
                })}
              </div>
            </div>
          </div>

          {/* Provider detail */}
          {selectedProvider ? (
            <div className="flex min-w-0 flex-1 flex-col overflow-hidden rounded-2xl border border-app-border bg-app-surface">
              <div className="min-h-0 flex-1 overflow-y-auto overscroll-contain [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
                {/* Header */}
                <div className="flex items-center justify-between border-b border-app-border px-5 py-4">
                  <div className="min-w-0 flex-1">
                    <div className="flex items-center gap-2.5">
                      <h3 className="text-[15px] font-semibold text-app-foreground">{selectedProvider.displayName}</h3>
                      {selectedProvider.kind === "custom" ? (
                        <span className="rounded-md border border-app-border bg-app-surface-muted px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-app-muted">
                          {t("settings.providers.customBadge")}
                        </span>
                      ) : (
                        <span className="rounded-md border border-app-border bg-app-surface-muted px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-app-muted">
                          {t("settings.providers.builtInBadge")}
                        </span>
                      )}
                      {selectedProvider.enabled ? (
                        <span className="rounded-md bg-app-success/15 px-1.5 py-0.5 text-[10px] font-medium text-app-success">
                          {t("settings.providers.activeBadge")}
                        </span>
                      ) : null}
                    </div>
                    <p className="mt-0.5 truncate text-[12px] text-app-subtle">{selectedProvider.baseUrl}</p>
                  </div>
                  <div className="flex shrink-0 items-center gap-2">
                    {selectedProvider.kind === "custom" ? (
                      <button
                        type="button"
                        title={t("settings.providers.deleteProvider")}
                        aria-label={t("settings.providers.deleteProvider")}
                        className="flex size-8 items-center justify-center rounded-lg border border-app-danger/30 text-app-danger transition-colors hover:bg-app-danger/10"
                        onClick={() => {
                          onRemoveProvider(selectedProvider.id);
                          setSelectedProviderId(providers.find((p) => p.id !== selectedProvider.id)?.id ?? null);
                        }}
                      >
                        <Trash2 className="size-3.5" />
                      </button>
                    ) : null}
                    <Switch
                      checked={selectedProvider.enabled}
                      aria-label={t("settings.providers.toggleProvider")}
                      onCheckedChange={(checked) => onUpdateProvider(selectedProvider.id, { enabled: checked })}
                    />
                  </div>
                </div>

                {/* Form fields */}
                <div className="space-y-5 px-5 py-4">
                  <ProviderField label={t("settings.providers.providerName")}>
                    <Input
                      value={selectedProvider.displayName}
                      onChange={(event) => onUpdateProvider(selectedProvider.id, { displayName: event.target.value })}
                      disabled={selectedProvider.lockedMapping}
                    />
                  </ProviderField>

                  <ProviderField label={t("settings.providers.baseUrl")}>
                    {isMiniMaxProvider ? (
                      <div className="space-y-2">
                        <div className="relative">
                          <select
                            value={minimaxBaseUrlMode}
                            onChange={(event) => {
                              if (event.target.value === "__custom__") {
                                setIsMiniMaxCustomBaseUrl(true);
                                return;
                              }

                              setIsMiniMaxCustomBaseUrl(false);
                              onUpdateProvider(selectedProvider.id, { baseUrl: event.target.value });
                            }}
                            className="h-9 w-full appearance-none rounded-lg border border-app-border bg-app-surface-muted px-3 pr-8 text-[13px] text-app-foreground outline-none transition-colors focus-visible:border-app-border-strong"
                          >
                            {MINIMAX_BASE_URL_OPTIONS.map((option) => (
                              <option key={option} value={option}>
                                {option}
                              </option>
                            ))}
                            <option value="__custom__">{t("settings.providers.customBaseUrl")}</option>
                          </select>
                          <ChevronDown className="pointer-events-none absolute right-2.5 top-1/2 size-3.5 -translate-y-1/2 text-app-subtle" />
                        </div>
                        {minimaxBaseUrlMode === "__custom__" ? (
                          <Input
                            value={selectedProvider.baseUrl}
                            onChange={(event) => onUpdateProvider(selectedProvider.id, { baseUrl: event.target.value })}
                            placeholder="https://your-endpoint.example.com/anthropic"
                          />
                        ) : null}
                        <p className="text-[11px] text-app-subtle">
                          {t("settings.providers.baseUrlHelper")}
                        </p>
                      </div>
                    ) : (
                      <Input
                        value={selectedProvider.baseUrl}
                        onChange={(event) => onUpdateProvider(selectedProvider.id, { baseUrl: event.target.value })}
                      />
                    )}
                  </ProviderField>

                <ProviderField label="API Key">
                  <div className="relative">
                    <Input
                      type={showApiKey ? "text" : "password"}
                      value={apiKeyDraft}
                      onChange={(event) => setApiKeyDraft(event.target.value)}
                      onBlur={handleApiKeySave}
                      onKeyDown={(event) => {
                        if (event.key === "Enter") {
                          event.preventDefault();
                          handleApiKeySave();
                        }
                      }}
                      className="pr-10"
                      placeholder={selectedProvider.hasApiKey && !apiKeyDraft ? "Saved in app" : "Not set"}
                    />
                    <button
                      type="button"
                      className="absolute right-2 top-1/2 flex size-6 -translate-y-1/2 items-center justify-center rounded-md text-app-subtle transition-colors hover:text-app-foreground"
                      onClick={() => setShowApiKey((current) => !current)}
                      aria-label={showApiKey ? "Hide API key" : "Show API key"}
                    >
                      {showApiKey ? <EyeOff className="size-3.5" /> : <Eye className="size-3.5" />}
                    </button>
                  </div>
                </ProviderField>

                <ProviderField
                  label="Provider Type"
                  action={(
                    <button
                      type="button"
                      title="Provider settings"
                      aria-label="Provider settings"
                      aria-pressed={showAdvancedSettings}
                      onClick={() => setShowAdvancedSettings((current) => !current)}
                      className={cn(
                        "flex size-8 items-center justify-center rounded-lg border transition-colors",
                        showAdvancedSettings
                          ? "border-app-border-strong bg-app-surface-hover text-app-foreground"
                          : "border-app-border text-app-muted hover:bg-app-surface-hover hover:text-app-foreground",
                      )}
                    >
                      <Settings2 className="size-3.5" />
                    </button>
                  )}
                >
                  {selectedProvider.lockedMapping ? (
                    <div className="rounded-lg border border-app-border bg-app-surface-muted px-3 py-2 text-[13px] text-app-foreground">
                      {selectedProvider.providerType}
                    </div>
                  ) : (
                    <div className="relative">
                      <select
                        value={selectedProvider.providerType}
                        onChange={(event) =>
                          onUpdateProvider(selectedProvider.id, { providerType: event.target.value as CustomProviderType })
                        }
                        className="h-9 w-full appearance-none rounded-lg border border-app-border bg-app-surface-muted px-3 pr-8 text-[13px] text-app-foreground outline-none transition-colors focus-visible:border-app-border-strong"
                      >
                        {customProviderTypeOptions.map((option) => (
                          <option key={option.value} value={option.value}>
                            {option.label}
                          </option>
                        ))}
                      </select>
                      <ChevronDown className="pointer-events-none absolute right-2.5 top-1/2 size-3.5 -translate-y-1/2 text-app-subtle" />
                    </div>
                  )}
                  <p className="mt-1.5 text-[11px] text-app-subtle">
                    {selectedProvider.lockedMapping
                      ? "Built-in providers stay mapped to their tiy-core provider type."
                      : "Choose which tiy-core provider type backs this custom provider."}
                  </p>
                  {showAdvancedSettings ? (
                    <div className="mt-3 rounded-xl border border-app-border bg-app-surface-muted/70 p-3">
                      <div className="mb-1 text-[12px] font-medium text-app-foreground">Custom HTTP Headers</div>
                      <p className="mb-3 text-[11px] leading-5 text-app-subtle">
                        Use JSON object format. Header values must be strings.
                      </p>
                      <Textarea
                        value={customHeadersInput}
                        onChange={(event) => handleCustomHeadersChange(event.target.value)}
                        aria-invalid={Boolean(customHeadersError)}
                        className="min-h-28 bg-app-surface text-[12px] leading-5"
                        placeholder={"{\n  \"HTTP-Referer\": \"https://example.com\",\n  \"X-Client\": \"tiycode\"\n}"}
                        spellCheck={false}
                      />
                      <p className={cn("mt-2 text-[11px]", customHeadersError ? "text-app-danger" : "text-app-subtle")}>
                        {customHeadersError ?? "Saved automatically when the JSON is valid."}
                      </p>
                    </div>
                  ) : null}
                </ProviderField>

                {/* Models section */}
                <div>
                  <div className="mb-3">
                    <h4 className="text-[13px] font-medium text-app-foreground">Models</h4>
                  </div>

                  {/* Add model row */}
                  <div className="mb-3 flex items-center gap-2">
                    <Input
                      placeholder="Model ID (e.g., gpt-4o)"
                      value={newModelId}
                      onChange={(event) => setNewModelId(event.target.value)}
                      className="min-w-0 flex-1"
                      onKeyDown={(event) => {
                        if (event.key === "Enter") handleAddModel();
                      }}
                    />
                    <Input
                      placeholder="Display Name (optional)"
                      value={newModelDisplayName}
                      onChange={(event) => setNewModelDisplayName(event.target.value)}
                      className="min-w-0 flex-1"
                      onKeyDown={(event) => {
                        if (event.key === "Enter") handleAddModel();
                      }}
                    />
                    <button
                      type="button"
                      className="inline-flex shrink-0 items-center gap-1 rounded-lg border border-app-border bg-app-surface px-2.5 py-2 text-[12px] font-medium text-app-foreground transition-colors hover:bg-app-surface-hover"
                      onClick={handleAddModel}
                    >
                      <Plus className="size-3" />
                      <span>Add</span>
                    </button>
                  </div>
                  <p className="mb-3 text-[11px] text-app-subtle">
                    Add models manually or use Fetch to load from API
                  </p>

                  {/* Model search */}
                  <div className="mb-3 flex items-center gap-2">
                    <div className="flex min-w-0 flex-1 items-center gap-2 rounded-lg border border-app-border bg-app-surface-muted px-3 py-1.5">
                      <Search className="size-3.5 shrink-0 text-app-subtle" />
                      <input
                        type="text"
                        placeholder="Search models..."
                        value={modelSearch}
                        onChange={(event) => setModelSearch(event.target.value)}
                        className="min-w-0 flex-1 bg-transparent text-[12px] text-app-foreground placeholder:text-app-subtle outline-none"
                      />
                    </div>
                    <button
                      type="button"
                      className="inline-flex shrink-0 items-center gap-1.5 rounded-lg border border-app-border bg-app-surface px-2.5 py-1.5 text-[12px] font-medium text-app-foreground transition-colors hover:bg-app-surface-hover"
                      onClick={() => {
                        void handleFetchModels();
                      }}
                      disabled={isFetchingModels}
                    >
                      {isFetchingModels ? <RefreshCw className="size-3 animate-spin" /> : <Download className="size-3" />}
                      <span>{isFetchingModels ? "Fetching..." : "Fetch"}</span>
                    </button>
                  </div>

                  <p className={cn(
                    "mb-2 text-[11px]",
                    fetchFeedback?.kind === "error" ? "text-app-danger" : "text-app-subtle",
                  )}>
                    {fetchFeedback?.message
                      ?? `Showing ${filteredModels.length} model${filteredModels.length !== 1 ? "s" : ""} (enabled models shown first)`}
                  </p>

                  {/* Model list */}
                  <div className="space-y-1">
                    {[...filteredModels]
                      .sort((a, b) => {
                        if (a.enabled !== b.enabled) {
                          return a.enabled ? -1 : 1;
                        }
                        if (!a.enabled && !b.enabled && Boolean(a.isManual) !== Boolean(b.isManual)) {
                          return a.isManual ? -1 : 1;
                        }
                        return a.sortIndex - b.sortIndex;
                      })
                      .map((model) => (
                        <ProviderModelRow
                          key={model.id}
                          model={model}
                          isExpanded={expandedModelId === model.id}
                          isTesting={testingModelId === model.id}
                          testFeedback={modelTestFeedback[model.id] ?? null}
                          onTestConnection={() => {
                            void handleTestModelConnection(model.id);
                          }}
                          onToggleExpanded={() =>
                            setExpandedModelId((current) => (current === model.id ? null : model.id))
                          }
                          onToggleEnabled={(checked) => handleToggleModel(model.id, checked)}
                          onRemove={() => handleRemoveModel(model.id)}
                          onUpdate={(patch) => handleUpdateModel(model.id, patch)}
                        />
                      ))}
                  </div>
                </div>
                </div>
              </div>
            </div>
          ) : (
            <div className="flex min-w-0 flex-1 items-center justify-center rounded-2xl border border-app-border bg-app-surface">
              <p className="text-[13px] text-app-subtle">Select a provider to configure</p>
            </div>
          )}
        </div>
      </section>
    </div>
  );
}

function ProviderModelRow({
  isExpanded,
  isTesting,
  model,
  testFeedback,
  onRemove,
  onTestConnection,
  onToggleEnabled,
  onToggleExpanded,
  onUpdate,
}: {
  isExpanded: boolean;
  isTesting: boolean;
  model: ProviderModel;
  testFeedback: ProviderModelConnectionTestResultDto | null;
  onRemove: () => void;
  onTestConnection: () => void;
  onToggleEnabled: (checked: boolean) => void;
  onToggleExpanded: () => void;
  onUpdate: (patch: Partial<ProviderModel>) => void;
}) {
  const t = useT();
  const capabilityMeta = getModelCapabilityMeta(t);
  const effectiveCapabilities = getEffectiveModelCapabilities(model);
  const activeCapabilities = capabilityMeta.filter((item) => effectiveCapabilities[item.key]);
  const [providerOptionsInput, setProviderOptionsInput] = useState(() => formatProviderOptions(model.providerOptions));
  const [providerOptionsError, setProviderOptionsError] = useState<string | null>(null);

  useEffect(() => {
    setProviderOptionsInput(formatProviderOptions(model.providerOptions));
    setProviderOptionsError(null);
  }, [model.id]);

  const handleProviderOptionsChange = (value: string) => {
    setProviderOptionsInput(value);
    const { providerOptions, error } = parseProviderOptionsInput(value);
    setProviderOptionsError(error);
    if (!providerOptions) {
      return;
    }

    onUpdate({ providerOptions });
  };

  const handleCapabilityToggle = (key: keyof ProviderModelCapabilities, checked: boolean) => {
    const inferredCapabilities = inferModelCapabilities(model.modelId);
    const nextOverrides = { ...model.capabilityOverrides };

    if (checked === inferredCapabilities[key]) {
      delete nextOverrides[key];
    } else {
      nextOverrides[key] = checked;
    }

    onUpdate({ capabilityOverrides: nextOverrides });
  };

  return (
    <div className="overflow-hidden rounded-xl border border-app-border bg-app-surface-muted">
      <div className="group/model flex items-center justify-between gap-3 px-3.5 py-2.5 transition-colors hover:bg-app-surface-hover/50">
        <div className="flex min-w-0 flex-1 items-center gap-3">
          <ModelBrandIcon modelId={model.modelId} displayName={model.displayName} className="size-5 text-[16px]" />
          <div className="min-w-0 flex-1">
            <div className="flex items-center gap-2">
              <span className="font-mono text-[13px] font-medium text-app-foreground">
                {model.displayName || model.modelId}
              </span>
              {model.isManual ? (
                <span className="rounded px-1 py-0.5 text-[10px] font-semibold text-app-muted">
                  Manual
                </span>
              ) : null}
            </div>
            <div className="mt-1 flex min-w-0 items-center gap-2">
              {activeCapabilities.length > 0 ? (
                <div className="flex shrink-0 items-center gap-1">
                  {activeCapabilities.map((capability) => {
                    const Icon = capability.icon;
                    return (
                      <span
                        key={capability.key}
                        title={capability.label}
                        className="flex size-5 items-center justify-center rounded-md border border-app-border bg-app-surface text-app-subtle"
                      >
                        <Icon className="size-3" />
                      </span>
                    );
                  })}
                </div>
              ) : null}
              {model.contextWindow ? (
                <span className="inline-flex shrink-0 items-center rounded-md border border-app-border bg-app-surface px-1.5 py-0.5 text-[10px] font-medium text-app-muted">
                  {model.contextWindow}
                </span>
              ) : null}
              <span className="truncate font-mono text-[11px] text-app-subtle">
                {model.modelId}
              </span>
            </div>
          </div>
        </div>
        <div className="flex shrink-0 items-center gap-2">
          <div
            className={cn(
              "flex items-center gap-1 transition-opacity",
              isExpanded
                ? "visible opacity-100"
                : "pointer-events-none invisible opacity-0 group-hover/model:pointer-events-auto group-hover/model:visible group-hover/model:opacity-100 group-focus-within/model:pointer-events-auto group-focus-within/model:visible group-focus-within/model:opacity-100",
            )}
          >
            <button
              type="button"
              title={isTesting ? "Testing connection" : "Test connection"}
              aria-label="Test model connection"
              onClick={onTestConnection}
              disabled={isTesting}
              className={cn(
                "flex size-6 items-center justify-center rounded-md text-app-subtle transition-colors hover:bg-app-surface hover:text-app-foreground",
                isTesting && "cursor-wait opacity-70",
              )}
            >
              {isTesting ? <RefreshCw className="size-3 animate-spin" /> : <MousePointerClick className="size-3" />}
            </button>
            <button
              type="button"
              title="Settings"
              aria-label="Model settings"
              aria-expanded={isExpanded}
              onClick={onToggleExpanded}
              className={cn(
                "flex size-6 items-center justify-center rounded-md transition-colors",
                isExpanded
                  ? "bg-app-surface text-app-foreground"
                  : "text-app-subtle hover:bg-app-surface hover:text-app-foreground",
              )}
            >
              <Settings2 className="size-3" />
            </button>
            {model.isManual ? (
              <button
                type="button"
                title="Remove model"
                aria-label="Remove model"
                className="flex size-6 items-center justify-center rounded-md text-app-subtle transition-colors hover:bg-app-surface hover:text-app-danger"
                onClick={onRemove}
              >
                <Trash2 className="size-3" />
              </button>
            ) : null}
          </div>
          <Switch
            size="sm"
            checked={model.enabled}
            aria-label={`Toggle ${model.displayName || model.modelId}`}
            onCheckedChange={onToggleEnabled}
          />
        </div>
      </div>

      {isTesting || testFeedback ? (
        <div className="border-t border-app-border/70 bg-app-surface px-4 py-2">
          <p
            className={cn(
              "text-[11px] leading-5",
              isTesting
                ? "text-app-subtle"
                : testFeedback?.success
                ? "text-app-success"
                : testFeedback?.unsupported
                ? "text-app-muted"
                : "text-app-danger",
            )}
          >
            {isTesting
              ? "Testing connection..."
              : testFeedback?.detail
              ? `${testFeedback.message} ${testFeedback.detail}`
              : testFeedback?.message}
          </p>
        </div>
      ) : null}

      {isExpanded ? (
        <div className="border-t border-app-border bg-app-surface px-4 py-4">
          <div className="grid gap-3 md:grid-cols-2">
            <div>
              <label className="mb-1.5 block text-[13px] font-medium text-app-foreground">Model ID</label>
              <Input
                value={model.modelId}
                onChange={(event) => onUpdate({ modelId: event.target.value })}
                placeholder="gpt-4o"
              />
            </div>
            <div>
              <label className="mb-1.5 block text-[13px] font-medium text-app-foreground">Display Name</label>
              <Input
                value={model.displayName}
                onChange={(event) => onUpdate({ displayName: event.target.value })}
                placeholder="GPT-4o"
              />
            </div>
          </div>

          <div className="mb-3 mt-4">
            <h5 className="text-[13px] font-medium text-app-foreground">Model Capabilities</h5>
            <p className="mt-1 text-[12px] text-app-muted">
              Override the auto-detected capabilities for this model.
            </p>
          </div>

          <div className="grid gap-3 md:grid-cols-2">
            {capabilityMeta.map((capability) => {
              const Icon = capability.icon;
              const isAuto = model.capabilityOverrides[capability.key] === undefined;
              return (
                <div
                  key={capability.key}
                  className="flex items-center justify-between rounded-xl border border-app-border bg-app-surface-muted px-3.5 py-3"
                >
                  <div className="flex min-w-0 items-center gap-2.5">
                    <span className="flex size-7 items-center justify-center rounded-lg bg-app-surface text-app-subtle">
                      <Icon className="size-4" />
                    </span>
                    <div className="min-w-0">
                      <div className="flex items-center gap-1.5">
                        <span className="text-[13px] font-medium text-app-foreground">{capability.label}</span>
                        {isAuto ? (
                          <span className="text-[11px] font-medium text-app-subtle">(auto)</span>
                        ) : null}
                      </div>
                    </div>
                  </div>
                  <Switch
                    checked={effectiveCapabilities[capability.key]}
                    onCheckedChange={(checked) => handleCapabilityToggle(capability.key, checked)}
                  />
                </div>
              );
            })}
          </div>

          <div className="mt-4 grid gap-3 md:grid-cols-2">
            <div>
              <label className="mb-1.5 block text-[13px] font-medium text-app-foreground">Context Window</label>
              <Input
                value={model.contextWindow ?? ""}
                onChange={(event) => onUpdate({ contextWindow: event.target.value })}
                placeholder="256000"
              />
            </div>
            <div>
              <label className="mb-1.5 block text-[13px] font-medium text-app-foreground">Max Output Tokens</label>
              <Input
                value={model.maxOutputTokens ?? ""}
                onChange={(event) => onUpdate({ maxOutputTokens: event.target.value })}
                placeholder="256000"
              />
            </div>
          </div>

          <div className="mt-4">
            <label className="mb-1.5 block text-[13px] font-medium text-app-foreground">Model Options (JSON)</label>
            <Textarea
              value={providerOptionsInput}
              onChange={(event) => handleProviderOptionsChange(event.target.value)}
              aria-invalid={Boolean(providerOptionsError)}
              className="min-h-40 bg-app-surface text-[12px] leading-6"
              placeholder={"{\n  \"thinking\": {\n    \"type\": \"disabled\"\n  }\n}"}
              spellCheck={false}
            />
            <p className={cn("mt-2 text-[11px] leading-5", providerOptionsError ? "text-app-danger" : "text-app-subtle")}>
              {providerOptionsError ?? "Example: { \"thinking\": { \"type\": \"disabled\" } }"}
            </p>
          </div>
        </div>
      ) : null}
    </div>
  );
}

function ProviderField({
  action,
  children,
  label,
}: {
  action?: ReactNode;
  children: ReactNode;
  label: string;
}) {
  return (
    <div>
      <div className="mb-1.5 flex items-center justify-between gap-3">
        <label className="block text-[13px] font-medium text-app-foreground">{label}</label>
        {action ? <div className="shrink-0">{action}</div> : null}
      </div>
      {children}
    </div>
  );
}

function ProviderIcon({ className, name }: { className?: string; name: string }) {
  const slug = matchProviderIcon(name);
  if (slug) {
    return <LocalLlmIcon className={cn("text-app-muted", className)} slug={slug} title={name} />;
  }
  const initial = getDisplayInitial(name);
  return (
    <div
      className={cn(
        "flex items-center justify-center rounded-lg bg-app-surface-muted text-[11px] font-semibold text-app-muted",
        className,
      )}
    >
      {initial}
    </div>
  );
}

function getDisplayInitial(value?: string) {
  const candidate = value?.trim();
  return candidate ? candidate.charAt(0).toUpperCase() : "?";
}

function WorkspaceSettingsPanel({
  description,
  systemMetadata,
  workspaces,
  onAddWorkspace,
  onRemoveWorkspace,
  onSetDefaultWorkspace,
}: {
  description: string;
  systemMetadata: SystemMetadata | null;
  workspaces: Array<WorkspaceEntry>;
  onAddWorkspace: (entry: Omit<WorkspaceEntry, "id">) => void;
  onRemoveWorkspace: (id: string) => void;
  onSetDefaultWorkspace: (id: string) => void;
}) {
  const t = useT();
  const [activeOpenWorkspaceId, setActiveOpenWorkspaceId] = useState<string | null>(null);
  const [openError, setOpenError] = useState<string | null>(null);
  const isMacOS = systemMetadata?.platform === "macos"
    || (typeof navigator !== "undefined" && navigator.userAgent.includes("Mac"));
  const isWindows = systemMetadata?.platform === "windows"
    || (typeof navigator !== "undefined" && navigator.userAgent.includes("Windows"));
  const openWorkspaceLabel = t("settings.workspace.openInFileManager");

  const handleAddWorkspace = () => {
    if (!isTauri()) {
      onAddWorkspace({
        name: "New Workspace",
        path: "TiyCode Workspaces/new-project",
        isDefault: false,
        isGit: false,
        autoWorkTree: false,
        kind: "standalone",
        parentWorkspaceId: null,
        worktreeHash: null,
      });
      return;
    }

    void open({
      directory: true,
      multiple: false,
      title: "Choose workspace folder",
    }).then((selectedPath) => {
      if (typeof selectedPath !== "string") {
        return;
      }

      onAddWorkspace({
        name: deriveWorkspaceNameFromPath(selectedPath),
        path: selectedPath,
        isDefault: false,
        isGit: false,
        autoWorkTree: false,
        kind: "standalone",
        parentWorkspaceId: null,
        worktreeHash: null,
      });
    });
  };

  const handleOpenWorkspace = (workspace: WorkspaceEntry) => {
    if (!isTauri() || !workspace.path || activeOpenWorkspaceId) {
      return;
    }

    const appId = isWindows ? "explorer" : "finder";

    void (async () => {
      setActiveOpenWorkspaceId(workspace.id);

      try {
        if (isWindows || isMacOS) {
          await invoke("open_workspace_in_app", {
            targetPath: workspace.path,
            appId,
            appPath: null,
          });
        } else {
          await openPath(workspace.path);
        }

        setOpenError(null);
      } catch (error) {
        setOpenError(getInvokeErrorMessage(error, `Couldn't open ${workspace.name}`));
      } finally {
        setActiveOpenWorkspaceId(null);
      }
    })();
  };

  return (
    <div className="flex flex-col gap-6">
      <PageHeading title="Workspace" description={description} />

      <SettingsSection
        title="Workspace"
        action={
          <button
            type="button"
            className="inline-flex items-center gap-1.5 rounded-lg border border-app-border bg-app-surface px-3 py-1.5 text-[12px] font-medium text-app-foreground transition-colors hover:bg-app-surface-hover"
            onClick={handleAddWorkspace}
          >
            <FolderPlus className="size-3.5" />
            <span>Add workspace</span>
          </button>
        }
      >
        {openError ? (
          <div className="border-b border-app-border bg-app-surface-muted px-4 py-2 text-[12px] text-red-500">
            {openError}
          </div>
        ) : null}

        {workspaces.length === 0 ? (
          <div className="px-4 py-8 text-center">
            <p className="text-[13px] text-app-muted">No workspaces configured.</p>
            <p className="mt-1 text-[12px] text-app-subtle">Add a workspace to get started.</p>
          </div>
        ) : (
          <div className="divide-y divide-app-border">
            {workspaces.map((workspace) => {
              const isWorktreeRow = workspace.kind === "worktree";
              const worktreeTag = workspace.worktreeHash && workspace.worktreeHash.length > 0
                ? workspace.worktreeHash
                : null;

              return (
                <div
                  key={workspace.id}
                  className="group flex items-center gap-3 px-4 py-3 transition-colors"
                >
                  <div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-app-surface-muted text-app-subtle">
                    {isWorktreeRow ? <Shuffle className="size-4" /> : <FolderOpen className="size-4" />}
                  </div>

                  <div className="min-w-0 flex-1">
                    <div className="flex items-center gap-2">
                      <span className="text-[13px] font-medium text-app-foreground">{workspace.name}</span>
                      {workspace.isDefault ? (
                        <span className="inline-flex items-center gap-1 rounded-md border border-app-border bg-app-surface-muted px-1.5 py-0.5 text-[11px] text-app-muted">
                          <Star className="size-2.5 fill-current" />
                          Default
                        </span>
                      ) : null}
                      {workspace.isGit ? (
                        <span className="inline-flex items-center gap-1 rounded-md border border-app-border bg-app-surface-muted px-1.5 py-0.5 text-[11px] text-app-muted">
                          <GitBranch className="size-2.5" />
                          Git
                        </span>
                      ) : null}
                      {worktreeTag ? (
                        <span
                          title={t("worktree.tag.label")}
                          className="inline-flex items-center gap-1 rounded-md border border-app-border bg-app-surface-muted px-1.5 py-0.5 font-mono text-[11px] text-app-muted"
                        >
                          <Shuffle className="size-2.5" />
                          {worktreeTag}
                        </span>
                      ) : null}
                    </div>
                    <p className="mt-0.5 truncate text-[12px] text-app-subtle" title={workspace.path}>
                      {workspace.path}
                    </p>
                  </div>

                  <div className="flex shrink-0 items-center gap-1">
                    <WorkspaceActionButton
                      icon={Star}
                      label="Set as default"
                      active={workspace.isDefault}
                      className="invisible group-hover:visible"
                      onClick={() => onSetDefaultWorkspace(workspace.id)}
                    />
                    <WorkspaceActionButton
                      icon={FolderOpen}
                      label={activeOpenWorkspaceId === workspace.id ? "Opening folder" : openWorkspaceLabel}
                      disabled={activeOpenWorkspaceId === workspace.id}
                      onClick={() => handleOpenWorkspace(workspace)}
                    />
                    <WorkspaceActionButton
                      icon={Trash2}
                      label="Remove workspace"
                      onClick={() => onRemoveWorkspace(workspace.id)}
                    />
                  </div>
                </div>
              );
            })}
          </div>
        )}
      </SettingsSection>
    </div>
  );
}

function WorkspaceActionButton({
  active,
  className,
  disabled,
  icon: Icon,
  label,
  onClick,
}: {
  active?: boolean;
  className?: string;
  disabled?: boolean;
  icon: typeof Star;
  label: string;
  onClick: () => void;
}) {
  return (
    <button
      type="button"
      title={label}
      aria-label={label}
      disabled={disabled}
      className={cn(
        "flex size-7 items-center justify-center rounded-md transition-colors",
        disabled && "cursor-not-allowed opacity-50",
        active
          ? "text-app-foreground"
          : "text-app-subtle hover:bg-app-surface-hover hover:text-app-foreground",
        className,
      )}
      onClick={onClick}
    >
      <Icon className={cn("size-3.5", active && "fill-current")} />
    </button>
  );
}

// ---------------------------------------------------------------------------
// Terminal Settings Panel
// ---------------------------------------------------------------------------

function TerminalSettingsPanel({
  description,
  terminal,
  availableShells,
  onUpdateTerminalSetting,
}: {
  description: string;
  terminal: TerminalSettings;
  availableShells: Array<{ path: string; name: string }>;
  onUpdateTerminalSetting: <Key extends keyof TerminalSettings>(key: Key, value: TerminalSettings[Key]) => void;
}) {
  const t = useT();
  const [customShellPath, setCustomShellPath] = useState(terminal.shellPath);
  const isCustomShell = terminal.shellPath !== "" && !availableShells.some((s) => s.path === terminal.shellPath);

  const CURSOR_STYLE_OPTIONS: ReadonlyArray<{ label: string; value: TerminalCursorStyle }> = useMemo(
    () => [
      { label: t("settings.terminal.cursorStyleBlock"), value: "block" },
      { label: t("settings.terminal.cursorStyleUnderline"), value: "underline" },
      { label: t("settings.terminal.cursorStyleBar"), value: "bar" },
    ],
    [t],
  );

  return (
    <div className="flex flex-col gap-6">
      <PageHeading title={t("settings.category.terminal")} description={description} />

      {/* ── Shell ── */}
      <SettingsSection title={t("settings.terminal.shell")}>
        <SettingsRow
          label={t("settings.terminal.shellLabel")}
          description={t("settings.terminal.shellDesc")}
          control={
            <select
              className="h-8 min-w-[200px] rounded-lg border border-app-border bg-app-surface px-2.5 text-[13px] text-app-foreground outline-none transition-colors hover:border-app-border-strong focus:border-app-accent"
              value={isCustomShell ? "__custom__" : terminal.shellPath}
              onChange={(event) => {
                const value = event.target.value;
                if (value === "__custom__") {
                  setCustomShellPath("");
                  onUpdateTerminalSetting("shellPath", "");
                } else {
                  onUpdateTerminalSetting("shellPath", value);
                }
              }}
            >
              <option value="">{t("settings.terminal.shellAutoDetect")}</option>
              {availableShells.map((shell) => (
                <option key={shell.path} value={shell.path}>
                  {shell.name} ({shell.path})
                </option>
              ))}
              <option value="__custom__">{t("settings.terminal.shellCustom")}</option>
            </select>
          }
        />
        {isCustomShell || terminal.shellPath === "" ? null : null}
        {(isCustomShell || (terminal.shellPath === "" ? false : !availableShells.some((s) => s.path === terminal.shellPath))) ? (
          <>
            <SectionDivider />
            <SettingsRow
              label={t("settings.terminal.shellCustom")}
              description=""
              control={
                <Input
                  className="h-8 w-[320px] text-[13px]"
                  placeholder="/path/to/shell"
                  value={customShellPath}
                  onChange={(event) => setCustomShellPath(event.target.value)}
                  onBlur={() => onUpdateTerminalSetting("shellPath", customShellPath.trim())}
                />
              }
            />
          </>
        ) : null}
        <SectionDivider />
        <SettingsRow
          label={t("settings.terminal.shellArgsLabel")}
          description={t("settings.terminal.shellArgsDesc")}
          control={
            <Input
              className="h-8 w-[240px] text-[13px]"
              placeholder={t("settings.terminal.shellArgsPlaceholder")}
              defaultValue={terminal.shellArgs}
              onBlur={(event) => onUpdateTerminalSetting("shellArgs", event.target.value.trim())}
            />
          }
        />
      </SettingsSection>

      {/* ── Appearance ── */}
      <SettingsSection title={t("settings.terminal.appearance")}>
        <SettingsRow
          label={t("settings.terminal.fontFamilyLabel")}
          description={t("settings.terminal.fontFamilyDesc")}
          control={
            <Input
              className="h-8 w-[320px] text-[13px]"
              defaultValue={terminal.fontFamily}
              onBlur={(event) => {
                const value = event.target.value.trim();
                if (value) onUpdateTerminalSetting("fontFamily", value);
              }}
            />
          }
        />
        <SectionDivider />
        <SettingsRow
          label={t("settings.terminal.fontSizeLabel")}
          description={t("settings.terminal.fontSizeDesc")}
          control={
            <Input
              type="number"
              min={8}
              max={32}
              className="h-8 w-[80px] text-[13px]"
              defaultValue={terminal.fontSize}
              onBlur={(event) => {
                const value = Math.min(32, Math.max(8, Number(event.target.value) || 12));
                onUpdateTerminalSetting("fontSize", value);
              }}
            />
          }
        />
        <SectionDivider />
        <SettingsRow
          label={t("settings.terminal.lineHeightLabel")}
          description={t("settings.terminal.lineHeightDesc")}
          control={
            <Input
              type="number"
              min={1.0}
              max={2.0}
              step={0.05}
              className="h-8 w-[80px] text-[13px]"
              defaultValue={terminal.lineHeight}
              onBlur={(event) => {
                const value = Math.min(2.0, Math.max(1.0, Number(event.target.value) || 1.35));
                onUpdateTerminalSetting("lineHeight", Math.round(value * 100) / 100);
              }}
            />
          }
        />
        <SectionDivider />
        <SettingsRow
          label={t("settings.terminal.cursorStyleLabel")}
          description={t("settings.terminal.cursorStyleDesc")}
          control={
            <ChoiceGroup
              options={CURSOR_STYLE_OPTIONS}
              value={terminal.cursorStyle}
              onValueChange={(value) => onUpdateTerminalSetting("cursorStyle", value as TerminalCursorStyle)}
            />
          }
        />
        <SectionDivider />
        <SettingsRow
          label={t("settings.terminal.cursorBlinkLabel")}
          description={t("settings.terminal.cursorBlinkDesc")}
          control={
            <Switch
              size="sm"
              checked={terminal.cursorBlink}
              onCheckedChange={(checked) => onUpdateTerminalSetting("cursorBlink", checked)}
            />
          }
        />
      </SettingsSection>

      {/* ── Behavior ── */}
      <SettingsSection title={t("settings.terminal.behavior")}>
        <SettingsRow
          label={t("settings.terminal.scrollbackLabel")}
          description={t("settings.terminal.scrollbackDesc")}
          control={
            <Input
              type="number"
              min={500}
              max={100000}
              step={500}
              className="h-8 w-[120px] text-[13px]"
              defaultValue={terminal.scrollback}
              onBlur={(event) => {
                const value = Math.min(100000, Math.max(500, Number(event.target.value) || 5000));
                onUpdateTerminalSetting("scrollback", value);
              }}
            />
          }
        />
        <SectionDivider />
        <SettingsRow
          label={t("settings.terminal.copyOnSelectLabel")}
          description={t("settings.terminal.copyOnSelectDesc")}
          control={
            <Switch
              size="sm"
              checked={terminal.copyOnSelect}
              onCheckedChange={(checked) => onUpdateTerminalSetting("copyOnSelect", checked)}
            />
          }
        />
      </SettingsSection>

      {/* ── Environment ── */}
      <SettingsSection title={t("settings.terminal.environment")}>
        <SettingsRow
          label={t("settings.terminal.termEnvLabel")}
          description={t("settings.terminal.termEnvDesc")}
          control={
            <Input
              className="h-8 w-[200px] text-[13px]"
              defaultValue={terminal.termEnv}
              onBlur={(event) => {
                const value = event.target.value.trim();
                if (value) onUpdateTerminalSetting("termEnv", value);
              }}
            />
          }
        />
      </SettingsSection>
    </div>
  );
}
