#!/usr/bin/env tsx
/**
 * STEG 2 – Skala receptkatalogen till ~200 verified AI-recept.
 *
 * - Kör batchvis (~25–30 recept per batch).
 * - Dedup mot hela växande banken (recipes + recipe_similarities).
 * - Seed verified kandidater i staging DB.
 * - Dra ~15% slumpat stickprov per batch till C:\Users\Public.
 * - Rapporterar yield, täckningsmatris, dedup-statistik, kostnad.
 *
 * Körning:
 *   GEMINI_API_KEY=$(aws ssm get-parameter --name "/openclaw/GEMINI_API_KEY" ...) \
 *   DATABASE_URL=... \
 *   pnpm tsx packages/recipe-generation/scripts/scale-batch.ts
 */

import { createAamosClient } from "@app/ai-contracts";
import { SEED_INGREDIENTS } from "@app/database/seed";
import { createDatabase, closeDatabase, schema } from "@app/database";
import { eq, sql, inArray } from "drizzle-orm";
import { runPipeline, type PipelineTarget, type PipelineIngredient } from "@app/recipe-generation";
import type { CanonicalIngredientLookup, SimilarityLookup } from "@app/recipe-generation";
import type { RecipeCandidate } from "@app/recipe-generation";
import { randomUUID } from "node:crypto";
import * as fs from "node:fs/promises";
import * as path from "node:path";

// ── 1. Bygg katalog och lookups ────────────────────────────────────────────
const catalog: PipelineIngredient[] = SEED_INGREDIENTS.map((i) => ({
  id: i.id,
  nameSv: i.nameSv,
  category: i.category,
  defaultUnit: i.defaultUnit,
  isVegan: i.isVegan,
  isVegetarian: i.isVegetarian,
  containsGluten: i.containsGluten,
  containsLactose: i.containsLactose,
  allergens: i.allergens,
}));

const ingredientLookup: CanonicalIngredientLookup = {
  getById(id: string) {
    const ing = SEED_INGREDIENTS.find((i) => i.id === id);
    if (!ing) return undefined;
    return {
      id: ing.id,
      nutritionPer100: ing.nutritionPer100,
      defaultUnit: ing.defaultUnit,
      densityGPerMl: ing.densityGPerMl ?? null,
      gramsPerPiece: ing.gramsPerPiece ?? null,
      allergens: ing.allergens,
      isVegan: ing.isVegan,
      isVegetarian: ing.isVegetarian,
      containsGluten: ing.containsGluten,
      containsLactose: ing.containsLactose,
      isPork: ing.isPork,
      isBeef: ing.isBeef,
      isAlcohol: ing.isAlcohol,
    };
  },
};

