import { createHash } from "node:crypto";
import { Prisma } from "@prisma/client";
import type { FilledFormAuditEntry } from "./pdf-documents.js";
import { generateClientFormFromTemplate } from "./intelligence.js";
import { runIterativeI485AutofillLoop } from "./i485-autofill-loop.js";
import { createId } from "./id.js";
import { prisma } from "./prisma.js";
import { listSpecializedForms } from "./specialized-forms.js";
import {
  computeFormRunMetrics,
  deriveFormAgentCode,
  detectImmigrationCaseFromSignals,
  normalizeFormCode,
  type FormRunMetrics,
  type ImmigrationCaseAnalysis,
  type SectionCoverageMetric,
} from "./form-engine-heuristics.js";

type DiscoveredFormAgent = {
  agentCode: string;
  templateId: string;
  formCode: string;
  formName: string;
  edition: string | null;
  versionLabel: string;
  description: string | null;
  prompt: string;
  fieldCount: number;
  usesSpecializedLoop: boolean;
};

type CrossFormClaim = {
  sourceTemplateId: string | null;
  sourceFormCode: string | null;
  sourceFormName: string;
  pdfFieldName: string;
  label: string;
  value: string;
  confidence: number | null;
  sourceTitles: string[];
  rationale: string | null;
  createdAt: string;
};

export type FormEngineGlobalEntities = {
  personProfile: {
    clientId: string;
    fullName: string;
    preferredName: string | null;
    dateOfBirth: string | null;
    gender: string | null;
    email: string | null;
    phone: string | null;
    preferredLanguage: string | null;
    countryOfBirth: string | null;
    countryOfCitizenship: string | null;
    immigrationStatus: string | null;
  };
  addresses: Array<{
    addressType: string;
    line1: string;
    line2: string | null;
    city: string;
    stateRegion: string | null;
    postalCode: string | null;
    countryCode: string;
    isPrimary: boolean;
    validFrom: string | null;
    validTo: string | null;
  }>;
  employmentHistory: Array<{
    relationType: string;
    name: string;
    notes: string | null;
  }>;
  immigrationHistory: Array<{
    fieldKey: string;
    label: string;
    value: string;
    confidence: number;
    statusCode: string;
  }>;
  familyRelations: Array<{
    relationType: string;
    entityType: string;
    name: string;
    dateOfBirth: string | null;
    countryOfCitizenship: string | null;
    notes: string | null;
  }>;
  documents: Array<{
    title: string;
    documentTypeCode: string;
    scopeCode: string | null;
    createdAt: string;
    extractedPreview: string | null;
  }>;
  identifiers: Array<{
    identifierType: string;
    value: string;
    issuingCountryCode: string | null;
    issuedAt: string | null;
    expiresAt: string | null;
    isPrimary: boolean;
  }>;
  crossFormClaims: CrossFormClaim[];
  caseSummary: {
    caseId: string;
    title: string;
    caseTypeCode: string;
    caseSubtypeCode: string | null;
    statusCode: string;
    description: string | null;
  } | null;
};

type FormAgentMemory = {
  id: string;
  form_template_id: string | null;
  agent_code: string;
  client_id: string | null;
  case_id: string | null;
  memory_scope: string;
  memory_type: string;
  summary_text: string;
  detail_json: string | null;
  success_score: number | null;
  coverage_ratio: number | null;
  created_at: Date;
};

type GenericIterationSnapshot = {
  iteration: number;
  coverage: number;
  filledCount: number;
  unresolvedCount: number;
  fileName: string;
};

type GenericFormAgentRunResult = {
  agentCode: string;
  templateId: string;
  formCode: string;
  formName: string;
  iterations: number;
  coverage: number;
  filledCount: number;
  totalFieldCount: number;
  unresolvedFields: string[];
  generatedDocumentId: string;
  repositoryItemId: string;
  fileId: string;
  fileName: string;
  fieldValues: Record<string, string | null>;
  fieldAuditEntries: FilledFormAuditEntry[];
  validationWarnings: string[];
  completionSuggestions: unknown;
  coverageBySection: SectionCoverageMetric[];
  iterationHistory: GenericIterationSnapshot[];
};

type I485LoopPayload = {
  filledCount: number;
  unresolved: string[];
  fieldAuditEntries: FilledFormAuditEntry[];
  validationWarnings: string[];
  fields: Record<string, string | null>;
};

type RunFormEngineAgentResult = {
  agentCode: string;
  templateId: string;
  formCode: string;
  formName: string;
  metrics: FormRunMetrics;
  iterations: number;
  generatedDocumentId: string | null;
  repositoryItemId: string | null;
  fileId: string | null;
  fileName: string | null;
  unresolvedFields: string[];
  validationWarnings: string[];
  fieldAuditEntries: FilledFormAuditEntry[];
  iterationHistory: GenericIterationSnapshot[];
};

export type FormEngineRunResult = {
  runId: string;
  clientId: string;
  caseId: string | null;
  selectedAgentCodes: string[];
  availableAgentCodes: string[];
  globalEntities: FormEngineGlobalEntities;
  caseAnalysis: ImmigrationCaseAnalysis;
  results: RunFormEngineAgentResult[];
  generatedFormsCount: number;
  targetCoverage: number;
  startedAt: string;
  completedAt: string;
};

function normalizeWhitespace(value: string | null | undefined) {
  return String(value ?? "")
    .replace(/\s+/g, " ")
    .trim();
}

function normalizeComparableText(value: string | null | undefined) {
  return normalizeWhitespace(value)
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, " ")
    .trim();
}

function normalizeComparableValue(value: string | null | undefined) {
  return normalizeWhitespace(value)
    .toLowerCase()
    .replace(/\s+/g, " ");
}

function safeJsonParse<T>(value: string | null | undefined): T | null {
  const normalizedValue = String(value ?? "").trim();
  if (!normalizedValue) {
    return null;
  }

  try {
    return JSON.parse(normalizedValue) as T;
  } catch {
    return null;
  }
}

