import type { PrismaClient } from "@prisma/client";
import { listRecipeSources } from "../lib/recipe-sources/recipe-source-registry.js";

/**
 * Upserts one `Source` row (schema.prisma) per adapter currently in
 * `recipe-source-registry.ts`, keyed by `adapter.key` — keeps the
 * `sources` catalog an exact mirror of "which sources are actually
 * implemented in code", rather than a hand-maintained list that can drift
 * out of sync the way `DIETS`/`UNITS` (`reference-seed-data.ts`) would if
 * copy-pasted here. Call once at startup (`prisma/seed.ts`) and in test
 * setup (`test-support/reset-db.ts`), the same place `seedReferenceData`
 * runs — kept as its own function rather than folded into that one, since
 * it reads from the adapter registry instead of a static array.
 *
 * Never deletes a `Source` row whose key fell out of the registry (e.g. an
 * adapter temporarily removed from code) — a recipe already imported from
 * it should keep citing it rather than having `sourceId` silently nulled
 * out from under it (see `onDelete: SetNull` on `Recipe.source` in
 * schema.prisma, which is what *would* happen on an actual delete).
 *
 * Safe to call with an empty registry (leaves the `sources` table
 * untouched) — the case whenever nothing has called `registerRecipeSource`
 * yet, e.g. most test files (see `apps/api/src/sources/index.ts` for where
 * the app's own concrete adapters — currently just TheMealDB — register
 * themselves at startup).
 */
export async function syncRecipeSources(prisma: PrismaClient): Promise<void> {
  try {
    for (const adapter of listRecipeSources()) {
      await prisma.source.upsert({
        where: { key: adapter.key },
        update: {
          name: adapter.name,
          official: adapter.official,
          iconUrl: adapter.iconUrl,
        },
        create: {
          key: adapter.key,
          name: adapter.name,
          official: adapter.official,
          iconUrl: adapter.iconUrl,
        },
      });
    }
  } catch (err) {
    // Rethrown as-is — callers (app startup, `sources.service.ts` via test
    // setup) already handle/log failures centrally; this function just
    // isn't allowed a bare `await` per the repo's async/try-catch convention.
    throw err;
  }
}

/**
 * Which of `externalIds` already have a `Recipe` imported from the source
 * registered under `sourceKey`, mapped to that `Recipe`'s id — the
 * DB-touching counterpart to `markAlreadyImported` (recipe-source-adapter.ts),
 * which stays pure and takes a plain `ReadonlySet<string>` (this map's
 * `.keys()`) rather than querying itself. The id (not just membership) is
 * what `sources.service.ts`'s browse endpoint needs to link an
 * already-imported item straight to its real `Recipe`, instead of a
 * caller having to look it up again. Returns an empty map (not an error)
 * for a `sourceKey` with no matching `Source` row — nothing can have been
 * imported from a source we don't even have a catalog entry for.
 */
export async function findImportedRecipeIds(
  prisma: PrismaClient,
  sourceKey: string,
  externalIds: string[],
): Promise<Map<string, number>> {
  try {
    if (externalIds.length === 0) return new Map();

    const source = await prisma.source.findUnique({
      where: { key: sourceKey },
    });
    if (!source) return new Map();

    const imported = await prisma.recipe.findMany({
      where: { sourceId: source.id, externalId: { in: externalIds } },
      select: { id: true, externalId: true },
    });
    return new Map(
      imported.flatMap((recipe) =>
        recipe.externalId !== null ? [[recipe.externalId, recipe.id]] : [],
      ),
    );
  } catch (err) {
    throw err; // see syncRecipeSources()'s catch comment above
  }
}