// ── 2. Täckningsmatris (mål ~220 rå-kandidater → ~200 verified) ─────────────
const BASE_TARGETS: PipelineTarget[] = [
  // Mejeri
  { mealType: "dinner", mainIngredientId: "milk_3", dietVariant: "standard", count: 3 },
  { mealType: "dinner", mainIngredientId: "cream", dietVariant: "standard", count: 3 },
  { mealType: "dinner", mainIngredientId: "creme_fraiche", dietVariant: "standard", count: 3 },
  { mealType: "dinner", mainIngredientId: "milk_3", dietVariant: "vegetarian", count: 2 },
  { mealType: "dinner", mainIngredientId: "cream", dietVariant: "vegetarian", count: 2 },
  { mealType: "dinner", mainIngredientId: "creme_fraiche", dietVariant: "gluten_free", count: 2 },
  { mealType: "dinner", mainIngredientId: "milk_3", dietVariant: "lactose_free", count: 2 },

  // Kyckling
  { mealType: "dinner", mainIngredientId: "chicken_breast", dietVariant: "standard", count: 5 },
  { mealType: "dinner", mainIngredientId: "chicken_thigh", dietVariant: "standard", count: 4 },
  { mealType: "dinner", mainIngredientId: "chicken_breast", dietVariant: "gluten_free", count: 3 },
  { mealType: "dinner", mainIngredientId: "chicken_breast", dietVariant: "lactose_free", count: 3 },

  // Köttfärs
  { mealType: "dinner", mainIngredientId: "minced_beef", dietVariant: "standard", count: 5 },
  { mealType: "dinner", mainIngredientId: "minced_mixed", dietVariant: "standard", count: 4 },
  { mealType: "dinner", mainIngredientId: "minced_beef", dietVariant: "gluten_free", count: 3 },
  { mealType: "dinner", mainIngredientId: "minced_beef", dietVariant: "lactose_free", count: 3 },

  // Fisk
  { mealType: "dinner", mainIngredientId: "salmon", dietVariant: "standard", count: 5 },
  { mealType: "dinner", mainIngredientId: "cod", dietVariant: "standard", count: 4 },
  { mealType: "dinner", mainIngredientId: "shrimp", dietVariant: "standard", count: 4 },
  { mealType: "dinner", mainIngredientId: "salmon", dietVariant: "gluten_free", count: 3 },
  { mealType: "dinner", mainIngredientId: "salmon", dietVariant: "lactose_free", count: 3 },

  // Pasta/ris
  { mealType: "dinner", mainIngredientId: "pasta_dry", dietVariant: "standard", count: 5 },
  { mealType: "dinner", mainIngredientId: "rice_white", dietVariant: "standard", count: 5 },
  { mealType: "dinner", mainIngredientId: "pasta_dry", dietVariant: "vegetarian", count: 3 },
  { mealType: "dinner", mainIngredientId: "rice_white", dietVariant: "vegan", count: 3 },
  { mealType: "dinner", mainIngredientId: "rice_white", dietVariant: "gluten_free", count: 3 },

  // Potatis
  { mealType: "dinner", mainIngredientId: "potato", dietVariant: "standard", count: 5 },
  { mealType: "dinner", mainIngredientId: "potato", dietVariant: "vegetarian", count: 3 },
  { mealType: "dinner", mainIngredientId: "potato", dietVariant: "vegan", count: 3 },
  { mealType: "dinner", mainIngredientId: "potato", dietVariant: "gluten_free", count: 3 },

  // Baljväxter
  { mealType: "dinner", mainIngredientId: "tofu", dietVariant: "vegan", count: 4 },
  { mealType: "dinner", mainIngredientId: "tofu", dietVariant: "vegetarian", count: 3 },
  { mealType: "dinner", mainIngredientId: "red_lentils", dietVariant: "vegan", count: 4 },
  { mealType: "dinner", mainIngredientId: "red_lentils", dietVariant: "vegetarian", count: 3 },
  { mealType: "dinner", mainIngredientId: "chickpeas_canned", dietVariant: "vegan", count: 4 },
  { mealType: "dinner", mainIngredientId: "black_beans_canned", dietVariant: "vegan", count: 3 },

  // Veckans grönsaker
  { mealType: "dinner", mainIngredientId: "tomato", dietVariant: "standard", count: 3 },
  { mealType: "dinner", mainIngredientId: "tomato", dietVariant: "vegetarian", count: 2 },
  { mealType: "dinner", mainIngredientId: "tomato", dietVariant: "vegan", count: 2 },
  { mealType: "dinner", mainIngredientId: "zucchini", dietVariant: "standard", count: 3 },
  { mealType: "dinner", mainIngredientId: "zucchini", dietVariant: "vegan", count: 2 },
  { mealType: "dinner", mainIngredientId: "paprika", dietVariant: "standard", count: 3 },
  { mealType: "dinner", mainIngredientId: "paprika", dietVariant: "vegan", count: 2 },
  { mealType: "dinner", mainIngredientId: "carrot", dietVariant: "standard", count: 3 },
  { mealType: "dinner", mainIngredientId: "carrot", dietVariant: "vegetarian", count: 2 },
  { mealType: "dinner", mainIngredientId: "spinach", dietVariant: "standard", count: 3 },
  { mealType: "dinner", mainIngredientId: "spinach", dietVariant: "vegan", count: 2 },
];

const GROUP_MAIN_IDS: Record<string, string[]> = {
  "1": ["milk_3", "cream", "creme_fraiche", "chicken_breast", "chicken_thigh"],
  "2": ["minced_beef", "minced_mixed", "salmon", "cod", "shrimp"],
  "3": ["pasta_dry", "rice_white", "potato", "tofu", "red_lentils", "chickpeas_canned", "black_beans_canned", "tomato", "zucchini", "paprika", "carrot", "spinach"],
};

