/**
 * 分類（ホールディングス／屋号）→ 施設ID の解決（指示書AK 段階3 §6-1）。
 *
 * ------------------------------------------------------------------------
 * ★★戻り値の契約は `resolveFacilityIdsByName()` と完全に同じ★★
 * ------------------------------------------------------------------------
 * | 状況 | 戻り値 |
 * |---|---|
 * | `groupId` も `customerId` も未指定 | ★`null`★（＝条件なし・全施設） |
 * | 指定されたが該当0件 | ★`[]`★（＝結果は0件） |
 * | 該当あり | 施設IDの配列 |
 *
 * ★★`null` と `[]` を取り違えないこと★★（★罠1 / 落とし穴1★）—
 *   `where` 側は `facility_ids !== null` で分岐する約束（`lib/db/index.ts`）なので、
 *   ★0件ヒットで `null` を返すと条件ごと消えて**全件表示**に化けます★。
 *   ★例外もテスト失敗も出ません。★
 *
 * ------------------------------------------------------------------------
 * ★★同時指定は AND（積集合）★★
 * ------------------------------------------------------------------------
 * `d-watch-api.yaml` の `GroupId` / `CustomerId` の description に書いた契約
 * （段階2 で明記済み）＝ ★「両方を満たす施設」★。★どちらか片方を捨てないこと★。
 * ★屋号がホールディングスに属していなければ、両方指定の結果は `[]` になる★
 * （それが正しい。`null` にしないこと）。
 *
 * ------------------------------------------------------------------------
 * ★★施設名の検索とは事情が違う★★（指示書AK §2-3）
 * ------------------------------------------------------------------------
 * 施設名がアプリ側照合なのは ★中点を無視する比較が MariaDB に無いから★
 * （`lib/facilities/resolve-by-name.ts` 冒頭の実測記録）。
 * ★こちらは整数の完全一致★なので、その制約はありません。
 * ★真似ているのは「`null` / `[]` の戻り値の契約」と「`in` に渡す形」だけ★です。
 *
 * ★★施設ごとにループでクエリを撃たないこと★★（★落とし穴14★）—
 *   施設マスタは実測12件なので ★1回読んでメモリで解決する★（§6-3）。
 */
import type { MasterIdFilters } from "@/lib/api/master-id";
import { db } from "@/lib/db";
import type { FacilityGroupRef } from "@/lib/types";

/**
 * 分類の条件を施設IDの配列に解決する★純粋関数★（DBアクセスを混ぜない）。
 *
 * @param refs 施設マスタの全行（`db.facilityGroupIndex()` の戻り値）
 * @param filters `parseMasterIdFilters()` が検証済みの `groupId` / `customerId`
 * @returns
 *   - ★★`null`★★ … どちらも未指定。＝ ★分類で絞らない★
 *   - ★★`[]`★★   … 絞ったが ★1件も当たらなかった★。＝ ★結果は0件★
 *   - それ以外 … 当たった施設IDの配列
 *
 * ★★どちらも未指定のときに `[]` を返さないこと★★ — `[]` は「0件ヒット」の意味なので、
 *   ★何も絞っていないのに一覧が空になります★。
 *
 * ★`group_id` の `null`（ホールディングスなし）は `groupId` 指定に当たらない★ —
 *   API の `group_id` は1以上しか受け付けない（`parseMasterIdParam()` が 0 を 422 にする）ので、
 *   ★「所属なしの施設だけを引く」経路はそもそも存在しません★（指示書AK §4-1 #3）。
 */
export function facilityIdsMatchingMasters(
  refs: readonly FacilityGroupRef[],
  filters: MasterIdFilters
): number[] | null {
  const { groupId, customerId } = filters;
  // ★★`!== null` で分岐すること★★ — `groupId || customerId` と書くと、
  //   将来 0 が入り込んだときに「未指定」に化ける（`parseMasterIdParam()` が
  //   0 を弾いているのは入口だけで、この関数の契約ではない）
  if (groupId === null && customerId === null) return null;
  return refs
    .filter(
      (ref) =>
        // ★★AND で畳むこと★★ — 片方が未指定ならその条件だけを素通しする
        (groupId === null || ref.group_id === groupId) &&
        (customerId === null || ref.customer_id === customerId)
    )
    .map((ref) => ref.id);
}

/**
 * 分類の条件を施設IDの配列に解決する（★API ルートから呼ぶ★）。
 *
 * ★★どちらも未指定のときは DB を読まずに `null` を返す★★ — 読んでも結果が変わらないので、
 *   ★「分類を選んでいない検索」で余計な往復を増やさない★
 *   （`resolveFacilityIdsByName()` が空文字でそうしているのと同じ）。
 *
 * ★★解決用の読み取りは `deleted_at` で絞らない★★（★罠7★）—
 *   理由は `loadFacilityGroupIndex()`（`lib/db/facility-group-source.ts`）の docstring。
 *   ★`db.facilities()` を流用しないこと★。
 */
export async function resolveFacilityIdsByMasters(
  filters: MasterIdFilters
): Promise<number[] | null> {
  if (filters.groupId === null && filters.customerId === null) return null;
  return facilityIdsMatchingMasters(await db.facilityGroupIndex(), filters);
}
