import { existsSync, readFileSync, statSync } from "node:fs";
import { basename } from "node:path";
import { loadMigrations, parseMigration, type Migration } from "@wrnexus/db";
import type { PackageMigrationDefinition } from "@wrnexus/plugin";

function migrationName(definition: PackageMigrationDefinition, name?: string): string {
  const prefix = definition.id.trim().replace(/[^a-zA-Z0-9_.-]+/g, "_");
  const suffix = name?.trim().replace(/[^a-zA-Z0-9_.-]+/g, "_");
  return suffix ? `${prefix}__${suffix}` : prefix;
}

function databaseMatches(definition: PackageMigrationDefinition, database?: string): boolean {
  const target = definition.database?.trim() || "default";
  return target === (database?.trim() || "default");
}

/** Resolve package-owned migrations into the same ordered contract as app migrations. */
export function resolvePackageMigrations(
  definitions: readonly PackageMigrationDefinition[],
  database?: string,
): Migration[] {
  const output: Migration[] = [];
  for (const definition of definitions) {
    if (!databaseMatches(definition, database)) continue;
    if (definition.source !== undefined) {
      const parsed = parseMigration(migrationName(definition), definition.source);
      output.push(parsed);
      continue;
    }
    const entry = definition.entry;
    if (!entry || !existsSync(entry)) {
      throw new Error(
        `WRN-PLUGIN-MIGRATION-MISSING: ${definition.id} points to ${entry ?? "<empty>"}.`,
      );
    }
    const stat = statSync(entry);
    if (stat.isDirectory()) {
      for (const migration of loadMigrations(entry)) {
        output.push({ ...migration, name: migrationName(definition, migration.name) });
      }
      continue;
    }
    if (!stat.isFile() || !entry.endsWith(".sql")) {
      throw new Error(
        `WRN-PLUGIN-MIGRATION-ENTRY: ${definition.id} must be a .sql file or directory.`,
      );
    }
    output.push(
      parseMigration(
        migrationName(definition, basename(entry, ".sql")),
        readFileSync(entry, "utf8"),
      ),
    );
  }
  return output;
}