// ── 2b. RUNDA 2: gap-fokuserade targets (~80 nya mål) ──────────────────────
const RUNDA_2_TARGETS: PipelineTarget[] = [
  // Paprika-gap (0 st) – variera kök
  { mealType: "dinner", mainIngredientId: "paprika", dietVariant: "standard", count: 4 },
  { mealType: "dinner", mainIngredientId: "paprika", dietVariant: "vegetarian", count: 3 },
  { mealType: "dinner", mainIngredientId: "paprika", dietVariant: "vegan", count: 3 },
  { mealType: "dinner", mainIngredientId: "paprika", dietVariant: "gluten_free", count: 2 },

  // Blandfärs-gap (2 st)
  { mealType: "dinner", mainIngredientId: "minced_mixed", dietVariant: "standard", count: 5 },
  { mealType: "dinner", mainIngredientId: "minced_mixed", dietVariant: "gluten_free", count: 3 },
  { mealType: "dinner", mainIngredientId: "minced_mixed", dietVariant: "lactose_free", count: 3 },

  // Räkor-gap (3 st)
  { mealType: "dinner", mainIngredientId: "shrimp", dietVariant: "standard", count: 5 },
  { mealType: "dinner", mainIngredientId: "shrimp", dietVariant: "gluten_free", count: 3 },
  { mealType: "dinner", mainIngredientId: "shrimp", dietVariant: "lactose_free", count: 3 },

  // Torsk-gap (4 st)
  { mealType: "dinner", mainIngredientId: "cod", dietVariant: "standard", count: 5 },
  { mealType: "dinner", mainIngredientId: "cod", dietVariant: "gluten_free", count: 3 },
  { mealType: "dinner", mainIngredientId: "cod", dietVariant: "lactose_free", count: 3 },

  // Svarta bönor-gap (4 st)
  { mealType: "dinner", mainIngredientId: "black_beans_canned", dietVariant: "vegan", count: 4 },
  { mealType: "dinner", mainIngredientId: "black_beans_canned", dietVariant: "vegetarian", count: 3 },
  { mealType: "dinner", mainIngredientId: "black_beans_canned", dietVariant: "gluten_free", count: 2 },

  // Kycklinglår (6 st) – fler varianter
  { mealType: "dinner", mainIngredientId: "chicken_thigh", dietVariant: "standard", count: 4 },
  { mealType: "dinner", mainIngredientId: "chicken_thigh", dietVariant: "gluten_free", count: 3 },
  { mealType: "dinner", mainIngredientId: "chicken_thigh", dietVariant: "lactose_free", count: 3 },

  // Tomat/zucchini (6 st vardera) – fler veg-varianter
  { mealType: "dinner", mainIngredientId: "tomato", dietVariant: "vegan", count: 4 },
  { mealType: "dinner", mainIngredientId: "tomato", dietVariant: "vegetarian", count: 3 },
  { mealType: "dinner", mainIngredientId: "tomato", dietVariant: "standard", count: 3 },
  { mealType: "dinner", mainIngredientId: "zucchini", dietVariant: "vegan", count: 4 },
  { mealType: "dinner", mainIngredientId: "zucchini", dietVariant: "vegetarian", count: 3 },
  { mealType: "dinner", mainIngredientId: "zucchini", dietVariant: "standard", count: 3 },

  // Tofu (8 st) – fler asiatiska varianter
  { mealType: "dinner", mainIngredientId: "tofu", dietVariant: "vegan", count: 4 },
  { mealType: "dinner", mainIngredientId: "tofu", dietVariant: "vegetarian", count: 3 },
  { mealType: "dinner", mainIngredientId: "tofu", dietVariant: "gluten_free", count: 2 },

  // Morot/spenat – lågt täckta
  { mealType: "dinner", mainIngredientId: "carrot", dietVariant: "vegetarian", count: 3 },
  { mealType: "dinner", mainIngredientId: "carrot", dietVariant: "vegan", count: 3 },
  { mealType: "dinner", mainIngredientId: "spinach", dietVariant: "standard", count: 3 },
  { mealType: "dinner", mainIngredientId: "spinach", dietVariant: "vegetarian", count: 3 },
  { mealType: "dinner", mainIngredientId: "spinach", dietVariant: "vegan", count: 2 },
];

