/**
 * ★清掃詳細ログの「予定外タスク」の行を読む★（指示書BG §1・§2-1。ユーザー指示 2026-10-29
 * 「エラー一覧以外も、予定外タスクを計算して出すようにしてください」）。
 *
 * 予定外タスク ＝ `clean_task_reports` のうち `clean_timetable_id` が 0 か NULL の行（TeamWorks 回答 2026-10-27）。
 * ★判定の正は `scheduledTimetableId()`（`isUnscheduledReport()`）★ — SQL の `UNSCHEDULED_TIMETABLE_ID_WHERE` はその写しで、
 * 取り寄せた行はメモリでも絞り直す（指示書BG §5「`=== null` / `{ not: null }` を直書きしない」）。
 *
 * | 条件 | 予定のある行（`mainTimetableWhere()`） | 予定外の行（ここ） |
 * |---|---|---|
 * | 起点 | 予定の行（`precheck = 0`） | 清掃結果の行 |
 * | 期間 | 予定の `start_at`（JST） | 清掃結果の `started_at`（JST。★実際に始めた時刻。CLAUDE.md §4.7★） |
 * | 施設 | `facility_id IN (...)` | 同じ（清掃結果の `facility_id`） |
 * | 削除済みの施設・清掃エリア | 出さない | ★同じく出さない★（`FACILITY_SECTION_VISIBLE_WHERE`。表U1 U1-5） |
 * | ロボット（機種・シリアル） | 清掃エリアの有効なロボットの先頭 | ★同じ出し方★（`section.robots`。P1 は1清掃エリア=1ロボット） |
 *
 * ★事前チェックの回は無い★（予定外タスクには予定の行が無い。事前チェックの4列は null）。
 */
import {
  formatModelName,
  isUnscheduledReport,
  type UnscheduledReportLike,
  type UnscheduledRowContext,
} from "@/lib/db/clean-log-mapping";
import { dbDateRange } from "@/lib/db/date-range";
import { UNSCHEDULED_TIMETABLE_ID_WHERE } from "@/lib/db/timetable-link";
import { FACILITY_SECTION_VISIBLE_WHERE } from "@/lib/db/visibility";
import { jstIsoFromDb } from "@/lib/format/date";
import type { Prisma, PrismaClient } from "@/lib/generated/prisma/client";
import type { CleanLogDbQuery } from "@/lib/types";

/**
 * ★`clean_task_reports` の `where`（予定外の行だけ）★。★純粋関数★（テストで形を固定する）。
 *
 * - ★`started_at` で期間を絞る★（`dbDateRange()` を通す。DB の DATETIME は JST の壁時計値。指示書AC C-6）
 * - ★`facility_ids` は `!== null` で分岐する★（罠1。`[]` は `in: []` で0件）
 * - ★単一ID（`query.id`）は見ない★ — 呼び出し側が単一ID の取得では予定外の行を引かない（予定外の行に `{id}` は無い）
 */
export function unscheduledCleanLogWhere(query: CleanLogDbQuery): Prisma.CleanTaskReportWhereInput {
  const range = dbDateRange(query.date_from, query.date_to);
  return {
    ...UNSCHEDULED_TIMETABLE_ID_WHERE,
    ...FACILITY_SECTION_VISIBLE_WHERE,
    ...(range.gte || range.lt ? { started_at: range } : {}),
    ...(query.facility_ids !== null
      ? { facility_id: { in: query.facility_ids.map((id) => BigInt(id)) } }
      : {}),
  };
}

/** 読む行の形（`include` の結果のうち使う列だけ。テストから作れるようにしてある） */
export interface UnscheduledReportRow {
  report_id: string;
  facility_id: bigint;
  section_id: bigint;
  clean_timetable_id: bigint | null;
  status: number;
  task_area: number;
  clean_area: number;
  clean_ratio: number;
  started_at: Date;
  facility: { facility_name: string };
  section: {
    robots: ReadonlyArray<{
      serial_number: string;
      device: { maker_id: number; model_name: string };
    }>;
  };
}

/** `loadUnscheduledCleanLogRows()` の1件 */
export interface UnscheduledCleanLogRow {
  report: UnscheduledReportLike;
  context: UnscheduledRowContext;
  /** ★有効なロボットの台数のログ（`reportSectionRobotCount()`）に使う★ */
  section_id: bigint;
  robot_count: number;
}

/**
 * 読んだ行 → 清掃詳細ログの材料（★純粋関数★）。
 * ★予定のある行（`clean_timetable_id` が 0 でも NULL でもない）は落とす★（SQL の写しが外れていても混ぜない）。
 */
export function unscheduledCleanLogRowsFromReports(
  rows: readonly UnscheduledReportRow[]
): UnscheduledCleanLogRow[] {
  return rows.filter(isUnscheduledReport).map((row) => {
    // ★P1 は1清掃エリア=1ロボット（CLAUDE.md §8）★。予定のある行と同じく、0台なら null、2台以上は `id` 昇順の先頭（指示書BB U2・D7）
    const robot = row.section.robots[0] ?? null;
    return {
      report: {
        report_id: row.report_id,
        clean_timetable_id: row.clean_timetable_id,
        status: row.status,
        task_area: row.task_area,
        clean_area: row.clean_area,
        clean_ratio: row.clean_ratio,
        // ★DB の DATETIME は JST の壁時計値★ — 必ず `jstIsoFromDb()` を通す（9時間を足さない・引かない。CLAUDE.md §3）
        started_at_iso: jstIsoFromDb(row.started_at),
      },
      context: {
        facility_id: Number(row.facility_id),
        facility_name: row.facility.facility_name,
        model_name:
          robot === null ? null : formatModelName(robot.device.maker_id, robot.device.model_name),
        serial_number: robot === null ? null : robot.serial_number,
      },
      section_id: row.section_id,
      robot_count: row.section.robots.length,
    };
  });
}

/**
 * ★予定外タスクの行を読む★（`prismaSource.cleanLogs()` から呼ぶ）。
 *
 * ★`clean_task_reports` には `facility` / `section` のリレーションがある★（`prisma/schema.prisma` の `CleanTaskReport`）ので
 * `include` で辿る（FK が無いのは `clean_timetable_id` の側だけ）。
 */
export async function loadUnscheduledCleanLogRows(
  prisma: PrismaClient,
  query: CleanLogDbQuery
): Promise<UnscheduledCleanLogRow[]> {
  const rows = await prisma.cleanTaskReport.findMany({
    where: unscheduledCleanLogWhere(query),
    include: {
      facility: { select: { facility_name: true } },
      section: {
        include: {
          robots: {
            where: { deleted_at: null },
            // ★2台以上なら `id` 昇順の先頭を採る（予定のある行と同じ。指示書BB D7）★
            orderBy: { id: "asc" },
            include: { device: true },
          },
        },
      },
    },
    orderBy: [{ started_at: "desc" }, { report_id: "asc" }],
  });
  return unscheduledCleanLogRowsFromReports(rows);
}