function limitText(value: string | null | undefined, maxLength = 320) {
  const normalizedValue = normalizeWhitespace(value);
  if (!normalizedValue) {
    return "";
  }

  if (normalizedValue.length <= maxLength) {
    return normalizedValue;
  }

  return `${normalizedValue.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
}

function uniqueByKey<T>(items: T[], keySelector: (item: T) => string) {
  const seen = new Set<string>();
  const output: T[] = [];

  for (const item of items) {
    const key = keySelector(item);
    if (!key || seen.has(key)) {
      continue;
    }
    seen.add(key);
    output.push(item);
  }

  return output;
}

function hashJson(value: unknown) {
  return createHash("sha256").update(JSON.stringify(value)).digest("hex");
}

function buildDefaultFormAgentPrompt(input: {
  formName: string;
  formCode: string;
  description: string | null;
}) {
  return [
    `You are a specialist legal form-filling agent for ${input.formName}${input.formCode ? ` (${input.formCode})` : ""}.`,
    input.description ? `Form description: ${input.description}` : null,
    "Use only evidence-supported facts.",
    "Never invent data.",
    "Always preserve source traceability and expose low-confidence or conflicting fields.",
    "Prefer already-confirmed client data and cross-form consistency when the legal meaning matches.",
    "Leave fields blank when the evidence is missing, conflicting, ambiguous, or legally unsafe to infer.",
  ]
    .filter(Boolean)
    .join("\n");
}

function summarizeGlobalEntitiesForPrompt(entities: FormEngineGlobalEntities) {
  return {
    personProfile: entities.personProfile,
    addresses: entities.addresses.slice(0, 5),
    identifiers: entities.identifiers.slice(0, 6),
    employmentHistory: entities.employmentHistory.slice(0, 6),
    immigrationHistory: entities.immigrationHistory.slice(0, 12),
    familyRelations: entities.familyRelations.slice(0, 8),
    crossFormClaims: entities.crossFormClaims.slice(0, 20).map((claim) => ({
      sourceFormCode: claim.sourceFormCode,
      sourceFormName: claim.sourceFormName,
      label: claim.label,
      value: claim.value,
      confidence: claim.confidence,
      sourceTitles: claim.sourceTitles.slice(0, 4),
    })),
    caseSummary: entities.caseSummary,
  };
}

function summarizeAgentMemoriesForPrompt(memories: FormAgentMemory[]) {
  const byType = new Map<string, FormAgentMemory[]>();

  for (const memory of memories) {
    const existing = byType.get(memory.memory_type) ?? [];
    existing.push(memory);
    byType.set(memory.memory_type, existing);
  }

  return {
    successfulPatterns: (byType.get("successful_pattern") ?? [])
      .slice(0, 5)
      .map((memory) => memory.summary_text),
    failedPatterns: (byType.get("failed_pattern") ?? [])
      .slice(0, 5)
      .map((memory) => memory.summary_text),
    humanCorrections: (byType.get("human_correction") ?? [])
      .slice(0, 5)
      .map((memory) => memory.summary_text),
    mappingImprovements: (byType.get("mapping_improvement") ?? [])
      .slice(0, 5)
      .map((memory) => memory.summary_text),
    questionOptimizations: (byType.get("question_optimization") ?? [])
      .slice(0, 5)
      .map((memory) => memory.summary_text),
    confidenceAdjustments: (byType.get("confidence_adjustment") ?? [])
      .slice(0, 5)
      .map((memory) => memory.summary_text),
  };
}

function buildExecutionPrompt(input: {
  basePrompt: string;
  agent: DiscoveredFormAgent;
  entities: FormEngineGlobalEntities;
  caseAnalysis: ImmigrationCaseAnalysis;
  memories: FormAgentMemory[];
  iteration: number;
  previousUnresolvedFields?: string[];
}) {
  const sections: string[] = [input.basePrompt.trim()];
  const memorySummary = summarizeAgentMemoriesForPrompt(input.memories);

  sections.push(
    "",
    "Cross-form intelligence policy:",
    "Reuse data already confirmed in other forms only when the legal meaning matches this form and there is no conflict.",
    "Treat the global entity layer and prior generated forms as evidence context, not as permission to invent new facts.",
    "If a cross-form claim conflicts with current evidence, surface the conflict and leave the field unanswered.",
    "",
    `Execution iteration: ${input.iteration}.`,
  );

  if (input.previousUnresolvedFields?.length) {
    sections.push(
      `Previous unresolved fields to re-evaluate conservatively: ${input.previousUnresolvedFields
        .slice(0, 25)
        .join(" | ")}.`,
    );
  }

  sections.push(
    "",
    "Global entity layer:",
    JSON.stringify(summarizeGlobalEntitiesForPrompt(input.entities), null, 2),
    "",
    "Detected immigration case analysis:",
    JSON.stringify(input.caseAnalysis, null, 2),
    "",
    "Agent memory:",
    JSON.stringify(memorySummary, null, 2),
  );

  return sections.join("\n");
}

async function ensureFormEngineTables() {
  await prisma.$executeRawUnsafe(`
    CREATE TABLE IF NOT EXISTS form_agent_memories (
      id CHAR(36) NOT NULL PRIMARY KEY,
      law_firm_id CHAR(36) NOT NULL,
      form_template_id CHAR(36) NULL,
      agent_code VARCHAR(80) NOT NULL,
      client_id CHAR(36) NULL,
      case_id CHAR(36) NULL,
      memory_scope VARCHAR(20) NOT NULL DEFAULT 'global',
      memory_type VARCHAR(50) NOT NULL,
      summary_text TEXT NOT NULL,
      detail_json JSON NULL,
      success_score DECIMAL(5,4) NULL,
      coverage_ratio DECIMAL(6,4) NULL,
      created_by_user_id CHAR(36) NULL,
      created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
      KEY idx_form_agent_memories_agent (law_firm_id, agent_code, created_at),
      KEY idx_form_agent_memories_client (client_id, created_at),
      KEY idx_form_agent_memories_case (case_id, created_at),
      KEY idx_form_agent_memories_template (form_template_id, created_at)
    )
  `);

  await prisma.$executeRawUnsafe(`
    CREATE TABLE IF NOT EXISTS form_global_entity_snapshots (
      id CHAR(36) NOT NULL PRIMARY KEY,
      law_firm_id CHAR(36) NOT NULL,
      client_id CHAR(36) NOT NULL,
      case_id CHAR(36) NULL,
      snapshot_hash CHAR(64) NOT NULL,
      snapshot_json JSON NOT NULL,
      source_summary_json JSON NULL,
      created_by_user_id CHAR(36) NULL,
      created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
      KEY idx_form_global_entity_snapshots_client (client_id, created_at),
      KEY idx_form_global_entity_snapshots_case (case_id, created_at),
      KEY idx_form_global_entity_snapshots_hash (snapshot_hash)
    )
  `);

  await prisma.$executeRawUnsafe(`
    CREATE TABLE IF NOT EXISTS immigration_case_analyses (
      id CHAR(36) NOT NULL PRIMARY KEY,
      law_firm_id CHAR(36) NOT NULL,
      client_id CHAR(36) NOT NULL,
      case_id CHAR(36) NULL,
      case_type_code VARCHAR(50) NOT NULL,
      stage_code VARCHAR(50) NOT NULL,
      detected_forms_json JSON NOT NULL,
      missing_forms_json JSON NOT NULL,
      recommended_next_steps_json JSON NOT NULL,
      evidence_json JSON NULL,
      created_by_user_id CHAR(36) NULL,
      created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
      KEY idx_immigration_case_analyses_client (client_id, created_at),
      KEY idx_immigration_case_analyses_case (case_id, created_at),
      KEY idx_immigration_case_analyses_type (case_type_code, stage_code)
    )
  `);

  await prisma.$executeRawUnsafe(`
    CREATE TABLE IF NOT EXISTS form_engine_runs (
      id CHAR(36) NOT NULL PRIMARY KEY,
      law_firm_id CHAR(36) NOT NULL,
      client_id CHAR(36) NOT NULL,
      case_id CHAR(36) NULL,
      run_status VARCHAR(30) NOT NULL DEFAULT 'running',
      target_coverage DECIMAL(6,4) NOT NULL DEFAULT 0.8000,
      selected_agents_json JSON NOT NULL,
      case_analysis_json JSON NULL,
      summary_json JSON NULL,
      created_by_user_id CHAR(36) NULL,
      started_at DATETIME NOT NULL,
      completed_at DATETIME NULL,
      created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
      updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
      KEY idx_form_engine_runs_client (client_id, created_at),
      KEY idx_form_engine_runs_case (case_id, created_at),
      KEY idx_form_engine_runs_status (run_status, created_at)
    )
  `);

  await prisma.$executeRawUnsafe(`
    CREATE TABLE IF NOT EXISTS form_engine_run_agents (
      id CHAR(36) NOT NULL PRIMARY KEY,
      form_engine_run_id CHAR(36) NOT NULL,
      agent_code VARCHAR(80) NOT NULL,
      form_template_id CHAR(36) NOT NULL,
      metrics_json JSON NOT NULL,
      result_json JSON NULL,
      generated_document_id CHAR(36) NULL,
      repository_item_id CHAR(36) NULL,
      file_id CHAR(36) NULL,
      created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
      updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
      KEY idx_form_engine_run_agents_run (form_engine_run_id, created_at),
      KEY idx_form_engine_run_agents_agent (agent_code, created_at)
    )
  `);
}

async function discoverFormAgents(input: {
  lawFirmId: string;
  actorUserId: string;
}) {
  try {
    await listSpecializedForms({
      lawFirmId: input.lawFirmId,
      actorUserId: input.actorUserId,
    });
  } catch {
    // Ignore bundled form bootstrap issues and keep tenant templates discoverable.
  }

  const rows = await prisma.$queryRaw<
    Array<{
      id: string;
      code: string;
      name: string;
      edition: string | null;
      version_label: string;
      description: string | null;
      agent_code: string | null;
      agent_prompt: string | null;
      field_count: bigint | number | string;
    }>
  >`
    SELECT
      ft.id,
      ft.code,
      ft.name,
      ft.edition,
      ft.version_label,
      ft.description,
      ft.agent_code,
      ft.agent_prompt,
      CAST(COUNT(ff.id) AS SIGNED) AS field_count
    FROM form_templates ft
    LEFT JOIN form_fields ff ON ff.form_template_id = ft.id
    WHERE ft.law_firm_id = ${input.lawFirmId}
      AND ft.is_active = 1
    GROUP BY
      ft.id,
      ft.code,
      ft.name,
      ft.edition,
      ft.version_label,
      ft.description,
      ft.agent_code,
      ft.agent_prompt
    ORDER BY ft.name ASC
  `;

  return rows.map((row) => ({
    agentCode: normalizeWhitespace(row.agent_code) || deriveFormAgentCode(row.code || row.name),
    templateId: row.id,
    formCode: normalizeFormCode(row.code || row.name),
    formName: row.name,
    edition: row.edition,
    versionLabel: row.version_label,
    description: row.description,
    prompt:
      normalizeWhitespace(row.agent_prompt) ||
      buildDefaultFormAgentPrompt({
        formName: row.name,
        formCode: normalizeFormCode(row.code || row.name),
        description: row.description,
      }),
    fieldCount: Number(row.field_count ?? 0),
    usesSpecializedLoop:
      (normalizeWhitespace(row.agent_code) || deriveFormAgentCode(row.code || row.name)) ===
      "uscis_i_485",
  })) satisfies DiscoveredFormAgent[];
}

async function loadGeneratedFormClaims(input: {
  lawFirmId: string;
  clientId: string;
  caseId?: string | null;
}) {
  const rows = await prisma.$queryRaw<
    Array<{
      body_text: string | null;
      created_at: Date;
      source_entity_type: string | null;
      source_entity_id: string | null;
      template_id: string | null;
      template_code: string | null;
      template_name: string | null;
      case_template_code: string | null;
      case_template_name: string | null;
    }>
  >`
    SELECT
      ri.body_text,
      ri.created_at,
      ri.source_entity_type,
      ri.source_entity_id,
      direct_template.id AS template_id,
      direct_template.code AS template_code,
      direct_template.name AS template_name,
      case_template.code AS case_template_code,
      case_template.name AS case_template_name
    FROM repository_items ri
    LEFT JOIN form_templates direct_template
      ON ri.source_entity_type = 'form_template'
      AND direct_template.id = ri.source_entity_id
    LEFT JOIN case_forms cf
      ON ri.source_entity_type = 'case_form'
      AND cf.id = ri.source_entity_id
    LEFT JOIN form_templates case_template
      ON case_template.id = cf.form_template_id
    WHERE ri.law_firm_id = ${input.lawFirmId}
      AND ri.client_id = ${input.clientId}
      AND (${input.caseId ?? null} IS NULL OR ri.case_id = ${input.caseId ?? null} OR ri.case_id IS NULL)
      AND ri.item_type_code = 'generated_form'
      AND ri.body_text IS NOT NULL
    ORDER BY ri.created_at DESC
    LIMIT 40
  `;

  const claims: CrossFormClaim[] = [];

  for (const row of rows) {
    const parsed = safeJsonParse<{
      fields?: Record<string, string | null>;
      fieldAuditEntries?: FilledFormAuditEntry[];
      formName?: string;
    }>(row.body_text);
    if (!parsed) {
      continue;
    }

    const sourceTemplateId = row.template_id ?? null;
    const sourceFormCode = normalizeFormCode(
      row.template_code ?? row.case_template_code ?? parsed.formName ?? "",
    );
    const sourceFormName =
      normalizeWhitespace(row.template_name ?? row.case_template_name ?? parsed.formName) ||
      "Generated Form";

    if (Array.isArray(parsed.fieldAuditEntries) && parsed.fieldAuditEntries.length > 0) {
      for (const entry of parsed.fieldAuditEntries) {
        const value = normalizeWhitespace(entry.value);
        if (!value) {
          continue;
        }

        claims.push({
          sourceTemplateId,
          sourceFormCode,
          sourceFormName,
          pdfFieldName: normalizeWhitespace(entry.pdfFieldName),
          label: normalizeWhitespace(entry.label),
          value,
          confidence: typeof entry.confidence === "number" ? entry.confidence : null,
          sourceTitles: Array.isArray(entry.sourceTitles) ? entry.sourceTitles : [],
          rationale: normalizeWhitespace(entry.rationale) || null,
          createdAt: row.created_at.toISOString(),
        });
      }
      continue;
    }

    for (const [pdfFieldName, rawValue] of Object.entries(parsed.fields ?? {})) {
      const value = normalizeWhitespace(rawValue);
      if (!value) {
        continue;
      }

      claims.push({
        sourceTemplateId,
        sourceFormCode,
        sourceFormName,
        pdfFieldName: normalizeWhitespace(pdfFieldName),
        label: normalizeWhitespace(pdfFieldName),
        value,
        confidence: 0.72,
        sourceTitles: [sourceFormName],
        rationale: "Reused from a prior generated form payload.",
        createdAt: row.created_at.toISOString(),
      });
    }
  }

  return uniqueByKey(
    claims.sort(
      (left, right) =>
        Number(right.confidence ?? 0) - Number(left.confidence ?? 0) ||
        new Date(right.createdAt).getTime() - new Date(left.createdAt).getTime(),
    ),
    (claim) =>
      `${normalizeComparableText(claim.sourceFormCode)}:${normalizeComparableText(claim.label)}:${normalizeComparableValue(claim.value)}`,
  );
}

async function buildGlobalEntityLayer(input: {
  lawFirmId: string;
  clientId: string;
  caseId?: string | null;
}) {
  const [client] = await prisma.$queryRaw<
    Array<{
      id: string;
      first_name: string;
      middle_name: string | null;
      last_name: string;
      preferred_name: string | null;
      date_of_birth: Date | null;
      gender: string | null;
      email: string | null;
      phone: string | null;
      preferred_language: string | null;
      country_of_birth: string | null;
      country_of_citizenship: string | null;
      immigration_status: string | null;
    }>
  >`
    SELECT
      id,
      first_name,
      middle_name,
      last_name,
      preferred_name,
      date_of_birth,
      gender,
      email,
      phone,
      preferred_language,
      country_of_birth,
      country_of_citizenship,
      immigration_status
    FROM clients
    WHERE id = ${input.clientId}
      AND law_firm_id = ${input.lawFirmId}
      AND deleted_at IS NULL
    LIMIT 1
  `;

  if (!client) {
    throw new Error("Client not found");
  }

  const [caseRow] =
    input.caseId ?
      await prisma.$queryRaw<
        Array<{
          id: string;
          title: string;
          case_type_code: string;
          case_subtype_code: string | null;
          status_code: string;
          description: string | null;
        }>
      >`
        SELECT id, title, case_type_code, case_subtype_code, status_code, description
        FROM cases
        WHERE id = ${input.caseId}
          AND law_firm_id = ${input.lawFirmId}
          AND client_id = ${input.clientId}
          AND deleted_at IS NULL
        LIMIT 1
      `
    : [null];

  const [addresses, identifiers, relatedParties, facts, documents, crossFormClaims] = await Promise.all([
    prisma.$queryRaw<
      Array<{
        address_type: string;
        address_line1: string;
        address_line2: string | null;
        city: string;
        state_region: string | null;
        postal_code: string | null;
        country_code: string;
        is_primary: number;
        valid_from: Date | null;
        valid_to: Date | null;
      }>
    >`
      SELECT
        address_type,
        address_line1,
        address_line2,
        city,
        state_region,
        postal_code,
        country_code,
        is_primary,
        valid_from,
        valid_to
      FROM client_addresses
      WHERE law_firm_id = ${input.lawFirmId}
        AND client_id = ${input.clientId}
      ORDER BY is_primary DESC, created_at ASC
    `,
    prisma.$queryRaw<
      Array<{
        identifier_type: string;
        identifier_value: string;
        issuing_country_code: string | null;
        issued_at: Date | null;
        expires_at: Date | null;
        is_primary: number;
      }>
    >`
      SELECT
        identifier_type,
        identifier_value,
        issuing_country_code,
        issued_at,
        expires_at,
        is_primary
      FROM client_identifiers
      WHERE law_firm_id = ${input.lawFirmId}
        AND client_id = ${input.clientId}
      ORDER BY is_primary DESC, created_at ASC
    `,
    prisma.$queryRaw<
      Array<{
        relation_type: string;
        entity_type: string;
        first_name: string | null;
        last_name: string | null;
        company_name: string | null;
        date_of_birth: Date | null;
        country_of_citizenship: string | null;
        notes: string | null;
      }>
    >`
      SELECT
        relation_type,
        entity_type,
        first_name,
        last_name,
        company_name,
        date_of_birth,
        country_of_citizenship,
        notes
      FROM related_parties
      WHERE law_firm_id = ${input.lawFirmId}
        AND client_id = ${input.clientId}
        AND deleted_at IS NULL
        AND (${input.caseId ?? null} IS NULL OR case_id = ${input.caseId ?? null} OR case_id IS NULL)
      ORDER BY created_at ASC
    `,
    prisma.$queryRaw<
      Array<{
        field_key: string;
        label: string;
        raw_value: string | null;
        confidence_score: number;
        status_code: string;
      }>
    >`
      SELECT
        df.field_key,
        df.label,
        cf.raw_value,
        cf.confidence_score,
        cf.status_code
      FROM case_facts cf
      JOIN data_fields df ON df.id = cf.data_field_id
      WHERE cf.law_firm_id = ${input.lawFirmId}
        AND cf.client_id = ${input.clientId}
        AND (${input.caseId ?? null} IS NULL OR cf.case_id = ${input.caseId ?? null} OR cf.case_id IS NULL)
        AND cf.deleted_at IS NULL
        AND cf.status_code <> 'rejected'
      ORDER BY
        CASE WHEN cf.status_code = 'confirmed' THEN 0 ELSE 1 END ASC,
        cf.confidence_score DESC,
        cf.created_at DESC
      LIMIT 120
    `,
    prisma.$queryRaw<
      Array<{
        title: string;
        document_type_code: string;
        extracted_text: string | null;
        created_at: Date;
      }>
    >`
      SELECT
        title,
        document_type_code,
        extracted_text,
        created_at
      FROM document_records
      WHERE law_firm_id = ${input.lawFirmId}
        AND client_id = ${input.clientId}
        AND (${input.caseId ?? null} IS NULL OR case_id = ${input.caseId ?? null} OR case_id IS NULL)
      ORDER BY created_at DESC
      LIMIT 40
    `,
    loadGeneratedFormClaims(input),
  ]);

  const employmentHistory = relatedParties
    .filter((party) => ["employer", "sponsor"].includes(normalizeComparableText(party.relation_type)))
    .map((party) => ({
      relationType: party.relation_type,
      name:
        normalizeWhitespace(
          party.company_name ||
            `${party.first_name ?? ""} ${party.last_name ?? ""}`,
        ) || "Related party",
      notes: normalizeWhitespace(party.notes) || null,
    }));

  for (const fact of facts) {
    const comparableKey = normalizeComparableText(fact.field_key);
    if (!/employ|occupation|job|work|sponsor|petitioner/.test(comparableKey)) {
      continue;
    }

    const value = normalizeWhitespace(fact.raw_value);
    if (!value) {
      continue;
    }

    employmentHistory.push({
      relationType: "fact",
      name: `${fact.label}: ${value}`,
      notes: `confidence ${Number(fact.confidence_score ?? 0).toFixed(2)}`,
    });
  }

  const immigrationHistory = facts
    .filter((fact) =>
      /passport|i94|i 94|visa|alien|uscis|status|arrival|admission|priority|petition|citizenship|birth/.test(
        normalizeComparableText(`${fact.field_key} ${fact.label}`),
      ),
    )
    .map((fact) => ({
      fieldKey: fact.field_key,
      label: fact.label,
      value: normalizeWhitespace(fact.raw_value),
      confidence: Number(fact.confidence_score ?? 0),
      statusCode: fact.status_code,
    }))
    .filter((fact) => fact.value);

  const familyRelations = relatedParties.map((party) => ({
    relationType: party.relation_type,
    entityType: party.entity_type,
    name:
      normalizeWhitespace(
        party.company_name || `${party.first_name ?? ""} ${party.last_name ?? ""}`,
      ) || "Related party",
    dateOfBirth: party.date_of_birth?.toISOString().slice(0, 10) ?? null,
    countryOfCitizenship: party.country_of_citizenship,
    notes: normalizeWhitespace(party.notes) || null,
  }));

  return {
    personProfile: {
      clientId: client.id,
      fullName: normalizeWhitespace(
        `${client.first_name} ${client.middle_name ?? ""} ${client.last_name}`,
      ),
      preferredName: client.preferred_name,
      dateOfBirth: client.date_of_birth?.toISOString().slice(0, 10) ?? null,
      gender: client.gender,
      email: client.email,
      phone: client.phone,
      preferredLanguage: client.preferred_language,
      countryOfBirth: client.country_of_birth,
      countryOfCitizenship: client.country_of_citizenship,
      immigrationStatus: client.immigration_status,
    },
    addresses: addresses.map((address) => ({
      addressType: address.address_type,
      line1: address.address_line1,
      line2: address.address_line2,
      city: address.city,
      stateRegion: address.state_region,
      postalCode: address.postal_code,
      countryCode: address.country_code,
      isPrimary: Boolean(address.is_primary),
      validFrom: address.valid_from?.toISOString().slice(0, 10) ?? null,
      validTo: address.valid_to?.toISOString().slice(0, 10) ?? null,
    })),
    identifiers: identifiers.map((identifier) => ({
      identifierType: identifier.identifier_type,
      value: identifier.identifier_value,
      issuingCountryCode: identifier.issuing_country_code,
      issuedAt: identifier.issued_at?.toISOString().slice(0, 10) ?? null,
      expiresAt: identifier.expires_at?.toISOString().slice(0, 10) ?? null,
      isPrimary: Boolean(identifier.is_primary),
    })),
    employmentHistory: uniqueByKey(
      employmentHistory,
      (entry) => `${normalizeComparableText(entry.relationType)}:${normalizeComparableText(entry.name)}`,
    ).slice(0, 20),
    immigrationHistory: uniqueByKey(
      immigrationHistory,
      (entry) => `${normalizeComparableText(entry.fieldKey)}:${normalizeComparableValue(entry.value)}`,
    ).slice(0, 24),
    familyRelations: familyRelations.slice(0, 24),
    documents: documents.map((document) => ({
      title: document.title,
      documentTypeCode: document.document_type_code,
      scopeCode: null,
      createdAt: document.created_at.toISOString(),
      extractedPreview: limitText(document.extracted_text, 260) || null,
    })),
    crossFormClaims: crossFormClaims.slice(0, 120),
    caseSummary:
      caseRow ?
        {
          caseId: caseRow.id,
          title: caseRow.title,
          caseTypeCode: caseRow.case_type_code,
          caseSubtypeCode: caseRow.case_subtype_code,
          statusCode: caseRow.status_code,
          description: caseRow.description,
        }
      : null,
  } satisfies FormEngineGlobalEntities;
}

async function persistGlobalEntityLayerSnapshot(input: {
  lawFirmId: string;
  clientId: string;
  caseId?: string | null;
  actorUserId: string;
  entities: FormEngineGlobalEntities;
}) {
  const snapshotHash = hashJson(input.entities);
  await prisma.$executeRaw(
    Prisma.sql`
      INSERT INTO form_global_entity_snapshots (
        id,
        law_firm_id,
        client_id,
        case_id,
        snapshot_hash,
        snapshot_json,
        source_summary_json,
        created_by_user_id,
        created_at
      ) VALUES (
        ${createId()},
        ${input.lawFirmId},
        ${input.clientId},
        ${input.caseId ?? null},
        ${snapshotHash},
        ${JSON.stringify(input.entities)},
        ${JSON.stringify({
          addressCount: input.entities.addresses.length,
          identifierCount: input.entities.identifiers.length,
          immigrationFactCount: input.entities.immigrationHistory.length,
          crossFormClaimsCount: input.entities.crossFormClaims.length,
          documentCount: input.entities.documents.length,
        })},
        ${input.actorUserId},
        CURRENT_TIMESTAMP
      )
    `,
  );
}

async function detectImmigrationCaseAnalysis(input: {
  lawFirmId: string;
  clientId: string;
  caseId?: string | null;
  entities: FormEngineGlobalEntities;
  agents: DiscoveredFormAgent[];
}) {
  const caseFormRows =
    input.caseId ?
      await prisma.$queryRaw<
        Array<{
          code: string;
          name: string;
        }>
      >`
        SELECT ft.code, ft.name
        FROM case_forms cf
        JOIN form_templates ft ON ft.id = cf.form_template_id
        WHERE cf.case_id = ${input.caseId}
          AND cf.law_firm_id = ${input.lawFirmId}
      `
    : [];

  const generatedFormSignals = input.entities.crossFormClaims
    .map((claim) => ({
      code: claim.sourceFormCode ?? "",
      name: claim.sourceFormName,
      source: "generated_form",
    }))
    .filter((claim) => claim.code);

  const agentSignals = input.agents.map((agent) => ({
    code: agent.formCode,
    name: agent.formName,
    source: "available_agent",
  }));

  const analysis = detectImmigrationCaseFromSignals({
    forms: [
      ...caseFormRows.map((form) => ({
        code: form.code,
        name: form.name,
        source: "case_form",
      })),
      ...generatedFormSignals.map((form) => ({
        code: form.code,
        name: form.name,
        source: String(form.source ?? "generated_form"),
      })),
      ...agentSignals.map((form) => ({
        code: form.code,
        name: form.name,
        source: String(form.source ?? "available_agent"),
      })),
    ],
    relationTypes: input.entities.familyRelations
      .map((relation) => relation.relationType)
      .concat(input.entities.employmentHistory.map((employment) => employment.relationType)),
    factKeys: input.entities.immigrationHistory.map((fact) => fact.fieldKey),
    caseTexts: [
      input.entities.caseSummary?.title ?? "",
      input.entities.caseSummary?.description ?? "",
      input.entities.personProfile.immigrationStatus ?? "",
    ],
  });

  return analysis;
}

async function persistImmigrationCaseAnalysis(input: {
  lawFirmId: string;
  clientId: string;
  caseId?: string | null;
  actorUserId: string;
  analysis: ImmigrationCaseAnalysis;
}) {
  await prisma.$executeRaw(
    Prisma.sql`
      INSERT INTO immigration_case_analyses (
        id,
        law_firm_id,
        client_id,
        case_id,
        case_type_code,
        stage_code,
        detected_forms_json,
        missing_forms_json,
        recommended_next_steps_json,
        evidence_json,
        created_by_user_id,
        created_at
      ) VALUES (
        ${createId()},
        ${input.lawFirmId},
        ${input.clientId},
        ${input.caseId ?? null},
        ${input.analysis.caseType},
        ${input.analysis.stageOfProcess},
        ${JSON.stringify(input.analysis.detectedForms)},
        ${JSON.stringify(input.analysis.missingForms)},
        ${JSON.stringify(input.analysis.recommendedNextSteps)},
        ${JSON.stringify(input.analysis.evidence)},
        ${input.actorUserId},
        CURRENT_TIMESTAMP
      )
    `,
  );
}

async function loadAgentMemories(input: {
  lawFirmId: string;
  agentCode: string;
  templateId: string;
  clientId: string;
  caseId?: string | null;
}) {
  return prisma.$queryRaw<FormAgentMemory[]>`
    SELECT
      id,
      form_template_id,
      agent_code,
      client_id,
      case_id,
      memory_scope,
      memory_type,
      summary_text,
      detail_json,
      success_score,
      coverage_ratio,
      created_at
    FROM form_agent_memories
    WHERE law_firm_id = ${input.lawFirmId}
      AND agent_code = ${input.agentCode}
      AND (form_template_id = ${input.templateId} OR form_template_id IS NULL)
      AND (
        memory_scope = 'global'
        OR (memory_scope = 'client' AND client_id = ${input.clientId})
        OR (memory_scope = 'case' AND case_id = ${input.caseId ?? null})
      )
    ORDER BY created_at DESC
    LIMIT 30
  `;
}

export async function createFormAgentMemory(input: {
  lawFirmId: string;
  actorUserId: string;
  agentCode: string;
  formTemplateId?: string | null;
  clientId?: string | null;
  caseId?: string | null;
  memoryScope: "global" | "client" | "case";
  memoryType:
    | "successful_pattern"
    | "failed_pattern"
    | "human_correction"
    | "mapping_improvement"
    | "question_optimization"
    | "confidence_adjustment";
  summaryText: string;
  detailJson?: unknown;
  successScore?: number | null;
  coverageRatio?: number | null;
}) {
  const id = createId();
  await prisma.$executeRaw(
    Prisma.sql`
      INSERT INTO form_agent_memories (
        id,
        law_firm_id,
        form_template_id,
        agent_code,
        client_id,
        case_id,
        memory_scope,
        memory_type,
        summary_text,
        detail_json,
        success_score,
        coverage_ratio,
        created_by_user_id,
        created_at
      ) VALUES (
        ${id},
        ${input.lawFirmId},
        ${input.formTemplateId ?? null},
        ${input.agentCode},
        ${input.clientId ?? null},
        ${input.caseId ?? null},
        ${input.memoryScope},
        ${input.memoryType},
        ${normalizeWhitespace(input.summaryText)},
        ${input.detailJson ? JSON.stringify(input.detailJson) : null},
        ${input.successScore ?? null},
        ${input.coverageRatio ?? null},
        ${input.actorUserId},
        CURRENT_TIMESTAMP
      )
    `,
  );

  return id;
}

function computeCoverageBySection(input: {
  templateFields: Array<{ sectionName: string | null; pdfFieldName: string }>;
  fieldValues: Record<string, string | null>;
}) {
  const grouped = new Map<string, { filled: number; total: number }>();

  for (const field of input.templateFields) {
    const sectionName = normalizeWhitespace(field.sectionName) || "Unsectioned";
    const group = grouped.get(sectionName) ?? { filled: 0, total: 0 };
    group.total += 1;
    if (normalizeWhitespace(input.fieldValues[field.pdfFieldName])) {
      group.filled += 1;
    }
    grouped.set(sectionName, group);
  }

  return Array.from(grouped.entries()).map(([sectionName, totals]) => ({
    sectionName,
    filled: totals.filled,
    total: totals.total,
    percentFilled: totals.total > 0 ? totals.filled / totals.total : 0,
  }));
}

async function loadTemplateFields(templateId: string) {
  return prisma.$queryRaw<
    Array<{
      pdf_field_name: string;
      label: string;
      section_name: string | null;
    }>
  >`
    SELECT pdf_field_name, label, section_name
    FROM form_fields
    WHERE form_template_id = ${templateId}
    ORDER BY page_number ASC, created_at ASC
  `;
}

function estimateCrossFormReuse(input: {
  currentFormCode: string;
  templateFields: Array<{ pdf_field_name: string; label: string; section_name: string | null }>;
  fieldValues: Record<string, string | null>;
  crossFormClaims: CrossFormClaim[];
}) {
  const currentFieldKeys = input.templateFields
    .map((field) => ({
      keys: [
        normalizeComparableText(field.label),
        normalizeComparableText(field.pdf_field_name),
      ].filter(Boolean),
      value: normalizeComparableValue(input.fieldValues[field.pdf_field_name]),
    }))
    .filter((field) => field.value);

  const matchingClaims = new Map<string, CrossFormClaim[]>();
  for (const claim of input.crossFormClaims) {
    if (normalizeFormCode(claim.sourceFormCode) === normalizeFormCode(input.currentFormCode)) {
      continue;
    }

    const keys = [
      normalizeComparableText(claim.label),
      normalizeComparableText(claim.pdfFieldName),
    ].filter(Boolean);

    for (const key of keys) {
      const existing = matchingClaims.get(key) ?? [];
      existing.push(claim);
      matchingClaims.set(key, existing);
    }
  }

  let reuseCount = 0;
  let conflictCount = 0;

  for (const currentField of currentFieldKeys) {
    const candidateClaims = uniqueByKey(
      currentField.keys.flatMap((key) => matchingClaims.get(key) ?? []),
      (claim) =>
        `${normalizeComparableText(claim.sourceFormCode)}:${normalizeComparableText(claim.label)}:${normalizeComparableValue(claim.value)}`,
    );
    if (!candidateClaims.length) {
      continue;
    }

    const sameValue = candidateClaims.some(
      (claim) => normalizeComparableValue(claim.value) === currentField.value,
    );
    const conflictingValue = candidateClaims.some(
      (claim) => normalizeComparableValue(claim.value) !== currentField.value,
    );

    if (sameValue) {
      reuseCount += 1;
    } else if (conflictingValue) {
      conflictCount += 1;
    }
  }

  return {
    reuseCount,
    conflictCount,
  };
}

function buildGenericIterationPrompt(input: {
  basePrompt: string;
  unresolvedFields: string[];
  iteration: number;
}) {
  if (input.iteration <= 1 || input.unresolvedFields.length === 0) {
    return input.basePrompt;
  }

  return [
    input.basePrompt.trim(),
    "",
    "Iteration improvement instructions:",
    `Focus on these unresolved fields: ${input.unresolvedFields.slice(0, 30).join(" | ")}.`,
    "Prefer direct evidence from the client profile, structured facts, document extractions, and cross-form confirmations.",
    "If a field remains unsupported, keep it blank and preserve the ambiguity.",
  ].join("\n");
}

async function runGenericFormAgent(input: {
  lawFirmId: string;
  clientId: string;
  actorUserId: string;
  agent: DiscoveredFormAgent;
  basePrompt: string;
  targetCoverage: number;
  maxIterations: number;
}) {
  const templateFields = await loadTemplateFields(input.agent.templateId);
  const iterationHistory: GenericIterationSnapshot[] = [];
  let bestResult: GenericFormAgentRunResult | null = null;
  let previousUnresolvedFields: string[] = [];

  for (let iteration = 1; iteration <= input.maxIterations; iteration += 1) {
    const generation = await generateClientFormFromTemplate({
      lawFirmId: input.lawFirmId,
      clientId: input.clientId,
      actorUserId: input.actorUserId,
      formTemplateId: input.agent.templateId,
      formName: input.agent.formName,
      agentCode: input.agent.agentCode,
      agentPrompt: buildGenericIterationPrompt({
        basePrompt: input.basePrompt,
        unresolvedFields: previousUnresolvedFields,
        iteration,
      }),
    });

    const coverage =
      Number(generation.totalFieldCount ?? 0) > 0 ?
        Number(generation.filledCount ?? 0) / Number(generation.totalFieldCount ?? 0)
      : 0;

    previousUnresolvedFields = generation.unresolvedFields ?? [];
    const currentResult: GenericFormAgentRunResult = {
      agentCode: input.agent.agentCode,
      templateId: input.agent.templateId,
      formCode: input.agent.formCode,
      formName: input.agent.formName,
      iterations: iteration,
      coverage,
      filledCount: Number(generation.filledCount ?? 0),
      totalFieldCount: Number(generation.totalFieldCount ?? 0),
      unresolvedFields: generation.unresolvedFields ?? [],
      generatedDocumentId: generation.generatedDocumentId,
      repositoryItemId: generation.repositoryItemId,
      fileId: generation.fileId,
      fileName: generation.fileName,
      fieldValues: generation.fieldValues ?? {},
      fieldAuditEntries: generation.fieldAuditEntries ?? [],
      validationWarnings: generation.validationWarnings ?? [],
      completionSuggestions: generation.completionSuggestions ?? null,
      coverageBySection: computeCoverageBySection({
        templateFields: templateFields.map((field) => ({
          sectionName: field.section_name,
          pdfFieldName: field.pdf_field_name,
        })),
        fieldValues: generation.fieldValues ?? {},
      }),
      iterationHistory: [],
    };

    iterationHistory.push({
      iteration,
      coverage,
      filledCount: currentResult.filledCount,
      unresolvedCount: currentResult.unresolvedFields.length,
      fileName: currentResult.fileName,
    });

    if (!bestResult || currentResult.coverage > bestResult.coverage) {
      bestResult = currentResult;
    }

    if (coverage >= input.targetCoverage) {
      break;
    }

    if (iteration >= 2 && coverage <= (bestResult?.coverage ?? 0)) {
      break;
    }
  }

  if (!bestResult) {
    throw new Error("Generic form agent run did not produce a result");
  }

  return {
    ...bestResult,
    iterationHistory,
    iterations: iterationHistory.length,
  };
}

async function loadI485RunPayload(repositoryItemId: string) {
  const [row] = await prisma.$queryRaw<Array<{ body_text: string | null }>>`
    SELECT body_text
    FROM repository_items
    WHERE id = ${repositoryItemId}
    LIMIT 1
  `;

  return (
    safeJsonParse<I485LoopPayload & { unresolved?: string[]; fields?: Record<string, string | null> }>(
      row?.body_text,
    ) ?? {
      filledCount: 0,
      unresolved: [],
      fieldAuditEntries: [],
      validationWarnings: [],
      fields: {},
    }
  );
}

async function runI485FormAgent(input: {
  lawFirmId: string;
  clientId: string;
  actorUserId: string;
  agent: DiscoveredFormAgent;
  basePrompt: string;
  targetCoverage: number;
  maxRuntimeMs: number;
}) {
  const report = await runIterativeI485AutofillLoop({
    lawFirmId: input.lawFirmId,
    clientId: input.clientId,
    actorUserId: input.actorUserId,
    targetCoverage: input.targetCoverage,
    maxRuntimeMs: input.maxRuntimeMs,
    basePromptOverride: input.basePrompt,
  });

  const payload = report.latestRepositoryItemId
    ? await loadI485RunPayload(report.latestRepositoryItemId)
    : {
        filledCount: 0,
        unresolved: [],
        fieldAuditEntries: [],
        validationWarnings: [],
        fields: {},
      };

  return {
    agentCode: report.agentCode,
    templateId: report.formTemplateId,
    formCode: input.agent.formCode,
    formName: input.agent.formName,
    iterations: report.iterations,
    coverage: report.coverage,
    filledCount: report.filledFields,
    totalFieldCount: report.totalFields,
    unresolvedFields: report.remainingGaps ?? payload.unresolved ?? [],
    generatedDocumentId: report.latestGeneratedDocumentId ?? null,
    repositoryItemId: report.latestRepositoryItemId ?? null,
    fileId: report.latestFileId ?? null,
    fileName: report.latestFileName ?? null,
    fieldValues: payload.fields ?? {},
    fieldAuditEntries: payload.fieldAuditEntries ?? [],
    validationWarnings: payload.validationWarnings ?? [],
    coverageBySection: (report.sectionBreakdown ?? []).map((section) => ({
      sectionName: section.title,
      filled: section.filledValidFields,
      total: section.totalFillableFields,
      percentFilled: section.coverage,
    })),
    iterationHistory: (report.iterationHistory ?? []).map((entry) => ({
      iteration: entry.iteration,
      coverage: entry.coverage,
      filledCount: entry.filledFields,
      unresolvedCount: entry.missingFields.length + entry.conflictingFields.length,
      fileName: entry.fileName ?? "",
    })),
  };
}

async function persistEngineRunAgentResult(input: {
  runId: string;
  agent: DiscoveredFormAgent;
  result: RunFormEngineAgentResult;
}) {
  await prisma.$executeRaw(
    Prisma.sql`
      INSERT INTO form_engine_run_agents (
        id,
        form_engine_run_id,
        agent_code,
        form_template_id,
        metrics_json,
        result_json,
        generated_document_id,
        repository_item_id,
        file_id,
        created_at,
        updated_at
      ) VALUES (
        ${createId()},
        ${input.runId},
        ${input.agent.agentCode},
        ${input.agent.templateId},
        ${JSON.stringify(input.result.metrics)},
        ${JSON.stringify({
          formCode: input.result.formCode,
          formName: input.result.formName,
          iterations: input.result.iterations,
          unresolvedFields: input.result.unresolvedFields,
          validationWarnings: input.result.validationWarnings,
          iterationHistory: input.result.iterationHistory,
        })},
        ${input.result.generatedDocumentId},
        ${input.result.repositoryItemId},
        ${input.result.fileId},
        CURRENT_TIMESTAMP,
        CURRENT_TIMESTAMP
      )
    `,
  );
}

async function recordFormAgentLearning(input: {
  lawFirmId: string;
  actorUserId: string;
  clientId: string;
  caseId?: string | null;
  agent: DiscoveredFormAgent;
  metrics: FormRunMetrics;
  unresolvedFields: string[];
  validationWarnings: string[];
  fieldAuditEntries: FilledFormAuditEntry[];
}) {
  const highConfidenceLabels = input.fieldAuditEntries
    .filter((entry) => Number(entry.confidence ?? 0) >= 0.85)
    .map((entry) => entry.label)
    .slice(0, 12);
  const lowConfidenceLabels = input.fieldAuditEntries
    .filter((entry) => Number(entry.confidence ?? 0) > 0 && Number(entry.confidence ?? 0) < 0.7)
    .map((entry) => entry.label)
    .slice(0, 8);

  if (highConfidenceLabels.length > 0) {
    await createFormAgentMemory({
      lawFirmId: input.lawFirmId,
      actorUserId: input.actorUserId,
      agentCode: input.agent.agentCode,
      formTemplateId: input.agent.templateId,
      clientId: input.clientId,
      caseId: input.caseId ?? null,
      memoryScope: "client",
      memoryType: "successful_pattern",
      summaryText: `${input.agent.formName} completed high-confidence fields: ${highConfidenceLabels.join(", ")}.`,
      detailJson: {
        labels: highConfidenceLabels,
        metrics: input.metrics,
      },
      successScore: input.metrics.percentHighConfidence,
      coverageRatio: input.metrics.percentFilled,
    });

    await createFormAgentMemory({
      lawFirmId: input.lawFirmId,
      actorUserId: input.actorUserId,
      agentCode: input.agent.agentCode,
      formTemplateId: input.agent.templateId,
      memoryScope: "global",
      memoryType: "mapping_improvement",
      summaryText: `For ${input.agent.formName}, prioritize reusable evidence for fields such as ${highConfidenceLabels.slice(0, 6).join(", ")}.`,
      detailJson: {
        labels: highConfidenceLabels.slice(0, 6),
        percentFilled: input.metrics.percentFilled,
      },
      successScore: input.metrics.percentFilled,
      coverageRatio: input.metrics.percentFilled,
    });
  }

  if (input.unresolvedFields.length > 0) {
    await createFormAgentMemory({
      lawFirmId: input.lawFirmId,
      actorUserId: input.actorUserId,
      agentCode: input.agent.agentCode,
      formTemplateId: input.agent.templateId,
      clientId: input.clientId,
      caseId: input.caseId ?? null,
      memoryScope: "client",
      memoryType: "failed_pattern",
      summaryText: `${input.agent.formName} still needed clarification for: ${input.unresolvedFields
        .slice(0, 12)
        .join(", ")}.`,
      detailJson: {
        unresolvedFields: input.unresolvedFields.slice(0, 20),
        validationWarnings: input.validationWarnings.slice(0, 12),
      },
      successScore: 1 - input.metrics.conflictRate,
      coverageRatio: input.metrics.percentFilled,
    });
  }

  if (lowConfidenceLabels.length > 0 || input.validationWarnings.length > 0) {
    await createFormAgentMemory({
      lawFirmId: input.lawFirmId,
      actorUserId: input.actorUserId,
      agentCode: input.agent.agentCode,
      formTemplateId: input.agent.templateId,
      clientId: input.clientId,
      caseId: input.caseId ?? null,
      memoryScope: "client",
      memoryType: "confidence_adjustment",
      summaryText: `${input.agent.formName} had low-confidence or warning-prone areas around: ${[
        ...lowConfidenceLabels,
        ...input.validationWarnings,
      ]
        .slice(0, 10)
        .join(", ")}.`,
      detailJson: {
        lowConfidenceLabels,
        validationWarnings: input.validationWarnings.slice(0, 16),
      },
      successScore: input.metrics.percentHighConfidence,
      coverageRatio: input.metrics.percentFilled,
    });
  }
}

function selectAgentsForRun(input: {
  availableAgents: DiscoveredFormAgent[];
  requestedAgentCodes?: string[] | null;
  caseAnalysis: ImmigrationCaseAnalysis;
}) {
  if (input.requestedAgentCodes?.length) {
    return input.availableAgents.filter((agent) =>
      input.requestedAgentCodes?.includes(agent.agentCode),
    );
  }

  const detectedFormCodes = new Set(
    input.caseAnalysis.detectedForms.map((formCode) => normalizeFormCode(formCode)),
  );
  const detectedAgents = input.availableAgents.filter((agent) =>
    detectedFormCodes.has(normalizeFormCode(agent.formCode)),
  );

  if (detectedAgents.length > 0) {
    return detectedAgents;
  }

  const defaultI485Agent = input.availableAgents.find((agent) => agent.agentCode === "uscis_i_485");
  if (defaultI485Agent) {
    return [defaultI485Agent];
  }

  return input.availableAgents.slice(0, 1);
}

function baselineCoverageFromMemories(memories: FormAgentMemory[]) {
  return memories.reduce((best, memory) => {
    const coverage = Number(memory.coverage_ratio ?? 0);
    return coverage > best ? coverage : best;
  }, 0);
}

async function insertEngineRun(input: {
  lawFirmId: string;
  clientId: string;
  caseId?: string | null;
  actorUserId: string;
  targetCoverage: number;
  selectedAgentCodes: string[];
  caseAnalysis: ImmigrationCaseAnalysis;
}) {
  const runId = createId();
  await prisma.$executeRaw(
    Prisma.sql`
      INSERT INTO form_engine_runs (
        id,
        law_firm_id,
        client_id,
        case_id,
        run_status,
        target_coverage,
        selected_agents_json,
        case_analysis_json,
        created_by_user_id,
        started_at,
        created_at,
        updated_at
      ) VALUES (
        ${runId},
        ${input.lawFirmId},
        ${input.clientId},
        ${input.caseId ?? null},
        'running',
        ${input.targetCoverage},
        ${JSON.stringify(input.selectedAgentCodes)},
        ${JSON.stringify(input.caseAnalysis)},
        ${input.actorUserId},
        NOW(),
        CURRENT_TIMESTAMP,
        CURRENT_TIMESTAMP
      )
    `,
  );
  return runId;
}

async function finalizeEngineRun(input: {
  runId: string;
  summary: unknown;
}) {
  await prisma.$executeRaw(
    Prisma.sql`
      UPDATE form_engine_runs
      SET
        run_status = 'completed',
        summary_json = ${JSON.stringify(input.summary)},
        completed_at = NOW(),
        updated_at = CURRENT_TIMESTAMP
      WHERE id = ${input.runId}
    `,
  );
}

export async function listFormEngineAgents(input: {
  lawFirmId: string;
  actorUserId: string;
}) {
  await ensureFormEngineTables();
  return discoverFormAgents(input);
}

export async function listFormAgentMemories(input: {
  lawFirmId: string;
  agentCode: string;
  templateId?: string | null;
  clientId?: string | null;
  caseId?: string | null;
}) {
  await ensureFormEngineTables();
  return prisma.$queryRaw<FormAgentMemory[]>`
    SELECT
      id,
      form_template_id,
      agent_code,
      client_id,
      case_id,
      memory_scope,
      memory_type,
      summary_text,
      detail_json,
      success_score,
      coverage_ratio,
      created_at
    FROM form_agent_memories
    WHERE law_firm_id = ${input.lawFirmId}
      AND agent_code = ${input.agentCode}
      AND (${input.templateId ?? null} IS NULL OR form_template_id = ${input.templateId ?? null})
      AND (${input.clientId ?? null} IS NULL OR client_id = ${input.clientId ?? null} OR memory_scope = 'global')
      AND (${input.caseId ?? null} IS NULL OR case_id = ${input.caseId ?? null} OR memory_scope != 'case')
    ORDER BY created_at DESC
    LIMIT 50
  `;
}

export async function analyzeImmigrationCaseForClient(input: {
  lawFirmId: string;
  actorUserId: string;
  clientId: string;
  caseId?: string | null;
}) {
  await ensureFormEngineTables();
  const agents = await discoverFormAgents({
    lawFirmId: input.lawFirmId,
    actorUserId: input.actorUserId,
  });
  const entities = await buildGlobalEntityLayer(input);
  const analysis = await detectImmigrationCaseAnalysis({
    lawFirmId: input.lawFirmId,
    clientId: input.clientId,
    caseId: input.caseId ?? null,
    entities,
    agents,
  });

  await persistGlobalEntityLayerSnapshot({
    ...input,
    entities,
  });
  await persistImmigrationCaseAnalysis({
    ...input,
    analysis,
  });

  return {
    globalEntities: entities,
    caseAnalysis: analysis,
    availableAgents: agents,
  };
}

export async function runFormEngineForClient(input: {
  lawFirmId: string;
  actorUserId: string;
  clientId: string;
  caseId?: string | null;
  agentCodes?: string[] | null;
  targetCoverage?: number;
  maxIterations?: number;
  maxRuntimeMs?: number;
}) {
  await ensureFormEngineTables();
  const startedAt = new Date();
  const targetCoverage = input.targetCoverage ?? 0.8;
  const maxIterations = Math.max(1, input.maxIterations ?? 2);
  const maxRuntimeMs = Math.max(60_000, input.maxRuntimeMs ?? 30 * 60 * 1000);

  const availableAgents = await discoverFormAgents({
    lawFirmId: input.lawFirmId,
    actorUserId: input.actorUserId,
  });
  const globalEntities = await buildGlobalEntityLayer(input);
  const caseAnalysis = await detectImmigrationCaseAnalysis({
    lawFirmId: input.lawFirmId,
    clientId: input.clientId,
    caseId: input.caseId ?? null,
    entities: globalEntities,
    agents: availableAgents,
  });
  const selectedAgents = selectAgentsForRun({
    availableAgents,
    requestedAgentCodes: input.agentCodes ?? null,
    caseAnalysis,
  });

  await persistGlobalEntityLayerSnapshot({
    lawFirmId: input.lawFirmId,
    actorUserId: input.actorUserId,
    clientId: input.clientId,
    caseId: input.caseId ?? null,
    entities: globalEntities,
  });
  await persistImmigrationCaseAnalysis({
    lawFirmId: input.lawFirmId,
    actorUserId: input.actorUserId,
    clientId: input.clientId,
    caseId: input.caseId ?? null,
    analysis: caseAnalysis,
  });

  const runId = await insertEngineRun({
    lawFirmId: input.lawFirmId,
    actorUserId: input.actorUserId,
    clientId: input.clientId,
    caseId: input.caseId ?? null,
    targetCoverage,
    selectedAgentCodes: selectedAgents.map((agent) => agent.agentCode),
    caseAnalysis,
  });

  const results: RunFormEngineAgentResult[] = [];

  for (const agent of selectedAgents) {
    const memories = await loadAgentMemories({
      lawFirmId: input.lawFirmId,
      agentCode: agent.agentCode,
      templateId: agent.templateId,
      clientId: input.clientId,
      caseId: input.caseId ?? null,
    });
    const basePrompt = buildExecutionPrompt({
      basePrompt: agent.prompt,
      agent,
      entities: globalEntities,
      caseAnalysis,
      memories,
      iteration: 1,
    });

    const runResult =
      agent.usesSpecializedLoop ?
        await runI485FormAgent({
          lawFirmId: input.lawFirmId,
          clientId: input.clientId,
          actorUserId: input.actorUserId,
          agent,
          basePrompt,
          targetCoverage,
          maxRuntimeMs,
        })
      : await runGenericFormAgent({
          lawFirmId: input.lawFirmId,
          clientId: input.clientId,
          actorUserId: input.actorUserId,
          agent,
          basePrompt,
          targetCoverage,
          maxIterations,
        });

    const templateFields = await loadTemplateFields(agent.templateId);
    const crossFormReuse = estimateCrossFormReuse({
      currentFormCode: agent.formCode,
      templateFields: templateFields.map((field) => ({
        pdf_field_name: field.pdf_field_name,
        label: field.label,
        section_name: field.section_name,
      })),
      fieldValues: runResult.fieldValues,
      crossFormClaims: globalEntities.crossFormClaims,
    });
    const metrics = computeFormRunMetrics({
      totalFieldCount: runResult.totalFieldCount,
      filledCount: runResult.filledCount,
      highConfidenceCount: runResult.fieldAuditEntries.filter(
        (entry) => Number(entry.confidence ?? 0) >= 0.85,
      ).length,
      unresolvedCount: runResult.unresolvedFields.length,
      reuseFromOtherForms: crossFormReuse.reuseCount,
      previousCoverage: baselineCoverageFromMemories(memories),
      conflictsCount: crossFormReuse.conflictCount,
      coverageBySection: runResult.coverageBySection.map((section) => ({
        sectionName: section.sectionName,
        filled: section.filled,
        total: section.total,
      })),
    });

    const finalAgentResult: RunFormEngineAgentResult = {
      agentCode: agent.agentCode,
      templateId: agent.templateId,
      formCode: agent.formCode,
      formName: agent.formName,
      metrics,
      iterations: runResult.iterations,
      generatedDocumentId: runResult.generatedDocumentId,
      repositoryItemId: runResult.repositoryItemId,
      fileId: runResult.fileId,
      fileName: runResult.fileName,
      unresolvedFields: runResult.unresolvedFields,
      validationWarnings: runResult.validationWarnings,
      fieldAuditEntries: runResult.fieldAuditEntries,
      iterationHistory: runResult.iterationHistory,
    };

    await persistEngineRunAgentResult({
      runId,
      agent,
      result: finalAgentResult,
    });
    await recordFormAgentLearning({
      lawFirmId: input.lawFirmId,
      actorUserId: input.actorUserId,
      clientId: input.clientId,
      caseId: input.caseId ?? null,
      agent,
      metrics,
      unresolvedFields: finalAgentResult.unresolvedFields,
      validationWarnings: finalAgentResult.validationWarnings,
      fieldAuditEntries: finalAgentResult.fieldAuditEntries,
    });

    results.push(finalAgentResult);
  }

  const completedAt = new Date();
  const summary = {
    generatedFormsCount: results.filter((result) => result.generatedDocumentId).length,
    bestCoverageByAgent: results.map((result) => ({
      agentCode: result.agentCode,
      formName: result.formName,
      percentFilled: result.metrics.percentFilled,
      percentHighConfidence: result.metrics.percentHighConfidence,
      questionsNeeded: result.metrics.questionsNeeded,
      reuseFromOtherForms: result.metrics.reuseFromOtherForms,
      learningGainFromMemory: result.metrics.learningGainFromMemory,
      conflictRate: result.metrics.conflictRate,
    })),
  };

  await finalizeEngineRun({
    runId,
    summary,
  });

  return {
    runId,
    clientId: input.clientId,
    caseId: input.caseId ?? null,
    selectedAgentCodes: selectedAgents.map((agent) => agent.agentCode),
    availableAgentCodes: availableAgents.map((agent) => agent.agentCode),
    globalEntities,
    caseAnalysis,
    results,
    generatedFormsCount: results.filter((result) => result.generatedDocumentId).length,
    targetCoverage,
    startedAt: startedAt.toISOString(),
    completedAt: completedAt.toISOString(),
  } satisfies FormEngineRunResult;
}