const targetGroup = process.env.TARGET_GROUP;
const isRunda2 = process.env.RUNDA === "2";
const SOURCE_TARGETS = isRunda2 ? RUNDA_2_TARGETS : BASE_TARGETS;
const ACTIVE_TARGETS = targetGroup && GROUP_MAIN_IDS[targetGroup]
  ? SOURCE_TARGETS.filter((t) => GROUP_MAIN_IDS[targetGroup]!.includes(t.mainIngredientId))
  : SOURCE_TARGETS;

const TOTAL_TARGETS = ACTIVE_TARGETS.reduce((s, t) => s + t.count, 0);
const BATCH_SIZE = 5;

// ── 3. Hjälpfunktioner ─────────────────────────────────────────────────────
const VALID_CUISINES = new Set([
  "swedish", "nordic", "italian", "french", "spanish", "greek", "thai",
  "chinese", "japanese", "korean", "vietnamese", "indian", "mexican",
  "american", "turkish", "lebanese", "moroccan", "middle_eastern", "international",
]);

function normalizeCuisine(raw: string | undefined): string {
  const c = (raw ?? "swedish").toLowerCase().replace(/[^a-z]/g, "_");
  if (VALID_CUISINES.has(c)) return c;
  if (c === "asian") return "international";
  if (c.includes("swedish") || c.includes("nordic")) return "swedish";
  if (c.includes("italian")) return "italian";
  if (c.includes("asian")) return "international";
  if (c.includes("japan")) return "japanese";
  if (c.includes("china")) return "chinese";
  if (c.includes("thai")) return "thai";
  if (c.includes("indian")) return "indian";
  if (c.includes("mexican")) return "mexican";
  if (c.includes("american")) return "american";
  if (c.includes("mediterranean")) return "greek";
  return "international";
}

function toSlug(title: string): string {
  return title
    .toLowerCase()
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "");
}

function normalizeTitle(title: string): string {
  return title.toLowerCase().replace(/[^a-z0-9åäö]/g, " ");
}

function shuffle<T>(arr: T[]): T[] {
  const a = [...arr];
  for (let i = a.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [a[i], a[j]] = [a[j], a[i]];
  }
  return a;
}

function formatRecipeMarkdown(r: SeededRecipe, idx: number): string {
  const lines: string[] = [];
  lines.push(`## ${idx + 1}. ${r.titleSv}`);
  lines.push(`**Status:** ${r.status} | **Kök:** ${r.cuisine} | **Portioner:** ${r.portions}`);
  lines.push(`**Tid:** ${r.prepTimeMinutes} min prep + ${r.cookTimeMinutes} min kok = ${r.totalTimeMinutes} min`);
  lines.push(`**Allergener:** ${r.allergens.join(", ") || "inget"}`);
  lines.push(`**Näring/portion:** ${r.nutritionText}`);
  lines.push("");
  lines.push(r.descriptionSv);
  lines.push("");
  lines.push("### Ingredienser");
  for (const ing of r.ingredients) {
    lines.push(`- ${ing.displayNameSv}: ${ing.quantity} ${ing.unit}${ing.optional ? " (valfri)" : ""}`);
  }
  lines.push("");
  lines.push("### Steg");
  for (const s of r.steps) {
    lines.push(`${s.stepNumber}. ${s.instructionSv}${s.temperatureC ? ` (${s.temperatureC}°C)` : ""}`);
  }
  if (r.flagReasons.length) {
    lines.push("");
    lines.push(`**Flaggat:** ${r.flagReasons.join("; ")}`);
  }
  lines.push("");
  return lines.join("\n");
}

interface SeededRecipe {
  id: string;
  titleSv: string;
  slug: string;
  descriptionSv: string;
  cuisine: string;
  status: string;
  portions: number;
  prepTimeMinutes: number;
  cookTimeMinutes: number;
  totalTimeMinutes: number;
  allergens: string[];
  nutritionText: string;
  ingredients: { displayNameSv: string; quantity: number; unit: string; optional: boolean; canonicalIngredientId: string }[];
  steps: { stepNumber: number; instructionSv: string; temperatureC: number | null }[];
  flagReasons: string[];
}

interface BatchResult {
  batchId: string;
  targets: PipelineTarget[];
  generated: number;
  verified: number;
  unverified: number;
  rejected: number;
  duplicate: number;
  seeded: number;
  costUsd: number;
  sample: SeededRecipe[];
}

