import type {
  AllergyView,
  DietView,
  IngredientView,
  SourceView,
  TechStepView,
  UnitView,
  UtensilView,
} from "@batch-cooking/shared";
import { prisma } from "../../db/prisma.js";

/**
 * All reference dietary regimes, ordered by `key` — small, static list (see
 * prisma/seed.ts). `key` is a stable slug, not the display label (see
 * {@link DietView}), so this is an alphabetical-by-slug order rather than a
 * true French alphabetical one — close enough for a 5-item list, and the
 * server has no other order to offer now that the label itself only exists
 * client-side (`apps/web`'s `locales/fr/translation.json`).
 */
export async function getDiets(): Promise<DietView[]> {
  try {
    return await prisma.diet.findMany({ orderBy: { key: "asc" } });
  } catch (err) {
    // Rethrown as-is — `wrapAsyncHandler`/the error middleware (which
    // already logs it, see `error-logger.ts`) is what actually handles it,
    // this service layer just isn't allowed a bare `async` body without a
    // try/catch per the repo's convention.
    throw err;
  }
}

/**
 * All reference allergens, ordered by key (see {@link getDiets} for why key,
 * not label). `Allergy` carries no `key` of its own — it's the selectable
 * instance of a keyed `Category` (see schema.prisma) — so this resolves
 * each allergen's key from its category and flattens the split away for
 * callers.
 */
export async function getAllergies(): Promise<AllergyView[]> {
  try {
    const allergies = await prisma.allergy.findMany({
      include: { category: { select: { key: true, kind: true } } },
      orderBy: { category: { key: "asc" } },
    });
    return allergies.map((allergy) => ({
      id: allergy.id,
      key: allergy.category.key,
      kind: allergy.category.kind,
    }));
  } catch (err) {
    throw err; // see getDiets()'s catch comment above
  }
}

/**
 * All reference recipe-ingredient units, ordered by key (see {@link getDiets}
 * for why) — small, static list (see `reference-seed-data.ts`'s `UNITS`).
 * `toBaseFactor` comes back as a Prisma `Decimal`, converted to a plain
 * `number` here the same way `recipe.service.ts` does for
 * `RecipeIngredient.quantity`.
 */
export async function getUnits(): Promise<UnitView[]> {
  try {
    const units = await prisma.unit.findMany({ orderBy: { key: "asc" } });
    return units.map((unit) => ({
      id: unit.id,
      key: unit.key,
      type: unit.type,
      toBaseFactor: Number(unit.toBaseFactor),
    }));
  } catch (err) {
    throw err; // see getDiets()'s catch comment above
  }
}

/**
 * All reference cooking techniques, ordered by key (see {@link getDiets}
 * for why) — small, static list (see `reference-seed-data.ts`'s
 * `TECH_STEPS`). Not consumed by the recipe UI yet — see {@link TechStepView}.
 */
export async function getTechSteps(): Promise<TechStepView[]> {
  try {
    return await prisma.techStep.findMany({ orderBy: { key: "asc" } });
  } catch (err) {
    throw err; // see getDiets()'s catch comment above
  }
}

/**
 * All reference cooking utensils, ordered by key (see {@link getDiets} for
 * why) — small, static list (see `reference-seed-data.ts`'s `UTENSILS`),
 * same bare `id`/`key` shape as {@link getTechSteps}.
 */
export async function getUtensils(): Promise<UtensilView[]> {
  try {
    return await prisma.utensil.findMany({ orderBy: { key: "asc" } });
  } catch (err) {
    throw err; // see getDiets()'s catch comment above
  }
}

/**
 * Every implemented recipe source, ordered by name (not `key` — unlike
 * every other reference catalog, `name` here *is* the display string a
 * household picks from, see {@link SourceView}, so alphabetical-by-name is
 * what a real picker should show). Empty until a concrete adapter is
 * registered (see `recipe-source-registry.ts`) and synced (see
 * `recipe-source-sync.ts`'s `syncRecipeSources`).
 */
export async function getSources(): Promise<SourceView[]> {
  try {
    // Explicit `select` — `url` exists on the `Source` row but isn't part of
    // `SourceView` yet, so it must not leak into the response the way a bare
    // `findMany()` would let it.
    return await prisma.source.findMany({
      select: {
        id: true,
        key: true,
        name: true,
        official: true,
        iconUrl: true,
      },
      orderBy: { name: "asc" },
    });
  } catch (err) {
    throw err; // see getDiets()'s catch comment above
  }
}

/**
 * All reference ingredients, ordered by key (see {@link getDiets} for why),
 * each resolved to its allergens (see `IngredientAllergy` in schema.prisma)
 * and compatible diet regimes (see `IngredientDiet`) — same flattening
 * approach as {@link getAllergies}. Ingredients with no linked
 * allergen/diet come back with `allergens: []`/`diets: []`.
 *
 * Excludes placeholder rows (`Ingredient.isPlaceholder` — the free-text
 * ingredients users type when the catalog falls short): this is the
 * *browsable* catalog, and a placeholder is a per-recipe-line stand-in, not
 * a real entry anyone should be able to pick again.
 */
export async function getIngredients(): Promise<IngredientView[]> {
  try {
    const ingredients = await prisma.ingredient.findMany({
      where: { isPlaceholder: false },
      include: {
        allergies: { include: { allergy: { include: { category: true } } } },
        diets: { include: { diet: true } },
      },
      orderBy: { key: "asc" },
    });
    return ingredients.map((ingredient) => ({
      id: ingredient.id,
      key: ingredient.key,
      icon: ingredient.icon,
      category: ingredient.category,
      subcategory: ingredient.subcategory,
      reproducible: ingredient.reproducible,
      allergens: ingredient.allergies.map(({ allergy }) => ({
        id: allergy.id,
        key: allergy.category.key,
        kind: allergy.category.kind,
      })),
      diets: ingredient.diets.map(({ diet }) => ({
        id: diet.id,
        key: diet.key,
      })),
      // Always a real catalog row here (placeholders are filtered out above).
      isPlaceholder: false,
      displayName: null,
    }));
  } catch (err) {
    throw err; // see getDiets()'s catch comment above
  }
}
