import { pathToFileURL } from "node:url";
import { prisma } from "../db/prisma.js";
import { techStepClassifier } from "../lib/recipe-matching/tech-step-matcher.js";
import { renumberStepTechSteps } from "../modules/recipe/recipe-tech-step-correction.service.js";

/**
 * Recomputes every existing `Step`'s `"auto"`-sourced `StepTechStep`
 * entries against the *current* classifier
 * (`tech-step-matcher.ts`, delegating to `services/tech-step-intent-service`),
 * the same way `updateRecipe` does when a user resaves a recipe through the UI —
 * always `"fr"` (`DEFAULT_TECH_STEP_LOCALE` in `recipe.service.ts`; there's
 * no persisted per-recipe locale to recover for a step that already
 * exists, so this matches real resave behavior exactly rather than
 * guessing).
 *
 * Needed because tech-step detection only ever runs at create/update time
 * (`recipe.service.ts`'s `matchStepsTechSteps`), never retroactively — a
 * step saved before a classifier/corpus change (new vocabulary, or the
 * `contextStart`/`contextEnd` columns a previous session added) keeps
 * whatever it was matched with at the time until it's next resaved.
 *
 * `"manual"`-sourced entries (a viewer's correction, applied immediately —
 * see `recipe-tech-step-correction.service.ts`'s `applyManualCorrection`)
 * are never touched by this: only rows with `source: "auto"` are deleted
 * and recreated, and any fresh classifier match overlapping an existing
 * `"manual"` entry's span is dropped rather than inserted — a manual
 * correction is meant to *override* the classifier at that exact spot,
 * and recomputing must never silently reintroduce (or duplicate-highlight)
 * what a user already corrected. `renumberStepTechSteps`
 * (`recipe-tech-step-correction.service.ts`) folds the surviving `"auto"` +
 * untouched `"manual"` rows back into one coherent reading-order sequence
 * afterward.
 *
 * Exported (not just called from this file's own CLI guard below) so
 * `retrain-tech-steps.ts` can run it as one step of its own larger
 * maintainer workflow, without shelling out to a second process.
 *
 * Safe to re-run: with no manual entries and no corpus change since the
 * last run, this is a no-op (the same `"auto"` matches get deleted and
 * recreated identically); with manual entries present, they're preserved
 * on every run by construction.
 */
export async function backfillTechSteps(): Promise<{ total: number; changed: number }> {
  const steps = await prisma.step.findMany({ select: { id: true, description: true } });
  console.info(`Recomputing tech steps for ${steps.length} step(s)...`);

  let changed = 0;
  for (const step of steps) {
    const matches = await techStepClassifier.matchTechStepSpans(step.description, "fr");

    await prisma.$transaction(async (tx) => {
      const manualRows = await tx.stepTechStep.findMany({
        where: { stepId: step.id, source: "manual" },
      });

      const nonOverlappingMatches = matches.filter(
        (match) =>
          !manualRows.some(
            (manual) =>
              manual.start !== null &&
              manual.end !== null &&
              manual.start < match.end &&
              match.start < manual.end,
          ),
      );

      await tx.stepTechStep.deleteMany({ where: { stepId: step.id, source: "auto" } });

      if (nonOverlappingMatches.length > 0) {
        // Placeholder orders, disjoint from the untouched manual rows'
        // existing ones (`renumberStepTechSteps` below folds everything
        // into a clean 0..N-1 sequence right after — these just need to
        // not collide with `@@id([stepId, order])` for this insert).
        const startOrder = manualRows.reduce((max, row) => Math.max(max, row.order), -1) + 1;
        await tx.stepTechStep.createMany({
          data: nonOverlappingMatches.map((match, index) => ({
            stepId: step.id,
            techStepId: match.techStepId,
            order: startOrder + index,
            start: match.start,
            end: match.end,
            contextStart: match.contextStart,
            contextEnd: match.contextEnd,
            source: "auto",
          })),
        });
      }

      await renumberStepTechSteps(tx, step.id);
    });
    changed += 1;
  }

  console.info(`Done — ${changed} step(s) recomputed.`);
  return { total: steps.length, changed };
}

// Only runs when this file is executed directly (`tsx
// src/scripts/backfill-tech-steps.ts`), not when `retrain-tech-steps.ts`
// imports `backfillTechSteps` above — the standard ESM "is this the entry
// module" check, first needed in this codebase by that new script; every
// prior script here (`seed-runtime.ts`) was always only ever run directly,
// never imported. `pathToFileURL` (not a naive `` `file://${process.argv[1]}` ``
// concatenation) is required for this to actually work on Windows — a
// native Windows path (backslashes, no leading slash before the drive
// letter) doesn't survive being pasted directly after `file://`, so the
// comparison against `import.meta.url` (already a real, correctly-escaped
// `file:///D:/...` URL) always came out false: this guard silently never
// matched, so running this script directly (`tsx
// src/scripts/backfill-tech-steps.ts`) did *nothing* — no error, no
// output, `backfillTechSteps()` simply never called — found only by
// running it for real and noticing zero output where several log lines
// were expected.
const isMainModule = import.meta.url === pathToFileURL(process.argv[1] ?? "").href;
if (isMainModule) {
  backfillTechSteps()
    .then(() => prisma.$disconnect())
    .catch(async (err) => {
      console.error(err);
      await prisma.$disconnect();
      process.exit(1);
    });
}
