"use client";

import { useQuery } from "@tanstack/react-query";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import {
  Activity,
  AlertTriangle,
  Archive,
  Check,
  MapPinned,
  Plus,
  RefreshCw,
  TrendingUp,
} from "lucide-react";
import { Fragment, useEffect, useMemo, useState } from "react";
import {
  Bar,
  BarChart,
  CartesianGrid,
  Line,
  LineChart,
  PolarAngleAxis,
  PolarGrid,
  PolarRadiusAxis,
  Radar,
  RadarChart,
  ResponsiveContainer,
  Tooltip,
  XAxis,
  YAxis,
} from "recharts";

import {
  archiveWatchlistItem,
  createDataSource,
  createIngestionRun,
  createRawArtifact,
  createAreaScoreModelRun,
  createWatchlistEvent,
  createWatchlistItem,
  fetchDataSources,
  fetchAreaScores,
  fetchAreaComparison,
  fetchAreaDetail,
  fetchAreaScoreRunDiff,
  fetchIngestionRuns,
  fetchMarketOverview,
  fetchModelRuns,
  fetchNeighborhoodComparison,
  fetchNeighborhoodDetail,
  fetchNeighborhoodScores,
  fetchRawArtifacts,
  fetchWatchlistEvents,
  fetchWatchlistItems,
  finishIngestionRun,
  updateWatchlistItem,
} from "@/lib/api";
import type {
  AreaScore,
  AreaComparison,
  AreaDetail,
  AreaScoreRunDiff,
  ComparisonMetric,
  CreateAreaScoreModelRun,
  CreateDataSource,
  CreateIngestionRun,
  CreateRawArtifact,
  CreateWatchlistEvent,
  CreateWatchlistItem,
  DataSource,
  IngestionRun,
  ModelRun,
  NeighborhoodComparison,
  NeighborhoodComparisonItem,
  NeighborhoodDetail,
  NeighborhoodScore,
  RawArtifact,
  SimilarNeighborhood,
  UpdateWatchlistItem,
  WatchlistEvent,
  WatchlistItem,
} from "@/lib/api";
import { formatNumber, formatPrice } from "@/lib/utils";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";

const MONTH = "2026-05";

export function MarketDashboard() {
  const queryClient = useQueryClient();
  const [selectedAreaId, setSelectedAreaId] = useState("");
  const [detailAreaId, setDetailAreaId] = useState("qiantan");
  const [detailNeighborhoodId, setDetailNeighborhoodId] = useState("");
  const [comparisonAreaIds, setComparisonAreaIds] = useState<string[]>([]);
  const [comparisonNeighborhoodIds, setComparisonNeighborhoodIds] = useState<string[]>([]);
  const [watchlistStatus, setWatchlistStatus] = useState("active");
  const [watchlistAreaId, setWatchlistAreaId] = useState("");
  const [watchlistLabel, setWatchlistLabel] = useState("");
  const [expandedWatchlistItemId, setExpandedWatchlistItemId] = useState<number | null>(null);
  const [baseModelRunId, setBaseModelRunId] = useState<number | null>(null);
  const [candidateModelRunId, setCandidateModelRunId] = useState<number | null>(null);
  const overview = useQuery({
    queryKey: ["market-overview", MONTH],
    queryFn: () => fetchMarketOverview(MONTH),
  });
  const scores = useQuery({
    queryKey: ["area-scores", MONTH],
    queryFn: () => fetchAreaScores(MONTH),
  });
  const areaDetail = useQuery({
    queryKey: ["area-detail", detailAreaId, MONTH],
    queryFn: () => fetchAreaDetail(detailAreaId, MONTH),
    enabled: Boolean(detailAreaId),
  });
  const neighborhoodScores = useQuery({
    queryKey: ["neighborhood-scores", MONTH],
    queryFn: () => fetchNeighborhoodScores(MONTH),
  });
  const neighborhoodDetail = useQuery({
    queryKey: ["neighborhood-detail", detailNeighborhoodId, MONTH],
    queryFn: () => fetchNeighborhoodDetail(detailNeighborhoodId, MONTH),
    enabled: Boolean(detailNeighborhoodId),
  });
  const areaComparison = useQuery({
    queryKey: ["area-comparison", comparisonAreaIds, MONTH],
    queryFn: () => fetchAreaComparison(comparisonAreaIds, MONTH),
    enabled: comparisonAreaIds.length >= 2,
  });
  const neighborhoodComparison = useQuery({
    queryKey: ["neighborhood-comparison", comparisonNeighborhoodIds, MONTH],
    queryFn: () => fetchNeighborhoodComparison(comparisonNeighborhoodIds, MONTH),
    enabled: comparisonNeighborhoodIds.length >= 2,
  });
  const watchlist = useQuery({
    queryKey: ["watchlist", watchlistStatus, watchlistAreaId, watchlistLabel],
    queryFn: () =>
      fetchWatchlistItems({
        status: watchlistStatus,
        area_id: watchlistAreaId,
        label: watchlistLabel,
      }),
  });
  const watchlistEvents = useQuery({
    queryKey: ["watchlist-events", expandedWatchlistItemId],
    queryFn: () => fetchWatchlistEvents(expandedWatchlistItemId ?? 0),
    enabled: expandedWatchlistItemId !== null,
  });
  const modelRuns = useQuery({
    queryKey: ["model-runs", "area_scores"],
    queryFn: () => fetchModelRuns({ model_name: "area_scores" }),
  });
  const areaScoreRunDiff = useQuery({
    queryKey: ["area-score-run-diff", baseModelRunId, candidateModelRunId],
    queryFn: () => fetchAreaScoreRunDiff(baseModelRunId ?? 0, candidateModelRunId ?? 0),
    enabled:
      baseModelRunId !== null &&
      candidateModelRunId !== null &&
      baseModelRunId !== candidateModelRunId,
  });
  const dataSources = useQuery({
    queryKey: ["data-sources"],
    queryFn: fetchDataSources,
  });
  const ingestionRuns = useQuery({
    queryKey: ["ingestion-runs"],
    queryFn: fetchIngestionRuns,
  });
  const rawArtifacts = useQuery({
    queryKey: ["raw-artifacts"],
    queryFn: fetchRawArtifacts,
  });
  const createMutation = useMutation({
    mutationFn: createWatchlistItem,
    onSuccess: async () => {
      await Promise.all([
        queryClient.invalidateQueries({ queryKey: ["watchlist"] }),
        queryClient.invalidateQueries({ queryKey: ["neighborhood-detail"] }),
      ]);
    },
  });
  const archiveMutation = useMutation({
    mutationFn: archiveWatchlistItem,
    onSuccess: async () => {
      await queryClient.invalidateQueries({ queryKey: ["watchlist"] });
    },
  });
  const updateWatchlistMutation = useMutation({
    mutationFn: ({ id, payload }: { id: number; payload: UpdateWatchlistItem }) =>
      updateWatchlistItem(id, payload),
    onSuccess: async () => {
      await Promise.all([
        queryClient.invalidateQueries({ queryKey: ["watchlist"] }),
        queryClient.invalidateQueries({ queryKey: ["watchlist-events"] }),
        queryClient.invalidateQueries({ queryKey: ["neighborhood-detail"] }),
      ]);
    },
  });
  const createWatchlistEventMutation = useMutation({
    mutationFn: ({ id, payload }: { id: number; payload: CreateWatchlistEvent }) =>
      createWatchlistEvent(id, payload),
    onSuccess: async () => {
      await queryClient.invalidateQueries({ queryKey: ["watchlist-events"] });
    },
  });
  const createAreaScoreModelRunMutation = useMutation({
    mutationFn: createAreaScoreModelRun,
    onSuccess: async (response) => {
      setCandidateModelRunId(response.model_run.model_run_id);
      if (baseModelRunId === null && modelRuns.data?.[0]) {
        setBaseModelRunId(modelRuns.data[0].model_run_id);
      }
      await Promise.all([
        queryClient.invalidateQueries({ queryKey: ["model-runs"] }),
        queryClient.invalidateQueries({ queryKey: ["area-scores"] }),
        queryClient.invalidateQueries({ queryKey: ["market-overview"] }),
        queryClient.invalidateQueries({ queryKey: ["area-detail"] }),
        queryClient.invalidateQueries({ queryKey: ["area-score-run-diff"] }),
      ]);
    },
  });
  const createDataSourceMutation = useMutation({
    mutationFn: createDataSource,
    onSuccess: async () => {
      await queryClient.invalidateQueries({ queryKey: ["data-sources"] });
    },
  });
  const createRunMutation = useMutation({
    mutationFn: createIngestionRun,
    onSuccess: async () => {
      await queryClient.invalidateQueries({ queryKey: ["ingestion-runs"] });
    },
  });
  const createRawArtifactMutation = useMutation({
    mutationFn: createRawArtifact,
    onSuccess: async () => {
      await Promise.all([
        queryClient.invalidateQueries({ queryKey: ["raw-artifacts"] }),
        queryClient.invalidateQueries({ queryKey: ["ingestion-runs"] }),
      ]);
    },
  });
  const finishRunMutation = useMutation({
    mutationFn: ({ id, rowCount }: { id: number; rowCount?: number }) =>
      finishIngestionRun(id, { status: "succeeded", row_count: rowCount }),
    onSuccess: async () => {
      await queryClient.invalidateQueries({ queryKey: ["ingestion-runs"] });
    },
  });

  useEffect(() => {
    if (!detailNeighborhoodId && neighborhoodScores.data?.[0]) {
      setDetailNeighborhoodId(neighborhoodScores.data[0].neighborhood_id);
    }
  }, [detailNeighborhoodId, neighborhoodScores.data]);

  useEffect(() => {
    if (comparisonAreaIds.length === 0 && scores.data && scores.data.length >= 2) {
      setComparisonAreaIds(scores.data.slice(0, 3).map((score) => score.area_id));
    }
  }, [comparisonAreaIds.length, scores.data]);

  useEffect(() => {
    if (
      comparisonNeighborhoodIds.length === 0 &&
      neighborhoodScores.data &&
      neighborhoodScores.data.length >= 2
    ) {
      setComparisonNeighborhoodIds(
        neighborhoodScores.data.slice(0, 3).map((score) => score.neighborhood_id),
      );
    }
  }, [comparisonNeighborhoodIds.length, neighborhoodScores.data]);

  useEffect(() => {
    if (modelRuns.data && modelRuns.data.length >= 2) {
      if (candidateModelRunId === null) {
        setCandidateModelRunId(modelRuns.data[0].model_run_id);
      }
      if (baseModelRunId === null) {
        setBaseModelRunId(modelRuns.data[1].model_run_id);
      }
    }
  }, [baseModelRunId, candidateModelRunId, modelRuns.data]);

  const isLoading =
    overview.isLoading ||
    scores.isLoading ||
    areaDetail.isLoading ||
    neighborhoodDetail.isLoading ||
    areaComparison.isLoading ||
    neighborhoodComparison.isLoading ||
    neighborhoodScores.isLoading ||
    watchlist.isLoading ||
    watchlistEvents.isLoading ||
    modelRuns.isLoading ||
    areaScoreRunDiff.isLoading ||
    dataSources.isLoading ||
    ingestionRuns.isLoading;
  const importLoading = ingestionRuns.isLoading || rawArtifacts.isLoading;
  const hasError =
    overview.isError ||
    scores.isError ||
    areaDetail.isError ||
    neighborhoodDetail.isError ||
    areaComparison.isError ||
    neighborhoodComparison.isError ||
    neighborhoodScores.isError ||
    watchlist.isError ||
    watchlistEvents.isError ||
    modelRuns.isError ||
    areaScoreRunDiff.isError ||
    dataSources.isError ||
    ingestionRuns.isError ||
    rawArtifacts.isError;

  return (
    <main className="min-h-screen px-4 py-5 sm:px-6 lg:px-8">
      <div className="mx-auto flex max-w-7xl flex-col gap-5">
        <header className="flex flex-col gap-3 border-b border-[var(--border)] pb-4 lg:flex-row lg:items-end lg:justify-between">
          <div>
            <p className="text-sm font-medium text-[var(--muted-foreground)]">上海房市投资研究系统</p>
            <h1 className="mt-1 text-2xl font-semibold tracking-normal text-slate-950">
              市场总览
            </h1>
          </div>
          <div className="flex items-center gap-2">
            <Badge variant="muted">{MONTH}</Badge>
            <Button
              variant="outline"
              onClick={() => {
                void overview.refetch();
                void scores.refetch();
                void areaDetail.refetch();
                void neighborhoodDetail.refetch();
                void areaComparison.refetch();
                void neighborhoodComparison.refetch();
                void neighborhoodScores.refetch();
                void watchlist.refetch();
                void dataSources.refetch();
                void ingestionRuns.refetch();
                void rawArtifacts.refetch();
                void modelRuns.refetch();
                void areaScoreRunDiff.refetch();
              }}
              disabled={isLoading}
              aria-label="刷新"
              title="刷新"
            >
              <RefreshCw className="h-4 w-4" />
            </Button>
          </div>
        </header>

        {hasError ? <ErrorState /> : null}

        <section className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
          <MetricCard
            title="样本板块"
            value={overview.data?.area_count ?? 0}
            suffix="个"
            icon={<MapPinned className="h-4 w-4" />}
            loading={isLoading}
          />
          <MetricCard
            title="平均综合分"
            value={overview.data?.average_investment_score ?? 0}
            icon={<Activity className="h-4 w-4" />}
            loading={isLoading}
          />
          <MetricCard
            title="平均租金收益率"
            value={overview.data?.average_rent_yield_pct ?? 0}
            suffix="%"
            icon={<TrendingUp className="h-4 w-4" />}
            loading={isLoading}
          />
          <MetricCard
            title="供应压力最高"
            textValue={overview.data?.highest_supply_risk_area?.name ?? "-"}
            icon={<AlertTriangle className="h-4 w-4" />}
            loading={isLoading}
            tone="warning"
          />
        </section>

        <section className="grid grid-cols-1 gap-4 xl:grid-cols-[minmax(0,1fr)_420px]">
          <Card>
            <CardHeader className="flex flex-row items-center justify-between gap-3">
              <CardTitle>板块投资观察评分</CardTitle>
              {overview.data?.top_area ? (
                <Badge variant="success">首位：{overview.data.top_area.name}</Badge>
              ) : null}
            </CardHeader>
            <CardContent className="h-[340px]">
              <ScoreChart scores={scores.data ?? []} loading={isLoading} />
            </CardContent>
          </Card>

          <Card>
            <CardHeader>
              <CardTitle>核心结论</CardTitle>
            </CardHeader>
            <CardContent className="space-y-4">
              <InsightRow
                label="最高评分"
                value={overview.data?.top_area?.name ?? "-"}
                detail={
                  overview.data?.top_area
                    ? `${formatNumber(overview.data.top_area.investment_score)} 分`
                    : "-"
                }
              />
              <InsightRow
                label="最低评分"
                value={overview.data?.weakest_area?.name ?? "-"}
                detail={
                  overview.data?.weakest_area
                    ? `${formatNumber(overview.data.weakest_area.investment_score)} 分`
                    : "-"
                }
              />
              <InsightRow
                label="供应压力"
                value={overview.data?.highest_supply_risk_area?.name ?? "-"}
                detail={overview.data?.highest_supply_risk_area?.recommendation ?? "-"}
              />
            </CardContent>
          </Card>
        </section>

        <Card>
          <CardHeader>
            <CardTitle>板块明细</CardTitle>
          </CardHeader>
          <CardContent className="overflow-x-auto p-0">
            <AreaScoreTable
              scores={scores.data ?? []}
              loading={isLoading}
              selectedAreaId={detailAreaId}
              onSelectArea={setDetailAreaId}
            />
          </CardContent>
        </Card>

        <AreaDetailPanel
          detail={areaDetail.data ?? null}
          loading={areaDetail.isLoading}
          onCreateWatchlist={(payload) => createMutation.mutate(payload)}
          creating={createMutation.isPending}
          onSelectNeighborhood={setDetailNeighborhoodId}
        />

        <NeighborhoodRankingPanel
          scores={scores.data ?? []}
          neighborhoodScores={neighborhoodScores.data ?? []}
          selectedAreaId={selectedAreaId}
          selectedNeighborhoodId={detailNeighborhoodId}
          loading={isLoading}
          creating={createMutation.isPending}
          onSelectArea={setSelectedAreaId}
          onSelectNeighborhood={setDetailNeighborhoodId}
          onCreate={(payload) => createMutation.mutate(payload)}
        />

        <NeighborhoodDetailPanel
          detail={neighborhoodDetail.data ?? null}
          loading={neighborhoodDetail.isLoading}
          creating={createMutation.isPending}
          onCreateWatchlist={(payload) => createMutation.mutate(payload)}
          onSelectNeighborhood={setDetailNeighborhoodId}
        />

        <ComparisonWorkspace
          areaScores={scores.data ?? []}
          neighborhoodScores={neighborhoodScores.data ?? []}
          areaComparison={areaComparison.data ?? null}
          neighborhoodComparison={neighborhoodComparison.data ?? null}
          selectedAreaIds={comparisonAreaIds}
          selectedNeighborhoodIds={comparisonNeighborhoodIds}
          loading={areaComparison.isLoading || neighborhoodComparison.isLoading}
          onAreaIdsChange={setComparisonAreaIds}
          onNeighborhoodIdsChange={setComparisonNeighborhoodIds}
        />

        <WatchlistPanel
          scores={scores.data ?? []}
          neighborhoodScores={neighborhoodScores.data ?? []}
          items={watchlist.data ?? []}
          events={watchlistEvents.data ?? []}
          loading={isLoading}
          creating={createMutation.isPending}
          archivingId={archiveMutation.variables ?? null}
          updatingId={updateWatchlistMutation.variables?.id ?? null}
          creatingEvent={createWatchlistEventMutation.isPending}
          statusFilter={watchlistStatus}
          areaFilter={watchlistAreaId}
          labelFilter={watchlistLabel}
          expandedItemId={expandedWatchlistItemId}
          onStatusFilterChange={setWatchlistStatus}
          onAreaFilterChange={setWatchlistAreaId}
          onLabelFilterChange={setWatchlistLabel}
          onToggleEvents={(id) =>
            setExpandedWatchlistItemId((current) => (current === id ? null : id))
          }
          onCreate={(payload) => createMutation.mutate(payload)}
          onUpdate={(id, payload) => updateWatchlistMutation.mutate({ id, payload })}
          onCreateEvent={(id, payload) => createWatchlistEventMutation.mutate({ id, payload })}
          onArchive={(id) => archiveMutation.mutate(id)}
        />

        <ModelRunPanel
          runs={modelRuns.data ?? []}
          diff={areaScoreRunDiff.data ?? null}
          baseRunId={baseModelRunId}
          candidateRunId={candidateModelRunId}
          loading={modelRuns.isLoading || areaScoreRunDiff.isLoading}
          creating={createAreaScoreModelRunMutation.isPending}
          onBaseRunChange={setBaseModelRunId}
          onCandidateRunChange={setCandidateModelRunId}
          onCreateRun={(payload) => createAreaScoreModelRunMutation.mutate(payload)}
        />

        <ImportCenterPanel
          sources={dataSources.data ?? []}
          runs={ingestionRuns.data ?? []}
          artifacts={rawArtifacts.data ?? []}
          loading={isLoading}
          importLoading={importLoading}
          creatingSource={createDataSourceMutation.isPending}
          creatingRun={createRunMutation.isPending}
          creatingArtifact={createRawArtifactMutation.isPending}
          finishingRunId={finishRunMutation.variables?.id ?? null}
          onCreateSource={(payload) => createDataSourceMutation.mutate(payload)}
          onCreateRun={(payload) => createRunMutation.mutate(payload)}
          onCreateArtifact={(payload) => createRawArtifactMutation.mutate(payload)}
          onFinishRun={(id, rowCount) => finishRunMutation.mutate({ id, rowCount })}
        />
      </div>
    </main>
  );
}

