import { Bot, Check, Plus, RefreshCcw, Save, Trash2 } from "lucide-react";
import type { ReactNode } from "react";
import {
  startTransition,
  useCallback,
  useEffect,
  useMemo,
  useState,
} from "react";
import { Link } from "react-router-dom";
import { toast } from "sonner";

import { MediaUrlControls } from "@/components/media-url-controls";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Select } from "@/components/ui/select";
import { Skeleton } from "@/components/ui/skeleton";
import { Textarea } from "@/components/ui/textarea";
import { adminApi, ApiError } from "@/lib/api";
import { formatDateTime } from "@/lib/admin-format";
import {
  formatWorkerProgress,
  getWorkerProgressPercent,
} from "@/lib/worker-progress";
import type {
  AdminSiteSettingsResponse,
  AiProviderConfig,
  HumanVerificationMode,
  MusicTrack,
  SiteSettingsPayload,
  WorkerJobRecord,
} from "@/lib/types";

function createEmptyMusicTrack(): MusicTrack {
  return {
    title: "",
    artist: "",
    album: "",
    url: "",
    cover_image_url: "",
    accent_color: "",
    description: "",
  };
}

function createAiProviderId() {
  if (
    typeof crypto !== "undefined" &&
    typeof crypto.randomUUID === "function"
  ) {
    return `provider-${crypto.randomUUID()}`;
  }

  return `provider-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
}

function createEmptyAiProvider(): AiProviderConfig {
  return {
    id: createAiProviderId(),
    name: "",
    provider: "openai",
    api_base: "",
    api_key: "",
    chat_model: "",
    image_model: "",
  };
}

const AI_PROVIDER_OPTIONS = [
  { value: "openai", label: "OpenAI" },
  { value: "anthropic", label: "Anthropic" },
  { value: "gemini", label: "Gemini" },
  { value: "cloudflare", label: "Cloudflare Workers AI" },
  { value: "newapi", label: "NewAPI / Responses" },
  { value: "openai-compatible", label: "OpenAI Compatible" },
] as const;

const MEDIA_STORAGE_PROVIDER_OPTIONS = [
  { value: "r2", label: "Cloudflare R2" },
  { value: "minio", label: "MinIO" },
] as const;

const NOTIFICATION_CHANNEL_OPTIONS = [
  { value: "webhook", label: "Webhook" },
  { value: "ntfy", label: "ntfy" },
] as const;

const HUMAN_VERIFICATION_MODE_OPTIONS = [
  { value: "off", label: "关闭" },
  { value: "captcha", label: "普通验证码" },
  { value: "turnstile", label: "Turnstile" },
] as const;

function normalizeHumanVerificationMode(
  value: string | null | undefined,
  fallback: HumanVerificationMode,
): HumanVerificationMode {
  switch ((value ?? "").trim().toLowerCase()) {
    case "off":
      return "off";
    case "captcha":
    case "normal":
    case "simple":
      return "captcha";
    case "turnstile":
      return "turnstile";
    default:
      return fallback;
  }
}

function isCloudflareProvider(provider: string | null | undefined) {
  const normalized = provider?.trim().toLowerCase();
  return (
    normalized === "cloudflare" ||
    normalized === "cloudflare-workers-ai" ||
    normalized === "workers-ai"
  );
}

function buildCloudflareAiPreset(current: AiProviderConfig): AiProviderConfig {
  return {
    ...current,
    name: current.name?.trim() ? current.name : "Cloudflare Workers AI",
    provider: "cloudflare",
    chat_model: current.chat_model?.trim() || "@cf/meta/llama-3.1-8b-instruct",
  };
}

function normalizeSettingsResponse(
  input: AdminSiteSettingsResponse,
): AdminSiteSettingsResponse {
  const aiProviders = Array.isArray(input.ai_providers)
    ? input.ai_providers
    : [];
  const searchSynonyms = Array.isArray(input.search_synonyms)
    ? input.search_synonyms
    : [];

  return {
    ...input,
    ai_providers: aiProviders,
    search_synonyms: searchSynonyms,
    comment_verification_mode: normalizeHumanVerificationMode(
      input.comment_verification_mode,
      input.comment_turnstile_enabled ? "turnstile" : "captcha",
    ),
    subscription_verification_mode: normalizeHumanVerificationMode(
      input.subscription_verification_mode,
      input.subscription_turnstile_enabled ? "turnstile" : "off",
    ),
    turnstile_site_key: input.turnstile_site_key ?? null,
    turnstile_secret_key: input.turnstile_secret_key ?? null,
    web_push_vapid_public_key: input.web_push_vapid_public_key ?? null,
    web_push_vapid_private_key: input.web_push_vapid_private_key ?? null,
    web_push_vapid_subject: input.web_push_vapid_subject ?? null,
    music_enabled: input.music_enabled ?? true,
    maintenance_mode_enabled: input.maintenance_mode_enabled ?? false,
    maintenance_access_code: input.maintenance_access_code ?? null,
    ai_active_provider_id:
      input.ai_active_provider_id ?? aiProviders[0]?.id ?? null,
  };
}

function Field({
  label,
  hint,
  children,
}: {
  label: string;
  hint?: string;
  children: ReactNode;
}) {
  return (
    <div className="space-y-2">
      <Label>{label}</Label>
      {children}
      {hint ? (
        <p className="text-xs leading-5 text-muted-foreground">{hint}</p>
      ) : null}
    </div>
  );
}

function toPayload(form: AdminSiteSettingsResponse): SiteSettingsPayload {
  const commentTurnstileEnabled =
    form.comment_verification_mode === "turnstile";
  const subscriptionTurnstileEnabled =
    form.subscription_verification_mode === "turnstile";

  return {
    siteName: form.site_name,
    siteShortName: form.site_short_name,
    siteUrl: form.site_url,
    siteTitle: form.site_title,
    siteDescription: form.site_description,
    heroTitle: form.hero_title,
    heroSubtitle: form.hero_subtitle,
    ownerName: form.owner_name,
    ownerTitle: form.owner_title,
    ownerBio: form.owner_bio,
    ownerAvatarUrl: form.owner_avatar_url,
    socialGithub: form.social_github,
    socialTwitter: form.social_twitter,
    socialEmail: form.social_email,
    location: form.location,
    techStack: form.tech_stack,
    musicPlaylist: form.music_playlist,
    musicEnabled: form.music_enabled,
    maintenanceModeEnabled: form.maintenance_mode_enabled,
    maintenanceAccessCode: form.maintenance_access_code,
    aiEnabled: form.ai_enabled,
    paragraphCommentsEnabled: form.paragraph_comments_enabled,
    commentVerificationMode: form.comment_verification_mode,
    commentTurnstileEnabled,
    subscriptionVerificationMode: form.subscription_verification_mode,
    subscriptionTurnstileEnabled,
    webPushEnabled: form.web_push_enabled,
    turnstileSiteKey: form.turnstile_site_key,
    turnstileSecretKey: form.turnstile_secret_key,
    webPushVapidPublicKey: form.web_push_vapid_public_key,
    webPushVapidPrivateKey: form.web_push_vapid_private_key,
    webPushVapidSubject: form.web_push_vapid_subject,
    aiProvider: form.ai_provider,
    aiApiBase: form.ai_api_base,
    aiApiKey: form.ai_api_key,
    aiChatModel: form.ai_chat_model,
    aiImageProvider: form.ai_image_provider,
    aiImageApiBase: form.ai_image_api_base,
    aiImageApiKey: form.ai_image_api_key,
    aiImageModel: form.ai_image_model,
    aiProviders: form.ai_providers,
    aiActiveProviderId: form.ai_active_provider_id,
    aiEmbeddingModel: form.ai_embedding_model,
    aiSystemPrompt: form.ai_system_prompt,
    aiTopK: form.ai_top_k,
    aiChunkSize: form.ai_chunk_size,
    mediaStorageProvider: form.media_storage_provider,
    mediaR2AccountId: form.media_r2_account_id,
    mediaR2Bucket: form.media_r2_bucket,
    mediaR2PublicBaseUrl: form.media_r2_public_base_url,
    mediaR2AccessKeyId: form.media_r2_access_key_id,
    mediaR2SecretAccessKey: form.media_r2_secret_access_key,
    seoFaviconUrl: form.seo_favicon_url,
    seoDefaultOgImage: form.seo_default_og_image,
    seoDefaultTwitterHandle: form.seo_default_twitter_handle,
    seoWechatShareQrEnabled: form.seo_wechat_share_qr_enabled,
    notificationWebhookUrl: form.notification_webhook_url,
    notificationChannelType: form.notification_channel_type,
    notificationCommentEnabled: form.notification_comment_enabled,
    notificationFriendLinkEnabled: form.notification_friend_link_enabled,
    subscriptionPopupEnabled: form.subscription_popup_enabled,
    subscriptionPopupTitle: form.subscription_popup_title,
    subscriptionPopupDescription: form.subscription_popup_description,
    subscriptionPopupDelaySeconds: form.subscription_popup_delay_seconds,
    searchSynonyms: form.search_synonyms,
  };
}

export function SiteSettingsPage() {
  const [form, setForm] = useState<AdminSiteSettingsResponse | null>(null);
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);
  const [reindexing, setReindexing] = useState(false);
  const [reindexJobId, setReindexJobId] = useState<number | null>(null);
  const [reindexJobStatus, setReindexJobStatus] = useState<
    WorkerJobRecord["status"] | null
  >(null);
  const [reindexJobResult, setReindexJobResult] =
    useState<WorkerJobRecord["result"]>(null);
  const [testingProvider, setTestingProvider] = useState(false);
  const [testingImageProvider, setTestingImageProvider] = useState(false);
  const [testingR2Storage, setTestingR2Storage] = useState(false);
  const [selectedTrackIndex, setSelectedTrackIndex] = useState(0);
  const [selectedProviderIndex, setSelectedProviderIndex] = useState(0);

  const loadSettings = useCallback(async (showToast = false) => {
    try {
      const next = normalizeSettingsResponse(await adminApi.getSiteSettings());
      let activeReindexJob: WorkerJobRecord | null | undefined = undefined;

      try {
        const workerJobs = await adminApi.listWorkerJobs({
          workerName: "worker.ai_reindex",
          limit: 8,
        });
        activeReindexJob =
          workerJobs.jobs.find(
            (item) => item.status === "queued" || item.status === "running",
          ) ?? null;
      } catch {
        activeReindexJob = undefined;
      }

      startTransition(() => {
        setForm(next);
        if (activeReindexJob !== undefined) {
          setReindexing(Boolean(activeReindexJob));
          setReindexJobId(activeReindexJob?.id ?? null);
          setReindexJobStatus(activeReindexJob?.status ?? null);
          setReindexJobResult(activeReindexJob?.result ?? null);
        }
      });

      if (showToast) {
        toast.success("站点设置已刷新。");
      }
    } catch (error) {
      if (error instanceof ApiError && error.status === 401) {
        return;
      }
      toast.error(
        error instanceof ApiError ? error.message : "无法加载站点设置。",
      );
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    void loadSettings(false);
  }, [loadSettings]);

  useEffect(() => {
    if (!form?.music_playlist.length) {
      setSelectedTrackIndex(0);
      return;
    }

    setSelectedTrackIndex((current) =>
      Math.min(current, form.music_playlist.length - 1),
    );
  }, [form?.music_playlist.length]);

  useEffect(() => {
    if (!form?.ai_providers.length) {
      setSelectedProviderIndex(0);
      return;
    }

    setSelectedProviderIndex((current) => {
      const activeIndex = form.ai_providers.findIndex(
        (provider) => provider.id === form.ai_active_provider_id,
      );

      if (activeIndex >= 0) {
        return activeIndex;
      }

      return Math.min(current, form.ai_providers.length - 1);
    });
  }, [form?.ai_active_provider_id, form?.ai_providers]);

  useEffect(() => {
    if (!reindexJobId) {
      return;
    }

    let cancelled = false;
    let timer: ReturnType<typeof setTimeout> | null = null;

    const scheduleNextPoll = () => {
      timer = setTimeout(() => {
        void pollJob();
      }, 4000);
    };

    const pollJob = async () => {
      try {
        const job = await adminApi.getWorkerJob(reindexJobId);
        if (cancelled) {
          return;
        }

        setReindexJobStatus(job.status);
        setReindexJobResult(job.result);

        if (job.status === "succeeded") {
          setReindexing(false);
          setReindexJobId(null);
          setReindexJobStatus(null);
          setReindexJobResult(null);
          const indexedChunks = Number(job.result?.indexed_chunks ?? 0);
          toast.success(
            indexedChunks > 0
              ? `AI 索引重建完成，共生成 ${indexedChunks} 个分块。`
              : "AI 索引重建完成。",
          );
          await loadSettings(false);
          return;
        }

        if (job.status === "failed" || job.status === "cancelled") {
          setReindexing(false);
          setReindexJobId(null);
          setReindexJobStatus(null);
          setReindexJobResult(null);
          toast.error(job.error_text?.trim() || "AI 重建索引失败。");
          return;
        }

        scheduleNextPoll();
      } catch (error) {
        if (cancelled) {
          return;
        }

        scheduleNextPoll();
        if (error instanceof ApiError && error.status === 401) {
          return;
        }
      }
    };

    void pollJob();

    return () => {
      cancelled = true;
      if (timer) {
        clearTimeout(timer);
      }
    };
  }, [loadSettings, reindexJobId]);

  const updateField = <K extends keyof AdminSiteSettingsResponse>(
    key: K,
    value: AdminSiteSettingsResponse[K],
  ) => {
    setForm((current) => (current ? { ...current, [key]: value } : current));
  };

  const updateMusicTrack = <K extends keyof MusicTrack>(
    index: number,
    key: K,
    value: MusicTrack[K],
  ) => {
    setForm((current) => {
      if (!current) {
        return current;
      }

      const nextPlaylist = current.music_playlist.map((track, trackIndex) =>
        trackIndex === index ? { ...track, [key]: value } : track,
      );

      return { ...current, music_playlist: nextPlaylist };
    });
  };

  const updateAiProvider = <K extends keyof AiProviderConfig>(
    index: number,
    key: K,
    value: AiProviderConfig[K],
  ) => {
    setForm((current) => {
      if (!current) {
        return current;
      }

      const nextProviders = current.ai_providers.map(
        (provider, providerIndex) =>
          providerIndex === index ? { ...provider, [key]: value } : provider,
      );

      return { ...current, ai_providers: nextProviders };
    });
  };

  const addMusicTrack = () => {
    setForm((current) => {
      if (!current) {
        return current;
      }

      const nextPlaylist = [...current.music_playlist, createEmptyMusicTrack()];
      setSelectedTrackIndex(nextPlaylist.length - 1);

      return { ...current, music_playlist: nextPlaylist };
    });
  };

  const removeMusicTrack = (index: number) => {
    setForm((current) => {
      if (!current) {
        return current;
      }

      const nextPlaylist = current.music_playlist.filter(
        (_, trackIndex) => trackIndex !== index,
      );
      setSelectedTrackIndex((currentIndex) =>
        Math.max(
          0,
          Math.min(
            currentIndex > index ? currentIndex - 1 : currentIndex,
            nextPlaylist.length - 1,
          ),
        ),
      );

      return {
        ...current,
        music_playlist: nextPlaylist,
      };
    });
  };

  const addAiProvider = () => {
    setForm((current) => {
      if (!current) {
        return current;
      }

      const nextProvider = createEmptyAiProvider();
      const nextProviders = [...current.ai_providers, nextProvider];
      setSelectedProviderIndex(nextProviders.length - 1);

      return {
        ...current,
        ai_providers: nextProviders,
        ai_active_provider_id: current.ai_active_provider_id ?? nextProvider.id,
      };
    });
  };

  const removeAiProvider = (index: number) => {
    setForm((current) => {
      if (!current) {
        return current;
      }

      const removed = current.ai_providers[index];
      const nextProviders = current.ai_providers.filter(
        (_, providerIndex) => providerIndex !== index,
      );
      const nextActiveProviderId =
        removed?.id === current.ai_active_provider_id
          ? (nextProviders[0]?.id ?? null)
          : current.ai_active_provider_id;

      setSelectedProviderIndex((currentIndex) =>
        Math.max(
          0,
          Math.min(
            currentIndex > index ? currentIndex - 1 : currentIndex,
            nextProviders.length - 1,
          ),
        ),
      );

      return {
        ...current,
        ai_providers: nextProviders,
        ai_active_provider_id: nextActiveProviderId,
      };
    });
  };

  const setActiveAiProvider = (providerId: string) => {
    updateField("ai_active_provider_id", providerId);
  };

  const applyCloudflarePreset = (index: number) => {
    setForm((current) => {
      if (!current) {
        return current;
      }

      const nextProviders = current.ai_providers.map(
        (provider, providerIndex) =>
          providerIndex === index
            ? buildCloudflareAiPreset(provider)
            : provider,
      );

      return {
        ...current,
        ai_providers: nextProviders,
      };
    });
  };

  const applyCloudflareImagePreset = () => {
    setForm((current) => {
      if (!current) {
        return current;
      }

      return {
        ...current,
        ai_image_provider: "cloudflare",
        ai_image_model:
          current.ai_image_model?.trim() ||
          "@cf/black-forest-labs/flux-2-klein-4b",
      };
    });
  };

  const techStackValue = useMemo(
    () => (form?.tech_stack.length ? form.tech_stack.join("\n") : ""),
    [form?.tech_stack],
  );
  const selectedTrack = useMemo(
    () => form?.music_playlist[selectedTrackIndex] ?? createEmptyMusicTrack(),
    [form, selectedTrackIndex],
  );
  const selectedProvider = useMemo(
    () => form?.ai_providers[selectedProviderIndex] ?? createEmptyAiProvider(),
    [form, selectedProviderIndex],
  );
  const activeProvider = useMemo(
    () =>
      form?.ai_providers.find(
        (provider) => provider.id === form.ai_active_provider_id,
      ) ?? null,
    [form],
  );
  const selectedProviderIsCloudflare = useMemo(
    () => isCloudflareProvider(selectedProvider.provider),
    [selectedProvider.provider],
  );
  const imageProviderIsCloudflare = useMemo(
    () => isCloudflareProvider(form?.ai_image_provider),
    [form?.ai_image_provider],
  );
  const mediaStorageProvider = useMemo(
    () =>
      form?.media_storage_provider?.trim().toLowerCase() === "minio"
        ? "minio"
        : "r2",
    [form?.media_storage_provider],
  );

  if (loading || !form) {
    return (
      <div className="space-y-6">
        <Skeleton className="h-48 rounded-3xl" />
        <Skeleton className="h-[540px] rounded-3xl" />
      </div>
    );
  }

  return (
    <div className="space-y-6">
      <div className="flex flex-col gap-4 xl:flex-row xl:items-end xl:justify-between">
        <div className="space-y-3">
          <Badge variant="secondary">站点设置</Badge>
          <div>
            <h2 className="text-3xl font-semibold tracking-tight">
              品牌、资料与 AI 控制
            </h2>
            <p className="mt-2 max-w-3xl text-sm leading-7 text-muted-foreground">
              这里统一维护前台站点使用的品牌信息、站长资料与 AI
              配置，确保和后端数据模型一致。
            </p>
          </div>
        </div>

        <div className="flex flex-wrap items-center gap-3">
          <Button
            variant="outline"
            onClick={() => void loadSettings(true)}
            data-testid="site-settings-refresh"
          >
            <RefreshCcw className="h-4 w-4" />
            刷新
          </Button>
          {reindexJobId ? (
            <Button
              variant="outline"
              asChild
              data-testid="site-settings-reindex-job"
            >
              <Link to={`/workers?job=${reindexJobId}`}>查看索引任务</Link>
            </Button>
          ) : null}
          <Button
            variant="secondary"
            disabled={reindexing}
            data-testid="site-settings-reindex"
            onClick={async () => {
              let queued = false;
              try {
                setReindexing(true);
                setReindexJobStatus("queued");
                setReindexJobResult(null);
                const result = await adminApi.reindexAi();
                queued = true;
                setReindexJobId(result.job.id);
                setReindexJobStatus(result.job.status);
                setReindexJobResult(result.job.result);
                toast.success(`AI 重建任务已入队：#${result.job.id}`);
              } catch (error) {
                setReindexJobId(null);
                setReindexJobStatus(null);
                setReindexJobResult(null);
                toast.error(
                  error instanceof ApiError
                    ? error.message
                    : "AI 重建索引失败。",
                );
              } finally {
                if (!queued) {
                  setReindexing(false);
                }
              }
            }}
          >
            <Bot className="h-4 w-4" />
            {reindexing ? "任务进行中..." : "重建 AI 索引"}
          </Button>
          <Button
            disabled={saving}
            data-testid="site-settings-save"
            onClick={async () => {
              if (
                form.maintenance_mode_enabled &&
                !form.maintenance_access_code?.trim()
              ) {
                toast.error("开启维护模式前请先填写访问口令。");
                return;
              }

              try {
                setSaving(true);
                const updated = await adminApi.updateSiteSettings(
                  toPayload(form),
                );
                startTransition(() => {
                  setForm(normalizeSettingsResponse(updated));
                });
                toast.success("站点设置已保存。");
              } catch (error) {
                toast.error(
                  error instanceof ApiError ? error.message : "保存失败。",
                );
              } finally {
                setSaving(false);
              }
            }}
          >
            <Save className="h-4 w-4" />
            {saving ? "保存中..." : "保存修改"}
          </Button>
        </div>
      </div>

      <div className="grid gap-6 xl:grid-cols-[minmax(0,1fr)_360px] 2xl:grid-cols-[minmax(0,1fr)_400px]">
        <div className="space-y-6">
          <Card>
            <CardHeader>
              <CardTitle>前台站点资料</CardTitle>
              <CardDescription>
                前台站点用于展示品牌、首页文案、站长资料和社交信息的全部字段。
              </CardDescription>
            </CardHeader>
            <CardContent className="grid gap-6 lg:grid-cols-2">
              <Field label="站点名称">
                <Input
                  data-testid="site-settings-site-name"
                  value={form.site_name ?? ""}
                  onChange={(event) =>
                    updateField("site_name", event.target.value)
                  }
                />
              </Field>
              <Field label="站点短名">
                <Input
                  value={form.site_short_name ?? ""}
                  onChange={(event) =>
                    updateField("site_short_name", event.target.value)
                  }
                />
              </Field>
              <Field label="站点 URL">
                <Input
                  value={form.site_url ?? ""}
                  onChange={(event) =>
                    updateField("site_url", event.target.value)
                  }
                />
              </Field>
              <Field label="所在地">
                <Input
                  value={form.location ?? ""}
                  onChange={(event) =>
                    updateField("location", event.target.value)
                  }
                />
              </Field>
              <Field label="站点标题" hint="用于页面标题与 SEO 展示。">
                <Input
                  value={form.site_title ?? ""}
                  onChange={(event) =>
                    updateField("site_title", event.target.value)
                  }
                />
              </Field>
              <Field label="站长头衔">
                <Input
                  value={form.owner_title ?? ""}
                  onChange={(event) =>
                    updateField("owner_title", event.target.value)
                  }
                />
              </Field>
              <div className="lg:col-span-2">
                <Field label="站点简介">
                  <Textarea
                    value={form.site_description ?? ""}
                    onChange={(event) =>
                      updateField("site_description", event.target.value)
                    }
                  />
                </Field>
              </div>
              <Field label="首页主标题">
                <Input
                  value={form.hero_title ?? ""}
                  onChange={(event) =>
                    updateField("hero_title", event.target.value)
                  }
                />
              </Field>
              <Field label="首页副标题">
                <Input
                  value={form.hero_subtitle ?? ""}
                  onChange={(event) =>
                    updateField("hero_subtitle", event.target.value)
                  }
                />
              </Field>
              <Field label="站长名称">
                <Input
                  value={form.owner_name ?? ""}
                  onChange={(event) =>
                    updateField("owner_name", event.target.value)
                  }
                />
              </Field>
              <Field
                label="头像 URL"
                hint="可直接填写外链，也可以上传 / 抓取 / 从媒体库选择后自动回填。"
              >
                <div className="space-y-3">
                  <Input
                    value={form.owner_avatar_url ?? ""}
                    onChange={(event) =>
                      updateField("owner_avatar_url", event.target.value)
                    }
                  />
                  <MediaUrlControls
                    value={form.owner_avatar_url ?? ""}
                    onChange={(ownerAvatarUrl) =>
                      updateField("owner_avatar_url", ownerAvatarUrl)
                    }
                    prefix="site-assets/"
                    contextLabel="站长头像上传"
                    remoteTitle={
                      form.owner_name || form.site_name || "站长头像"
                    }
                    dataTestIdPrefix="site-owner-avatar"
                  />
                </div>
              </Field>
              <div className="lg:col-span-2">
                <Field label="站长简介">
                  <Textarea
                    value={form.owner_bio ?? ""}
                    onChange={(event) =>
                      updateField("owner_bio", event.target.value)
                    }
                  />
                </Field>
              </div>
              <Field label="GitHub">
                <Input
                  value={form.social_github ?? ""}
                  onChange={(event) =>
                    updateField("social_github", event.target.value)
                  }
                />
              </Field>
              <Field label="Twitter / X">
                <Input
                  value={form.social_twitter ?? ""}
                  onChange={(event) =>
                    updateField("social_twitter", event.target.value)
                  }
                />
              </Field>
              <div className="lg:col-span-2">
                <Field label="邮箱 / mailto">
                  <Input
                    value={form.social_email ?? ""}
                    onChange={(event) =>
                      updateField("social_email", event.target.value)
                    }
                  />
                </Field>
              </div>
              <div className="lg:col-span-2">
                <Field label="技术栈" hint="每行填写一个项目。">
                  <Textarea
                    value={techStackValue}
                    onChange={(event) =>
                      updateField(
                        "tech_stack",
                        event.target.value
                          .split("\n")
                          .map((item) => item.trim())
                          .filter(Boolean),
                      )
                    }
                  />
                </Field>
              </div>
            </CardContent>
          </Card>

          <Card>
            <CardHeader>
              <CardTitle>订阅弹窗</CardTitle>
              <CardDescription>
                前台会在用户停留一段时间并有滚动/交互后，再延迟弹出订阅窗口；这里统一控制开关和文案。
              </CardDescription>
            </CardHeader>
            <CardContent className="space-y-5">
              <label className="flex items-start gap-3 rounded-2xl border border-border/70 bg-background/60 p-4">
                <input
                  type="checkbox"
                  checked={form.subscription_popup_enabled}
                  onChange={(event) =>
                    updateField(
                      "subscription_popup_enabled",
                      event.target.checked,
                    )
                  }
                  className="mt-1 h-4 w-4 rounded border-input text-primary focus:ring-ring"
                />
                <div>
                  <div className="font-medium">开启前台订阅弹窗</div>
                  <p className="mt-1 text-sm leading-6 text-muted-foreground">
                    开启后，首页不再塞一个突兀的大块订阅卡片，而是通过风格统一的弹窗承接订阅转化。
                  </p>
                </div>
              </label>

              <div className="grid gap-4 md:grid-cols-2">
                <label className="flex items-start gap-3 rounded-2xl border border-border/70 bg-background/60 p-4">
                  <input
                    type="checkbox"
                    checked={form.web_push_enabled}
                    onChange={(event) =>
                      updateField("web_push_enabled", event.target.checked)
                    }
                    className="mt-1 h-4 w-4 rounded border-input text-primary focus:ring-ring"
                  />
                  <div>
                    <div className="font-medium">开启浏览器推送</div>
                    <p className="mt-1 text-sm leading-6 text-muted-foreground">
                      前台订阅弹窗会增加浏览器通知授权入口。保存下方 VAPID
                      公私钥后，前台会直接读取当前后台配置。
                    </p>
                  </div>
                </label>

                <div className="rounded-2xl border border-border/70 bg-background/60 p-4">
                  <Field
                    label="订阅提交验证方式"
                    hint="可选 关闭 / 普通验证码 / Turnstile；若 Turnstile key 未配置完整，会自动回退到普通验证码。"
                  >
                    <Select
                      value={form.subscription_verification_mode}
                      onChange={(event) =>
                        updateField(
                          "subscription_verification_mode",
                          normalizeHumanVerificationMode(
                            event.target.value,
                            "off",
                          ),
                        )
                      }
                    >
                      {HUMAN_VERIFICATION_MODE_OPTIONS.map((option) => (
                        <option key={option.value} value={option.value}>
                          {option.label}
                        </option>
                      ))}
                    </Select>
                  </Field>
                </div>
              </div>

              <div className="grid gap-4 lg:grid-cols-2">
                <Field
                  label="弹窗标题"
                  hint="建议直接传达价值，例如“订阅更新”或“别错过新文章”。"
                >
                  <Input
                    data-testid="site-settings-popup-title"
                    value={form.subscription_popup_title}
                    onChange={(event) =>
                      updateField(
                        "subscription_popup_title",
                        event.target.value,
                      )
                    }
                  />
                </Field>
                <Field
                  label="触发延迟（秒）"
                  hint="建议保持在 10～20 秒，避免首屏强打断。"
                >
                  <Input
                    type="number"
                    min={3}
                    max={120}
                    value={form.subscription_popup_delay_seconds}
                    onChange={(event) =>
                      updateField(
                        "subscription_popup_delay_seconds",
                        event.target.value ? Number(event.target.value) : 18,
                      )
                    }
                  />
                </Field>
              </div>

              <Field
                label="弹窗说明"
                hint="建议明确订阅收益、需要邮箱确认，以及可随时退订，降低打扰感。"
              >
                <Textarea
                  value={form.subscription_popup_description}
                  onChange={(event) =>
                    updateField(
                      "subscription_popup_description",
                      event.target.value,
                    )
                  }
                />
              </Field>
            </CardContent>
          </Card>

          <Card>
            <CardHeader>
              <CardTitle>维护模式</CardTitle>
              <CardDescription>
                开启后，前台访问者需要先输入口令才能看到内容，适合开发联调、灰度预览或上线前封站检查。
              </CardDescription>
            </CardHeader>
            <CardContent className="space-y-5">
              <label className="flex items-start gap-3 rounded-2xl border border-border/70 bg-background/60 p-4">
                <input
                  type="checkbox"
                  checked={form.maintenance_mode_enabled}
                  onChange={(event) =>
                    updateField(
                      "maintenance_mode_enabled",
                      event.target.checked,
                    )
                  }
                  className="mt-1 h-4 w-4 rounded border-input text-primary focus:ring-ring"
                />
                <div>
                  <div className="font-medium">开启前台维护模式</div>
                  <p className="mt-1 text-sm leading-6 text-muted-foreground">
                    开启后，访问首页、文章页、分类页等前台内容都会先进入维护页；只有输入正确口令后才会放行。
                  </p>
                </div>
              </label>

              <div className="grid gap-4 lg:grid-cols-[minmax(0,1fr)_auto]">
                <Field
                  label="访问口令"
                  hint="建议设置成临时口令后发给测试同事；修改口令后，旧口令拿到的访问凭证会自动失效。"
                >
                  <Input
                    type="password"
                    value={form.maintenance_access_code ?? ""}
                    onChange={(event) =>
                      updateField("maintenance_access_code", event.target.value)
                    }
                    placeholder="例如：staging-2026"
                  />
                </Field>

                <div className="flex items-end">
                  <Badge
                    variant={
                      form.maintenance_mode_enabled ? "warning" : "outline"
                    }
                  >
                    {form.maintenance_mode_enabled ? "维护中" : "正常开放"}
                  </Badge>
                </div>
              </div>
            </CardContent>
          </Card>

          <Card>
            <CardHeader>
              <CardTitle>运行时安全 / 推送配置</CardTitle>
              <CardDescription>
                大部分前台安全与推送相关配置都可以直接在这里改；数据库配置优先，环境变量只作为回退。
              </CardDescription>
            </CardHeader>
            <CardContent className="grid gap-6 lg:grid-cols-2">
              <Field
                label="Turnstile Site Key"
                hint="评论区和订阅弹窗共用这一套站点 key，保存后前台会在运行时读取。"
              >
                <Input
                  value={form.turnstile_site_key ?? ""}
                  onChange={(event) =>
                    updateField("turnstile_site_key", event.target.value)
                  }
                  placeholder="0x4AAAA..."
                />
              </Field>
              <Field
                label="Turnstile Secret Key"
                hint="后端验证 token 使用；留空可清除数据库配置并回退到环境变量。"
              >
                <Input
                  value={form.turnstile_secret_key ?? ""}
                  onChange={(event) =>
                    updateField("turnstile_secret_key", event.target.value)
                  }
                  placeholder="ts-secret-key"
                />
              </Field>
              <Field
                label="Web Push VAPID Public Key"
                hint="浏览器订阅按钮会把这把 public key 下发到前台。"
              >
                <Textarea
                  value={form.web_push_vapid_public_key ?? ""}
                  onChange={(event) =>
                    updateField("web_push_vapid_public_key", event.target.value)
                  }
                  placeholder="BEl6..."
                  rows={3}
                />
              </Field>
              <Field
                label="Web Push VAPID Private Key"
                hint="后端发送浏览器推送时签名使用。"
              >
                <Textarea
                  value={form.web_push_vapid_private_key ?? ""}
                  onChange={(event) =>
                    updateField(
                      "web_push_vapid_private_key",
                      event.target.value,
                    )
                  }
                  placeholder="5aQ..."
                  rows={3}
                />
              </Field>
              <div className="lg:col-span-2">
                <Field
                  label="Web Push Subject"
                  hint="推荐填写 mailto:you@example.com；留空时会优先回退到环境变量，再退回站点 URL / 默认值。"
                >
                  <Input
                    value={form.web_push_vapid_subject ?? ""}
                    onChange={(event) =>
                      updateField("web_push_vapid_subject", event.target.value)
                    }
                    placeholder="mailto:admin@example.com"
                  />
                </Field>
              </div>
            </CardContent>
          </Card>

          <Card>
            <CardHeader>
              <CardTitle>SEO、搜索与通知</CardTitle>
              <CardDescription>
                统一维护默认 OG 图、Twitter 标识、Webhook 通知与搜索同义词。
              </CardDescription>
            </CardHeader>
            <CardContent className="grid gap-6 lg:grid-cols-2">
              <Field
                label="Favicon URL"
                hint="浏览器标签页图标；支持外链，也支持上传 / 抓取 / 选择媒体库。"
              >
                <div className="space-y-3">
                  <Input
                    value={form.seo_favicon_url ?? ""}
                    onChange={(event) =>
                      updateField("seo_favicon_url", event.target.value)
                    }
                  />
                  <MediaUrlControls
                    value={form.seo_favicon_url ?? ""}
                    onChange={(seoFaviconUrl) =>
                      updateField("seo_favicon_url", seoFaviconUrl)
                    }
                    prefix="seo-assets/"
                    contextLabel="站点 favicon 上传"
                    remoteTitle={
                      form.site_name || form.site_title || "站点 favicon"
                    }
                    dataTestIdPrefix="site-favicon"
                  />
                </div>
              </Field>
              <Field
                label="默认 OG 图 URL"
                hint="文章未单独设置时作为分享图回退，也支持上传 / 抓取 / 选择媒体库。"
              >
                <div className="space-y-3">
                  <Input
                    value={form.seo_default_og_image ?? ""}
                    onChange={(event) =>
                      updateField("seo_default_og_image", event.target.value)
                    }
                  />
                  <MediaUrlControls
                    value={form.seo_default_og_image ?? ""}
                    onChange={(seoDefaultOgImage) =>
                      updateField("seo_default_og_image", seoDefaultOgImage)
                    }
                    prefix="seo-assets/"
                    contextLabel="默认 OG 图上传"
                    remoteTitle={
                      form.site_name || form.site_title || "默认 OG 图"
                    }
                    dataTestIdPrefix="site-default-og"
                  />
                </div>
              </Field>
              <Field label="Twitter / X Handle" hint="例如 @initcool。">
                <Input
                  value={form.seo_default_twitter_handle ?? ""}
                  onChange={(event) =>
                    updateField(
                      "seo_default_twitter_handle",
                      event.target.value,
                    )
                  }
                />
              </Field>
              <div className="lg:col-span-2">
                <label className="flex items-start gap-3 rounded-2xl border border-border/70 bg-background/60 p-4">
                  <input
                    type="checkbox"
                    checked={form.seo_wechat_share_qr_enabled}
                    onChange={(event) =>
                      updateField(
                        "seo_wechat_share_qr_enabled",
                        event.target.checked,
                      )
                    }
                    className="mt-1 h-4 w-4 rounded border-input text-primary focus:ring-ring"
                  />
                  <div>
                    <div className="font-medium">开启文章页微信扫码分享</div>
                    <p className="mt-1 text-sm leading-6 text-muted-foreground">
                      开启后，文章摘要卡片会出现本地生成的微信二维码弹层，方便移动端扫码打开规范链接。
                    </p>
                  </div>
                </label>
              </div>
              <div className="grid gap-4 lg:col-span-2 md:grid-cols-[220px_minmax(0,1fr)]">
                <Field label="通知渠道" hint="可选 Webhook 或 ntfy。">
                  <Select
                    value={form.notification_channel_type}
                    onChange={(event) =>
                      updateField(
                        "notification_channel_type",
                        event.target.value,
                      )
                    }
                  >
                    {NOTIFICATION_CHANNEL_OPTIONS.map((item) => (
                      <option key={item.value} value={item.value}>
                        {item.label}
                      </option>
                    ))}
                  </Select>
                </Field>
                <Field
                  label="通知目标"
                  hint={
                    form.notification_channel_type === "ntfy"
                      ? "支持 topic 名称或完整 ntfy URL。"
                      : "评论和友链申请会向这个地址推送 JSON。"
                  }
                >
                  <Input
                    value={form.notification_webhook_url ?? ""}
                    onChange={(event) =>
                      updateField(
                        "notification_webhook_url",
                        event.target.value,
                      )
                    }
                    placeholder={
                      form.notification_channel_type === "ntfy"
                        ? "blog-admin 或 https://ntfy.example.com/blog-admin"
                        : "https://example.com/hooks/termi"
                    }
                  />
                </Field>
              </div>
              <div className="lg:col-span-2 grid gap-4 md:grid-cols-2">
                <label className="flex items-start gap-3 rounded-2xl border border-border/70 bg-background/60 p-4">
                  <input
                    type="checkbox"
                    checked={form.notification_comment_enabled}
                    onChange={(event) =>
                      updateField(
                        "notification_comment_enabled",
                        event.target.checked,
                      )
                    }
                    className="mt-1 h-4 w-4 rounded border-input text-primary focus:ring-ring"
                  />
                  <div>
                    <div className="font-medium">新评论通知</div>
                    <p className="mt-1 text-sm leading-6 text-muted-foreground">
                      有新评论创建时，按上面的渠道配置推送待审核提醒。
                    </p>
                  </div>
                </label>
                <label className="flex items-start gap-3 rounded-2xl border border-border/70 bg-background/60 p-4">
                  <input
                    type="checkbox"
                    checked={form.notification_friend_link_enabled}
                    onChange={(event) =>
                      updateField(
                        "notification_friend_link_enabled",
                        event.target.checked,
                      )
                    }
                    className="mt-1 h-4 w-4 rounded border-input text-primary focus:ring-ring"
                  />
                  <div>
                    <div className="font-medium">友链申请通知</div>
                    <p className="mt-1 text-sm leading-6 text-muted-foreground">
                      有新的友链申请时，同样走上面的通知渠道。
                    </p>
                  </div>
                </label>
              </div>
              <div className="lg:col-span-2">
                <Field
                  label="搜索同义词"
                  hint="每行一组，逗号分隔。例如：ai, llm, gpt 或 rust, cargo, crates。"
                >
                  <Textarea
                    value={form.search_synonyms.join("\n")}
                    onChange={(event) =>
                      updateField(
                        "search_synonyms",
                        event.target.value
                          .split("\n")
                          .map((item) => item.trim())
                          .filter(Boolean),
                      )
                    }
                  />
                </Field>
              </div>
            </CardContent>
          </Card>

          <Card>
            <CardHeader>
              <CardTitle>互动功能</CardTitle>
              <CardDescription>
                控制文章正文里的段落评论入口是否在前台显示。
              </CardDescription>
            </CardHeader>
            <CardContent className="space-y-5">
              <label className="flex items-start gap-3 rounded-2xl border border-border/70 bg-background/60 p-4">
                <input
                  type="checkbox"
                  checked={form.paragraph_comments_enabled}
                  onChange={(event) =>
                    updateField(
                      "paragraph_comments_enabled",
                      event.target.checked,
                    )
                  }
                  className="mt-1 h-4 w-4 rounded border-input text-primary focus:ring-ring"
                />
                <div>
                  <div className="font-medium">开启前台段落评论</div>
                  <p className="mt-1 text-sm leading-6 text-muted-foreground">
                    开启后，文章段落旁会在悬停时显示评论徽标；关闭后整套段落评论入口会从前台隐藏。
                  </p>
                </div>
              </label>

              <div className="rounded-2xl border border-border/70 bg-background/60 p-4">
                <Field
                  label="评论区验证方式"
                  hint="文章评论和段落评论都走这里；若选择 Turnstile 但 key / secret 不完整，会自动回退到普通验证码。"
                >
                  <Select
                    value={form.comment_verification_mode}
                    onChange={(event) =>
                      updateField(
                        "comment_verification_mode",
                        normalizeHumanVerificationMode(
                          event.target.value,
                          "captcha",
                        ),
                      )
                    }
                  >
                    {HUMAN_VERIFICATION_MODE_OPTIONS.map((option) => (
                      <option key={option.value} value={option.value}>
                        {option.label}
                      </option>
                    ))}
                  </Select>
                </Field>
              </div>
            </CardContent>
          </Card>

          <Card>
            <CardHeader>
              <CardTitle>AI 模块</CardTitle>
              <CardDescription>
                把文本问答和封面图生成拆成两套配置，检索参数仍在这里统一管理。
              </CardDescription>
            </CardHeader>
            <CardContent className="space-y-5">
              <label className="flex items-start gap-3 rounded-2xl border border-border/70 bg-background/60 p-4">
                <input
                  type="checkbox"
                  checked={form.ai_enabled}
                  onChange={(event) =>
                    updateField("ai_enabled", event.target.checked)
                  }
                  className="mt-1 h-4 w-4 rounded border-input text-primary focus:ring-ring"
                />
                <div>
                  <div className="font-medium">开启前台 AI 问答</div>
                  <p className="mt-1 text-sm leading-6 text-muted-foreground">
                    关闭后，前台 AI
                    问答入口只会展示为不可用状态，用户不能直接发起提问。
                  </p>
                </div>
              </label>

              <div className="rounded-[1.75rem] border border-border/70 bg-background/55 p-4">
                <div className="flex flex-wrap items-center justify-between gap-3">
                  <div>
                    <p className="text-sm font-medium">文本问答 Provider</p>
                    <p className="mt-1 text-sm leading-6 text-muted-foreground">
                      这里用于站内问答、文章元数据生成和文案润色。
                    </p>
                  </div>
                  <Button
                    type="button"
                    variant="outline"
                    onClick={addAiProvider}
                  >
                    <Plus className="h-4 w-4" />
                    添加提供商
                  </Button>
                </div>

                <div className="mt-4 grid gap-4 xl:grid-cols-[280px_minmax(0,1fr)]">
                  <div className="space-y-3">
                    {form.ai_providers.length ? (
                      form.ai_providers.map((provider, index) => {
                        const active =
                          provider.id === form.ai_active_provider_id;
                        const selected = index === selectedProviderIndex;

                        return (
                          <button
                            key={provider.id}
                            type="button"
                            onClick={() => setSelectedProviderIndex(index)}
                            className={
                              selected
                                ? "w-full rounded-[1.35rem] border border-primary/30 bg-primary/10 px-4 py-4 text-left shadow-[0_12px_28px_rgba(37,99,235,0.12)]"
                                : "w-full rounded-[1.35rem] border border-border/70 bg-background/70 px-4 py-4 text-left transition hover:border-border hover:bg-accent/35"
                            }
                          >
                            <div className="flex items-start justify-between gap-3">
                              <div className="min-w-0 flex-1">
                                <p className="truncate font-medium">
                                  {provider.name?.trim() ||
                                    `提供商 ${index + 1}`}
                                </p>
                                <p className="mt-1 truncate text-sm text-muted-foreground">
                                  Provider：
                                  {provider.provider?.trim() || "未填写"}
                                </p>
                              </div>
                              {active ? (
                                <Badge variant="secondary" className="shrink-0">
                                  当前启用
                                </Badge>
                              ) : null}
                            </div>
                            <p className="mt-3 truncate font-mono text-[11px] text-muted-foreground">
                              {provider.chat_model?.trim() || "未填写模型"}
                            </p>
                          </button>
                        );
                      })
                    ) : (
                      <div className="rounded-[1.35rem] border border-dashed border-border/70 bg-background/60 px-4 py-6 text-sm leading-6 text-muted-foreground">
                        还没有配置任何模型提供商，先添加一套即可开始切换使用。
                      </div>
                    )}
                  </div>

                  <div className="rounded-[1.5rem] border border-border/70 bg-background/65 p-5">
                    {form.ai_providers.length ? (
                      <div className="space-y-4">
                        <div className="flex flex-wrap items-start justify-between gap-3">
                          <div>
                            <p className="text-xs uppercase tracking-[0.2em] text-muted-foreground">
                              当前编辑
                            </p>
                            <p className="mt-2 text-lg font-semibold">
                              {selectedProvider.name?.trim() ||
                                `提供商 ${selectedProviderIndex + 1}`}
                            </p>
                            <p className="mt-1 text-sm text-muted-foreground">
                              保存后，系统会使用“当前启用”的提供商处理文本 AI
                              请求。
                            </p>
                          </div>
                          <div className="flex flex-wrap gap-2">
                            <Button
                              type="button"
                              variant="outline"
                              onClick={() =>
                                applyCloudflarePreset(selectedProviderIndex)
                              }
                            >
                              <RefreshCcw className="h-4 w-4" />
                              套用 Cloudflare
                            </Button>
                            <Button
                              type="button"
                              variant="outline"
                              data-testid="site-settings-test-provider"
                              disabled={testingProvider}
                              onClick={async () => {
                                try {
                                  setTestingProvider(true);
                                  const result =
                                    await adminApi.testAiProvider(
                                      selectedProvider,
                                    );
                                  toast.success(
                                    `连通成功：${result.provider} / ${result.chat_model} / ${result.reply_preview}`,
                                  );
                                } catch (error) {
                                  toast.error(
                                    error instanceof ApiError
                                      ? error.message
                                      : "模型连通性测试失败。",
                                  );
                                } finally {
                                  setTestingProvider(false);
                                }
                              }}
                            >
                              <Bot className="h-4 w-4" />
                              {testingProvider ? "测试中..." : "测试连通性"}
                            </Button>
                            <Button
                              type="button"
                              variant={
                                selectedProvider.id ===
                                form.ai_active_provider_id
                                  ? "secondary"
                                  : "outline"
                              }
                              onClick={() =>
                                setActiveAiProvider(selectedProvider.id)
                              }
                            >
                              <Check className="h-4 w-4" />
                              {selectedProvider.id ===
                              form.ai_active_provider_id
                                ? "已启用"
                                : "设为启用"}
                            </Button>
                            <Button
                              type="button"
                              variant="outline"
                              onClick={() =>
                                removeAiProvider(selectedProviderIndex)
                              }
                            >
                              <Trash2 className="h-4 w-4" />
                              删除
                            </Button>
                          </div>
                        </div>

                        <Field
                          label="显示名称"
                          hint="例如 OpenAI 主通道、Gemini 备用线路。"
                        >
                          <Input
                            value={selectedProvider.name ?? ""}
                            onChange={(event) =>
                              updateAiProvider(
                                selectedProviderIndex,
                                "name",
                                event.target.value,
                              )
                            }
                          />
                        </Field>
                        <Field
                          label="Provider"
                          hint="选择文本模型提供方。Cloudflare 文本模型也支持。"
                        >
                          <Select
                            value={selectedProvider.provider ?? ""}
                            onChange={(event) =>
                              updateAiProvider(
                                selectedProviderIndex,
                                "provider",
                                event.target.value,
                              )
                            }
                          >
                            {AI_PROVIDER_OPTIONS.map((option) => (
                              <option key={option.value} value={option.value}>
                                {option.label}
                              </option>
                            ))}
                          </Select>
                        </Field>
                        <Field
                          label="API 地址"
                          hint={
                            selectedProviderIsCloudflare
                              ? "Cloudflare 可直接填写 Account ID，或填写 https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>。"
                              : undefined
                          }
                        >
                          <Input
                            value={selectedProvider.api_base ?? ""}
                            onChange={(event) =>
                              updateAiProvider(
                                selectedProviderIndex,
                                "api_base",
                                event.target.value,
                              )
                            }
                            placeholder={
                              selectedProviderIsCloudflare
                                ? "Cloudflare Account ID 或完整 accounts URL"
                                : undefined
                            }
                          />
                        </Field>
                        <Field
                          label="API 密钥"
                          hint={
                            selectedProviderIsCloudflare
                              ? "请填写 Cloudflare Workers AI API Token。该 Token 需要 Workers AI Read 和 Edit 权限。"
                              : undefined
                          }
                        >
                          <Input
                            value={selectedProvider.api_key ?? ""}
                            onChange={(event) =>
                              updateAiProvider(
                                selectedProviderIndex,
                                "api_key",
                                event.target.value,
                              )
                            }
                          />
                        </Field>
                        <Field
                          label="对话模型"
                          hint={
                            selectedProviderIsCloudflare
                              ? "例如 @cf/meta/llama-3.1-8b-instruct，用于问答与连通性测试。"
                              : undefined
                          }
                        >
                          <Input
                            value={selectedProvider.chat_model ?? ""}
                            onChange={(event) =>
                              updateAiProvider(
                                selectedProviderIndex,
                                "chat_model",
                                event.target.value,
                              )
                            }
                          />
                        </Field>
                        {selectedProviderIsCloudflare ? (
                          <div className="rounded-2xl border border-primary/15 bg-primary/5 px-4 py-3 text-sm leading-6 text-muted-foreground">
                            <p className="font-medium text-foreground">
                              文本问答 / Cloudflare 说明
                            </p>
                            <ul className="mt-2 list-disc space-y-1 pl-5">
                              <li>API 地址可直接填 Cloudflare Account ID。</li>
                              <li>
                                这里的模型只负责文本问答与后台文字类 AI 能力。
                              </li>
                            </ul>
                          </div>
                        ) : null}
                      </div>
                    ) : (
                      <div className="flex h-full min-h-[240px] items-center justify-center rounded-[1.35rem] border border-dashed border-border/70 bg-background/60 px-6 text-center text-sm leading-6 text-muted-foreground">
                        添加第一套 provider 后，就可以在这里编辑它的 API
                        地址、密钥和模型名。
                      </div>
                    )}
                  </div>
                </div>
              </div>
              <div className="rounded-2xl border border-border/70 bg-background/60 p-4 text-sm leading-6 text-muted-foreground">
                文本 AI 当前生效：
                {activeProvider
                  ? `${activeProvider.provider || activeProvider.name} / ${activeProvider.chat_model || "未填写模型"}`
                  : "未选择提供商"}
              </div>
              <div className="rounded-[1.75rem] border border-border/70 bg-background/55 p-5">
                <div className="flex flex-wrap items-start justify-between gap-3">
                  <div>
                    <p className="text-sm font-medium">图片生成（封面）</p>
                    <p className="mt-1 text-sm leading-6 text-muted-foreground">
                      后台“AI 生成封面”单独走这一套配置，不再复用文本问答设置。
                    </p>
                  </div>
                  <div className="flex flex-wrap gap-2">
                    <Button
                      type="button"
                      variant="outline"
                      onClick={applyCloudflareImagePreset}
                    >
                      <RefreshCcw className="h-4 w-4" />
                      套用 Cloudflare
                    </Button>
                    <Button
                      type="button"
                      variant="outline"
                      data-testid="site-settings-test-image-provider"
                      disabled={testingImageProvider}
                      onClick={async () => {
                        try {
                          setTestingImageProvider(true);
                          const result = await adminApi.testAiImageProvider({
                            provider: form.ai_image_provider ?? "",
                            api_base: form.ai_image_api_base,
                            api_key: form.ai_image_api_key,
                            image_model: form.ai_image_model,
                          });
                          toast.success(
                            `图片连通成功：${result.provider} / ${result.image_model} / ${result.result_preview}`,
                          );
                        } catch (error) {
                          toast.error(
                            error instanceof ApiError
                              ? error.message
                              : "图片模型连通性测试失败。",
                          );
                        } finally {
                          setTestingImageProvider(false);
                        }
                      }}
                    >
                      <Bot className="h-4 w-4" />
                      {testingImageProvider ? "测试中..." : "测试图片连通性"}
                    </Button>
                  </div>
                </div>

                <div className="mt-4 grid gap-4 lg:grid-cols-2">
                  <Field
                    label="图片 Provider"
                    hint="选择图片模型提供方。这里专门用于封面图生成。"
                  >
                    <Select
                      value={form.ai_image_provider ?? ""}
                      onChange={(event) =>
                        updateField("ai_image_provider", event.target.value)
                      }
                    >
                      {AI_PROVIDER_OPTIONS.map((option) => (
                        <option key={option.value} value={option.value}>
                          {option.label}
                        </option>
                      ))}
                    </Select>
                  </Field>
                  <Field
                    label="图片模型"
                    hint={
                      imageProviderIsCloudflare
                        ? "Cloudflare 建议使用 @cf/black-forest-labs/flux-2-klein-4b。"
                        : "这里填写图片模型名。"
                    }
                  >
                    <Input
                      value={form.ai_image_model ?? ""}
                      onChange={(event) =>
                        updateField("ai_image_model", event.target.value)
                      }
                      placeholder={
                        imageProviderIsCloudflare
                          ? "@cf/black-forest-labs/flux-2-klein-4b"
                          : undefined
                      }
                    />
                  </Field>
                  <div className="lg:col-span-2">
                    <Field
                      label="图片 API 地址"
                      hint={
                        imageProviderIsCloudflare
                          ? "Cloudflare 可直接填写 Account ID，或填写完整 accounts URL。"
                          : "填写图片服务的 API 地址。"
                      }
                    >
                      <Input
                        value={form.ai_image_api_base ?? ""}
                        onChange={(event) =>
                          updateField("ai_image_api_base", event.target.value)
                        }
                        placeholder={
                          imageProviderIsCloudflare
                            ? "Cloudflare Account ID 或完整 accounts URL"
                            : undefined
                        }
                      />
                    </Field>
                  </div>
                  <div className="lg:col-span-2">
                    <Field
                      label="图片 API 密钥"
                      hint={
                        imageProviderIsCloudflare
                          ? "请填写 Cloudflare Workers AI API Token。"
                          : "填写图片服务的 API Key。"
                      }
                    >
                      <Input
                        value={form.ai_image_api_key ?? ""}
                        onChange={(event) =>
                          updateField("ai_image_api_key", event.target.value)
                        }
                      />
                    </Field>
                  </div>
                </div>

                <div className="mt-4 rounded-2xl border border-border/70 bg-background/60 p-4 text-sm leading-6 text-muted-foreground">
                  图片 AI 当前配置：
                  {form.ai_image_provider?.trim()
                    ? `${form.ai_image_provider} / ${form.ai_image_model || "未填写模型"}`
                    : "未填写，封面图会回退到旧配置"}
                </div>

                {imageProviderIsCloudflare ? (
                  <div className="mt-4 rounded-2xl border border-primary/15 bg-primary/5 px-4 py-3 text-sm leading-6 text-muted-foreground">
                    <p className="font-medium text-foreground">
                      封面图 / Cloudflare 说明
                    </p>
                    <ul className="mt-2 list-disc space-y-1 pl-5">
                      <li>API 地址可直接填 Cloudflare Account ID。</li>
                      <li>这套配置只用于后台“AI 生成封面”。</li>
                      <li>文本问答和图片生成现在是两套独立设置。</li>
                    </ul>
                  </div>
                ) : null}
              </div>
              <Field
                label="向量模型"
                hint={`本地选项：${form.ai_local_embedding}`}
              >
                <Input
                  value={form.ai_embedding_model ?? ""}
                  onChange={(event) =>
                    updateField("ai_embedding_model", event.target.value)
                  }
                />
              </Field>
              <div className="grid gap-4 sm:grid-cols-2">
                <Field label="Top K">
                  <Input
                    type="number"
                    value={form.ai_top_k ?? ""}
                    onChange={(event) =>
                      updateField(
                        "ai_top_k",
                        event.target.value ? Number(event.target.value) : null,
                      )
                    }
                  />
                </Field>
                <Field label="分块大小">
                  <Input
                    type="number"
                    value={form.ai_chunk_size ?? ""}
                    onChange={(event) =>
                      updateField(
                        "ai_chunk_size",
                        event.target.value ? Number(event.target.value) : null,
                      )
                    }
                  />
                </Field>
              </div>
              <Field label="系统提示词">
                <Textarea
                  value={form.ai_system_prompt ?? ""}
                  onChange={(event) =>
                    updateField("ai_system_prompt", event.target.value)
                  }
                />
              </Field>
            </CardContent>
          </Card>

          <Card>
            <CardHeader>
              <div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
                <div>
                  <CardTitle>媒体对象存储</CardTitle>
                  <CardDescription>
                    AI 封面图和评测封面上传都会优先走这里的对象存储。支持
                    Cloudflare R2 / MinIO。
                  </CardDescription>
                </div>
                <Button
                  type="button"
                  variant="outline"
                  data-testid="site-settings-test-storage"
                  disabled={testingR2Storage}
                  onClick={async () => {
                    try {
                      setTestingR2Storage(true);
                      const result = await adminApi.testR2Storage();
                      toast.success(
                        `存储连通成功：${result.bucket} / ${result.public_base_url}`,
                      );
                    } catch (error) {
                      toast.error(
                        error instanceof ApiError
                          ? error.message
                          : "对象存储连通性测试失败。",
                      );
                    } finally {
                      setTestingR2Storage(false);
                    }
                  }}
                >
                  <Bot className="h-4 w-4" />
                  {testingR2Storage ? "测试中..." : "测试存储连通性"}
                </Button>
              </div>
            </CardHeader>
            <CardContent className="space-y-5">
              <div className="grid gap-4 lg:grid-cols-2">
                <Field label="存储 Provider" hint="选择媒体资源存储后端。">
                  <Select
                    value={form.media_storage_provider ?? "r2"}
                    onChange={(event) =>
                      updateField("media_storage_provider", event.target.value)
                    }
                  >
                    {MEDIA_STORAGE_PROVIDER_OPTIONS.map((option) => (
                      <option key={option.value} value={option.value}>
                        {option.label}
                      </option>
                    ))}
                  </Select>
                </Field>
                <Field
                  label={
                    mediaStorageProvider === "minio" ? "Endpoint" : "Account ID"
                  }
                  hint={
                    mediaStorageProvider === "minio"
                      ? "例如 http://10.0.0.2:9100 或你的 MinIO API 地址。"
                      : "Cloudflare 账户 ID，用来拼接 R2 S3 兼容 endpoint。"
                  }
                >
                  <Input
                    value={form.media_r2_account_id ?? ""}
                    onChange={(event) =>
                      updateField("media_r2_account_id", event.target.value)
                    }
                    placeholder={
                      mediaStorageProvider === "minio"
                        ? "http://10.0.0.2:9100"
                        : undefined
                    }
                  />
                </Field>
                <Field
                  label="Bucket"
                  hint={
                    mediaStorageProvider === "minio"
                      ? "存放封面图的 MinIO bucket 名称。"
                      : "存放封面图的 R2 bucket 名称。"
                  }
                >
                  <Input
                    value={form.media_r2_bucket ?? ""}
                    onChange={(event) =>
                      updateField("media_r2_bucket", event.target.value)
                    }
                  />
                </Field>
                <div className="lg:col-span-2">
                  <Field
                    label="Public Base URL"
                    hint={
                      mediaStorageProvider === "minio"
                        ? "例如 https://s3.init.cool/你的bucket 或 http://10.0.0.2:9100/你的bucket。系统会把对象 key 拼到这个地址后面。"
                        : "例如 https://image.init.cool 或你的 R2 公网域名。系统会把对象 key 拼到这个地址后面。"
                    }
                  >
                    <Input
                      value={form.media_r2_public_base_url ?? ""}
                      onChange={(event) =>
                        updateField(
                          "media_r2_public_base_url",
                          event.target.value,
                        )
                      }
                    />
                  </Field>
                </div>
                <Field
                  label="Access Key ID"
                  hint={
                    mediaStorageProvider === "minio"
                      ? "MinIO / S3 的 Access Key ID。"
                      : "R2 S3 API 的 Access Key ID。"
                  }
                >
                  <Input
                    value={form.media_r2_access_key_id ?? ""}
                    onChange={(event) =>
                      updateField("media_r2_access_key_id", event.target.value)
                    }
                  />
                </Field>
                <Field
                  label="Secret Access Key"
                  hint={
                    mediaStorageProvider === "minio"
                      ? "MinIO / S3 的 Secret Access Key。后端会用 Rust SDK 上传图片。"
                      : "R2 S3 API 的 Secret Access Key。后端会用 Rust SDK 上传图片。"
                  }
                >
                  <Input
                    value={form.media_r2_secret_access_key ?? ""}
                    onChange={(event) =>
                      updateField(
                        "media_r2_secret_access_key",
                        event.target.value,
                      )
                    }
                  />
                </Field>
              </div>

              <div className="rounded-2xl border border-border/70 bg-background/60 p-4 text-sm leading-6 text-muted-foreground">
                <p className="font-medium text-foreground">当前用途</p>
                <ul className="mt-2 list-disc space-y-1 pl-5">
                  <li>文章 AI 生成封面：上传到 `post-covers/`</li>
                  <li>评测封面上传：上传到 `review-covers/`</li>
                  <li>
                    {mediaStorageProvider === "minio"
                      ? "当前会按 MinIO / S3 兼容方式上传。"
                      : "当前会按 Cloudflare R2 方式上传。"}
                  </li>
                </ul>
              </div>
            </CardContent>
          </Card>

          <Card>
            <CardHeader>
              <CardTitle>索引状态</CardTitle>
              <CardDescription>当前 AI 知识库的只读状态信息。</CardDescription>
            </CardHeader>
            <CardContent className="space-y-4">
              <div className="rounded-2xl border border-border/70 bg-background/70 p-4">
                <p className="text-xs uppercase tracking-[0.2em] text-muted-foreground">
                  已索引分块
                </p>
                <p className="mt-3 text-3xl font-semibold">
                  {form.ai_chunks_count}
                </p>
              </div>
              <div className="rounded-2xl border border-border/70 bg-background/70 p-4">
                <p className="text-xs uppercase tracking-[0.2em] text-muted-foreground">
                  最近索引时间
                </p>
                <p className="mt-3 text-sm leading-6 text-muted-foreground">
                  {form.ai_last_indexed_at
                    ? formatDateTime(form.ai_last_indexed_at)
                    : "索引尚未建立。"}
                </p>
                {reindexJobId ? (
                  <p className="mt-3 text-xs leading-6 text-muted-foreground">
                    当前后台任务 #{reindexJobId}
                    {reindexJobStatus ? `，状态：${reindexJobStatus}` : ""}
                  </p>
                ) : null}
                {reindexJobResult ? (
                  <p className="mt-2 text-xs leading-6 text-muted-foreground">
                    {formatWorkerProgress({ result: reindexJobResult }) ??
                      "任务已经开始，正在等待下一次进度更新。"}
                  </p>
                ) : null}
                {reindexJobResult &&
                getWorkerProgressPercent({ result: reindexJobResult }) !==
                  null ? (
                  <div className="mt-3 h-2 overflow-hidden rounded-full bg-border/70">
                    <div
                      className="h-full rounded-full bg-primary transition-[width] duration-300"
                      style={{
                        width: `${getWorkerProgressPercent({ result: reindexJobResult })}%`,
                      }}
                    />
                  </div>
                ) : null}
              </div>
            </CardContent>
          </Card>
        </div>

        <div className="space-y-6 xl:sticky xl:top-24 xl:self-start">
          <Card className="overflow-hidden">
            <CardHeader className="border-b border-border/70 bg-background/45">
              <div className="flex items-start justify-between gap-3">
                <div>
                  <CardTitle>音乐侧栏</CardTitle>
                  <CardDescription>
                    可以直接控制前台是否显示音乐播放器，歌单配置会继续保留在后台。
                  </CardDescription>
                </div>
                <div className="flex flex-wrap items-center gap-2">
                  <Badge variant={form.music_enabled ? "default" : "outline"}>
                    {form.music_enabled ? "前台已开启" : "前台已关闭"}
                  </Badge>
                  <Badge variant="outline">
                    {form.music_playlist.length} 首
                  </Badge>
                </div>
              </div>
            </CardHeader>
            <CardContent className="space-y-5 pt-6">
              <label className="flex items-start gap-3 rounded-2xl border border-border/70 bg-background/60 p-4">
                <input
                  type="checkbox"
                  checked={form.music_enabled}
                  onChange={(event) =>
                    updateField("music_enabled", event.target.checked)
                  }
                  className="mt-1 h-4 w-4 rounded border-input text-primary focus:ring-ring"
                />
                <div>
                  <div className="font-medium">前台显示音乐播放器</div>
                  <p className="mt-1 text-sm leading-6 text-muted-foreground">
                    关闭后前台头部和移动菜单里的音乐模块会整体隐藏，但下面维护的歌单内容不会丢失。
                  </p>
                </div>
              </label>

              <div className="space-y-3">
                {form.music_playlist.map((track, index) => {
                  const active = index === selectedTrackIndex;

                  return (
                    <button
                      key={`${track.title}-${index}`}
                      type="button"
                      onClick={() => setSelectedTrackIndex(index)}
                      className={
                        active
                          ? "w-full rounded-[1.5rem] border border-primary/30 bg-primary/10 px-4 py-4 text-left shadow-[0_14px_32px_rgba(37,99,235,0.14)]"
                          : "w-full rounded-[1.5rem] border border-border/70 bg-background/60 px-4 py-4 text-left transition hover:border-border hover:bg-accent/35"
                      }
                    >
                      <div className="flex items-start justify-between gap-3">
                        <div className="min-w-0 flex-1">
                          <p className="truncate font-medium">
                            {track.title?.trim()
                              ? track.title
                              : `曲目 ${index + 1}`}
                          </p>
                          <p className="mt-1 truncate text-sm text-muted-foreground">
                            {track.artist?.trim() || "未填写歌手"}
                          </p>
                        </div>
                        {track.accent_color ? (
                          <span
                            className="mt-1 h-4 w-4 shrink-0 rounded-full border border-white/60"
                            style={{ backgroundColor: track.accent_color }}
                          />
                        ) : null}
                      </div>
                      <p className="mt-3 truncate font-mono text-[11px] text-muted-foreground">
                        {track.url || "未填写音频 URL"}
                      </p>
                    </button>
                  );
                })}

                <Button
                  type="button"
                  variant="outline"
                  onClick={addMusicTrack}
                  className="w-full"
                >
                  <Plus className="h-4 w-4" />
                  添加曲目
                </Button>
              </div>

              <div className="rounded-[1.8rem] border border-border/70 bg-background/55 p-5">
                <div className="mb-5 flex items-start justify-between gap-3">
                  <div>
                    <p className="text-xs uppercase tracking-[0.2em] text-muted-foreground">
                      当前编辑
                    </p>
                    <p className="mt-2 text-lg font-semibold">
                      {selectedTrack.title?.trim()
                        ? selectedTrack.title
                        : `曲目 ${selectedTrackIndex + 1}`}
                    </p>
                    <p className="mt-1 text-sm text-muted-foreground">
                      单独修改标题、资源、封面、主题色和说明。
                    </p>
                  </div>
                  <Button
                    type="button"
                    variant="outline"
                    onClick={() => removeMusicTrack(selectedTrackIndex)}
                    disabled={form.music_playlist.length === 1}
                  >
                    <Trash2 className="h-4 w-4" />
                    删除
                  </Button>
                </div>

                {selectedTrack.cover_image_url ? (
                  <div className="mb-5 overflow-hidden rounded-3xl border border-border/70 bg-black/5">
                    <img
                      src={selectedTrack.cover_image_url}
                      alt={
                        selectedTrack.title || `曲目 ${selectedTrackIndex + 1}`
                      }
                      className="h-44 w-full object-cover"
                    />
                  </div>
                ) : null}

                <div className="space-y-4">
                  <Field label="标题">
                    <Input
                      value={selectedTrack.title ?? ""}
                      onChange={(event) =>
                        updateMusicTrack(
                          selectedTrackIndex,
                          "title",
                          event.target.value,
                        )
                      }
                    />
                  </Field>
                  <div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-1 2xl:grid-cols-2">
                    <Field label="歌手">
                      <Input
                        value={selectedTrack.artist ?? ""}
                        onChange={(event) =>
                          updateMusicTrack(
                            selectedTrackIndex,
                            "artist",
                            event.target.value,
                          )
                        }
                      />
                    </Field>
                    <Field label="专辑">
                      <Input
                        value={selectedTrack.album ?? ""}
                        onChange={(event) =>
                          updateMusicTrack(
                            selectedTrackIndex,
                            "album",
                            event.target.value,
                          )
                        }
                      />
                    </Field>
                  </div>
                  <Field label="音频 URL">
                    <Input
                      value={selectedTrack.url ?? ""}
                      onChange={(event) =>
                        updateMusicTrack(
                          selectedTrackIndex,
                          "url",
                          event.target.value,
                        )
                      }
                    />
                  </Field>
                  <Field
                    label="封面图 URL"
                    hint="可直接填写外链，也可以上传 / 抓取 / 从媒体库选择后自动回填。"
                  >
                    <div className="space-y-3">
                      <Input
                        value={selectedTrack.cover_image_url ?? ""}
                        onChange={(event) =>
                          updateMusicTrack(
                            selectedTrackIndex,
                            "cover_image_url",
                            event.target.value,
                          )
                        }
                      />
                      <MediaUrlControls
                        value={selectedTrack.cover_image_url ?? ""}
                        onChange={(coverImageUrl) =>
                          updateMusicTrack(
                            selectedTrackIndex,
                            "cover_image_url",
                            coverImageUrl,
                          )
                        }
                        prefix="music-covers/"
                        contextLabel="音乐封面上传"
                        remoteTitle={
                          selectedTrack.title ||
                          `曲目 ${selectedTrackIndex + 1} 封面`
                        }
                        dataTestIdPrefix="site-music-cover"
                      />
                    </div>
                  </Field>
                  <Field
                    label="主题色"
                    hint="例如 `#2f6b5f`，前台播放器会读取这个颜色。"
                  >
                    <div className="flex items-center gap-3">
                      <Input
                        value={selectedTrack.accent_color ?? ""}
                        onChange={(event) =>
                          updateMusicTrack(
                            selectedTrackIndex,
                            "accent_color",
                            event.target.value,
                          )
                        }
                        placeholder="#2f6b5f"
                      />
                      <span
                        className="h-11 w-11 shrink-0 rounded-2xl border border-border/70 bg-background"
                        style={{
                          backgroundColor:
                            selectedTrack.accent_color?.trim() || "transparent",
                        }}
                      />
                    </div>
                  </Field>
                  <Field label="备注">
                    <Textarea
                      value={selectedTrack.description ?? ""}
                      onChange={(event) =>
                        updateMusicTrack(
                          selectedTrackIndex,
                          "description",
                          event.target.value,
                        )
                      }
                    />
                  </Field>
                </div>
              </div>
            </CardContent>
          </Card>
        </div>
      </div>
    </div>
  );
}
