./knowledge-base/frontend/src/pages/SearchPage.tsx

import { useState, useMemo } from "react";
import { useQuery } from "@tanstack/react-query";
import {
  Container,
  Box,
  Typography,
  TextField,
  Button,
  Grid,
  Card,
  CardActionArea,
  FormControl,
  InputLabel,
  Select,
  MenuItem,
  Chip,
  CircularProgress,
  AppBar,
  Toolbar,
  Switch,
  FormControlLabel,
  Paper,
} from "@mui/material";
import {
  Search as SearchIcon,
  Upload as UploadIcon,
  Home as HomeIcon,
} from "@mui/icons-material";
import { useNavigate } from "react-router-dom";
import {
  searchPages,
  loadEraMasterList,
  fetchPrograms,
  fetchBookCounts,
  fetchAllDocuments,
} from "../api/client";
import type {
  SearchRequest,
  SearchResultItem,
  ProgramMaster,
  DocumentSummary,
} from "../types";
import { ERA_LABELS } from "../utils/eraUtils";
import {
  type BookGroup,
  SearchResultCard,
  BookGroupCard,
} from "../components/SearchCards";
import { DetailDialog, BookPagesDialog } from "../components/SearchDialogs";
import {
  BrowseEraList,
  BrowseProgramList,
  BrowseBookTitleList,
} from "../components/BrowseLists";
import { DOCUMENT_GENRE_OPTIONS } from "../components/DocumentGenreSelector";

const UNSET_FILTER_VALUE = "__UNSET__";
const UNSET_LABEL = "未設定";

function normalizeFilterKey(value: string): string {
  return value.trim().replace(/戰/g, "戦");
}