function ImportCenterPanel({
  sources,
  runs,
  artifacts,
  loading,
  importLoading,
  creatingSource,
  creatingRun,
  creatingArtifact,
  finishingRunId,
  onCreateSource,
  onCreateRun,
  onCreateArtifact,
  onFinishRun,
}: {
  sources: DataSource[];
  runs: IngestionRun[];
  artifacts: RawArtifact[];
  loading: boolean;
  importLoading: boolean;
  creatingSource: boolean;
  creatingRun: boolean;
  creatingArtifact: boolean;
  finishingRunId: number | null;
  onCreateSource: (payload: CreateDataSource) => void;
  onCreateRun: (payload: CreateIngestionRun) => void;
  onCreateArtifact: (payload: CreateRawArtifact) => void;
  onFinishRun: (id: number, rowCount?: number) => void;
}) {
  const [sourceName, setSourceName] = useState("");
  const [sourceType, setSourceType] = useState("manual");
  const [sourceUrl, setSourceUrl] = useState("");
  const [sourceId, setSourceId] = useState("");
  const [rawUri, setRawUri] = useState("");
  const [rowCount, setRowCount] = useState("");
  const [artifactRunId, setArtifactRunId] = useState("");
  const [artifactSourceId, setArtifactSourceId] = useState("");
  const [artifactFilename, setArtifactFilename] = useState("");
  const [artifactRawUri, setArtifactRawUri] = useState("");
  const [artifactSha256, setArtifactSha256] = useState("");
  const [artifactSizeBytes, setArtifactSizeBytes] = useState("");

  function submitSource(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();
    if (!sourceName.trim()) {
      return;
    }
    onCreateSource({
      name: sourceName.trim(),
      source_type: sourceType,
      url: sourceUrl.trim() || undefined,
      cadence: "ad hoc",
      reliability: sourceType === "official" ? "high" : "medium",
      notes: "front-end registered source",
    });
    setSourceName("");
    setSourceUrl("");
  }

  function submitRun(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const parsedSourceId = Number(sourceId);
    if (!Number.isFinite(parsedSourceId) || parsedSourceId <= 0) {
      return;
    }
    onCreateRun({
      source_id: parsedSourceId,
      raw_uri: rawUri.trim() || undefined,
      status: "running",
    });
    setRawUri("");
  }

  function submitArtifact(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const parsedRunId = Number(artifactRunId);
    const parsedSourceId = Number(artifactSourceId);
    const parsedSizeBytes = Number(artifactSizeBytes);
    if (
      !artifactFilename.trim() ||
      !artifactRawUri.trim() ||
      artifactSha256.trim().length !== 64 ||
      !Number.isFinite(parsedSizeBytes) ||
      parsedSizeBytes < 0
    ) {
      return;
    }
    onCreateArtifact({
      run_id: Number.isFinite(parsedRunId) && parsedRunId > 0 ? parsedRunId : undefined,
      source_id:
        Number.isFinite(parsedSourceId) && parsedSourceId > 0 ? parsedSourceId : undefined,
      original_filename: artifactFilename.trim(),
      raw_uri: artifactRawUri.trim(),
      sha256: artifactSha256.trim(),
      size_bytes: parsedSizeBytes,
      uploaded_by: "dashboard",
    });
    setArtifactFilename("");
    setArtifactRawUri("");
    setArtifactSha256("");
    setArtifactSizeBytes("");
  }

  return (
    <Card>
      <CardHeader className="flex flex-col gap-3 xl:flex-row xl:items-start xl:justify-between">
        <div>
          <CardTitle>导入中心</CardTitle>
          <p className="mt-1 text-sm text-[var(--muted-foreground)]">
            数据源登记与导入批次审计
          </p>
        </div>
        <div className="grid w-full gap-3 xl:w-auto xl:grid-cols-2">
          <form className="flex flex-col gap-2 sm:flex-row" onSubmit={submitSource}>
            <input
              className="h-9 w-full rounded-md border border-[var(--border)] bg-white px-3 text-sm sm:w-40"
              placeholder="数据源名称"
              value={sourceName}
              onChange={(event) => setSourceName(event.target.value)}
            />
            <select
              className="h-9 rounded-md border border-[var(--border)] bg-white px-3 text-sm"
              value={sourceType}
              onChange={(event) => setSourceType(event.target.value)}
              aria-label="数据源类型"
            >
              <option value="manual">manual</option>
              <option value="official">official</option>
              <option value="market">market</option>
            </select>
            <input
              className="h-9 w-full rounded-md border border-[var(--border)] bg-white px-3 text-sm sm:w-52"
              placeholder="URL"
              value={sourceUrl}
              onChange={(event) => setSourceUrl(event.target.value)}
            />
            <Button disabled={!sourceName.trim() || creatingSource} aria-label="新增数据源" title="新增数据源">
              <Plus className="h-4 w-4" />
            </Button>
          </form>
          <form className="flex flex-col gap-2 sm:flex-row" onSubmit={submitRun}>
            <select
              className="h-9 min-w-40 rounded-md border border-[var(--border)] bg-white px-3 text-sm"
              value={sourceId}
              onChange={(event) => setSourceId(event.target.value)}
              aria-label="导入数据源"
            >
              <option value="">数据源</option>
              {sources.map((source) => (
                <option key={source.source_id} value={source.source_id}>
                  {source.name}
                </option>
              ))}
            </select>
            <input
              className="h-9 w-full rounded-md border border-[var(--border)] bg-white px-3 text-sm sm:w-56"
              placeholder="raw 路径"
              value={rawUri}
              onChange={(event) => setRawUri(event.target.value)}
            />
            <Button disabled={!sourceId || creatingRun} aria-label="创建导入批次" title="创建导入批次">
              <Plus className="h-4 w-4" />
            </Button>
          </form>
        </div>
      </CardHeader>
      <CardContent className="grid gap-4 xl:grid-cols-[420px_minmax(0,1fr)]">
        <DataSourceTable sources={sources} loading={loading} />
        <IngestionRunTable
          runs={runs}
          loading={importLoading}
          rowCount={rowCount}
          onRowCountChange={setRowCount}
          finishingRunId={finishingRunId}
          onFinishRun={(id) => {
            const parsedRowCount = Number(rowCount);
            onFinishRun(id, Number.isFinite(parsedRowCount) && parsedRowCount >= 0 ? parsedRowCount : undefined);
            setRowCount("");
          }}
        />
        <div className="xl:col-span-2">
          <form className="mb-3 grid gap-2 lg:grid-cols-[120px_120px_1fr_1fr_1.5fr_120px_44px]" onSubmit={submitArtifact}>
            <select
              className="h-9 rounded-md border border-[var(--border)] bg-white px-3 text-sm"
              value={artifactRunId}
              onChange={(event) => setArtifactRunId(event.target.value)}
              aria-label="原始文件批次"
            >
              <option value="">批次</option>
              {runs.map((run) => (
                <option key={run.run_id} value={run.run_id}>
                  #{run.run_id}
                </option>
              ))}
            </select>
            <select
              className="h-9 rounded-md border border-[var(--border)] bg-white px-3 text-sm"
              value={artifactSourceId}
              onChange={(event) => setArtifactSourceId(event.target.value)}
              aria-label="原始文件数据源"
            >
              <option value="">数据源</option>
              {sources.map((source) => (
                <option key={source.source_id} value={source.source_id}>
                  {source.name}
                </option>
              ))}
            </select>
            <input
              className="h-9 rounded-md border border-[var(--border)] bg-white px-3 text-sm"
              placeholder="文件名"
              value={artifactFilename}
              onChange={(event) => setArtifactFilename(event.target.value)}
            />
            <input
              className="h-9 rounded-md border border-[var(--border)] bg-white px-3 text-sm"
              placeholder="raw uri"
              value={artifactRawUri}
              onChange={(event) => setArtifactRawUri(event.target.value)}
            />
            <input
              className="h-9 rounded-md border border-[var(--border)] bg-white px-3 text-sm"
              placeholder="sha256"
              value={artifactSha256}
              onChange={(event) => setArtifactSha256(event.target.value)}
            />
            <input
              className="h-9 rounded-md border border-[var(--border)] bg-white px-3 text-sm"
              inputMode="numeric"
              placeholder="字节"
              value={artifactSizeBytes}
              onChange={(event) => setArtifactSizeBytes(event.target.value)}
            />
            <Button
              disabled={
                creatingArtifact ||
                !artifactFilename.trim() ||
                artifactSha256.trim().length !== 64
              }
              aria-label="登记原始文件"
              title="登记原始文件"
            >
              <Plus className="h-4 w-4" />
            </Button>
          </form>
          <RawArtifactTable artifacts={artifacts} loading={importLoading} />
        </div>
      </CardContent>
    </Card>
  );
}

