"use client";

import Link from "next/link";
import { useRouter } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
import { AreaModal } from "@/components/features/detail-log/AreaModal";
import { CleanLogTable } from "@/components/features/detail-log/CleanLogTable";
import { Pill } from "@/components/ui/Pill";
import { StatCard } from "@/components/ui/StatCard";
import { useTheme } from "@/components/theme/ThemeProvider";
import { apiFetch, ApiFetchError } from "@/lib/api/client";
import type { AreaFilter } from "@/lib/clean-logs/areas";
import { DASHBOARD_LATEST_RUN, SUCCESS_RATE_TARGET } from "@/lib/config";
import { formatMd } from "@/lib/format/date";
import { displayRate } from "@/lib/judge";
import { THEME_TOKENS } from "@/lib/theme";
import type {
  AreaCompletionsResponse,
  CleanLogAdviceResponse,
  CleanLogItem,
  DashboardResponse,
} from "@/lib/types";
import { RescueRequiredCard } from "./RescueRequiredCard";
import { ScheduleProgressCard } from "./ScheduleProgressCard";
import { SecurityBanner } from "./SecurityBanner";
import styles from "./DashboardScreen.module.css";

/**
 * エリアモーダルを開いている対象。
 *
 * ★`logId` は「エリアを特定するキー」ではなく、施設と日付を特定するための入口★
 * （指示書Z 決定1）。施設名・日付はヘッダに出すためだけに持つ。
 *
 * ★`DetailLogScreen.tsx` の同名の型と同じ形をあえて各画面に持たせている★ —
 * API契約（`d-watch-api.yaml`）のスキーマではなく画面内部の都合の型なので、
 * `lib/types/`（YAMLのスキーマ名と揃える場所。`CLAUDE.md` §9.2）には置かない。
 */
interface AreaTarget {
  logId: number;
  facilityName: string;
  dateLabel: string;
}

/**
 * コンパクト版の清掃詳細ログに出す件数（★`problem_logs` の先頭5件★。D-159 / 指示書AV 段階5-1）。
 * ★API は件数で切らない★（1清掃エリア最大1行なので、エリア数が自然な上限。指示書AV D11）。
 * 要レスキューの初期表示（`RESCUE_INITIAL_VISIBLE`）はこの5件に揃えてある。
 * 旧: `/api/clean-logs` に `per_page: "5"` を付けて取っていた
 */
const COMPACT_LOG_ROWS = 5;

