/**
 * fixtures 版 `cleanArea()` の集計材料づくり（指示書AN ★段階3★ / §6-4）。
 *
 * ★★`USE_FIXTURES` の true/false で数字が変わってはいけない★★（指示書AC 禁止事項6）—
 *   Prisma 版が SQL に降ろした条件（`started_at` の範囲・施設ID）と ★同じ意味★ を再現し、
 *   ★★その先のロールアップは同じ `aggregateCleanArea()` を通します★★
 *   （`CLAUDE.md` §9.5。★fixtures 用に別の集計を書かないこと★）。
 *
 * ------------------------------------------------------------------------
 * ★★なぜ `areaCompletions`（`lib/fixtures/areas.ts`）を材料にするのか★★
 * ------------------------------------------------------------------------
 * ★fixtures で面積（`clean_area` / `task_area`）を持っているのはこの1本だけ★です。
 * `AreaCompletionRow` は ★1行 = 1清掃エリア × 1日★（= P1 では 1ロボット。`CLAUDE.md` §8）で、
 * ★実DBの `clean_task_reports` の1行に対応します★（旧: `clean_status_histories`。指示書AQ 決定3）。
 *
 * ------------------------------------------------------------------------
 * ★★`completion_rate === null` の行を数えないこと★★（★静かにズレる箇所★）
 * ------------------------------------------------------------------------
 * `lib/fixtures/areas.ts` の組み立て（`build()`）は
 * ★「`completion_rate` が `null` の行は `task_area` を 0 にする」ことを検査で強制★しており、
 * これは ★★「その清掃にはレポートの行が無い（出発失敗・未実施）」★★ という意味です（指示書AQ §1-1）。
 *
 * ★実DBではレポートの行そのものが存在しない★ので、実施回数にも入りません。
 * ★ここで数えると `executed_count`（清掃実施回数）が fixtures だけ多くなります★
 * （★面積は 0 なので合計は変わらず、件数だけが静かに食い違う★）。
 *
 * ------------------------------------------------------------------------
 * ★★完了率は `runCompletionRate()` を通す★★（2026-10-02。D-155 / 指示書AS 段階4-3）
 * ------------------------------------------------------------------------
 * Prisma 版と同じく ★1回ごとの完了率を面積から `runCompletionRate()` で作り★、★常に★ Σ と件数に足す
 * （`task_area = 0` も 0% として足す。P-5=B。D-158）。★fixtures の行の `completion_rate` をそのまま足さないこと★（規則が2箇所に散る）。
 *
 * ★★fixtures（`lib/fixtures/areas.ts`）には予定外（`clean_timetable_id` が NULL）の行が無い★★ —
 *   どの行も本清掃の清掃ログ（`clean_log_id`）に紐づいている。★予定外を完了率・実施回数から外す規則は
 *   Prisma 版の純粋関数 `cleanAreaStatsFromRows()` のテスト（表4）で確かめる★。
 */
import { runCompletionRate } from "@/lib/judge";
import type {
  AreaCompletionRow,
  CleanAreaDbQuery,
  CleanAreaFacilityStats,
} from "@/lib/types";

/**
 * fixtures の面積行から、施設ごとの集計材料を作る（★純粋関数★。DBアクセスを混ぜない）。
 *
 * ★日付の比較は文字列のまま行う★ — `AreaCompletionRow.date` は既に JST の `YYYY-MM-DD`
 *   なので、実行環境のタイムゾーンに依存しません（`filterFixtureAreaCompletions()` と同じ作法）。
 *   ★`new Date()` を挟むと、Prisma 版が壁時計値で絞っているのと基準がずれます★。
 *   ★`date_to` はその日を**含む**★（`dbDateRange()` と同じ契約）。
 *
 * ★★`facility_ids` は `!== null` で分岐すること★★（★罠1 / 落とし穴3★）—
 *   ★`null` = 絞らない / `[]` = 0件ヒット★。`?.length` で書くと
 *   ★「0件ヒット」が「全件」に化けます★（例外もテスト失敗も出ない）。
 *
 * ★戻りは `facility_id` の昇順★ — Prisma 側の `groupBy` は順序を約束しないが、
 *   ★fixtures が入力順に依存して揺れると、テストで固定できるものが減る★ので決め切っておく
 *   （★並びの正は `aggregateCleanArea()` の `compareRows()`★。ここは単なる決定性のため）。
 */
export function cleanAreaStatsFromFixtureAreas(
  rows: readonly AreaCompletionRow[],
  query: CleanAreaDbQuery
): CleanAreaFacilityStats[] {
  const byFacility = new Map<number, CleanAreaFacilityStats>();
  const facilityIds =
    query.facility_ids !== null ? new Set(query.facility_ids) : null;

  for (const row of rows) {
    // ★「レポートの行が無い」行は実DBに存在しない★（上の docstring）
    if (row.completion_rate === null) continue;
    if (facilityIds !== null && !facilityIds.has(row.facility_id)) continue;
    if (query.date_from !== null && row.date < query.date_from) continue;
    if (query.date_to !== null && row.date > query.date_to) continue;

    // ★1回ごとの完了率は `runCompletionRate()` だけが決める★（Prisma 版と同じ。指示書AS D1）
    const rate = runCompletionRate(row);
    const current = byFacility.get(row.facility_id);
    if (current === undefined) {
      byFacility.set(row.facility_id, {
        facility_id: row.facility_id,
        clean_area: row.clean_area,
        task_area: row.task_area,
        executed_count: 1,
        // ★完了率の材料（1回ごとの完了率の単純平均。D-155）★ — ★`runCompletionRate()` を通す★
        clean_ratio_sum: rate,
        clean_ratio_count: 1,
      });
      continue;
    }
    // ★`_sum` の `null` は fixtures には無い★（面積は常に数値）ので `?? 0` は要らないが、
    //   ★型は Prisma 版と同じ `number | null`★なので足し込みでは `?? 0` を通す。
    current.clean_area = (current.clean_area ?? 0) + row.clean_area;
    current.task_area = (current.task_area ?? 0) + row.task_area;
    current.executed_count += 1;
    current.clean_ratio_sum = (current.clean_ratio_sum ?? 0) + rate;
    current.clean_ratio_count += 1;
  }

  return [...byFacility.values()].sort((a, b) => a.facility_id - b.facility_id);
}