function DataSourceTable({ sources, loading }: { sources: DataSource[]; loading: boolean }) {
  if (loading) {
    return <div className="h-48 rounded-md bg-[var(--muted)]" />;
  }

  return (
    <div className="overflow-x-auto rounded-md border border-[var(--border)]">
      <Table>
        <TableHeader>
          <TableRow>
            <TableHead>数据源</TableHead>
            <TableHead>类型</TableHead>
            <TableHead>可靠性</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          {sources.map((source) => (
            <TableRow key={source.source_id}>
              <TableCell className="font-medium">{source.name}</TableCell>
              <TableCell>{source.source_type}</TableCell>
              <TableCell>
                <Badge variant={source.reliability === "high" ? "success" : "muted"}>
                  {source.reliability}
                </Badge>
              </TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  );
}

function IngestionRunTable({
  runs,
  loading,
  rowCount,
  onRowCountChange,
  finishingRunId,
  onFinishRun,
}: {
  runs: IngestionRun[];
  loading: boolean;
  rowCount: string;
  onRowCountChange: (value: string) => void;
  finishingRunId: number | null;
  onFinishRun: (id: number) => void;
}) {
  if (loading) {
    return <div className="h-48 rounded-md bg-[var(--muted)]" />;
  }

  return (
    <div className="overflow-x-auto rounded-md border border-[var(--border)]">
      <Table>
        <TableHeader>
          <TableRow>
            <TableHead>批次</TableHead>
            <TableHead>数据源</TableHead>
            <TableHead>状态</TableHead>
            <TableHead>raw 路径</TableHead>
            <TableHead className="text-right">行数</TableHead>
            <TableHead className="w-44" />
          </TableRow>
        </TableHeader>
        <TableBody>
          {runs.map((run) => (
            <TableRow key={run.run_id}>
              <TableCell className="font-medium">#{run.run_id}</TableCell>
              <TableCell>{run.source_name ?? "-"}</TableCell>
              <TableCell>
                <Badge variant={run.status === "succeeded" ? "success" : "warning"}>
                  {run.status}
                </Badge>
              </TableCell>
              <TableCell className="max-w-64 truncate text-[var(--muted-foreground)]">
                {run.raw_uri ?? "-"}
              </TableCell>
              <TableCell className="text-right">{run.row_count ?? "-"}</TableCell>
              <TableCell>
                {run.status === "running" ? (
                  <div className="flex items-center justify-end gap-2">
                    <input
                      className="h-9 w-20 rounded-md border border-[var(--border)] bg-white px-2 text-sm"
                      inputMode="numeric"
                      placeholder="行数"
                      value={rowCount}
                      onChange={(event) => onRowCountChange(event.target.value)}
                    />
                    <Button
                      variant="outline"
                      disabled={finishingRunId === run.run_id}
                      onClick={() => onFinishRun(run.run_id)}
                      aria-label="完成导入"
                      title="完成导入"
                    >
                      <Check className="h-4 w-4" />
                    </Button>
                  </div>
                ) : null}
              </TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  );
}

function RawArtifactTable({
  artifacts,
  loading,
}: {
  artifacts: RawArtifact[];
  loading: boolean;
}) {
  if (loading) {
    return <div className="h-48 rounded-md bg-[var(--muted)]" />;
  }

  return (
    <div className="overflow-x-auto rounded-md border border-[var(--border)]">
      <Table>
        <TableHeader>
          <TableRow>
            <TableHead>原始文件</TableHead>
            <TableHead>数据源</TableHead>
            <TableHead>批次</TableHead>
            <TableHead>SHA-256</TableHead>
            <TableHead className="text-right">大小</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          {artifacts.map((artifact) => (
            <TableRow key={artifact.artifact_id}>
              <TableCell className="font-medium">{artifact.original_filename}</TableCell>
              <TableCell>{artifact.source_name ?? "-"}</TableCell>
              <TableCell>{artifact.run_id ? `#${artifact.run_id}` : "-"}</TableCell>
              <TableCell className="max-w-80 truncate font-mono text-xs text-[var(--muted-foreground)]">
                {artifact.sha256}
              </TableCell>
              <TableCell className="text-right">{formatNumber(artifact.size_bytes)}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  );
}

function AreaDetailPanel({
  detail,
  loading,
  creating,
  onCreateWatchlist,
  onSelectNeighborhood,
}: {
  detail: AreaDetail | null;
  loading: boolean;
  creating: boolean;
  onCreateWatchlist: (payload: CreateWatchlistItem) => void;
  onSelectNeighborhood: (neighborhoodId: string) => void;
}) {
  if (loading) {
    return <div className="h-80 rounded-md bg-[var(--muted)]" />;
  }

  if (!detail) {
    return null;
  }

  const score = detail.current_score;
  const lineage = detail.score_lineage;
  const latestMetric = detail.monthly_metrics[0];

  return (
    <Card>
      <CardHeader className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
        <div>
          <CardTitle>{detail.profile.name}板块详情</CardTitle>
          <p className="mt-1 text-sm text-[var(--muted-foreground)]">
            {detail.profile.district} · {detail.profile.segment}
          </p>
        </div>
        <div className="flex items-center gap-2">
          {score ? <Badge variant="success">{score.recommendation}</Badge> : null}
          <Button
            variant="outline"
            disabled={creating}
            onClick={() =>
              onCreateWatchlist({
                area_id: detail.profile.area_id,
                target_price_psm: score
                  ? Math.round(score.transaction_price_psm * 0.96)
                  : undefined,
                notes: `${MONTH} 板块详情：${score?.recommendation ?? "待评分"}`,
              })
            }
            aria-label="加入观察池"
            title="加入观察池"
          >
            <Plus className="h-4 w-4" />
          </Button>
        </div>
      </CardHeader>
      <CardContent className="grid gap-4 xl:grid-cols-[360px_minmax(0,1fr)]">
        <div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-1">
          <InsightRow
            label="综合分"
            value={score ? formatNumber(score.investment_score) : "-"}
            detail={lineage?.model_version ?? "gold"}
          />
          <InsightRow
            label="成交均价/㎡"
            value={latestMetric ? formatPrice(latestMetric.transaction_price_psm) : "-"}
            detail={latestMetric?.month ?? MONTH}
          />
          <InsightRow
            label="挂牌压力"
            value={score ? formatNumber(score.listing_pressure_ratio, 2) : "-"}
            detail={latestMetric ? `${latestMetric.listing_count} 套` : "-"}
          />
          <InsightRow
            label="数据追溯"
            value={lineage?.source_name ?? "-"}
            detail={lineage?.raw_artifact_id ? `#${lineage.raw_artifact_id}` : "-"}
          />
        </div>
        <div className="grid gap-4">
          <div className="h-72 rounded-md border border-[var(--border)] p-3">
            <AreaMetricTrendChart metrics={detail.monthly_metrics} />
          </div>
          <AreaDiagnosticsPanel diagnostics={detail.diagnostics} />
          <div className="grid gap-4 lg:grid-cols-2">
            <AreaMetricHistoryTable metrics={detail.monthly_metrics} />
            <AreaNeighborhoodTable
              scores={detail.neighborhood_scores}
              creating={creating}
              onCreate={onCreateWatchlist}
              onSelectNeighborhood={onSelectNeighborhood}
            />
          </div>
          {lineage ? (
            <div className="rounded-md border border-[var(--border)] px-3 py-2 text-xs text-[var(--muted-foreground)]">
              SHA-256：<span className="font-mono">{lineage.sha256 ?? "-"}</span>
            </div>
          ) : null}
        </div>
      </CardContent>
    </Card>
  );
}

function AreaDiagnosticsPanel({ diagnostics }: { diagnostics: AreaDetail["diagnostics"] }) {
  if (!diagnostics) {
    return (
      <div className="rounded-md border border-[var(--border)] px-4 py-8 text-sm text-[var(--muted-foreground)]">
        暂无历史分位
      </div>
    );
  }

  return (
    <div className="rounded-md border border-[var(--border)] p-3">
      <div className="mb-3 flex items-center justify-between gap-3">
        <p className="text-sm font-semibold text-slate-950">历史分位与异常</p>
        <Badge variant={diagnostics.anomaly_count > 0 ? "warning" : "success"}>
          {diagnostics.anomaly_count} 项异常
        </Badge>
      </div>
      <div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
        {diagnostics.metrics.map((metric) => (
          <div key={metric.key} className="rounded-md border border-[var(--border)] bg-white p-3">
            <div className="flex items-center justify-between gap-2">
              <span className="text-sm font-medium text-slate-950">{metric.label}</span>
              <Badge variant={metric.anomaly ? "warning" : "muted"}>{metric.severity}</Badge>
            </div>
            <p className="mt-2 text-lg font-semibold text-slate-950">
              {formatMetricValue(metric.current_value, metric.unit)}
            </p>
            <div className="mt-3 h-2 rounded-full bg-[var(--muted)]">
              <div
                className={
                  metric.anomaly
                    ? "h-2 rounded-full bg-[var(--warning)]"
                    : "h-2 rounded-full bg-[var(--primary)]"
                }
                style={{ width: `${Math.max(0, Math.min(100, metric.percentile))}%` }}
              />
            </div>
            <div className="mt-2 flex items-center justify-between text-xs text-[var(--muted-foreground)]">
              <span>P{formatNumber(metric.percentile, 1)}</span>
              <span>
                {formatMetricValue(metric.historical_min, metric.unit)}-
                {formatMetricValue(metric.historical_max, metric.unit)}
              </span>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

function AreaMetricTrendChart({ metrics }: { metrics: AreaDetail["monthly_metrics"] }) {
  const data = [...metrics].reverse();
  if (data.length === 0) {
    return (
      <div className="flex h-full items-center text-sm text-[var(--muted-foreground)]">
        暂无月度指标
      </div>
    );
  }

  return (
    <ResponsiveContainer width="100%" height="100%">
      <LineChart data={data} margin={{ top: 8, right: 12, left: 0, bottom: 4 }}>
        <CartesianGrid stroke="#e2e8f0" vertical={false} />
        <XAxis dataKey="month" tickLine={false} axisLine={false} tick={{ fontSize: 12 }} />
        <YAxis yAxisId="price" tickLine={false} axisLine={false} tick={{ fontSize: 12 }} />
        <YAxis
          yAxisId="volume"
          orientation="right"
          tickLine={false}
          axisLine={false}
          tick={{ fontSize: 12 }}
        />
        <Tooltip
          formatter={(value, name) => {
            if (name === "transaction_price_psm") {
              return [formatPrice(Number(value)), "成交均价/㎡"];
            }
            return [formatNumber(Number(value)), "成交套数"];
          }}
        />
        <Line
          yAxisId="price"
          type="monotone"
          dataKey="transaction_price_psm"
          stroke="#0f766e"
          strokeWidth={2}
          dot={false}
        />
        <Line
          yAxisId="volume"
          type="monotone"
          dataKey="transaction_count"
          stroke="#b45309"
          strokeWidth={2}
          dot={false}
        />
      </LineChart>
    </ResponsiveContainer>
  );
}

function AreaMetricHistoryTable({ metrics }: { metrics: AreaDetail["monthly_metrics"] }) {
  if (metrics.length === 0) {
    return (
      <div className="rounded-md border border-[var(--border)] px-4 py-8 text-sm text-[var(--muted-foreground)]">
        暂无月度指标
      </div>
    );
  }

  return (
    <div className="overflow-x-auto rounded-md border border-[var(--border)]">
      <Table>
        <TableHeader>
          <TableRow>
            <TableHead>月份</TableHead>
            <TableHead className="text-right">成交</TableHead>
            <TableHead className="text-right">均价/㎡</TableHead>
            <TableHead className="text-right">挂牌</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          {metrics.slice(0, 6).map((metric) => (
            <TableRow key={`${metric.area_id}-${metric.month}`}>
              <TableCell className="font-medium">{metric.month}</TableCell>
              <TableCell className="text-right">{metric.transaction_count}</TableCell>
              <TableCell className="text-right">{formatPrice(metric.transaction_price_psm)}</TableCell>
              <TableCell className="text-right">{metric.listing_count}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  );
}

function AreaNeighborhoodTable({
  scores,
  creating,
  onCreate,
  onSelectNeighborhood,
}: {
  scores: NeighborhoodScore[];
  creating: boolean;
  onCreate: (payload: CreateWatchlistItem) => void;
  onSelectNeighborhood: (neighborhoodId: string) => void;
}) {
  if (scores.length === 0) {
    return (
      <div className="rounded-md border border-[var(--border)] px-4 py-8 text-sm text-[var(--muted-foreground)]">
        暂无小区评分
      </div>
    );
  }

  return (
    <div className="overflow-x-auto rounded-md border border-[var(--border)]">
      <Table>
        <TableHeader>
          <TableRow>
            <TableHead>小区</TableHead>
            <TableHead className="text-right">分数</TableHead>
            <TableHead className="text-right">均价/㎡</TableHead>
            <TableHead className="w-14" />
          </TableRow>
        </TableHeader>
        <TableBody>
          {scores.slice(0, 6).map((score) => (
            <TableRow
              key={score.neighborhood_id}
              className="cursor-pointer hover:bg-slate-50"
              onClick={() => onSelectNeighborhood(score.neighborhood_id)}
            >
              <TableCell className="font-medium">{score.name}</TableCell>
              <TableCell className="text-right">{formatNumber(score.investment_score)}</TableCell>
              <TableCell className="text-right">{formatPrice(score.transaction_price_psm)}</TableCell>
              <TableCell className="text-right">
                <Button
                  variant="outline"
                  disabled={creating}
                  onClick={(event) => {
                    event.stopPropagation();
                    onCreate({
                      neighborhood_id: score.neighborhood_id,
                      target_price_psm: Math.round(score.transaction_price_psm * 0.96),
                      notes: `${score.month} 板块详情：${score.recommendation}`,
                    });
                  }}
                  aria-label="加入观察池"
                  title="加入观察池"
                >
                  <Plus className="h-4 w-4" />
                </Button>
              </TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  );
}

function NeighborhoodRankingPanel({
  scores,
  neighborhoodScores,
  selectedAreaId,
  selectedNeighborhoodId,
  loading,
  creating,
  onSelectArea,
  onSelectNeighborhood,
  onCreate,
}: {
  scores: AreaScore[];
  neighborhoodScores: NeighborhoodScore[];
  selectedAreaId: string;
  selectedNeighborhoodId: string;
  loading: boolean;
  creating: boolean;
  onSelectArea: (areaId: string) => void;
  onSelectNeighborhood: (neighborhoodId: string) => void;
  onCreate: (payload: CreateWatchlistItem) => void;
}) {
  const filteredScores = selectedAreaId
    ? neighborhoodScores.filter((score) => score.area_id === selectedAreaId)
    : neighborhoodScores;
  const topNeighborhood = filteredScores[0];

  return (
    <Card>
      <CardHeader className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
        <div className="flex items-center gap-2">
          <CardTitle>小区排行</CardTitle>
          {topNeighborhood ? <Badge variant="success">首位：{topNeighborhood.name}</Badge> : null}
        </div>
        <select
          className="h-9 w-full rounded-md border border-[var(--border)] bg-white px-3 text-sm lg:w-44"
          value={selectedAreaId}
          onChange={(event) => onSelectArea(event.target.value)}
          aria-label="小区板块筛选"
        >
          <option value="">全部板块</option>
          {scores.map((score) => (
            <option key={score.area_id} value={score.area_id}>
              {score.name}
            </option>
          ))}
        </select>
      </CardHeader>
      <CardContent className="overflow-x-auto p-0">
        <NeighborhoodScoreTable
          scores={filteredScores}
          loading={loading}
          creating={creating}
          selectedNeighborhoodId={selectedNeighborhoodId}
          onSelectNeighborhood={onSelectNeighborhood}
          onCreate={onCreate}
        />
      </CardContent>
    </Card>
  );
}

function NeighborhoodScoreTable({
  scores,
  loading,
  creating,
  selectedNeighborhoodId,
  onSelectNeighborhood,
  onCreate,
}: {
  scores: NeighborhoodScore[];
  loading: boolean;
  creating: boolean;
  selectedNeighborhoodId: string;
  onSelectNeighborhood: (neighborhoodId: string) => void;
  onCreate: (payload: CreateWatchlistItem) => void;
}) {
  if (loading) {
    return <div className="h-56 bg-[var(--muted)]" />;
  }

  if (scores.length === 0) {
    return <div className="px-4 py-8 text-sm text-[var(--muted-foreground)]">暂无小区评分</div>;
  }

  return (
    <Table>
      <TableHeader>
        <TableRow>
          <TableHead>小区</TableHead>
          <TableHead>板块</TableHead>
          <TableHead className="text-right">综合分</TableHead>
          <TableHead>建议</TableHead>
          <TableHead className="text-right">成交均价/㎡</TableHead>
          <TableHead className="text-right">租金收益率</TableHead>
          <TableHead className="text-right">流动性</TableHead>
          <TableHead className="text-right">位置</TableHead>
          <TableHead className="w-14" />
        </TableRow>
      </TableHeader>
      <TableBody>
        {scores.map((score) => (
          <TableRow
            key={score.neighborhood_id}
            className={
              selectedNeighborhoodId === score.neighborhood_id
                ? "cursor-pointer bg-teal-50/60"
                : "cursor-pointer hover:bg-slate-50"
            }
            onClick={() => onSelectNeighborhood(score.neighborhood_id)}
          >
            <TableCell className="font-medium">{score.name}</TableCell>
            <TableCell>{score.area_name}</TableCell>
            <TableCell className="text-right font-semibold">
              {formatNumber(score.investment_score)}
            </TableCell>
            <TableCell>
              <Badge variant={score.recommendation === "重点研究" ? "success" : "muted"}>
                {score.recommendation}
              </Badge>
            </TableCell>
            <TableCell className="text-right">{formatPrice(score.transaction_price_psm)}</TableCell>
            <TableCell className="text-right">
              {formatNumber(score.annual_rent_yield_pct, 2)}%
            </TableCell>
            <TableCell className="text-right">{formatNumber(score.liquidity_score)}</TableCell>
            <TableCell className="text-right">{formatNumber(score.location_score)}</TableCell>
            <TableCell className="text-right">
              <Button
                variant="outline"
                disabled={creating}
                onClick={(event) => {
                  event.stopPropagation();
                  onCreate({
                    neighborhood_id: score.neighborhood_id,
                    target_price_psm: Math.round(score.transaction_price_psm * 0.96),
                    notes: `${score.month} 小区排行：${score.recommendation}`,
                  });
                }}
                aria-label="加入观察池"
                title="加入观察池"
              >
                <Plus className="h-4 w-4" />
              </Button>
            </TableCell>
          </TableRow>
        ))}
      </TableBody>
    </Table>
  );
}

function NeighborhoodDetailPanel({
  detail,
  loading,
  creating,
  onCreateWatchlist,
  onSelectNeighborhood,
}: {
  detail: NeighborhoodDetail | null;
  loading: boolean;
  creating: boolean;
  onCreateWatchlist: (payload: CreateWatchlistItem) => void;
  onSelectNeighborhood: (neighborhoodId: string) => void;
}) {
  if (loading) {
    return <div className="h-80 rounded-md bg-[var(--muted)]" />;
  }

  if (!detail) {
    return null;
  }

  const score = detail.current_score;
  const latestMetric = detail.monthly_metrics[0];
  const referencePrice = score?.transaction_price_psm ?? latestMetric?.transaction_price_psm ?? 0;
  const lowerWatchPrice = referencePrice > 0 ? Math.round(referencePrice * 0.92) : null;
  const upperWatchPrice = referencePrice > 0 ? Math.round(referencePrice * 0.96) : null;
  const activeWatchlistItem = detail.watchlist_items[0];

  return (
    <Card>
      <CardHeader className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
        <div>
          <CardTitle>{detail.profile.name}小区详情</CardTitle>
          <p className="mt-1 text-sm text-[var(--muted-foreground)]">
            {detail.profile.district} · {detail.profile.area_name} · {detail.profile.property_type}
          </p>
        </div>
        <div className="flex items-center gap-2">
          {score ? <Badge variant="success">{score.recommendation}</Badge> : null}
          {activeWatchlistItem ? <Badge variant="muted">{activeWatchlistItem.status}</Badge> : null}
          <Button
            variant="outline"
            disabled={creating}
            onClick={() =>
              onCreateWatchlist({
                neighborhood_id: detail.profile.neighborhood_id,
                target_price_psm: upperWatchPrice ?? undefined,
                notes: `${MONTH} 小区详情：${score?.recommendation ?? "待评分"}`,
              })
            }
            aria-label="加入观察池"
            title="加入观察池"
          >
            <Plus className="h-4 w-4" />
          </Button>
        </div>
      </CardHeader>
      <CardContent className="grid gap-4 xl:grid-cols-[360px_minmax(0,1fr)]">
        <div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-1">
          <InsightRow
            label="综合分"
            value={score ? formatNumber(score.investment_score) : "-"}
            detail={score?.month ?? MONTH}
          />
          <InsightRow
            label="观察价区间/㎡"
            value={
              lowerWatchPrice && upperWatchPrice
                ? `${formatPrice(lowerWatchPrice)} - ${formatPrice(upperWatchPrice)}`
                : "-"
            }
            detail="当前价 92%-96%"
          />
          <InsightRow
            label="建成年份"
            value={detail.profile.built_year ? `${detail.profile.built_year}` : "-"}
            detail={score ? `${formatNumber(score.building_age_score)} 分` : "-"}
          />
          <InsightRow
            label="地铁距离"
            value={
              detail.profile.metro_distance_m ? `${detail.profile.metro_distance_m} 米` : "-"
            }
            detail={detail.profile.school_quality ?? "-"}
          />
        </div>
        <div className="grid gap-4">
          <div className="grid gap-4 lg:grid-cols-[minmax(0,1fr)_320px]">
            <div className="h-72 rounded-md border border-[var(--border)] p-3">
              <NeighborhoodMetricTrendChart metrics={detail.monthly_metrics} />
            </div>
            <NeighborhoodScoreBreakdown score={score} />
          </div>
          <div className="grid gap-4 lg:grid-cols-2">
            <NeighborhoodMetricHistoryTable metrics={detail.monthly_metrics} />
            <NeighborhoodProfileTable detail={detail} />
          </div>
          <SimilarNeighborhoodTable
            items={detail.similar_neighborhoods}
            onSelectNeighborhood={onSelectNeighborhood}
          />
        </div>
      </CardContent>
    </Card>
  );
}

function NeighborhoodMetricTrendChart({
  metrics,
}: {
  metrics: NeighborhoodDetail["monthly_metrics"];
}) {
  const data = [...metrics].reverse();
  if (data.length === 0) {
    return (
      <div className="flex h-full items-center text-sm text-[var(--muted-foreground)]">
        暂无月度指标
      </div>
    );
  }

  return (
    <ResponsiveContainer width="100%" height="100%">
      <LineChart data={data} margin={{ top: 8, right: 12, left: 0, bottom: 4 }}>
        <CartesianGrid stroke="#e2e8f0" vertical={false} />
        <XAxis dataKey="month" tickLine={false} axisLine={false} tick={{ fontSize: 12 }} />
        <YAxis yAxisId="price" tickLine={false} axisLine={false} tick={{ fontSize: 12 }} />
        <YAxis
          yAxisId="volume"
          orientation="right"
          tickLine={false}
          axisLine={false}
          tick={{ fontSize: 12 }}
        />
        <Tooltip
          formatter={(value, name) => {
            if (name === "transaction_price_psm") {
              return [formatPrice(Number(value)), "成交均价/㎡"];
            }
            if (name === "listing_price_psm") {
              return [formatPrice(Number(value)), "挂牌均价/㎡"];
            }
            return [formatNumber(Number(value)), "成交套数"];
          }}
        />
        <Line
          yAxisId="price"
          type="monotone"
          dataKey="transaction_price_psm"
          stroke="#0f766e"
          strokeWidth={2}
          dot={false}
        />
        <Line
          yAxisId="price"
          type="monotone"
          dataKey="listing_price_psm"
          stroke="#2563eb"
          strokeWidth={2}
          dot={false}
        />
        <Line
          yAxisId="volume"
          type="monotone"
          dataKey="transaction_count"
          stroke="#b45309"
          strokeWidth={2}
          dot={false}
        />
      </LineChart>
    </ResponsiveContainer>
  );
}

function NeighborhoodScoreBreakdown({ score }: { score: NeighborhoodScore | null }) {
  const items = score
    ? [
        ["流动性", score.liquidity_score],
        ["租金支撑", score.rent_support_score],
        ["价格安全", score.price_safety_score],
        ["位置", score.location_score],
        ["楼龄", score.building_age_score],
      ]
    : [];

  if (!score) {
    return (
      <div className="rounded-md border border-[var(--border)] px-4 py-8 text-sm text-[var(--muted-foreground)]">
        暂无评分拆解
      </div>
    );
  }

  return (
    <div className="rounded-md border border-[var(--border)] p-4">
      <p className="text-sm font-semibold text-slate-950">评分拆解</p>
      <div className="mt-4 grid gap-3">
        {items.map(([label, value]) => (
          <div key={label} className="grid gap-1">
            <div className="flex items-center justify-between text-sm">
              <span className="text-[var(--muted-foreground)]">{label}</span>
              <span className="font-medium text-slate-950">{formatNumber(Number(value))}</span>
            </div>
            <div className="h-2 rounded-full bg-[var(--muted)]">
              <div
                className="h-2 rounded-full bg-[var(--primary)]"
                style={{ width: `${Math.max(0, Math.min(100, Number(value)))}%` }}
              />
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

function NeighborhoodMetricHistoryTable({
  metrics,
}: {
  metrics: NeighborhoodDetail["monthly_metrics"];
}) {
  if (metrics.length === 0) {
    return (
      <div className="rounded-md border border-[var(--border)] px-4 py-8 text-sm text-[var(--muted-foreground)]">
        暂无月度指标
      </div>
    );
  }

  return (
    <div className="overflow-x-auto rounded-md border border-[var(--border)]">
      <Table>
        <TableHeader>
          <TableRow>
            <TableHead>月份</TableHead>
            <TableHead className="text-right">成交</TableHead>
            <TableHead className="text-right">成交均价/㎡</TableHead>
            <TableHead className="text-right">可售</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          {metrics.slice(0, 6).map((metric) => (
            <TableRow key={`${metric.neighborhood_id}-${metric.month}`}>
              <TableCell className="font-medium">{metric.month}</TableCell>
              <TableCell className="text-right">{metric.transaction_count}</TableCell>
              <TableCell className="text-right">{formatPrice(metric.transaction_price_psm)}</TableCell>
              <TableCell className="text-right">{metric.available_units}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  );
}

function NeighborhoodProfileTable({ detail }: { detail: NeighborhoodDetail }) {
  const latestMetric = detail.monthly_metrics[0];
  const activeWatchlistItem = detail.watchlist_items[0];
  const rows = [
    ["挂牌均价/㎡", latestMetric ? formatPrice(latestMetric.listing_price_psm) : "-"],
    ["租金/㎡", latestMetric ? formatPrice(latestMetric.rent_price_psm) : "-"],
    ["去化天数", latestMetric ? formatNumber(latestMetric.median_days_on_market) : "-"],
    ["数据批次", latestMetric?.ingestion_run_id ? `#${latestMetric.ingestion_run_id}` : "-"],
    ["原始文件", latestMetric?.raw_artifact_id ? `#${latestMetric.raw_artifact_id}` : "-"],
    [
      "观察池目标/㎡",
      activeWatchlistItem?.target_price_psm ? formatPrice(activeWatchlistItem.target_price_psm) : "-",
    ],
  ];

  return (
    <div className="overflow-x-auto rounded-md border border-[var(--border)]">
      <Table>
        <TableBody>
          {rows.map(([label, value]) => (
            <TableRow key={label}>
              <TableCell className="text-[var(--muted-foreground)]">{label}</TableCell>
              <TableCell className="text-right font-medium">{value}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  );
}

function SimilarNeighborhoodTable({
  items,
  onSelectNeighborhood,
}: {
  items: SimilarNeighborhood[];
  onSelectNeighborhood: (neighborhoodId: string) => void;
}) {
  if (items.length === 0) {
    return (
      <div className="rounded-md border border-[var(--border)] px-4 py-8 text-sm text-[var(--muted-foreground)]">
        暂无相似资产
      </div>
    );
  }

  return (
    <div className="overflow-x-auto rounded-md border border-[var(--border)]">
      <Table>
        <TableHeader>
          <TableRow>
            <TableHead>相似资产</TableHead>
            <TableHead>板块</TableHead>
            <TableHead className="text-right">相似度</TableHead>
            <TableHead className="text-right">相对价格</TableHead>
            <TableHead className="text-right">成交均价/㎡</TableHead>
            <TableHead className="text-right">综合分</TableHead>
            <TableHead>匹配原因</TableHead>
            <TableHead className="w-12 text-right">查看</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          {items.map((item) => (
            <TableRow key={item.neighborhood_id}>
              <TableCell className="min-w-40">
                <span className="block font-medium text-slate-950">{item.name}</span>
                <span className="block text-xs text-[var(--muted-foreground)]">
                  {item.property_type} · {item.built_year ?? "-"}
                </span>
              </TableCell>
              <TableCell className="min-w-32">
                <span className="block">{item.area_name}</span>
                <span className="block text-xs text-[var(--muted-foreground)]">
                  {item.district}
                </span>
              </TableCell>
              <TableCell className="text-right font-medium">
                {formatNumber(item.similarity_score)}
              </TableCell>
              <TableCell className="text-right">
                <Badge variant={relativePriceVariant(item.relative_price_pct)}>
                  {formatRelativePrice(item.relative_price_pct)}
                </Badge>
              </TableCell>
              <TableCell className="text-right">{formatPrice(item.transaction_price_psm)}</TableCell>
              <TableCell className="text-right">
                <span className="block font-medium">{formatNumber(item.investment_score)}</span>
                <span className="block text-xs text-[var(--muted-foreground)]">
                  {item.recommendation}
                </span>
              </TableCell>
              <TableCell className="min-w-64">
                <div className="flex flex-wrap gap-1">
                  {item.reasons.slice(0, 5).map((reason) => (
                    <Badge key={reason} variant="muted" className="h-5">
                      {formatSimilarityReason(reason)}
                    </Badge>
                  ))}
                </div>
              </TableCell>
              <TableCell className="text-right">
                <Button
                  variant="outline"
                  className="h-8 w-8 p-0"
                  onClick={() => onSelectNeighborhood(item.neighborhood_id)}
                  aria-label={`查看${item.name}`}
                  title="查看小区详情"
                >
                  <MapPinned className="h-4 w-4" />
                </Button>
              </TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  );
}

function ComparisonWorkspace({
  areaScores,
  neighborhoodScores,
  areaComparison,
  neighborhoodComparison,
  selectedAreaIds,
  selectedNeighborhoodIds,
  loading,
  onAreaIdsChange,
  onNeighborhoodIdsChange,
}: {
  areaScores: AreaScore[];
  neighborhoodScores: NeighborhoodScore[];
  areaComparison: AreaComparison | null;
  neighborhoodComparison: NeighborhoodComparison | null;
  selectedAreaIds: string[];
  selectedNeighborhoodIds: string[];
  loading: boolean;
  onAreaIdsChange: (ids: string[]) => void;
  onNeighborhoodIdsChange: (ids: string[]) => void;
}) {
  return (
    <Card>
      <CardHeader className="flex flex-col gap-3 xl:flex-row xl:items-start xl:justify-between">
        <div>
          <CardTitle>对比工作台</CardTitle>
          <p className="mt-1 text-sm text-[var(--muted-foreground)]">
            板块与小区并排比较
          </p>
        </div>
        <div className="grid w-full gap-3 xl:w-[640px] xl:grid-cols-2">
          <ComparisonSelector
            label="板块"
            options={areaScores.map((score) => ({
              id: score.area_id,
              label: score.name,
              detail: score.district,
            }))}
            selectedIds={selectedAreaIds}
            onChange={onAreaIdsChange}
          />
          <ComparisonSelector
            label="小区"
            options={neighborhoodScores.map((score) => ({
              id: score.neighborhood_id,
              label: score.name,
              detail: score.area_name,
            }))}
            selectedIds={selectedNeighborhoodIds}
            onChange={onNeighborhoodIdsChange}
          />
        </div>
      </CardHeader>
      <CardContent className="grid gap-4">
        {loading ? <div className="h-72 rounded-md bg-[var(--muted)]" /> : null}
        {!loading ? (
          <div className="grid gap-4 xl:grid-cols-[minmax(0,1fr)_360px]">
            <ComparisonMatrix
              title="板块矩阵"
              comparison={areaComparison}
              getId={(item) => item.area_id}
              getName={(item) => item.name}
              getMeta={(item) => `${item.district} · ${item.segment}`}
            />
            <ComparisonRadar
              title="板块雷达"
              metrics={areaComparison?.metrics ?? []}
              items={areaComparison?.items ?? []}
              getId={(item) => item.area_id}
              getName={(item) => item.name}
            />
            <ComparisonMatrix
              title="小区矩阵"
              comparison={neighborhoodComparison}
              getId={(item) => item.neighborhood_id}
              getName={(item) => item.name}
              getMeta={(item) =>
                `${item.area_name} · ${item.built_year ?? "-"} · ${item.property_type}`
              }
            />
            <ComparisonRadar
              title="小区雷达"
              metrics={neighborhoodComparison?.metrics ?? []}
              items={neighborhoodComparison?.items ?? []}
              getId={(item) => item.neighborhood_id}
              getName={(item) => item.name}
            />
          </div>
        ) : null}
      </CardContent>
    </Card>
  );
}

function ComparisonSelector({
  label,
  options,
  selectedIds,
  onChange,
}: {
  label: string;
  options: Array<{ id: string; label: string; detail: string }>;
  selectedIds: string[];
  onChange: (ids: string[]) => void;
}) {
  function toggle(id: string) {
    if (selectedIds.includes(id)) {
      if (selectedIds.length > 2) {
        onChange(selectedIds.filter((selectedId) => selectedId !== id));
      }
      return;
    }
    if (selectedIds.length < 4) {
      onChange([...selectedIds, id]);
    }
  }

  return (
    <div className="rounded-md border border-[var(--border)] p-3">
      <div className="mb-2 flex items-center justify-between">
        <p className="text-sm font-semibold text-slate-950">{label}</p>
        <Badge variant="muted">{selectedIds.length}/4</Badge>
      </div>
      <div className="grid max-h-44 gap-2 overflow-y-auto pr-1">
        {options.map((option) => {
          const selected = selectedIds.includes(option.id);
          const locked = selected && selectedIds.length <= 2;
          return (
            <label
              key={option.id}
              className={
                selected
                  ? "flex cursor-pointer items-center gap-2 rounded-md border border-teal-200 bg-teal-50 px-2 py-2"
                  : "flex cursor-pointer items-center gap-2 rounded-md border border-[var(--border)] px-2 py-2 hover:bg-slate-50"
              }
            >
              <input
                type="checkbox"
                checked={selected}
                disabled={locked}
                onChange={() => toggle(option.id)}
              />
              <span className="min-w-0">
                <span className="block truncate text-sm font-medium text-slate-950">
                  {option.label}
                </span>
                <span className="block truncate text-xs text-[var(--muted-foreground)]">
                  {option.detail}
                </span>
              </span>
            </label>
          );
        })}
      </div>
    </div>
  );
}

function ComparisonMatrix<T>({
  title,
  comparison,
  getId,
  getName,
  getMeta,
}: {
  title: string;
  comparison: { items: T[]; metrics: ComparisonMetric[]; missing_ids: string[] } | null;
  getId: (item: T) => string;
  getName: (item: T) => string;
  getMeta: (item: T) => string;
}) {
  if (!comparison || comparison.items.length === 0) {
    return (
      <div className="rounded-md border border-[var(--border)] px-4 py-8 text-sm text-[var(--muted-foreground)]">
        暂无对比数据
      </div>
    );
  }

  const nameById = new Map(comparison.items.map((item) => [getId(item), getName(item)]));
  const metaById = new Map(comparison.items.map((item) => [getId(item), getMeta(item)]));

  return (
    <div className="overflow-x-auto rounded-md border border-[var(--border)]">
      <Table>
        <TableHeader>
          <TableRow>
            <TableHead>{title}</TableHead>
            {comparison.items.map((item) => (
              <TableHead key={getId(item)} className="min-w-36 text-right">
                <span className="block text-slate-950">{getName(item)}</span>
                <span className="block font-normal text-[var(--muted-foreground)]">
                  {metaById.get(getId(item))}
                </span>
              </TableHead>
            ))}
          </TableRow>
        </TableHeader>
        <TableBody>
          {comparison.metrics.map((metric) => (
            <TableRow key={metric.key}>
              <TableCell className="font-medium">
                {metric.label}
                <span className="ml-1 text-xs font-normal text-[var(--muted-foreground)]">
                  {metric.unit}
                </span>
              </TableCell>
              {comparison.items.map((item) => {
                const id = getId(item);
                const value = metric.values.find((metricValue) => metricValue.id === id)?.value;
                return (
                  <TableCell key={`${metric.key}-${id}`} className="text-right">
                    {formatMetricValue(value, metric.unit)}
                  </TableCell>
                );
              })}
            </TableRow>
          ))}
          {comparison.missing_ids.length > 0 ? (
            <TableRow>
              <TableCell className="text-[var(--muted-foreground)]">缺失</TableCell>
              <TableCell
                colSpan={comparison.items.length}
                className="text-right text-[var(--muted-foreground)]"
              >
                {comparison.missing_ids.map((id) => nameById.get(id) ?? id).join(", ")}
              </TableCell>
            </TableRow>
          ) : null}
        </TableBody>
      </Table>
    </div>
  );
}

function ComparisonRadar<T>({
  title,
  metrics,
  items,
  getId,
  getName,
}: {
  title: string;
  metrics: ComparisonMetric[];
  items: T[];
  getId: (item: T) => string;
  getName: (item: T) => string;
}) {
  const radarMetrics = metrics.filter(
    (metric) =>
      metric.unit === "分" ||
      metric.key === "annual_rent_yield_pct" ||
      metric.key === "transaction_count",
  );
  const data = radarMetrics.map((metric) => {
    const row: Record<string, string | number> = { metric: metric.label };
    for (const item of items) {
      const id = getId(item);
      const value = metric.values.find((metricValue) => metricValue.id === id)?.value ?? 0;
      row[id] = metric.direction === "lower" ? 100 - value : value;
    }
    return row;
  });
  const colors = ["#0f766e", "#2563eb", "#b45309", "#be123c"];

  if (data.length === 0 || items.length === 0) {
    return (
      <div className="rounded-md border border-[var(--border)] px-4 py-8 text-sm text-[var(--muted-foreground)]">
        暂无雷达图
      </div>
    );
  }

  return (
    <div className="h-80 rounded-md border border-[var(--border)] p-3">
      <p className="mb-2 text-sm font-semibold text-slate-950">{title}</p>
      <ResponsiveContainer width="100%" height="90%">
        <RadarChart data={data}>
          <PolarGrid stroke="#dbe1ea" />
          <PolarAngleAxis dataKey="metric" tick={{ fontSize: 12 }} />
          <PolarRadiusAxis angle={90} domain={[0, 100]} tick={false} axisLine={false} />
          <Tooltip formatter={(value) => [formatNumber(Number(value)), "标准化"]} />
          {items.map((item, index) => (
            <Radar
              key={getId(item)}
              name={getName(item)}
              dataKey={getId(item)}
              stroke={colors[index % colors.length]}
              fill={colors[index % colors.length]}
              fillOpacity={0.14}
            />
          ))}
        </RadarChart>
      </ResponsiveContainer>
    </div>
  );
}

function formatMetricValue(value: number | null | undefined, unit: string) {
  if (value === null || value === undefined) {
    return "-";
  }
  if (unit === "元/㎡" || unit === "米") {
    return formatPrice(value);
  }
  if (unit === "%") {
    return `${formatNumber(value, 2)}%`;
  }
  if (unit === "套") {
    return formatNumber(value, 0);
  }
  return formatNumber(value);
}

function formatSignedNumber(value: number) {
  const sign = value > 0 ? "+" : "";
  return `${sign}${formatNumber(value)}`;
}

function formatRelativePrice(value: number) {
  if (value === 0) {
    return "持平";
  }
  const label = value > 0 ? "溢价" : "折价";
  return `${label} ${formatSignedNumber(value)}%`;
}

function relativePriceVariant(value: number): "success" | "warning" | "muted" {
  if (value <= -3) {
    return "success";
  }
  if (value >= 3) {
    return "warning";
  }
  return "muted";
}

function formatSimilarityReason(reason: string) {
  if (reason === "property_type_match") {
    return "物业类型一致";
  }
  if (reason === "same_area") {
    return "同板块";
  }
  if (reason === "same_district") {
    return "同行政区";
  }
  const [key, rawValue] = reason.split(":");
  if (key === "price_gap_pct" && rawValue) {
    return `价差 ${rawValue}%`;
  }
  if (key === "building_age_gap_years" && rawValue) {
    return `楼龄差 ${rawValue}年`;
  }
  if (key === "metro_gap_m" && rawValue) {
    return `地铁差 ${rawValue}米`;
  }
  return reason;
}

function WatchlistPanel({
  scores,
  neighborhoodScores,
  items,
  events,
  loading,
  creating,
  archivingId,
  updatingId,
  creatingEvent,
  statusFilter,
  areaFilter,
  labelFilter,
  expandedItemId,
  onStatusFilterChange,
  onAreaFilterChange,
  onLabelFilterChange,
  onToggleEvents,
  onCreate,
  onUpdate,
  onCreateEvent,
  onArchive,
}: {
  scores: AreaScore[];
  neighborhoodScores: NeighborhoodScore[];
  items: WatchlistItem[];
  events: WatchlistEvent[];
  loading: boolean;
  creating: boolean;
  archivingId: number | null;
  updatingId: number | null;
  creatingEvent: boolean;
  statusFilter: string;
  areaFilter: string;
  labelFilter: string;
  expandedItemId: number | null;
  onStatusFilterChange: (value: string) => void;
  onAreaFilterChange: (value: string) => void;
  onLabelFilterChange: (value: string) => void;
  onToggleEvents: (id: number) => void;
  onCreate: (payload: CreateWatchlistItem) => void;
  onUpdate: (id: number, payload: UpdateWatchlistItem) => void;
  onCreateEvent: (id: number, payload: CreateWatchlistEvent) => void;
  onArchive: (id: number) => void;
}) {
  const [targetType, setTargetType] = useState<"area" | "neighborhood">("area");
  const [areaId, setAreaId] = useState("");
  const [neighborhoodId, setNeighborhoodId] = useState("");
  const [targetPrice, setTargetPrice] = useState("");
  const [triggerPrice, setTriggerPrice] = useState("");
  const [triggerOperator, setTriggerOperator] = useState<"lte" | "gte">("lte");
  const [label, setLabel] = useState("核心");
  const [priority, setPriority] = useState<"low" | "medium" | "high">("medium");
  const [notes, setNotes] = useState("");

  const areaNameById = useMemo(() => {
    return new Map(scores.map((score) => [score.area_id, score.name]));
  }, [scores]);
  const neighborhoodNameById = useMemo(() => {
    return new Map(
      neighborhoodScores.map((score) => [
        score.neighborhood_id,
        `${score.name}（${score.area_name}）`,
      ]),
    );
  }, [neighborhoodScores]);

  function submit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();
    if (targetType === "area" && !areaId) {
      return;
    }
    if (targetType === "neighborhood" && !neighborhoodId) {
      return;
    }
    const parsedTarget = Number(targetPrice);
    const parsedTrigger = Number(triggerPrice);
    onCreate({
      area_id: targetType === "area" ? areaId : undefined,
      neighborhood_id: targetType === "neighborhood" ? neighborhoodId : undefined,
      target_price_psm: Number.isFinite(parsedTarget) && parsedTarget > 0 ? parsedTarget : undefined,
      trigger_price_psm:
        Number.isFinite(parsedTrigger) && parsedTrigger > 0 ? parsedTrigger : undefined,
      trigger_operator: triggerOperator,
      label: label.trim() || undefined,
      priority,
      notes: notes.trim() || undefined,
    });
    setAreaId("");
    setNeighborhoodId("");
    setTargetPrice("");
    setTriggerPrice("");
    setNotes("");
  }

  return (
    <Card>
      <CardHeader className="flex flex-col gap-3">
        <div className="flex flex-col gap-3 xl:flex-row xl:items-center xl:justify-between">
          <CardTitle>观察池</CardTitle>
          <div className="grid gap-2 sm:grid-cols-3 xl:w-[620px]">
            <select
              className="h-9 rounded-md border border-[var(--border)] bg-white px-3 text-sm"
              value={statusFilter}
              onChange={(event) => onStatusFilterChange(event.target.value)}
              aria-label="观察池状态筛选"
            >
              <option value="active">active</option>
              <option value="paused">paused</option>
              <option value="archived">archived</option>
            </select>
            <select
              className="h-9 rounded-md border border-[var(--border)] bg-white px-3 text-sm"
              value={areaFilter}
              onChange={(event) => onAreaFilterChange(event.target.value)}
              aria-label="观察池板块筛选"
            >
              <option value="">全部板块</option>
              {scores.map((score) => (
                <option key={score.area_id} value={score.area_id}>
                  {score.name}
                </option>
              ))}
            </select>
            <input
              className="h-9 rounded-md border border-[var(--border)] bg-white px-3 text-sm"
              placeholder="标签"
              value={labelFilter}
              onChange={(event) => onLabelFilterChange(event.target.value)}
            />
          </div>
        </div>
        <form className="grid gap-2 lg:grid-cols-[100px_1fr_1fr_120px_120px_90px_90px_100px_1fr_44px]" onSubmit={submit}>
          <select
            className="h-9 rounded-md border border-[var(--border)] bg-white px-3 text-sm"
            value={targetType}
            onChange={(event) => setTargetType(event.target.value as "area" | "neighborhood")}
            aria-label="观察标的类型"
          >
            <option value="area">板块</option>
            <option value="neighborhood">小区</option>
          </select>
          <select
            className="h-9 min-w-36 rounded-md border border-[var(--border)] bg-white px-3 text-sm"
            value={areaId}
            onChange={(event) => setAreaId(event.target.value)}
            aria-label="板块"
            disabled={targetType !== "area"}
          >
            <option value="">板块</option>
            {scores.map((score) => (
              <option key={score.area_id} value={score.area_id}>
                {score.name}
              </option>
            ))}
          </select>
          <select
            className="h-9 min-w-36 rounded-md border border-[var(--border)] bg-white px-3 text-sm"
            value={neighborhoodId}
            onChange={(event) => setNeighborhoodId(event.target.value)}
            aria-label="小区"
            disabled={targetType !== "neighborhood"}
          >
            <option value="">小区</option>
            {neighborhoodScores.map((score) => (
              <option key={score.neighborhood_id} value={score.neighborhood_id}>
                {score.name}
              </option>
            ))}
          </select>
          <input
            className="h-9 rounded-md border border-[var(--border)] bg-white px-3 text-sm"
            inputMode="numeric"
            placeholder="目标价/㎡"
            value={targetPrice}
            onChange={(event) => setTargetPrice(event.target.value)}
          />
          <input
            className="h-9 rounded-md border border-[var(--border)] bg-white px-3 text-sm"
            inputMode="numeric"
            placeholder="触发价/㎡"
            value={triggerPrice}
            onChange={(event) => setTriggerPrice(event.target.value)}
          />
          <select
            className="h-9 rounded-md border border-[var(--border)] bg-white px-3 text-sm"
            value={triggerOperator}
            onChange={(event) => setTriggerOperator(event.target.value as "lte" | "gte")}
            aria-label="触发条件"
          >
            <option value="lte">低于</option>
            <option value="gte">高于</option>
          </select>
          <select
            className="h-9 rounded-md border border-[var(--border)] bg-white px-3 text-sm"
            value={priority}
            onChange={(event) => setPriority(event.target.value as "low" | "medium" | "high")}
            aria-label="优先级"
          >
            <option value="high">high</option>
            <option value="medium">medium</option>
            <option value="low">low</option>
          </select>
          <input
            className="h-9 rounded-md border border-[var(--border)] bg-white px-3 text-sm"
            placeholder="标签"
            value={label}
            onChange={(event) => setLabel(event.target.value)}
          />
          <input
            className="h-9 rounded-md border border-[var(--border)] bg-white px-3 text-sm"
            placeholder="备注"
            value={notes}
            onChange={(event) => setNotes(event.target.value)}
          />
          <Button
            disabled={
              creating ||
              (targetType === "area" && !areaId) ||
              (targetType === "neighborhood" && !neighborhoodId)
            }
            aria-label="加入观察池"
            title="加入观察池"
          >
            <Plus className="h-4 w-4" />
          </Button>
        </form>
      </CardHeader>
      <CardContent className="overflow-x-auto p-0">
        <WatchlistTable
          items={items}
          areaNameById={areaNameById}
          neighborhoodNameById={neighborhoodNameById}
          loading={loading}
          archivingId={archivingId}
          updatingId={updatingId}
          expandedItemId={expandedItemId}
          events={events}
          creatingEvent={creatingEvent}
          onUpdate={onUpdate}
          onCreateEvent={onCreateEvent}
          onArchive={onArchive}
          onToggleEvents={onToggleEvents}
        />
      </CardContent>
    </Card>
  );
}

function WatchlistTable({
  items,
  areaNameById,
  neighborhoodNameById,
  loading,
  archivingId,
  updatingId,
  expandedItemId,
  events,
  creatingEvent,
  onUpdate,
  onCreateEvent,
  onArchive,
  onToggleEvents,
}: {
  items: WatchlistItem[];
  areaNameById: Map<string, string>;
  neighborhoodNameById: Map<string, string>;
  loading: boolean;
  archivingId: number | null;
  updatingId: number | null;
  expandedItemId: number | null;
  events: WatchlistEvent[];
  creatingEvent: boolean;
  onUpdate: (id: number, payload: UpdateWatchlistItem) => void;
  onCreateEvent: (id: number, payload: CreateWatchlistEvent) => void;
  onArchive: (id: number) => void;
  onToggleEvents: (id: number) => void;
}) {
  const [eventSummary, setEventSummary] = useState("");

  if (loading) {
    return <div className="h-32 bg-[var(--muted)]" />;
  }

  if (items.length === 0) {
    return <div className="px-4 py-8 text-sm text-[var(--muted-foreground)]">暂无观察标的</div>;
  }

  return (
    <Table>
      <TableHeader>
        <TableRow>
          <TableHead>标的</TableHead>
          <TableHead className="text-right">目标价/㎡</TableHead>
          <TableHead className="text-right">触发价/㎡</TableHead>
          <TableHead>标签</TableHead>
          <TableHead>优先级</TableHead>
          <TableHead>状态</TableHead>
          <TableHead>复核</TableHead>
          <TableHead>备注</TableHead>
          <TableHead className="w-44" />
        </TableRow>
      </TableHeader>
      <TableBody>
        {items.map((item) => {
          const targetName = item.neighborhood_id
            ? neighborhoodNameById.get(item.neighborhood_id) ?? item.neighborhood_id
            : item.area_id
              ? areaNameById.get(item.area_id) ?? item.area_id
              : "-";
          const expanded = expandedItemId === item.watchlist_item_id;
          return (
            <Fragment key={item.watchlist_item_id}>
              <TableRow>
                <TableCell className="font-medium">{targetName}</TableCell>
                <TableCell className="text-right">
                  {item.target_price_psm ? formatPrice(item.target_price_psm) : "-"}
                </TableCell>
                <TableCell className="text-right">
                  {item.trigger_price_psm
                    ? `${item.trigger_operator === "lte" ? "≤" : "≥"} ${formatPrice(item.trigger_price_psm)}`
                    : "-"}
                </TableCell>
                <TableCell>
                  <Badge variant="muted">{item.label}</Badge>
                </TableCell>
                <TableCell>
                  <Badge variant={item.priority === "high" ? "warning" : "muted"}>
                    {item.priority}
                  </Badge>
                </TableCell>
                <TableCell>
                  <select
                    className="h-8 rounded-md border border-[var(--border)] bg-white px-2 text-sm"
                    value={item.status}
                    disabled={updatingId === item.watchlist_item_id}
                    onChange={(event) =>
                      onUpdate(item.watchlist_item_id, {
                        status: event.target.value as "active" | "paused" | "archived",
                      })
                    }
                    aria-label="观察池状态"
                  >
                    <option value="active">active</option>
                    <option value="paused">paused</option>
                    <option value="archived">archived</option>
                  </select>
                </TableCell>
                <TableCell className="text-[var(--muted-foreground)]">
                  {item.last_reviewed_at ? item.last_reviewed_at.slice(0, 10) : "-"}
                </TableCell>
                <TableCell className="max-w-80 truncate text-[var(--muted-foreground)]">
                  {item.notes || "-"}
                </TableCell>
                <TableCell className="text-right">
                  <div className="flex justify-end gap-2">
                    <Button
                      variant="outline"
                      disabled={updatingId === item.watchlist_item_id}
                      onClick={() =>
                        onUpdate(item.watchlist_item_id, {
                          mark_reviewed: true,
                        })
                      }
                      aria-label="复核"
                      title="复核"
                    >
                      <Check className="h-4 w-4" />
                    </Button>
                    <Button
                      variant="outline"
                      onClick={() => onToggleEvents(item.watchlist_item_id)}
                      aria-label="历史"
                      title="历史"
                    >
                      <Activity className="h-4 w-4" />
                    </Button>
                    <Button
                      variant="outline"
                      disabled={archivingId === item.watchlist_item_id}
                      onClick={() => onArchive(item.watchlist_item_id)}
                      aria-label="归档"
                      title="归档"
                    >
                      <Archive className="h-4 w-4" />
                    </Button>
                  </div>
                </TableCell>
              </TableRow>
              {expanded ? (
                <TableRow>
                  <TableCell colSpan={9} className="bg-slate-50">
                    <div className="grid gap-3 lg:grid-cols-[minmax(0,1fr)_360px]">
                      <div className="grid gap-2">
                        {events.length === 0 ? (
                          <p className="text-sm text-[var(--muted-foreground)]">暂无历史</p>
                        ) : (
                          events.map((event) => (
                            <div
                              key={event.event_id}
                              className="rounded-md border border-[var(--border)] bg-white px-3 py-2"
                            >
                              <div className="flex items-center justify-between gap-3">
                                <Badge variant="muted">{event.event_type}</Badge>
                                <span className="text-xs text-[var(--muted-foreground)]">
                                  {event.created_at.slice(0, 16)}
                                </span>
                              </div>
                              <p className="mt-2 text-sm text-slate-950">{event.summary}</p>
                            </div>
                          ))
                        )}
                      </div>
                      <form
                        className="flex gap-2"
                        onSubmit={(event) => {
                          event.preventDefault();
                          if (!eventSummary.trim()) {
                            return;
                          }
                          onCreateEvent(item.watchlist_item_id, {
                            event_type: "note",
                            summary: eventSummary.trim(),
                          });
                          setEventSummary("");
                        }}
                      >
                        <input
                          className="h-9 min-w-0 flex-1 rounded-md border border-[var(--border)] bg-white px-3 text-sm"
                          placeholder="新增历史备注"
                          value={eventSummary}
                          onChange={(event) => setEventSummary(event.target.value)}
                        />
                        <Button
                          disabled={creatingEvent || !eventSummary.trim()}
                          aria-label="新增历史"
                          title="新增历史"
                        >
                          <Plus className="h-4 w-4" />
                        </Button>
                      </form>
                    </div>
                  </TableCell>
                </TableRow>
              ) : null}
            </Fragment>
          );
        })}
      </TableBody>
    </Table>
  );
}

function ModelRunPanel({
  runs,
  diff,
  baseRunId,
  candidateRunId,
  loading,
  creating,
  onBaseRunChange,
  onCandidateRunChange,
  onCreateRun,
}: {
  runs: ModelRun[];
  diff: AreaScoreRunDiff | null;
  baseRunId: number | null;
  candidateRunId: number | null;
  loading: boolean;
  creating: boolean;
  onBaseRunChange: (id: number | null) => void;
  onCandidateRunChange: (id: number | null) => void;
  onCreateRun: (payload: CreateAreaScoreModelRun) => void;
}) {
  const [modelVersion, setModelVersion] = useState("area-score-rust-v1");

  function submit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();
    onCreateRun({
      month: MONTH,
      model_version: modelVersion.trim() || undefined,
    });
  }

  return (
    <Card>
      <CardHeader className="flex flex-col gap-3 xl:flex-row xl:items-start xl:justify-between">
        <div>
          <CardTitle>模型运行中心</CardTitle>
          <p className="mt-1 text-sm text-[var(--muted-foreground)]">
            板块评分批次与版本差异
          </p>
        </div>
        <form className="flex flex-col gap-2 sm:flex-row" onSubmit={submit}>
          <input
            className="h-9 w-full rounded-md border border-[var(--border)] bg-white px-3 text-sm sm:w-56"
            placeholder="模型版本"
            value={modelVersion}
            onChange={(event) => setModelVersion(event.target.value)}
          />
          <Button disabled={creating} aria-label="重算板块评分" title="重算板块评分">
            <RefreshCw className="h-4 w-4" />
          </Button>
        </form>
      </CardHeader>
      <CardContent className="grid gap-4 xl:grid-cols-[420px_minmax(0,1fr)]">
        <ModelRunTable runs={runs} loading={loading} />
        <div className="grid gap-3">
          <div className="grid gap-2 sm:grid-cols-2">
            <ModelRunSelect
              label="基准批次"
              runs={runs}
              value={baseRunId}
              onChange={onBaseRunChange}
            />
            <ModelRunSelect
              label="候选批次"
              runs={runs}
              value={candidateRunId}
              onChange={onCandidateRunChange}
            />
          </div>
          <AreaScoreRunDiffTable diff={diff} loading={loading} />
        </div>
      </CardContent>
    </Card>
  );
}

function ModelRunTable({ runs, loading }: { runs: ModelRun[]; loading: boolean }) {
  if (loading) {
    return <div className="h-56 rounded-md bg-[var(--muted)]" />;
  }

  if (runs.length === 0) {
    return (
      <div className="rounded-md border border-[var(--border)] px-4 py-8 text-sm text-[var(--muted-foreground)]">
        暂无模型运行
      </div>
    );
  }

  return (
    <div className="overflow-x-auto rounded-md border border-[var(--border)]">
      <Table>
        <TableHeader>
          <TableRow>
            <TableHead>批次</TableHead>
            <TableHead>版本</TableHead>
            <TableHead>月份</TableHead>
            <TableHead>状态</TableHead>
            <TableHead className="text-right">行数</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          {runs.slice(0, 8).map((run) => (
            <TableRow key={run.model_run_id}>
              <TableCell className="font-medium">#{run.model_run_id}</TableCell>
              <TableCell className="max-w-40 truncate">{run.model_version}</TableCell>
              <TableCell>{run.target_month}</TableCell>
              <TableCell>
                <Badge variant={run.status === "succeeded" ? "success" : "warning"}>
                  {run.status}
                </Badge>
              </TableCell>
              <TableCell className="text-right">{run.row_count ?? "-"}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  );
}

function ModelRunSelect({
  label,
  runs,
  value,
  onChange,
}: {
  label: string;
  runs: ModelRun[];
  value: number | null;
  onChange: (id: number | null) => void;
}) {
  return (
    <label className="grid gap-1">
      <span className="text-sm font-medium text-slate-950">{label}</span>
      <select
        className="h-9 rounded-md border border-[var(--border)] bg-white px-3 text-sm"
        value={value ?? ""}
        onChange={(event) => onChange(event.target.value ? Number(event.target.value) : null)}
      >
        <option value="">选择批次</option>
        {runs.map((run) => (
          <option key={run.model_run_id} value={run.model_run_id}>
            #{run.model_run_id} · {run.model_version} · {run.target_month}
          </option>
        ))}
      </select>
    </label>
  );
}

function AreaScoreRunDiffTable({
  diff,
  loading,
}: {
  diff: AreaScoreRunDiff | null;
  loading: boolean;
}) {
  if (loading) {
    return <div className="h-56 rounded-md bg-[var(--muted)]" />;
  }

  if (!diff || diff.items.length === 0) {
    return (
      <div className="rounded-md border border-[var(--border)] px-4 py-8 text-sm text-[var(--muted-foreground)]">
        暂无评分差异
      </div>
    );
  }

  return (
    <div className="overflow-x-auto rounded-md border border-[var(--border)]">
      <Table>
        <TableHeader>
          <TableRow>
            <TableHead>板块</TableHead>
            <TableHead>行政区</TableHead>
            <TableHead className="text-right">基准</TableHead>
            <TableHead className="text-right">候选</TableHead>
            <TableHead className="text-right">差异</TableHead>
            <TableHead>建议变化</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          {diff.items.map((item) => (
            <TableRow key={item.area_id}>
              <TableCell className="font-medium">{item.name}</TableCell>
              <TableCell>{item.district}</TableCell>
              <TableCell className="text-right">
                {item.base_score === null ? "-" : formatNumber(item.base_score)}
              </TableCell>
              <TableCell className="text-right">
                {item.candidate_score === null ? "-" : formatNumber(item.candidate_score)}
              </TableCell>
              <TableCell className="text-right font-semibold">
                {item.score_delta === null ? "-" : formatSignedNumber(item.score_delta)}
              </TableCell>
              <TableCell>
                {item.base_recommendation === item.candidate_recommendation ? (
                  <Badge variant="muted">{item.candidate_recommendation ?? "-"}</Badge>
                ) : (
                  <Badge variant="warning">
                    {item.base_recommendation ?? "-"} → {item.candidate_recommendation ?? "-"}
                  </Badge>
                )}
              </TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  );
}

function MetricCard({
  title,
  value,
  textValue,
  suffix,
  icon,
  loading,
  tone = "default",
}: {
  title: string;
  value?: number;
  textValue?: string;
  suffix?: string;
  icon: React.ReactNode;
  loading: boolean;
  tone?: "default" | "warning";
}) {
  return (
    <Card>
      <CardContent className="flex min-h-28 items-center justify-between gap-3">
        <div className="min-w-0">
          <p className="text-sm text-[var(--muted-foreground)]">{title}</p>
          <p className="mt-2 truncate text-2xl font-semibold text-slate-950">
            {loading ? "-" : textValue ?? `${formatNumber(value ?? 0)}${suffix ?? ""}`}
          </p>
        </div>
        <div
          className={
            tone === "warning"
              ? "flex h-10 w-10 shrink-0 items-center justify-center rounded-md bg-amber-50 text-[var(--warning)]"
              : "flex h-10 w-10 shrink-0 items-center justify-center rounded-md bg-teal-50 text-[var(--primary)]"
          }
        >
          {icon}
        </div>
      </CardContent>
    </Card>
  );
}

function ScoreChart({ scores, loading }: { scores: AreaScore[]; loading: boolean }) {
  if (loading) {
    return <div className="h-full rounded-md bg-[var(--muted)]" />;
  }

  return (
    <ResponsiveContainer width="100%" height="100%">
      <BarChart data={scores} margin={{ top: 8, right: 12, left: 0, bottom: 4 }}>
        <CartesianGrid stroke="#e2e8f0" vertical={false} />
        <XAxis dataKey="name" tickLine={false} axisLine={false} tick={{ fontSize: 12 }} />
        <YAxis domain={[0, 100]} tickLine={false} axisLine={false} tick={{ fontSize: 12 }} />
        <Tooltip
          cursor={{ fill: "#eef2f7" }}
          formatter={(value) => [`${formatNumber(Number(value))} 分`, "综合分"]}
          labelStyle={{ color: "#172033" }}
        />
        <Bar dataKey="investment_score" fill="#0f766e" radius={[4, 4, 0, 0]} />
      </BarChart>
    </ResponsiveContainer>
  );
}

function InsightRow({ label, value, detail }: { label: string; value: string; detail: string }) {
  return (
    <div className="flex items-center justify-between gap-3 border-b border-[var(--border)] pb-3 last:border-0 last:pb-0">
      <div className="min-w-0">
        <p className="text-sm text-[var(--muted-foreground)]">{label}</p>
        <p className="mt-1 truncate text-base font-semibold text-slate-950">{value}</p>
      </div>
      <Badge variant="muted">{detail}</Badge>
    </div>
  );
}

function AreaScoreTable({
  scores,
  loading,
  selectedAreaId,
  onSelectArea,
}: {
  scores: AreaScore[];
  loading: boolean;
  selectedAreaId: string;
  onSelectArea: (areaId: string) => void;
}) {
  if (loading) {
    return <div className="h-48 bg-[var(--muted)]" />;
  }

  return (
    <Table>
      <TableHeader>
        <TableRow>
          <TableHead>板块</TableHead>
          <TableHead>行政区</TableHead>
          <TableHead className="text-right">综合分</TableHead>
          <TableHead>建议</TableHead>
          <TableHead className="text-right">成交均价/㎡</TableHead>
          <TableHead className="text-right">成交套数</TableHead>
          <TableHead className="text-right">租金收益率</TableHead>
          <TableHead className="text-right">供应风险</TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        {scores.map((score) => (
          <TableRow
            key={score.area_id}
            className={
              selectedAreaId === score.area_id
                ? "cursor-pointer bg-teal-50/60"
                : "cursor-pointer hover:bg-slate-50"
            }
            onClick={() => onSelectArea(score.area_id)}
          >
            <TableCell className="font-medium">{score.name}</TableCell>
            <TableCell>{score.district}</TableCell>
            <TableCell className="text-right font-semibold">
              {formatNumber(score.investment_score)}
            </TableCell>
            <TableCell>
              <Badge variant={score.recommendation === "观察池" ? "success" : "muted"}>
                {score.recommendation}
              </Badge>
            </TableCell>
            <TableCell className="text-right">{formatPrice(score.transaction_price_psm)}</TableCell>
            <TableCell className="text-right">{score.transaction_count}</TableCell>
            <TableCell className="text-right">
              {formatNumber(score.annual_rent_yield_pct, 2)}%
            </TableCell>
            <TableCell className="text-right">{formatNumber(score.supply_risk_score)}</TableCell>
          </TableRow>
        ))}
      </TableBody>
    </Table>
  );
}

function ErrorState() {
  return (
    <div className="rounded-lg border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-800">
      API 暂时不可用
    </div>
  );
}