export function DashboardScreen() {
  const { t, theme } = useTheme();
  const c = THEME_TOKENS[theme];
  const router = useRouter();

  const [data, setData] = useState<DashboardResponse | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  const [expandedId, setExpandedId] = useState<number | null>(null);
  const [adviceById, setAdviceById] = useState<
    Record<number, CleanLogAdviceResponse | undefined>
  >({});
  const [adviceLoadingId, setAdviceLoadingId] = useState<number | null>(null);
  const [areaTarget, setAreaTarget] = useState<AreaTarget | null>(null);
  // 「判定 ?」の凡例。★ボタンは compact でも出ている★ので、状態を持たせないと
  // 「押しても何も起きないボタン」になる（D-143 §3 #2 / 指示書AO 段階6）
  const [showGradeInfo, setShowGradeInfo] = useState(false);
  // ★既定は "problem"（YAML の default と同じ）★
  const [areaFilter, setAreaFilter] = useState<AreaFilter>("problem");
  const [areaData, setAreaData] = useState<AreaCompletionsResponse | null>(null);
  const [areaLoading, setAreaLoading] = useState(false);
  const [areaError, setAreaError] = useState<string | null>(null);

  const fetchAll = useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      // ★`/api/dashboard` の1本だけ★（D-159 / 指示書AV D7）。コンパクト版ログは
      // レスポンスの `problem_logs`（数値カードと★同じ `now`・同じ行の集合★の △×）を使う。
      // ★`/api/clean-logs` を別に呼ばない★ — 2本に分けると、それぞれが別々に「現在」を取るので
      // 境界（終了＋落ち着き時間ちょうど付近）でカードと表の母集団がずれうる。
      // トグルは持たせない（全件を見たいときは「全項目 →」で F-02 に遷移する切り分け）
      const dashboard = await apiFetch<DashboardResponse>("/api/dashboard");
      setData(dashboard);
    } catch (e) {
      // 失敗したら画面全体をエラー表示にする（部分表示はこの画面の要件にない）
      setError(
        (e instanceof ApiFetchError ? e.serverMessage : null) ??
          "データの取得に失敗しました"
      );
      setData(null);
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    // eslint-disable-next-line react-hooks/set-state-in-effect -- fetch 起点の初期表示
    void fetchAll();
  }, [fetchAll]);

  /**
   * 行を開く / 閉じる（`DetailLogScreen.tsx` からの移植。指示書AO 段階5）。
   *
   * ★compact でも展開する★ — かつて「ダッシュボードではアドバイスを一切出さない
   *   決まりがある」とされていたが、そのような決まりはどの資料にも存在しなかった（grep 0件）。
   *   ユーザー判断で展開を有効にした（2026-09-16 / D-143 §3 #6）。
   */
  async function toggleRow(row: CleanLogItem) {
    if (expandedId === row.id) {
      setExpandedId(null);
      return;
    }
    setExpandedId(row.id);
    if (adviceById[row.id]) return;
    setAdviceLoadingId(row.id);
    try {
      const body = await apiFetch<CleanLogAdviceResponse>(
        `/api/clean-logs/${row.id}/advice`
      );
      setAdviceById((prev) => ({ ...prev, [row.id]: body }));
    } catch {
      // 取得できなくても行は開く。一覧が持っている値で最小限の中身を埋める
      setAdviceById((prev) => ({
        ...prev,
        [row.id]: {
          escalation_level: row.escalation_level,
          consecutive_problem_days: row.consecutive_problem_days,
          // ★一覧の行も同じ上限を通ってきている★（指示書AF 段階6。/clean-logs と
          //   /clean-logs/{id}/advice で連続日数が食い違わないようにするため）。
          //   一覧が古くてフラグを持っていなければ「頭打ちではない」として扱う
          consecutive_problem_days_capped:
            row.consecutive_problem_days_capped ?? false,
          max_level: 0,
          advices: [],
        },
      }));
    } finally {
      setAdviceLoadingId(null);
    }
  }

  /**
   * エリアモーダルを開く。
   *
   * ★ここでは取得しない★ — 取得は下の `useEffect` に寄せてある。
   *   ★`filter` を切り替えたら**取り直す**必要がある★（絞り込みはサーバ側。指示書Z 決定8）ので、
   *   「開いたとき」と「切り替えたとき」で取得経路が2本に分かれないようにした。
   * ★開くたびに `filter` を既定（問題エリアのみ）に戻す★ — 前に見た行の選択が残ると、
   *   別の行を開いたときに何を見ているのか分からなくなる。
   */
  function openAreas(row: CleanLogItem) {
    setAreaFilter("problem");
    setAreaTarget({
      logId: row.id,
      facilityName: row.facility_name,
      dateLabel: formatMd(row.scheduled_at),
    });
  }

  const areaLogId = areaTarget?.logId ?? null;
  useEffect(() => {
    if (areaLogId === null) return;
    // 取得中に別の行を開く / filter を切り替えると、古い応答が後から届いて
    // 新しい表示を上書きしうるので破棄する
    let cancelled = false;
    // eslint-disable-next-line react-hooks/set-state-in-effect -- fetch 起点のモーダル更新
    setAreaLoading(true);
    setAreaError(null);
    setAreaData(null);
    void (async () => {
      try {
        const body = await apiFetch<AreaCompletionsResponse>(
          `/api/clean-logs/${areaLogId}/areas?filter=${areaFilter}`
        );
        if (!cancelled) setAreaData(body);
      } catch (e) {
        // ★取得できなくてもモーダルは開いたままにする★ —
        //   ボタンを押して何も起きないのがいちばん困るため、失敗はモーダルの中に出す
        if (!cancelled) {
          setAreaError(
            (e instanceof ApiFetchError ? e.serverMessage : null) ??
              "データの取得に失敗しました"
          );
        }
      } finally {
        if (!cancelled) setAreaLoading(false);
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [areaLogId, areaFilter]);

  // 「成功 / N件」の N は schedule_progress.planned とは別概念（D-049）。
  // planned はスケジュールマスタ由来（当日）、こちらは summary の母集団の行数（成功+失敗+未実施+★結果待ち★）。
  // ★結果待ちも N に入れる★（ユーザー判断 2026-10-07。D-173 / 表2 C8）— カード5枚の数の合計が N になる。
  //   ★成功率の分母には入らない★（API の `success_rate`。C5）ので、「成功 / N件」と成功率は一致しないことがある。
  // ★直近モード（`date` なし。この画面）では、7日以内に終わった予定がある清掃エリアの数★
  // （1清掃エリアにつき直近の1件。D-159）。日数は `DASHBOARD_LATEST_RUN.lookbackDays`。
  // 施設数とは一致しない（1施設に複数の清掃エリアがある場合は施設数を上回る）。
  // ラベルも「件」であって「施設」ではない。
  const totalLogs = data
    ? data.summary.success +
      data.summary.failed +
      data.summary.not_executed +
      data.summary.pending
    : 0;
  // ★先頭5件だけ出す★（並びは API のまま＝データソースの順。画面で並べ替えない）
  const compactLogs = data ? data.problem_logs.slice(0, COMPACT_LOG_ROWS) : [];

  return (
    <div className={styles.page}>
      <div className={styles.header}>
        <div>
          <h1 className={styles.title}>{t("ダッシュボード")}</h1>
          {/* ★モック（`d-watch-mock.jsx:476`）と文言が違う★ — D-159 でユーザーが変えた
              （数値カードと清掃詳細ログは「各清掃エリアの直近の清掃（7日以内）」になったため。
              旧: 当日の日付を添えた「昨夜の…」の見出し）。D-120 / D-121 と同じ扱いで、★モックは書き換えない★。
              ★日数は直書きしない★（`DASHBOARD_LATEST_RUN.lookbackDays` を {n} に差し込む。指示書AV 4-13）。
              ★このコメントを消さないこと★ — 次にモックと突き合わせた人が文言を戻してしまう */}
          <div className={styles.subtitle}>
            {t("直近の清掃結果サマリー（各清掃エリアの最後の清掃・{n}日以内）").replace(
              "{n}",
              String(DASHBOARD_LATEST_RUN.lookbackDays)
            )}
          </div>
        </div>
        <div className={styles.headerRight}>
          {data && data.security_banner.length > 0 && (
            <Pill text={`D-Sec ${data.security_banner.length}件`} color={c.wn} />
          )}
        </div>
      </div>

      {loading && <div className={styles.loading}>読み込み中…</div>}
      {!loading && error && <div className={styles.error}>{error}</div>}

      {!loading && !error && data && (
        <>
          <SecurityBanner alerts={data.security_banner} />

          <div className={styles.statGrid}>
            <StatCard
              value={String(data.summary.success)}
              label={t("成功")}
              sub={`/ ${totalLogs}件`}
              tone="ok"
            />
            <StatCard
              value={String(data.summary.failed)}
              label={t("失敗")}
              tone="ng"
              onClick={() => router.push("/detail-log")}
            />
            <StatCard
              value={String(data.summary.not_executed)}
              label={t("未実施")}
              sub={t("予定時刻経過・実績なし")}
              tone="wn"
              onClick={() => router.push("/detail-log")}
            />
            {/* ★結果待ち★（ユーザー判断 2026-10-07「ダッシュボードに『結果待ち』カードを足す」。D-173 / 表2 C8）—
                レポートがまだ届いていない清掃（予定の終了から `RESULT_WAIT_MINUTES` 分以内）。失敗に数えない。
                ★モックに無いカード★（D-120 / D-121 と同じ扱いで、モックは書き換えない）。
                ★色は控えめに★: 中立の `mt`（成功率の `ac` と見分けるため。監督判断 2026-10-07）。 */}
            <StatCard
              value={String(data.summary.pending)}
              label={t("結果待ち")}
              sub={t("レポートの到着待ち")}
              tone="mt"
            />
            <StatCard
              // "-" は displayRate() の既存の null 表記をそのまま使う（アプリ全体で
              // completion_rate 等の null 表記と統一するため。ここだけ別の記号にしない）
              value={displayRate(data.summary.success_rate)}
              label={t("成功率")}
              sub={`目標: ${SUCCESS_RATE_TARGET}%`}
              tone="ac"
            />
          </div>

          {/*
            要レスキュー店舗（ユーザー指示 2026-09-15 / 指示書AI 段階6）。
            ★位置は「数値カードの直後・清掃詳細ログの直前」で固定★ —
            ユーザー指示の原文「要レスキュー店舗をダッシュボード上の詳細ログの上に表示」。

            ★★モックに対応する画面が無い（`CLAUDE.md` §5 の例外）★★ —
            D-120 / D-121 と同じ扱いで、`d-watch-mock.jsx` は1文字も書き換えていない。
            ★このコメントを消さないこと★ — 次にモックと突き合わせた人が
            「余計なものがある」と判断して消してしまう。理由は `RescueRequiredCard.tsx` の
            docstring に書いてある。

            ★★数値カード（上）とは母集団が違う★★ — こちらは
            ★当日に清掃予定が無い施設も含む★（指示書AI §1-5）ので、件数は一致しない。
            ★数値カードは直近モード（各清掃エリアの直近の清掃・7日以内。D-159）、
            要レスキューは当日基準★（基準日は当日のまま。指示書AV 4-1）。
          */}
          <RescueRequiredCard items={data.rescue_required} />

          <div className={styles.card}>
            <div className={styles.cardHeader}>
              <span className={styles.cardTitle}>{t("清掃詳細ログ")}</span>
              <Link href="/detail-log" className={styles.allLink}>
                {t("全項目 →")}
              </Link>
            </div>
            <div className={styles.tableWrap}>
              <CleanLogTable
                rows={compactLogs}
                compact
                expandedId={expandedId}
                adviceById={adviceById}
                adviceLoadingId={adviceLoadingId}
                showGradeInfo={showGradeInfo}
                onToggleGradeInfo={() => setShowGradeInfo((v) => !v)}
                onToggleRow={toggleRow}
                onOpenAreas={openAreas}
              />
            </div>
          </div>

          <div
            className={styles.bottomGrid}
            style={{
              gridTemplateColumns: data.failure_breakdown ? "1fr 1fr" : "1fr",
            }}
          >
            <ScheduleProgressCard progress={data.schedule_progress} />
            {/*
              failure_breakdown は M2 では返さない（D-048）。undefined のときは
              このブロックごと非表示にする。M3 で値が来たら自動的に表示へ戻る作りにしておく
              （プロパティを消してしまうと、値が来たときにここを書き直す必要が出る）
            */}
            {data.failure_breakdown && (
              <div className={`${styles.card} ${styles.panel}`}>
                <div className={styles.failureHeading}>失敗の内訳</div>
                {data.failure_breakdown.map((f) => (
                  <div key={f.type} className={styles.failureRow}>
                    <span>{f.label}</span>
                    <span>{f.count}</span>
                  </div>
                ))}
              </div>
            )}
          </div>
        </>
      )}

      {/* ★エリア別詳細はモーダルで開く（`/detail-log` へ遷移させない）★ —
          モックは compact でもモーダルを開く（`d-watch-mock.jsx:358` は `LogTable` の
          中にあり `compact` で分岐していない）。遷移にすると開いていた行が失われる。
          ★`components/features/detail-log/AreaModal` を import して使う（複製しない）★ */}
      {areaTarget && (
        <AreaModal
          facilityName={areaTarget.facilityName}
          dateLabel={areaTarget.dateLabel}
          filter={areaFilter}
          onChangeFilter={setAreaFilter}
          data={areaData}
          loading={areaLoading}
          error={areaError}
          onClose={() => setAreaTarget(null)}
        />
      )}
    </div>
  );
}
