/**
 * 解決済みの施設ID配列で「行」を絞る純粋関数（指示書AK 段階4）。
 *
 * ------------------------------------------------------------------------
 * ★★なぜ SQL ではなくここで絞るのか（この判断を消す前に読むこと）★★
 * ------------------------------------------------------------------------
 * 分類（`group_id` / `customer_id`）の絞り込みは、★データ層のクエリに降ろすのが本筋★です
 * （`db.cleanLogs()` / `db.prechecks()` は `facility_ids` を受け取り、
 *  `where: { facility_id: { in: [...] } }` に落としています）。
 *
 * ★★ただし `dsDevices()` / `dsAlerts()` / `facilities()` / `schedules()` の4本は
 *   引数を取らない契約★★で、★`dsAlerts()` と `schedules()` は `/dashboard` からも
 *   呼ばれています★。ここに引数を足すと ★`/dashboard`（段階4b・別担当）の
 *   呼び出し側を書き換えることになる★ため、段階4a では触っていません。
 *   ★4本とも、もともと絞り込み・並べ替え・ページングを**ルート側の純粋関数**で
 *   行う作り★（`filterDsDevicesByQ()` / `filterFacilitiesByFields()` /
 *   `filterSchedules()` …）なので、★分類の絞り込みも同じ場所に置く★のが素直でした。
 *
 * ★★申し送り★★ — ★4本に引数を足して SQL に降ろすなら、`/dashboard` の
 *   呼び出し側（`db.schedules()` / `db.dsAlerts()`）と同時に直すこと★。
 *   ★そのときも `null`（絞らない）と `[]`（0件ヒット）の区別を崩さないこと★。
 *
 * ------------------------------------------------------------------------
 * ★★`null` と `[]` は別物★★（★罠1 / 落とし穴1★）
 * ------------------------------------------------------------------------
 *   - `null` … ★分類で絞っていない★（＝全行を素通しする）
 *   - `[]`   … ★絞ったが1件も当たらなかった★（＝★0件を返す★）
 *
 * ★★`facilityIds?.length` で分岐しないこと★★ — `[]` が「未指定」に化け、
 *   ★0件ヒットが全件表示になります。例外もテスト失敗も出ません★。
 *   分岐は必ず ★`=== null`★ で書くこと（`lib/facilities/intersect-ids.ts` と同じ作法）。
 *
 * ★純粋関数にすること★（`CLAUDE.md` §9.4）— DBアクセスやログ出力を混ぜない。
 */

/**
 * `facility_id` を持つ行（`DsDevice` / `DsAlert` / `Schedule` 等）を絞る。
 *
 * ★★呼ぶ場所はサマリ計算より**前**★★ — `/ds-devices` / `/ds-alerts` の `summary` は
 *   ★q / status の絞り込みの影響を受けない★約束（D-054）ですが、
 *   ★分類の絞り込みは「見え方」ではなく「母集団」を選ぶもの★なので、
 *   ★★サマリにも効かせる★★（「この屋号の異常◯件」が見たい。指示書AK §7-2 が
 *   `/dashboard` の数値カードについて言っているのと同じ理由）。
 *   ★一覧とサマリで母集団がずれないよう、必ず同じ配列から作ること★。
 *
 * ★並び順は入力のまま★（並べ替えは各ルートの `sortXxx()` の仕事）。
 */
export function filterRowsByFacilityIds<T extends { facility_id: number }>(
  rows: readonly T[],
  facilityIds: number[] | null
): T[] {
  if (facilityIds === null) return [...rows];
  const allowed = new Set(facilityIds);
  return rows.filter((row) => allowed.has(row.facility_id));
}

/**
 * 施設そのものの一覧（`Facility[]`）を絞る。
 *
 * ★★見る列が違う★★ — `/facilities` が返すのは ★施設の行そのもの★なので、
 *   突き合わせるのは `facility_id` ではなく ★`id`★ です
 *   （`DsDevice` 等は「施設に紐づく行」なので `facility_id`）。
 *
 * ★★`Facility` は段階5 で `customer_id` / `group_id` を持ったが、それでも
 *   ここで直接絞らないこと★★ — ★絞り込みの入口は `resolveFacilityIdsByMasters()` のまま★に
 *   しておく（指示書AK §2-2 / 段階5）。★施設名の解決（`resolveFacilityIdsByName()`）との
 *   積集合を同じ形（施設IDの配列）で取れるようにするため★で、
 *   ここに所属の判定を写すと ★同じ規則が2箇所に散る★。
 */
export function filterFacilitiesByIds<T extends { id: number }>(
  rows: readonly T[],
  facilityIds: number[] | null
): T[] {
  if (facilityIds === null) return [...rows];
  const allowed = new Set(facilityIds);
  return rows.filter((row) => allowed.has(row.id));
}
