/**
 * ダッシュボード（F-01）の集計。**純粋関数のみ**（DBアクセスを混ぜない）。
 *
 * 定義の正は `docs/decisions.md` D-049。
 *
 * ★`lib/judge/kpi.ts` は使わない★
 *   kpi.ts は週次/月次/年次の集計期間の KPI（CLAUDE.md §4.2）用で、
 *   ここでやりたい日次の**件数カウント**とは別物。混ぜると分母の意味が変わる。
 *
 * ★丸めない★
 *   率は生値のまま返し、表示用の丸めは画面側で行う（CLAUDE.md §4.3 / §9.3）。
 */
import type {
  CleanLogItem,
  DashboardResponse,
  DsAlert,
  Schedule,
} from "@/lib/types";

/**
 * 成功とみなす判定（◎○）
 *
 * ★アラートの `main_run_succeeded`（`lib/db/precheck-mapping.ts`。D-171）もこの定数を使う★ —
 *   「本清掃は成功」の定義をダッシュボードの「成功」と揃えるため。★別の場所に同じ配列を書かないこと★
 */
export const SUCCESS_GRADES = ["◎", "○"] as const;
/** 失敗とみなす判定（△×） */
const FAILURE_GRADES = ["△", "×"] as const;

/**
 * 清掃結果サマリ。
 *
 * `failed` を「出発した△×」に限定するのは、未出発の行を
 * `not_executed` と `failed` の両方に数えて二重計上しないため。
 *
 * ★結果待ち（`result_pending`）は `pending` に数え、success / failed / not_executed に入れない★
 * （ユーザー判断 2026-10-07。D-173）。★`success_rate` の分母にも入れない★（分母0なら null）。
 * ★旗を `departed` より先に見ること★ — 結果待ちの行は `departed: false` / `grade: "×"` のままなので、
 * 後に回すと未実施に数えてしまう。
 */
export function computeSummary(
  logs: CleanLogItem[]
): DashboardResponse["summary"] {
  let success = 0;
  let failed = 0;
  let notExecuted = 0;
  let pending = 0;

  for (const row of logs) {
    if (row.result_pending) {
      pending += 1;
      continue;
    }
    if (!row.departed) {
      notExecuted += 1;
      continue;
    }
    if ((SUCCESS_GRADES as readonly string[]).includes(row.grade)) {
      success += 1;
      continue;
    }
    if ((FAILURE_GRADES as readonly string[]).includes(row.grade)) {
      failed += 1;
    }
  }

  // ★`pending` は分母に入れない★（D-173）
  const denominator = success + failed + notExecuted;
  return {
    success,
    failed,
    not_executed: notExecuted,
    pending,
    success_rate: denominator === 0 ? null : (success / denominator) * 100,
  };
}

/**
 * 日付文字列（YYYY-MM-DD）から曜日番号（0=日曜）を求める。
 *
 * `new Date("YYYY-MM-DD...").getDay()` は実行環境のTZで結果が変わるため使わない。
 * UTC で組み立てて `getUTCDay()` を読むことで、どのTZでも同じ答えになる。
 */
function weekdayIndex(date: string): number | null {
  const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date);
  if (!m) return null;
  const [, y, mo, d] = m;
  return new Date(Date.UTC(Number(y), Number(mo) - 1, Number(d))).getUTCDay();
}

/**
 * スケジュール進捗。
 *
 * `planned` を**スケジュール由来**にするのが要点（D-049）。
 * 清掃ログの行数を使うと、収集処理が止まって当日の行が0件のときに
 * 「本当に定休日」と「収集処理の障害」が区別できなくなる。
 *
 * @param logs 当日分に絞り込んだ清掃ログ
 * @param date 対象日（YYYY-MM-DD, JST）
 */
export function computeScheduleProgress(
  logs: CleanLogItem[],
  schedules: Schedule[],
  date: string
): DashboardResponse["schedule_progress"] {
  const dow = weekdayIndex(date);
  const planned =
    dow === null
      ? 0
      : schedules.filter((s) => s.active && s.weekdays[dow] === "1").length;

  // 「完了」ではなく「出発」。成功+失敗の合計になる（D-049）
  const completed = logs.filter((row) => row.departed).length;

  return {
    planned,
    completed,
    rate: planned === 0 ? null : (completed / planned) * 100,
  };
}

/**
 * セキュリティ復帰漏れのバナー。
 *
 * 対応済み（`done`）以外を未解消として扱う。
 * 返すのは `facility_name` / `section_name` / `since` の3つだけで、
 * 自由記述の `detail` / `memo` は含めない（§4 の記録禁止フィールド。
 * レスポンスに載せる必要も無い）。
 */
export function computeSecurityBanner(
  alerts: DsAlert[]
): DashboardResponse["security_banner"] {
  return alerts
    .filter((a) => a.alert_type === "security_off" && a.resolution_status !== "done")
    .map((a) => ({
      facility_name: a.facility_name,
      section_name: a.section_name,
      since: a.detected_at,
    }));
}
