/**
 * `GET /errors`（エラー一覧。指示書AW）で使うクエリ処理（純粋関数のみ）。
 *
 * ★絞り込みの「正」は `filterSortErrorReportKeys()`★ — SQL 側（`errorReportListWhere()`。`lib/db/error-report-mapping.ts`）は
 *   その写し（指示書AC C-5 と同じ考え方）。★fixtures と Prisma は同じこの関数を通す★ので `USE_FIXTURES` で件数が変わらない。
 * ★並べ替えとページ分けはアプリ側★（指示書AW D8）— 検知時刻（`errored_at ?? created_at`）の順は Prisma のクエリビルダで書けない。
 *
 * テストは `lib/error-reports/__tests__/query.test.ts`（表1 の並び順・表2）。
 */
import { parseMasterIdFilters } from "@/lib/api/master-id";
import { fail, type QueryParseResult } from "@/lib/api/query-result";
import { parseDate } from "@/lib/clean-logs/query";
import { PAGINATION } from "@/lib/config";
import type { ErrorReportKey } from "@/lib/db/error-report-mapping";
import { robotErrorStatusFromResolutionStatus } from "@/lib/db/precheck-mapping";
import { includesFoldPlain } from "@/lib/format/search-text";
import type { ErrorReportDbQuery, PrecheckStatus } from "@/lib/types";

export interface ErrorReportQuery {
  /** JST の `YYYY-MM-DD`（検知時刻の日付）。★空文字は `null` に正規化★（`parsePrecheckQuery()` の注記と同じ理由） */
  dateFrom: string | null;
  /** JST の `YYYY-MM-DD`（★その日を含む★）。★空文字は `null`★ */
  dateTo: string | null;
  /** false = 未解消・対応中だけ（既定）/ true = 解消も含める */
  includeResolved: boolean;
  /** 施設名の部分一致（trim 済み）。★`""` = 絞らない★ */
  facilityName: string;
  /** 種類の部分一致（trim 済み）。★`""` = 絞らない★ */
  errorType: string;
  /** ホールディングス（`groups.id`）。★`null` = 絞らない★ */
  groupId: number | null;
  /** 顧客（`customers.id`）。★`null` = 絞らない★ */
  customerId: number | null;
  page: number;
  perPage: number;
}

function toInt(value: string | null, fallback: number): number {
  if (!value) return fallback;
  const n = Number.parseInt(value, 10);
  return Number.isNaN(n) ? fallback : n;
}

/**
 * クエリパラメータをパースする。不正な値があれば `{ ok: false }`（422）を返す。
 *
 * - 日付の形式は `parseDate()`（`lib/clean-logs/query.ts`）。★`date_from > date_to` は 422（表2 F7）★。
 *   文言は `parseCleanLogQuery()` と同じ。★期間の日数の上限は設けない★（`/prechecks` と同じ。§11 Q13）
 * - `include_resolved`: ★無い・空・`"false"` → false、`"true"` → true、それ以外 → 422★（表2 F3。黙って false に倒さない）
 * - 分類は `parseMasterIdFilters()`（`lib/api/master-id.ts`）の1本に任せる（★`Number.parseInt()` で書き直さない。D-123★）
 * - `page` / `per_page` は不正値でも失敗させず、既定値・上限へ丸める（`parsePrecheckQuery()` と同じ）
 * - `facility_name` / `error_type` は前後の空白を除く（空白だけ → `""` = 絞らない。表2 F5）
 *
 * ★エラーに入力値をエコーしない★（`parseMasterIdParam()` と同じ理由）。
 */
export function parseErrorReportQuery(
  searchParams: URLSearchParams
): QueryParseResult<ErrorReportQuery> {
  // ★★空文字は「指定なし」として `null` に正規化する★★（`parsePrecheckQuery()` の注記と同じ理由）—
  //   `""` のまま下へ渡すと、Prisma 版（`dbDateRange("")` → 条件なし）と
  //   fixtures 版（文字列比較で全行除外）で件数が割れる（指示書AC 禁止事項6）
  const dateFromRaw = searchParams.get("date_from") || null;
  const dateToRaw = searchParams.get("date_to") || null;
  const dateFrom = parseDate(dateFromRaw);
  const dateTo = parseDate(dateToRaw);
  if (dateFromRaw && !dateFrom) {
    return fail(422, "validation_failed", "date_fromの形式が不正です", {
      date_from: ["YYYY-MM-DD形式で指定してください"],
    });
  }
  if (dateToRaw && !dateTo) {
    return fail(422, "validation_failed", "date_toの形式が不正です", {
      date_to: ["YYYY-MM-DD形式で指定してください"],
    });
  }
  if (dateFrom && dateTo && dateFrom > dateTo) {
    return fail(422, "validation_failed", "date_fromはdate_to以前の日付を指定してください");
  }

  const includeResolvedRaw = searchParams.get("include_resolved") ?? "";
  if (includeResolvedRaw !== "" && includeResolvedRaw !== "true" && includeResolvedRaw !== "false") {
    return fail(422, "validation_failed", "include_resolvedの値が不正です", {
      include_resolved: ["true / false のいずれかを指定してください"],
    });
  }

  const masters = parseMasterIdFilters(searchParams);
  if (!masters.ok) return masters;

  const page = Math.max(1, toInt(searchParams.get("page"), 1));
  const perPage = Math.min(
    PAGINATION.maxPerPage,
    Math.max(1, toInt(searchParams.get("per_page"), PAGINATION.defaultPerPage))
  );

  return {
    ok: true,
    query: {
      dateFrom: dateFromRaw,
      dateTo: dateToRaw,
      includeResolved: includeResolvedRaw === "true",
      facilityName: searchParams.get("facility_name")?.trim() ?? "",
      errorType: searchParams.get("error_type")?.trim() ?? "",
      groupId: masters.query.groupId,
      customerId: masters.query.customerId,
      page,
      perPage,
    },
  };
}

