import { transpilePhase2Guard } from "@specverse/engines/inference";
function generatePrismaGuards(context) {
  const { model } = context;
  if (!model) {
    throw new Error("Model is required for guards generation");
  }
  const constraints = model.constraints ?? [];
  if (constraints.length === 0) {
    return generateEmptyGuardsModule(model.name);
  }
  const guardFns = [];
  const constraintsTable = [];
  let hasSubqueryGuard = false;
  for (const c of constraints) {
    const transpiled = transpilePhase2Guard(
      c.requires.name,
      c.requires.params ?? `self: any, actor: any`,
      c.requires.body
    );
    const rewritten = rewriteSubqueriesAsync(transpiled.typescript, c.requires.name);
    guardFns.push(rewritten);
    if (rewritten.includes("ctx?.query?.")) hasSubqueryGuard = true;
    const onArrayLiteral = `[${c.on.map((o) => JSON.stringify(o)).join(", ")}]`;
    const sourceLiteral = JSON.stringify(
      c.requires.source.input ?? ""
    );
    constraintsTable.push(
      `  {
    on: ${onArrayLiteral},
    guard: ${c.requires.name},
    name: ${JSON.stringify(c.requires.name)},
    source: ${sourceLiteral}
  }`
    );
  }
  return `/**
 * Auto-generated by SpecVerse Phase 2 (Validate-Centric Constraints).
 *
 * Guard functions for ${model.name}'s declared constraints.
 * DO NOT EDIT \u2014 regenerated on every \`spv realize all\`.
 *
 * Slice 1 limitations:
 *   - \`self\` is the input payload (not the loaded entity). For Update/Delete/
 *     Evolve, paths through \`self\` see only the fields the caller sent.
 *   - \`actor\` defaults to null. Constraints using \`actor.*\` paths fail at
 *     runtime when called without a user context.
 */

export interface Violation {
  constraint: string;
  scope: string;
  source: string;
  message: string;
}

/**
 * GuardContext is provided by the controller's validate() method. The
 * \`query\` accessor returns a per-model helper that backs subquery sugars
 * like \`{Actor} has not {verb} on {Target}\` (which the transpiler converts
 * to \`<Model>.some(predicate)\`). Implementations call the ORM's findMany
 * (or the in-memory store for dynamic interpreters).
 */
export interface GuardContext {
  query?: (modelName: string) => {
    exists: (predicate: (entity: any) => boolean) => Promise<boolean>;
  } | undefined;
}

export interface ConstraintRecord {
  on: string[];
  // Pure self/actor guards stay sync; subquery guards (those that reference
  // bare-Capitalized model names like \`Vote.some(...)\`) are rewritten by
  // the generator into async functions that take a third ctx argument.
  // runGuards awaits the result either way.
  guard: (self: any, actor: any, ctx?: GuardContext) => boolean | Promise<boolean>;
  name: string;
  source: string;
}
${hasSubqueryGuard ? `
/**
 * Date-safe operand coercion for relational (< <= > >=) comparisons in
 * subquery guards. A stored value arrives as a Date (from the ORM) while the
 * input payload carries an ISO string; comparing them raw yields NaN. Map
 * either to epoch ms; pass non-dates (numbers, ids) through unchanged.
 */
function __cmp(x: any): any {
  if (x instanceof Date) return x.getTime();
  if (typeof x === 'string' && /^\\d{4}-\\d{2}-\\d{2}T/.test(x)) {
    const t = Date.parse(x);
    return Number.isNaN(t) ? x : t;
  }
  return x;
}
` : ""}
${guardFns.join("\n\n")}

export const MODEL_CONSTRAINTS: ConstraintRecord[] = [
${constraintsTable.join(",\n")}
];

/**
 * Match a runtime operation against a constraint's \`on:\` array.
 *
 * Op shapes:
 *   - exact string: 'create', 'update', 'delete', 'retrieve'
 *   - 'evolve.<transition>': specific lifecycle transition
 *
 * \`on:\` entry shapes:
 *   - '*': always matches
 *   - 'evolve.*': matches any 'evolve.X' op
 *   - else: exact-string equality
 */
export function matchesOp(constraintOn: string[], op: string): boolean {
  for (const o of constraintOn) {
    if (o === '*') return true;
    if (o === op) return true;
    if (o === 'evolve.*' && op.startsWith('evolve.')) return true;
  }
  return false;
}

/**
 * Run all constraints whose \`on:\` matches the requested op. Each guard
 * is invoked with (input, actor). Returns the collected violations.
 *
 * Failure-mode policy (fail-OPEN on exceptions):
 *   - Guard returns false \u2192 one Violation appended
 *   - Guard returns true  \u2192 skip
 *   - Guard throws        \u2192 console.error + treat as PASS (no violation)
 *
 * Rationale: a throw indicates a guard-internal defect (transpile bug,
 * undefined-path traversal, type mismatch) rather than a real constraint
 * failure. Blocking the mutation would deny legitimate user actions for
 * a bug we already log loudly. Real data-integrity violations still fall
 * back to database constraints + Slice 3 mode-\u03B3 preflight retries.
 */
export async function runGuards(
  input: any,
  op: string,
  actor: any = null,
  ctx: GuardContext | null = null,
): Promise<Violation[]> {
  const violations: Violation[] = [];
  for (const c of MODEL_CONSTRAINTS) {
    if (!matchesOp(c.on, op)) continue;
    let passed = true;
    try {
      // await wraps both sync (boolean) and async (Promise<boolean>) guards
      // uniformly. Subquery guards need ctx; sync guards ignore it.
      passed = await c.guard(input, actor, ctx ?? undefined);
    } catch (e: any) {
      // Fail-open: log loudly, do NOT block the mutation. See policy above.
      console.error(
        \`[runGuards] constraint "\${c.name}" (source: \${c.source}) threw during op="\${op}" \u2014 treating as PASS:\`,
        e?.stack ?? e?.message ?? e,
      );
      continue;
    }
    if (!passed) {
      violations.push({
        constraint: c.name,
        scope: op,
        source: c.source,
        message: \`Constraint "\${c.source}" failed\`,
      });
    }
  }
  return violations;
}
`;
}
function generateEmptyGuardsModule(modelName) {
  return `/**
 * Auto-generated by SpecVerse Phase 2 \u2014 no constraints declared on ${modelName}.
 * Empty stub kept for symmetry; controllers gate their imports on this file.
 */

export interface Violation {
  constraint: string;
  scope: string;
  source: string;
  message: string;
}

export const MODEL_CONSTRAINTS: any[] = [];

export function matchesOp(_constraintOn: string[], _op: string): boolean {
  return false;
}

export async function runGuards(_input: any, _op: string, _actor: any = null, _ctx: any = null): Promise<Violation[]> {
  return [];
}
`;
}
function rewriteSubqueriesAsync(transpiled, guardName) {
  const subqueryPattern = /(?<![\w.])([A-Z][A-Za-z0-9_]*)\.some\(/g;
  const matches = Array.from(transpiled.matchAll(subqueryPattern));
  if (matches.length === 0) {
    return transpiled;
  }
  const modelNames = Array.from(new Set(matches.map((m) => m[1])));
  let body = transpiled.replace(subqueryPattern, (_match, modelName) => {
    return `await __${modelName}.exists(`;
  });
  body = body.replace(
    /export\s+function\s+(\w+)\(([^)]*)\)\s*:\s*boolean\s*\{/,
    (_match, fnName, params) => {
      return `export async function ${fnName}(${params}, ctx?: any): Promise<boolean> {`;
    }
  );
  const shimLines = modelNames.map(
    (m) => `  const __${m} = ctx?.query?.(${JSON.stringify(m)});
  if (!__${m}) return true; // fail-open: no query ctx, skip subquery`
  ).join("\n");
  body = body.replace(
    /(:\s*Promise<boolean>\s*\{\n)/,
    `$1${shimLines}
`
  );
  body = body.replace(
    /([A-Za-z_$][\w$]*(?:\.[\w$]+)+)\s*(<=?|>=?)\s*([A-Za-z_$][\w$]*(?:\.[\w$]+)+)/g,
    "__cmp($1) $2 __cmp($3)"
  );
  void guardName;
  return body;
}
export {
  generatePrismaGuards as default
};