export default function SearchPage() {
  const navigate = useNavigate();

  // ---- 検索フォーム state ----
  const [query, setQuery] = useState("");
  const [searchTitle, setSearchTitle] = useState("");
  const [selectedProgram, setSelectedProgram] = useState("");
  // 時代:9ラベルから複数選択
  const [selectedEraLabels, setSelectedEraLabels] = useState<string[]>([]);

  const [searchParams, setSearchParams] = useState<SearchRequest | null>(null);
  const [detailItem, setDetailItem] = useState<SearchResultItem | null>(null);
  const [groupByBook, setGroupByBook] = useState(true);
  const [selectedBook, setSelectedBook] = useState<BookGroup | null>(null);
  // DetailDialog が BookPagesDialog 経由で開かれた場合 true(「一覧に戻る」表示制御)
  const [detailFromBook, setDetailFromBook] = useState(false);

  // ブラウズモード
  const [browseMode, setBrowseMode] = useState<
    null | "era" | "program" | "book" | "genre"
  >(null);
  const [browseEra, setBrowseEra] = useState<string | null>(null);
  const [browseProgram, setBrowseProgram] = useState<string | null>(null);
  const [browseBook, setBrowseBook] = useState<string | null>(null);
  // 書籍タイトルブラウズ内のジャンル絞り込み state
  // null → ジャンル選択画面、文字列 → 書籍一覧画面
  const [browseBookGenre, setBrowseBookGenre] = useState<string | null>(null);
  // 検索フォームの資料ジャンルフィルタ
  const [selectedDocGenre, setSelectedDocGenre] = useState("");

  // 時代マスタを読み込み
  const { data: eraList = [] } = useQuery({
    queryKey: ["era-master"],
    queryFn: loadEraMasterList,
    staleTime: Infinity,
  });

  // 番組マスタを読み込み
  const { data: programList = [] } = useQuery<ProgramMaster[]>({
    queryKey: ["programs"],
    queryFn: fetchPrograms,
    staleTime: 5 * 60 * 1000,
  });

  // 書籍タイトル一覧(書籍タイトルで検索・ジャンルで検索用)
  const { data: documentList = [], isLoading: documentListLoading } = useQuery<
    DocumentSummary[]
  >({
    queryKey: ["all-documents"],
    queryFn: fetchAllDocuments,
    staleTime: 5 * 60 * 1000,
    enabled: browseMode === "book" || browseMode === "genre",
  });

  // 書籍数集計(時代・番組名)
  const { data: bookCounts } = useQuery({
    queryKey: ["book-counts"],
    queryFn: fetchBookCounts,
    staleTime: 60 * 1000,
  });

  const unsetFilterValue = bookCounts?.unset_filter_value || UNSET_FILTER_VALUE;

  const eraCountMap = useMemo(() => {
    const map = new Map<string, number>();
    for (const item of bookCounts?.era_counts || []) {
      map.set(item.filter_value, item.count);
      map.set(normalizeFilterKey(item.filter_value), item.count);
    }
    return map;
  }, [bookCounts]);

  const programCountMap = useMemo(() => {
    const map = new Map<string, number>();
    for (const item of bookCounts?.program_counts || []) {
      map.set(item.filter_value, item.count);
      map.set(normalizeFilterKey(item.filter_value), item.count);
    }
    return map;
  }, [bookCounts]);

  // ブラウズモード検索パラメータ
  const browseSearchRequest = useMemo<SearchRequest | null>(() => {
    if (browseMode === "era" && browseEra) {
      const filters: SearchRequest["filters"] = { era: browseEra };
      return { query: "", filters, page: 1, page_size: 100 };
    }
    if (browseMode === "program" && browseProgram) {
      return {
        query: "",
        filters: { programs: [browseProgram] },
        page: 1,
        page_size: 100,
      };
    }
    return null;
  }, [browseMode, browseEra, browseProgram]);

  // 検索実行
  const {
    data: searchResults,
    isLoading,
    error,
  } = useQuery({
    queryKey: ["search", searchParams],
    queryFn: () => searchPages(searchParams!),
    enabled: !!searchParams,
  });

  // 書籍グループ化(document_id 単位でまとめ、page_number 最小を代表サムネにする)
  const bookGroups = useMemo<BookGroup[]>(() => {
    if (!searchResults) return [];
    const map = new Map<string, BookGroup>();
    for (const item of searchResults.results) {
      const key = item.document_id;
      if (!map.has(key)) {
        map.set(key, {
          document_id: key,
          title: item.title,
          thumbnailItem: item,
          pages: [item],
        });
      } else {
        const group = map.get(key)!;
        group.pages.push(item);
        // page_number が小さい方を代表サムネに
        if (item.page_number < group.thumbnailItem.page_number) {
          group.thumbnailItem = item;
        }
      }
    }
    // ページ数降順で並べ替え(ヒット数が多い書籍を上位に)
    return Array.from(map.values()).sort(
      (a, b) => b.pages.length - a.pages.length,
    );
  }, [searchResults]);

  // ブラウズモード検索実行
  const { data: browseResults, isLoading: browseLoading } = useQuery({
    queryKey: ["browse", browseSearchRequest],
    queryFn: () => searchPages(browseSearchRequest!),
    enabled: !!browseSearchRequest,
  });

  // ブラウズモード書籍グループ化
  const browseBookGroups = useMemo<BookGroup[]>(() => {
    if (!browseResults) return [];
    const map = new Map<string, BookGroup>();
    for (const item of browseResults.results) {
      const key = item.document_id;
      if (!map.has(key)) {
        map.set(key, {
          document_id: key,
          title: item.title,
          thumbnailItem: item,
          pages: [item],
        });
      } else {
        const group = map.get(key)!;
        group.pages.push(item);
        if (item.page_number < group.thumbnailItem.page_number) {
          group.thumbnailItem = item;
        }
      }
    }
    return Array.from(map.values()).sort(
      (a, b) => b.pages.length - a.pages.length,
    );
  }, [browseResults]);

  const handleSearch = () => {
    // ブラウズモードを解除して通常検索に切り替え
    setBrowseMode(null);
    setBrowseEra(null);
    setBrowseProgram(null);
    setBrowseBook(null);
    setBrowseBookGenre(null);

    const filters: SearchRequest["filters"] = {};

    // タイトルフィルタ
    if (searchTitle.trim()) filters.title = searchTitle.trim();

    // 番組フィルタ
    if (selectedProgram) filters.programs = [selectedProgram];

    // 資料ジャンルフィルタ
    if (selectedDocGenre) filters.document_genre = selectedDocGenre;

    // 時代フィルタ:選択済みラベル配列を渡す
    if (selectedEraLabels.length > 0) filters.era = selectedEraLabels;

    const params: SearchRequest = {
      query,
      filters: Object.keys(filters).length > 0 ? filters : undefined,
      page: 1,
      page_size: groupByBook ? 100 : 20,
    };
    setSearchParams(params);
  };

  const browseEraLabel =
    browseEra === unsetFilterValue ? UNSET_LABEL : browseEra || "";
  const browseProgramLabel =
    browseProgram === unsetFilterValue ? UNSET_LABEL : browseProgram || "";

  return (
    <>
      {/* ヘッダー */}
      <AppBar position="static">
        <Toolbar>
          <Button
            color="inherit"
            startIcon={<HomeIcon />}
            onClick={() => navigate("/")}
            sx={{ mr: 1 }}
          >
            ホーム
          </Button>
          <Box
            component="img"
            src="/img/fav.png"
            alt="ロゴ"
            sx={{ height: 28, width: 28, mr: 1, borderRadius: 1 }}
          />
          <Typography variant="h6" sx={{ flexGrow: 1 }}>
            時代考証システム - 検索
          </Typography>
          <Button
            color="inherit"
            startIcon={<UploadIcon />}
            onClick={() => navigate("/upload")}
          >
            アップロード
          </Button>
        </Toolbar>
      </AppBar>

      {/* メインコンテンツ */}
      <Container maxWidth="lg" sx={{ mt: 4, mb: 4 }}>
        {/* ━━━ 統合検索フォーム ━━━ */}
        <Paper elevation={2} sx={{ p: 3, mb: 3 }}>
          <Grid container spacing={2}>
            {/* キーワード */}
            <Grid item xs={12} md={6}>
              <TextField
                fullWidth
                label="キーワード"
                value={query}
                onChange={(e) => setQuery(e.target.value)}
                onKeyDown={(e) => e.key === "Enter" && handleSearch()}
                placeholder="例: 昭和時代の台所"
              />
            </Grid>

            {/* タイトル */}
            <Grid item xs={12} md={6}>
              <TextField
                fullWidth
                label="タイトル(書籍名)"
                value={searchTitle}
                onChange={(e) => setSearchTitle(e.target.value)}
                onKeyDown={(e) => e.key === "Enter" && handleSearch()}
                placeholder="例: 江戸風俗図説"
              />
            </Grid>

            {/* 番組名 */}
            <Grid item xs={12} md={4}>
              <Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
                <Box
                  component="img"
                  src="/img/TV.png"
                  alt="番組名"
                  sx={{
                    width: 32,
                    height: 32,
                    objectFit: "contain",
                    flexShrink: 0,
                  }}
                />
                <FormControl fullWidth>
                  <InputLabel>番組名</InputLabel>
                  <Select
                    value={selectedProgram}
                    onChange={(e) => setSelectedProgram(e.target.value)}
                    label="番組名"
                  >
                    <MenuItem value="">すべて</MenuItem>
                    {programList.map((p) => (
                      <MenuItem key={p.id} value={p.name}>
                        {p.name}
                      </MenuItem>
                    ))}
                  </Select>
                </FormControl>
              </Box>
            </Grid>

            {/* 時代 セクション */}
            <Grid item xs={12}>
              <Box sx={{ display: "flex", alignItems: "flex-start", gap: 1 }}>
                <Box
                  component="img"
                  src="/img/kabuto.png"
                  alt="時代"
                  sx={{
                    width: 32,
                    height: 32,
                    objectFit: "contain",
                    flexShrink: 0,
                    mt: 0.5,
                  }}
                />
                <Box sx={{ flexGrow: 1 }}>
                  <Typography
                    variant="caption"
                    color="text.secondary"
                    sx={{ display: "block", mb: 0.5 }}
                  >
                    時代(複数選択可・OR条件)
                  </Typography>
                  <Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
                    {ERA_LABELS.map((label) => (
                      <Chip
                        key={label}
                        label={label}
                        onClick={() =>
                          setSelectedEraLabels((prev) =>
                            prev.includes(label)
                              ? prev.filter((l) => l !== label)
                              : [...prev, label],
                          )
                        }
                        color={
                          selectedEraLabels.includes(label)
                            ? "primary"