/** 既定（解消を含めない）で出す対応状況 */
const OPEN_STATUSES: readonly PrecheckStatus[] = ["unresolved", "in_progress"];
/** 「解消済みも表示」で出す対応状況 */
const ALL_STATUSES: readonly PrecheckStatus[] = ["unresolved", "in_progress", "resolved"];

/**
 * 一覧に出す `robot_error_reports.status` のコード（指示書AW D1 / D2）。
 *
 * ★0/1/2 を直書きしない★ — 変換は `robotErrorStatusFromResolutionStatus()`（`lib/db/precheck-mapping.ts`）の1本。
 *   SQL 側（`errorReportListWhere()`）も純粋関数側（`filterSortErrorReportKeys()`）もここから作る。
 */
export function errorReportStatusCodes(includeResolved: boolean): number[] {
  return (includeResolved ? ALL_STATUSES : OPEN_STATUSES).map(robotErrorStatusFromResolutionStatus);
}

/**
 * 並び順（指示書AW D7）: ★検知時刻の降順、同時刻は `error_id` の昇順（文字列の辞書順）★。
 *
 * ★`localeCompare` を使わない★ — 実行環境のロケールで順が変わる。`<` / `>`（コードポイント順）で比べる。
 * ★`error_id` は数値である保証が無い★（varchar(50)。D-105 項2）ので数値順にはしない（`"E-10"` が `"E-9"` より先）。
 * 検知時刻は `Date.parse` の差で比べる（`+09:00` 付きの ISO。`sortPrechecks()` と同じ並び）。
 */
export function compareErrorReportKeys(a: ErrorReportKey, b: ErrorReportKey): number {
  const diff = Date.parse(b.detected_at) - Date.parse(a.detected_at);
  if (diff !== 0) return diff;
  return a.error_id < b.error_id ? -1 : a.error_id > b.error_id ? 1 : 0;
}

/**
 * ★絞り込みの「正」★ → 並べ替え（指示書AW 2-5）。ページ分けは呼び出し側（データ層）。
 *
 * 0. ★`visible` が false の行（削除済みの施設・清掃エリア）は外す★（指示書AX U8。`meta.total` にも数えない）
 * 1. `status` が `errorReportStatusCodes(include_resolved)` に入る（★生の数値で比べる★ — 0/1/2 以外の行は
 *    例外にせず外れる。表2 F12）
 * 2. `facility_ids !== null` なら `facility_id` が入る（★`[]` は0件。罠1★）
 * 3. ★`detected_at` の先頭10文字（JST の日付）★ が `date_from` 以上・`date_to` 以下（★文字列で比べる★。
 *    `filterFixturePrechecks()` と同じ）— `errored_at` が NULL の行は `created_at` の日付で当たる（表2 F6）
 * 4. `error_type !== ""` なら `includesFoldPlain()`（部分一致・大小文字を区別しない。D9）
 * 5. `compareErrorReportKeys()` で並べる（★入力の配列は書き換えない★。`filter` が新しい配列を返す）
 */
export function filterSortErrorReportKeys(
  keys: readonly ErrorReportKey[],
  query: ErrorReportDbQuery
): ErrorReportKey[] {
  const codes = errorReportStatusCodes(query.include_resolved);
  return keys
    .filter((key) => {
      if (!key.visible) return false;
      if (!codes.includes(key.status)) return false;
      if (query.facility_ids !== null && !query.facility_ids.includes(key.facility_id)) return false;
      const day = key.detected_at.slice(0, 10);
      if (query.date_from !== null && day < query.date_from) return false;
      if (query.date_to !== null && day > query.date_to) return false;
      if (query.error_type !== "" && !includesFoldPlain(key.error_type, query.error_type)) return false;
      return true;
    })
    .sort(compareErrorReportKeys);
}
