// src/lib/backlink/assert-link-source.ts — garde tenancy : les linkSourceIds
// doivent appartenir à l'org active (IDOR cross-org, finding B1/B2 audit sécu).
//
// link_sources.org_id est NOT NULL (voir domain-schema.ts) : même les entrées
// du catalogue curé (global=true) sont copiées avec l'org_id du site cible à
// la provision (org-bootstrap.ts) — aucune ligne n'est réellement partagée
// entre orgs. Filtrer sur org_id = org active suffit, pas de cas "global" à
// traiter à part.
import "server-only";
import { and, eq, inArray } from "drizzle-orm";
import { getActiveOrgId } from "@/lib/active-org";
import { db } from "@/lib/db/client";
import { linkSources } from "@/lib/db/domain-schema";

/** Requête nue, testable hors session. */
export async function linkSourceIdsInOrg(
  sourceIds: string[],
  orgId: string,
): Promise<string[]> {
  if (sourceIds.length === 0) return [];
  const rows = await db
    .select({ id: linkSources.id })
    .from(linkSources)
    .where(
      and(eq(linkSources.orgId, orgId), inArray(linkSources.id, sourceIds)),
    );
  return rows.map((r) => r.id);
}

/**
 * Tenancy filter (ELEVA — audit sécu B1/B2) — garde uniquement les
 * linkSourceIds qui appartiennent réellement à l'org active ; les ids
 * étrangers (autre org) sont silencieusement filtrés (fail-closed bulk),
 * même sémantique que `filterKeywordIdsForSite`.
 */
export async function filterLinkSourceIdsForActiveOrg(
  sourceIds: string[],
): Promise<string[]> {
  if (sourceIds.length === 0) return [];
  const orgId = await getActiveOrgId();
  return linkSourceIdsInOrg(sourceIds, orgId);
}
