/**
* 施設IDの絞り込み結果どうしの積集合(指示書AK 段階3 §6-2)。
*
* ★★「施設名の絞り込み」と「分類の絞り込み」は同時に指定されうる★★ので、
* ★両方の結果を AND で畳んでから `where: { facility_id: { in: [...] } }` に渡します★
* (★どちらか片方を捨てないこと★。指示書AK §2-2)。
*
* ★★`null` と `[]` の意味は `resolveFacilityIdsByName()` / `resolveFacilityIdsByMasters()`
* と同じ★★:
* - `null` … ★その条件は指定されていない(絞らない)★
* - `[]` … ★指定されたが0件ヒット★
*
* 期待値表(★この6行がこの関数の契約★。テストで固定してある):
*
* | 施設名の解決結果 | 分類の解決結果 | 戻り値 |
* |---|---|---|
* | `null` | `null` | ★`null`★ |
* | `[1,2,3]` | `null` | `[1,2,3]` |
* | `null` | `[2,3,4]` | `[2,3,4]` |
* | `[1,2,3]` | `[2,3,4]` | ★`[2,3]`★ |
* | ★`[1]`★ | ★`[2]`★ | ★★`[]`★★(★`null` にしないこと★) |
* | `[]` | `[1,2]` | ★`[]`★ |
*
* ★★最後の2行が要です★★(★落とし穴2★)— ★積集合が空になったら `[]` を返すこと★。
* ★`null` にすると条件ごと消えて全件表示に化けます。例外もテスト失敗も出ません。★
*
* ★純粋関数にすること★(`CLAUDE.md` §9.4)— DBアクセスやログ出力を混ぜない。
*/
/**
* 2つの施設ID絞り込み結果の積集合を取る。
*
* ★★`a ?? b` や `[...(a ?? []), ...(b ?? [])]` で済ませないこと★★ —
* 前者は片方を捨て、後者は和集合(OR)になります。★契約は AND★。
*
* ★並び順は `a` の順序を保つ★(★並び替えない★)—
* `facility_id: { in: [...] }` は順序を見ないので意味は変わりませんが、
* ★入力の順序を勝手に変えるとテストの期待値が理由なく揺れます★。
*
* ★重複は取り除かない★ — 解決側(`facilityIdsMatchingName()` /
* `facilityIdsMatchingMasters()`)が施設マスタを1行ずつ写した配列を返すので、
* ★そもそも重複が入らない★。ここで `Set` に通して「直した」ことにすると、
* ★重複が出る不具合が上流で起きても気づけなくなります★。
*/
export function intersectFacilityIds(
a: number[] | null,
b: number[] | null
): number[] | null {
// ★★`!== null` で分岐すること★★ — `a?.length` で書くと `[]`(0件ヒット)が
// 「未指定」に化け、★0件が全件表示になります★(罠1 / 落とし穴1・2)
if (a === null) return b;
if (b === null) return a;
const inB = new Set(b);
return a.filter((id) => inB.has(id));
}