// ── 4. Huvudflöde ──────────────────────────────────────────────────────────
async function main() {
  const dbUrl = process.env.DATABASE_URL;
  if (!dbUrl) {
    console.error("[scale-batch] DATABASE_URL saknas");
    process.exit(1);
  }

  const { db, pool } = createDatabase(dbUrl);
  const client = createAamosClient(process.env);

  console.error(`[scale-batch] Startar STEG 2: ${TOTAL_TARGETS} mål, batchar om ~${BATCH_SIZE}`);
  console.error(`[scale-batch] Katalog: ${catalog.length} ingredienser`);

  // Ladda befintliga titlar för dedup
  const existingRecipes = await db.query.recipes.findMany({
    columns: { id: true, titleSv: true },
  });
  const knownTitles = new Set(existingRecipes.map((r) => normalizeTitle(r.titleSv)));
  const knownSlugs = new Set(existingRecipes.map((r) => r.slug));
  console.error(`[scale-batch] Befintliga recept i DB: ${existingRecipes.length}`);

  // Ladda befintliga similarities (vi kommer bara skriva nya, men bra att ha om vi vill kolla)
  // För närvarande räcker titel-dedup för STEG 2.

  const similarityLookup: SimilarityLookup = {
    async hasSimilarity(title: string) {
      return knownTitles.has(normalizeTitle(title));
    },
  };

  // Dela upp targets i batchar
  const batches: PipelineTarget[][] = [];
  let currentBatch: PipelineTarget[] = [];
  let currentCount = 0;
  for (const t of ACTIVE_TARGETS) {
    if (currentCount + t.count > BATCH_SIZE && currentBatch.length > 0) {
      batches.push(currentBatch);
      currentBatch = [];
      currentCount = 0;
    }
    currentBatch.push(t);
    currentCount += t.count;
  }
  if (currentBatch.length) batches.push(currentBatch);
  console.error(`[scale-batch] ${batches.length} batchar planerade`);

  const batchResults: BatchResult[] = [];
  const allSeeded: SeededRecipe[] = [];
  let totalCost = 0;
  let totalGenerated = 0;
  let totalVerified = 0;
  let totalUnverified = 0;
  let totalRejected = 0;
  let totalDuplicate = 0;
  let totalSeeded = 0;

  for (let batchIdx = 0; batchIdx < batches.length; batchIdx++) {
    const batchTargets = batches[batchIdx]!;
    const batchId = `steg2-batch-${batchIdx + 1}-${Date.now()}`;
    console.error(`\n[scale-batch] Batch ${batchIdx + 1}/${batches.length} (${batchTargets.reduce((s, t) => s + t.count, 0)} recept)`);

    const result = await runPipeline(
      client,
      batchTargets,
      catalog,
      ingredientLookup,
      similarityLookup,
      { maxPrepTimeMinutes: 30, maxCookTimeMinutes: 45, portions: 4 },
    );

    totalGenerated += result.candidates.length;
    totalVerified += result.verifiedCount;
    totalUnverified += result.unverifiedCount;
    totalRejected += result.rejectedCount;
    totalCost += result.geminiResult.costUsd ?? 0;

    if (result.geminiResult.status !== "ok" || result.candidates.length === 0) {
      console.error(`[scale-batch] Batch ${batchIdx + 1} gav inga kandidater: ${result.geminiResult.error ?? "ok utan output"}`);
      batchResults.push({
        batchId,
        targets: batchTargets,
        generated: 0,
        verified: 0,
        unverified: 0,
        rejected: 0,
        duplicate: 0,
        seeded: 0,
        costUsd: result.geminiResult.costUsd ?? 0,
        sample: [],
      });
      continue;
    }

    // Seed verified kandidater som inte är dubbletter
    let batchDuplicate = 0;
    let batchSeeded = 0;
    const batchSeededRecipes: SeededRecipe[] = [];

    for (let i = 0; i < result.candidates.length; i++) {
      const candidate = result.candidates[i]!;
      const v = result.verificationResults[i]!;

      if (v.status !== "verified") continue;

      const titleNorm = normalizeTitle(candidate.titleSv);
      if (knownTitles.has(titleNorm)) {
        batchDuplicate++;
        continue;
      }

      let slug = toSlug(candidate.titleSv);
      if (knownSlugs.has(slug)) {
        slug = `${slug}-${randomUUID().slice(0, 8)}`;
      }
      knownSlugs.add(slug);
      knownTitles.add(titleNorm);

      const recipeId = randomUUID();
      const nutrition = v.nutritionPerPortion ?? {
        kcal: 0, proteinG: 0, carbsG: 0, fatG: 0,
        saturatedFatG: 0, fiberG: 0, sugarG: 0, saltG: 0,
      };

      try {
        await db.insert(schema.recipes).values({
          id: recipeId,
          slug,
          titleSv: candidate.titleSv,
          descriptionSv: candidate.descriptionSv,
          cuisine: normalizeCuisine(candidate.cuisine),
          mealTypes: candidate.mealTypes,
          tags: [],
          methods: [],
          equipment: [],
          prepTimeMinutes: candidate.prepTimeMinutes,
          cookTimeMinutes: candidate.cookTimeMinutes,
          totalTimeMinutes: candidate.totalTimeMinutes,
          portions: candidate.portions,
          nutritionPerPortion: nutrition,
          allergens: v.allergens,
          spiceLevel: candidate.spiceLevel,
          storageGuidanceSv: candidate.storageGuidanceSv ?? null,
          mealPrepFriendly: candidate.mealPrepFriendly,
          freezerFriendly: candidate.freezerFriendly,
          dna: {
            cuisine: normalizeCuisine(candidate.cuisine) as "swedish",
            protein: candidate.ingredients.find((i) => {
              const ing = ingredientLookup.getById(i.canonicalIngredientId);
              return ing && (ing.isBeef || ing.isPork || ["chicken_breast", "chicken_thigh", "salmon", "cod", "shrimp", "tofu", "red_lentils", "chickpeas_canned", "black_beans_canned", "minced_beef", "minced_mixed"].includes(i.canonicalIngredientId));
            })?.canonicalIngredientId,
            carbohydrate: candidate.ingredients.find((i) => ["rice_white", "pasta_dry", "potato"].includes(i.canonicalIngredientId))?.canonicalIngredientId,
            vegetables: candidate.ingredients.filter((i) => {
              const ing = ingredientLookup.getById(i.canonicalIngredientId);
              return ing?.category === "gronsaker" || ["tomato", "zucchini", "paprika", "carrot", "spinach", "onion", "garlic"].includes(i.canonicalIngredientId);
            }).map((i) => i.canonicalIngredientId),
            flavorProfile: inferDietTags(candidate),
            spiceLevel: candidate.spiceLevel,
            method: "stovetop",
            timeMinutes: candidate.totalTimeMinutes,
            calories: Math.round(nutrition.kcal),
            proteinGrams: Math.round(nutrition.proteinG),
          },
          variantType: "standard",
          status: "draft",
          verificationStatus: "verified",
          sourceType: "ai_assisted_reviewed",
          creatorDisplayName: "Cibello AI",
        });
      } catch (err: any) {
        if (err?.message?.includes("recipes_slug_unique") || err?.code === "23505") {
          batchDuplicate++;
          continue;
        }
        throw err;
      }

      for (let ingIdx = 0; ingIdx < candidate.ingredients.length; ingIdx++) {
        const ing = candidate.ingredients[ingIdx]!;
        await db.insert(schema.recipeIngredients).values({
          recipeId,
          canonicalIngredientId: ing.canonicalIngredientId,
          displayNameSv: ing.displayNameSv,
          quantity: ing.quantity,
          unit: ing.unit,
          note: ing.note ?? null,
          optional: ing.optional,
          sortOrder: ingIdx,
        });
      }

      for (let stepIdx = 0; stepIdx < candidate.steps.length; stepIdx++) {
        const step = candidate.steps[stepIdx]!;
        await db.insert(schema.recipeSteps).values({
          recipeId,
          stepNumber: step.stepNumber,
          instructionSv: step.instructionSv,
          timerSeconds: step.timerSeconds,
          temperatureC: step.temperatureC,
          tip: step.tip ?? null,
        });
      }

      const flagReasons = flagRecipe(candidate, nutrition);
      const seededRecipe: SeededRecipe = {
        id: recipeId,
        titleSv: candidate.titleSv,
        slug,
        descriptionSv: candidate.descriptionSv,
        cuisine: normalizeCuisine(candidate.cuisine),
        status: "verified",
        portions: candidate.portions,
        prepTimeMinutes: candidate.prepTimeMinutes,
        cookTimeMinutes: candidate.cookTimeMinutes,
        totalTimeMinutes: candidate.totalTimeMinutes,
        allergens: v.allergens,
        nutritionText: `${Math.round(nutrition.kcal)} kcal, P ${nutrition.proteinG.toFixed(1)}g, K ${nutrition.carbsG.toFixed(1)}g, F ${nutrition.fatG.toFixed(1)}g`,
        ingredients: candidate.ingredients.map((ing) => ({
          displayNameSv: ing.displayNameSv,
          quantity: ing.quantity,
          unit: ing.unit,
          optional: ing.optional,
          canonicalIngredientId: ing.canonicalIngredientId,
        })),
        steps: candidate.steps.map((s) => ({
          stepNumber: s.stepNumber,
          instructionSv: s.instructionSv,
          temperatureC: s.temperatureC,
        })),
        flagReasons,
      };
      batchSeededRecipes.push(seededRecipe);
      allSeeded.push(seededRecipe);
      batchSeeded++;
    }

    totalDuplicate += batchDuplicate;
    totalSeeded += batchSeeded;

    // Stickprov: ~15% slumpat av denna batchs seeded recept
    const sampleSize = Math.max(1, Math.round(batchSeededRecipes.length * 0.15));
    const sample = shuffle(batchSeededRecipes).slice(0, sampleSize);

    batchResults.push({
      batchId,
      targets: batchTargets,
      generated: result.candidates.length,
      verified: result.verifiedCount,
      unverified: result.unverifiedCount,
      rejected: result.rejectedCount,
      duplicate: batchDuplicate,
      seeded: batchSeeded,
      costUsd: result.geminiResult.costUsd ?? 0,
      sample,
    });

    // Logga varför unverified recept misslyckades (för debugging / prompt-förbättring)
    for (let i = 0; i < result.candidates.length; i++) {
      const v = result.verificationResults[i]!;
      if (v.status === "unverified" && v.reasons.length > 0) {
        console.error(`[scale-batch]   unverified: "${result.candidates[i]!.titleSv}" => ${v.reasons.join("; ")}`);
      }
    }

    console.error(`[scale-batch] Batch ${batchIdx + 1} klar: gen=${result.candidates.length}, ver=${result.verifiedCount}, unv=${result.unverifiedCount}, rej=${result.rejectedCount}, dup=${batchDuplicate}, seeded=${batchSeeded}, cost=$${(result.geminiResult.costUsd ?? 0).toFixed(4)}`);
  }

  // ── 5. Spara stickprov ────────────────────────────────────────────────────
  const publicDir = "/mnt/c/Users/Public";
  const sampleRecipes = shuffle(allSeeded).slice(0, Math.max(1, Math.round(allSeeded.length * 0.15)));
  const sampleMarkdown = [
    "# Cibello STEG 2 – Stickprov för smakkoll",
    `Genererad: ${new Date().toISOString()}`,
    `Totalt seedade: ${allSeeded.length}`,
    `Stickprov: ${sampleRecipes.length}`,
    "",
    ...sampleRecipes.map((r, i) => formatRecipeMarkdown(r, i)),
  ].join("\n");
  await fs.writeFile(path.join(publicDir, "cibello-steg2-sample.md"), sampleMarkdown, "utf-8");

  // ── 6. Slutrapport ────────────────────────────────────────────────────────
  const report = {
    runId: `steg2-${Date.now()}`,
    generatedAt: new Date().toISOString(),
    totalTargets: TOTAL_TARGETS,
    batches: batchResults.length,
    totalGenerated,
    totalVerified,
    totalUnverified,
    totalRejected,
    totalDuplicate,
    totalSeeded,
    totalCostUsd: totalCost,
    batchSummary: batchResults.map((b) => ({
      batchId: b.batchId,
      targets: b.targets.reduce((s, t) => s + t.count, 0),
      generated: b.generated,
      verified: b.verified,
      unverified: b.unverified,
      rejected: b.rejected,
      duplicate: b.duplicate,
      seeded: b.seeded,
      costUsd: b.costUsd,
      sampleSize: b.sample.length,
    })),
    coverageMatrix: buildCoverageMatrix(allSeeded),
  };

  await fs.writeFile(
    path.join(publicDir, "cibello-steg2-report.json"),
    JSON.stringify(report, null, 2),
    "utf-8",
  );

  // Skriv också till workspace för enkel åtkomst
  await fs.writeFile(
    "/home/dator_ubuntujpb/.openclaw/workspace/cibello-steg2-report.json",
    JSON.stringify(report, null, 2),
    "utf-8",
  );

  console.error("\n# STEG 2 – Slutrapport\n");
  console.error(`- Batchar: ${batchResults.length}`);
  console.error(`- Genererade kandidater: ${totalGenerated}`);
  console.error(`- Verified: ${totalVerified}`);
  console.error(`- Unverified: ${totalUnverified}`);
  console.error(`- Rejected: ${totalRejected}`);
  console.error(`- Dubbletter: ${totalDuplicate}`);
  console.error(`- Seedade i DB: ${totalSeeded}`);
  console.error(`- Total Gemini-kostnad: $${totalCost.toFixed(4)}`);
  console.error(`- Stickprov: ${sampleRecipes.length} recept → ${publicDir}\\cibello-steg2-sample.md`);
  console.error(`- Rapport: ${publicDir}\\cibello-steg2-report.json`);

  await closeDatabase();
}

function inferDietTags(candidate: RecipeCandidate): string[] {
  const tags: string[] = [];
  const ingIds = new Set(candidate.ingredients.map((i) => i.canonicalIngredientId));
  const ings = candidate.ingredients.map((i) => ingredientLookup.getById(i.canonicalIngredientId)).filter(Boolean);
  const allVegan = ings.every((i) => i?.isVegan);
  const allVegetarian = ings.every((i) => i?.isVegetarian);
  const noGluten = ings.every((i) => !i?.containsGluten);
  const noLactose = ings.every((i) => !i?.containsLactose);
  if (allVegan) tags.push("vegan");
  else if (allVegetarian) tags.push("vegetarian");
  if (noGluten) tags.push("gluten_free");
  if (noLactose) tags.push("lactose_free");
  return tags;
}

function flagRecipe(candidate: RecipeCandidate, nutrition: { kcal: number }): string[] {
  const reasons: string[] = [];
  if (nutrition.kcal > 1000) reasons.push("hög kalorihalt");
  if (nutrition.kcal < 150) reasons.push("låg kalorihalt");
  if (candidate.ingredients.length < 4) reasons.push("få ingredienser");
  const riskyAllergens = ["peanuts", "tree_nuts", "shellfish", "crustaceans", "fish", "milk", "gluten", "eggs"];
  const ingIds = new Set(candidate.ingredients.map((i) => i.canonicalIngredientId));
  for (const id of ingIds) {
    const ing = ingredientLookup.getById(id);
    if (ing?.allergens.some((a) => riskyAllergens.includes(a))) {
      reasons.push("kant-allergener");
      break;
    }
  }
  return reasons;
}

function buildCoverageMatrix(seeded: SeededRecipe[]) {
  const mainIngredientIds = new Set(ACTIVE_TARGETS.map((t) => t.mainIngredientId));
  const matrix: Record<string, Record<string, number>> = {};

  for (const r of seeded) {
    // Hitta den mest sannolika huvudingrediensen: första ingrediensen som är en target-huvudingrediens
    const mainId = r.ingredients.find((i) => mainIngredientIds.has(i.canonicalIngredientId))?.canonicalIngredientId;
    if (!mainId) continue;

    // Härled dietvariant från ingredienserna
    const ings = r.ingredients.map((i) => ingredientLookup.getById(i.canonicalIngredientId)).filter(Boolean);
    const isVegan = ings.every((i) => i?.isVegan);
    const isVegetarian = ings.every((i) => i?.isVegetarian);
    const isGlutenFree = ings.every((i) => !i?.containsGluten);
    const isLactoseFree = ings.every((i) => !i?.containsLactose);
    let diet = "standard";
    if (isVegan) diet = "vegan";
    else if (isVegetarian) diet = "vegetarian";
    if (isGlutenFree && !isVegan && !isVegetarian) diet = "gluten_free";
    if (isLactoseFree && !isVegan && !isVegetarian && !isGlutenFree) diet = "lactose_free";

    matrix[mainId] ??= {};
    matrix[mainId]![diet] = (matrix[mainId]![diet] ?? 0) + 1;
  }

  return matrix;
}

main().catch((err) => {
  console.error("[scale-batch] Fatal:", err);
  process.exit(1);
});
