import { Prisma } from "@prisma/client";
import { createId } from "./id.js";
import { prisma } from "./prisma.js";
import { generateClientFormFromTemplate, type I485OptimizationFinalReport, type I485OptimizationIterationSummary } from "./intelligence.js";
import { prepareSpecializedFormGeneration } from "./specialized-forms.js";
import { runJsonChatCompletion } from "./tenant-ai.js";

type I485LoopField = {
  pdfFieldName: string;
  label: string;
  dataType: string;
  sectionName: string | null;
  pageNumber: number | null;
  value: string | null;
};

type I485LoopSectionDefinition = {
  code: string;
  title: string;
  description: string;
  keywords: string[];
  matchesField: (field: I485LoopField) => boolean;
};

type I485LoopEvidenceDocument = {
  title: string;
  document_type_code: string;
  extracted_text: string | null;
  created_at: Date;
};

type I485LoopEvidenceNote = {
  item_type_code: string;
  subject: string | null;
  body_text: string | null;
  created_at: Date;
};

type I485LoopEvidence = {
  clientProfile: {
    first_name: string;
    middle_name: string | null;
    last_name: string;
    preferred_name: string | null;
    date_of_birth: Date | null;
    email: string | null;
    phone: string | null;
    preferred_language: string | null;
    country_of_citizenship: string | null;
    immigration_status: string | null;
  } | null;
  documents: I485LoopEvidenceDocument[];
  notes: I485LoopEvidenceNote[];
};

type I485CoverageAssessmentStatus = "missing_but_supported" | "conflicting" | "unsupported";

type I485CoverageAssessment = {
  pdfFieldName: string;
  status: I485CoverageAssessmentStatus;
  reason: string;
};

type I485SectionCoverageBreakdown = I485OptimizationIterationSummary["sectionBreakdown"][number];

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

function limitText(value: string | null | undefined, maxLength = 3200) {
  const normalized = String(value ?? "").replace(/\s+/g, " ").trim();
  if (!normalized) {
    return "";
  }
  if (normalized.length <= maxLength) {
    return normalized;
  }
  return `${normalized.slice(0, Math.max(0, maxLength - 3)).trim()}...`;
}

function extractKnowledgeSearchTerms(value: string) {
  return Array.from(
    new Set(
      normalizeSearchText(value)
        .split(" ")
        .filter((item) => item.length >= 3),
    ),
  ).slice(0, 64);
}

function selectRelevantPlainItems<T>(
  items: T[],
  searchTerms: string[],
  textSelector: (item: T) => string,
  limit: number,
) {
  const ranked = items
    .map((item) => {
      const haystack = normalizeSearchText(textSelector(item));
      let score = 0;
      for (const term of searchTerms) {
        if (haystack.includes(term)) {
          score += term.length >= 8 ? 4 : 2;
        }
      }
      return { item, score };
    })
    .filter((entry) => entry.score > 0)
    .sort((left, right) => right.score - left.score)
    .slice(0, limit);

  if (ranked.length >= limit) {
    return ranked.map((entry) => entry.item);
  }

  const fallback = items
    .filter((item) => !ranked.some((entry) => entry.item === item))
    .slice(0, Math.max(0, limit - ranked.length));

  return [...ranked.map((entry) => entry.item), ...fallback];
}

function buildI485FieldSearchText(field: Pick<I485LoopField, "pdfFieldName" | "label" | "dataType">) {
  return normalizeSearchText(`${field.pdfFieldName} ${field.label} ${field.dataType ?? ""}`);
}

function isI485AdministrativeField(field: Pick<I485LoopField, "pdfFieldName" | "label" | "dataType">) {
  const haystack = buildI485FieldSearchText(field);
  return (
    haystack.includes("pdf417barcode") ||
    haystack.includes("read only field") ||
    /\b(attorney|accredited representative|g 28|interpreter|preparer|uscis officer|volag)\b/.test(
      haystack,
    ) ||
    /\b(signature|sign here|daytime telephone number if any|today s date|declaration|certification)\b/.test(
      haystack,
    ) ||
    /\b(page number|part number|item number)\b/.test(haystack)
  );
}

const I485_LOOP_SECTION_DEFINITIONS: I485LoopSectionDefinition[] = [
  {
    code: "identity_biographic",
    title: "Identity and biographic data",
    description: "Legal names, date and place of birth, sex, marital status, A-number, USCIS online account, I-94 and core identity data.",
    keywords: ["family name", "given name", "middle name", "date of birth", "place of birth", "country of citizenship", "a number", "uscis online account", "i 94", "marital status", "sex"],
    matchesField: (field) => !isI485AdministrativeField(field) && /\bpart 1\b/.test(buildI485FieldSearchText(field)) && /family name|given name|middle name|date of birth|city town of birth|country of birth|country of citizenship|sex|marital status|alien number|uscis online account|i 94/i.test(field.label),
  },
  {
    code: "immigration_history",
    title: "Immigration history",
    description: "Passport, travel document, last arrival, status at arrival, current status, status history and prior admissions.",
    keywords: ["passport", "travel document", "last arrival", "admitted", "status", "visa", "arrival", "expiration date"],
    matchesField: (field) => !isI485AdministrativeField(field) && /\bpart 1\b/.test(buildI485FieldSearchText(field)) && /passport|travel document|date of arrival|status on arrival|current immigration status|passport country|expiration/i.test(field.label),
  },
  {
    code: "addresses_and_contact",
    title: "Addresses and contact",
    description: "Current mailing/physical addresses, address history, phone numbers and email.",
    keywords: ["mailing address", "physical address", "address history", "city", "state", "zip code", "country", "phone", "email"],
    matchesField: (field) => !isI485AdministrativeField(field) && /\bpart 1\b/.test(buildI485FieldSearchText(field)) && /address|city|state|province|postal code|zip code|country|phone|email/i.test(field.label),
  },
  {
    code: "application_basis",
    title: "Application basis",
    description: "Adjustment category and eligibility basis, including employment-based classification.",
    keywords: ["part 2", "application category", "employment based", "principal applicant", "approved immigrant petition", "i 140", "supplement j"],
    matchesField: (field) => !isI485AdministrativeField(field) && /\bpart 2\b/.test(buildI485FieldSearchText(field)),
  },
  {
    code: "background_employment_and_consular",
    title: "Employment, prior status, and consular history",
    description: "Employment and immigration background sections relevant to the I-485 narrative and history.",
    keywords: ["employment", "employer", "occupation", "consulate", "visa", "student", "history", "part 3", "part 4"],
    matchesField: (field) => !isI485AdministrativeField(field) && /\bpart (3|4)\b/.test(buildI485FieldSearchText(field)),
  },
  {
    code: "parents",
    title: "Parents",
    description: "Mother and father names, birth details and related parent history.",
    keywords: ["part 5", "father", "mother", "parent"],
    matchesField: (field) => !isI485AdministrativeField(field) && /\bpart 5\b/.test(buildI485FieldSearchText(field)),
  },
  {
    code: "marital_history",
    title: "Marital history",
    description: "Current spouse, prior marriages, spouse biographic details and marriage history.",
    keywords: ["part 6", "spouse", "marriage", "divorced", "widowed"],
    matchesField: (field) => !isI485AdministrativeField(field) && /\bpart 6\b/.test(buildI485FieldSearchText(field)),
  },
  {
    code: "children",
    title: "Children",
    description: "Children names, dates of birth, locations and relationship details.",
    keywords: ["part 7", "child", "children", "son", "daughter"],
    matchesField: (field) => !isI485AdministrativeField(field) && /\bpart 7\b/.test(buildI485FieldSearchText(field)),
  },
  {
    code: "biographic_information",
    title: "Biographic information",
    description: "Ethnicity, race, height, weight, eye color and hair color.",
    keywords: ["part 8", "ethnicity", "race", "height", "weight", "eye color", "hair color"],
    matchesField: (field) => !isI485AdministrativeField(field) && /\bpart 8\b/.test(buildI485FieldSearchText(field)),
  },
  {
    code: "inadmissibility",
    title: "Inadmissibility and security",
    description: "Public charge, criminal history, immigration violations, organizations and security yes/no questions.",
    keywords: ["part 9", "inadmissibility", "criminal", "arrest", "immigration violation", "public charge", "organization", "security"],
    matchesField: (field) => !isI485AdministrativeField(field) && /\bpart 9\b/.test(buildI485FieldSearchText(field)),
  },
];

function buildSectionSearchTerms(section: I485LoopSectionDefinition, fields: I485LoopField[]) {
  return extractKnowledgeSearchTerms(
    `${section.title} ${section.description} ${section.keywords.join(" ")} ${fields.map((field) => field.label).join(" ")}`,
  );
}

function getI485SectionPartNumber(sectionCode: string) {
  switch (sectionCode) {
    case "identity_biographic":
    case "immigration_history":
    case "addresses_and_contact":
      return 1;
    case "application_basis":
      return 2;
    case "background_employment_and_consular":
      return 3;
    case "parents":
      return 5;
    case "marital_history":
      return 6;
    case "children":
      return 7;
    case "biographic_information":
      return 8;
    case "inadmissibility":
      return 9;
    default:
      return null;
  }
}

function buildSectionAwareTextExcerpt(document: I485LoopEvidenceDocument, section: I485LoopSectionDefinition, fields: I485LoopField[]) {
  const normalizedText = String(document.extracted_text ?? "")
    .replace(/\r/g, "\n")
    .replace(/[ \t]+/g, " ")
    .replace(/\n{3,}/g, "\n\n")
    .trim();

  if (!normalizedText) {
    return "";
  }

  if (!/i[\s-]?485/i.test(document.title) || normalizedText.length <= 4200) {
    return limitText(normalizedText, 3200);
  }

  const lowerText = normalizedText.toLowerCase();
  const keywords = Array.from(
    new Set(
      [...section.keywords, ...fields.map((field) => field.label)]
        .flatMap((value) => String(value ?? "").split(/[.:()]/))
        .map((value) => value.trim().toLowerCase())
        .filter((value) => value.length >= 5),
    ),
  ).slice(0, 48);

  const windows: Array<{ start: number; end: number }> = [];
  for (const keyword of keywords) {
    let cursor = 0;
    let matches = 0;
    while (matches < 2) {
      const index = lowerText.indexOf(keyword, cursor);
      if (index < 0) {
        break;
      }
      windows.push({
        start: Math.max(0, index - 700),
        end: Math.min(normalizedText.length, index + keyword.length + 1400),
      });
      cursor = index + keyword.length;
      matches += 1;
    }
  }

  const partNumber = getI485SectionPartNumber(section.code);
  if (partNumber) {
    const startPattern = new RegExp(`\\bPart\\s+${partNumber}\\b`, "i");
    const match = startPattern.exec(normalizedText);
    if (match && match.index >= 0) {
      const start = match.index;
      const tail = normalizedText.slice(start + match[0].length);
      let end = normalizedText.length;
      for (let nextPart = partNumber + 1; nextPart <= 14; nextPart += 1) {
        const nextPattern = new RegExp(`\\bPart\\s+${nextPart}\\b`, "i");
        const nextIndex = tail.search(nextPattern);
        if (nextIndex >= 0) {
          end = start + match[0].length + nextIndex;
          break;
        }
      }
      windows.push({ start, end: Math.min(end, start + 10000) });
    }
  }

  if (!windows.length) {
    return limitText(normalizedText, 3200);
  }

  windows.sort((left, right) => left.start - right.start);
  const merged: Array<{ start: number; end: number }> = [];
  for (const window of windows) {
    const previous = merged.at(-1);
    if (!previous || window.start > previous.end + 120) {
      merged.push({ ...window });
    } else {
      previous.end = Math.max(previous.end, window.end);
    }
  }

  const excerpts: string[] = [];
  let totalLength = 0;
  for (const window of merged) {
    const excerpt = normalizedText.slice(window.start, window.end).trim();
    if (!excerpt) {
      continue;
    }
    excerpts.push(excerpt);
    totalLength += excerpt.length;
    if (totalLength >= 5200) {
      break;
    }
  }

  return limitText(excerpts.join("\n\n---\n\n"), 4200);
}

async function ensureI485AutofillLoopTables() {
  await prisma.$executeRawUnsafe(`
    CREATE TABLE IF NOT EXISTS specialized_form_autofill_runs (
      id CHAR(36) NOT NULL PRIMARY KEY,
      law_firm_id CHAR(36) NOT NULL,
      client_id CHAR(36) NOT NULL,
      form_template_id CHAR(36) NOT NULL,
      agent_code VARCHAR(50) NOT NULL,
      created_by_user_id CHAR(36) NULL,
      target_coverage DECIMAL(6,4) NOT NULL DEFAULT 0.8000,
      max_runtime_seconds INT NOT NULL DEFAULT 7200,
      status_code VARCHAR(30) NOT NULL DEFAULT 'running',
      iteration_count INT NOT NULL DEFAULT 0,
      best_coverage DECIMAL(6,4) NOT NULL DEFAULT 0.0000,
      best_generated_document_id CHAR(36) NULL,
      best_repository_item_id CHAR(36) NULL,
      best_file_id CHAR(36) NULL,
      best_file_name VARCHAR(255) NULL,
      started_at DATETIME NOT NULL,
      completed_at DATETIME NULL,
      final_report_json JSON NULL,
      created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
      updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
      INDEX idx_specialized_form_autofill_runs_law_firm (law_firm_id, client_id, status_code)
    )
  `);

  await prisma.$executeRawUnsafe(`
    CREATE TABLE IF NOT EXISTS specialized_form_autofill_iterations (
      id CHAR(36) NOT NULL PRIMARY KEY,
      run_id CHAR(36) NOT NULL,
      iteration_number INT NOT NULL,
      coverage_ratio DECIMAL(6,4) NOT NULL DEFAULT 0.0000,
      filled_valid_fields INT NOT NULL DEFAULT 0,
      total_fillable_fields INT NOT NULL DEFAULT 0,
      missing_fields_json JSON NULL,
      conflicting_fields_json JSON NULL,
      unsupported_fields_json JSON NULL,
      improvements_applied_json JSON NULL,
      section_breakdown_json JSON NULL,
      generated_document_id CHAR(36) NULL,
      repository_item_id CHAR(36) NULL,
      file_id CHAR(36) NULL,
      file_name VARCHAR(255) NULL,
      runtime_ms BIGINT NOT NULL DEFAULT 0,
      detail_json JSON NULL,
      created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
      updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
      CONSTRAINT fk_specialized_form_autofill_iterations_run
        FOREIGN KEY (run_id) REFERENCES specialized_form_autofill_runs(id)
        ON DELETE CASCADE,
      UNIQUE KEY uq_specialized_form_autofill_iterations_run_iteration (run_id, iteration_number),
      INDEX idx_specialized_form_autofill_iterations_run (run_id)
    )
  `);
}

async function loadI485CoverageEvidence(input: {
  lawFirmId: string;
  clientId: string;
}) {
  const [clientProfile] = await prisma.$queryRaw<I485LoopEvidence["clientProfile"][]>`
    SELECT
      first_name,
      middle_name,
      last_name,
      preferred_name,
      date_of_birth,
      email,
      phone,
      preferred_language,
      country_of_citizenship,
      immigration_status
    FROM clients
    WHERE id = ${input.clientId}
      AND law_firm_id = ${input.lawFirmId}
      AND deleted_at IS NULL
    LIMIT 1
  `;

  const documents = await prisma.$queryRaw<I485LoopEvidenceDocument[]>`
    SELECT title, document_type_code, extracted_text, created_at
    FROM document_records
    WHERE law_firm_id = ${input.lawFirmId}
      AND client_id = ${input.clientId}
      AND extracted_text IS NOT NULL
      AND extracted_text <> ''
    ORDER BY created_at DESC
    LIMIT 160
  `;

  const notes = await prisma.$queryRaw<I485LoopEvidenceNote[]>`
    SELECT item_type_code, subject, body_text, created_at
    FROM repository_items
    WHERE law_firm_id = ${input.lawFirmId}
      AND client_id = ${input.clientId}
      AND body_text IS NOT NULL
      AND body_text <> ''
      AND item_type_code NOT IN ('document', 'generated_form', 'final_packet')
    ORDER BY occurred_at DESC, created_at DESC
    LIMIT 160
  `;

  return {
    clientProfile: clientProfile ?? null,
    documents,
    notes,
  } satisfies I485LoopEvidence;
}

async function assessSectionCoverage(input: {
  lawFirmId: string;
  formName: string;
  section: I485LoopSectionDefinition;
  fields: I485LoopField[];
  evidence: I485LoopEvidence;
  canonicalDossier: unknown;
}) {
  const nonAdminFields = input.fields.filter((field) => !isI485AdministrativeField(field));
  const filledFields = nonAdminFields.filter((field) => field.value?.trim());
  const unresolvedFields = nonAdminFields.filter((field) => !field.value?.trim());

  const searchTerms = buildSectionSearchTerms(input.section, nonAdminFields);
  const selectedDocuments = selectRelevantPlainItems(
    input.evidence.documents,
    searchTerms,
    (document) => `${document.title} ${document.document_type_code} ${document.extracted_text ?? ""}`,
    12,
  );
  const selectedNotes = selectRelevantPlainItems(
    input.evidence.notes,
    searchTerms,
    (note) => `${note.item_type_code} ${note.subject ?? ""} ${note.body_text ?? ""}`,
    8,
  );

  const assessments: I485CoverageAssessment[] = [];
  const chunkSize = 48;

  for (let index = 0; index < unresolvedFields.length; index += chunkSize) {
    const chunk = unresolvedFields.slice(index, index + chunkSize);
    if (!chunk.length) {
      continue;
    }

    try {
      const completion = await runJsonChatCompletion({
        lawFirmId: input.lawFirmId,
        systemPrompt: [
          "You are auditing coverage for USCIS Form I-485.",
          `Focus only on section: ${input.section.title}.`,
          'Return JSON only in the shape {"assessments":[{"pdfFieldName":"string","status":"missing_but_supported|conflicting|unsupported","reason":"string"}]}',
          "For unresolved fields, mark missing_but_supported only when the evidence clearly contains the answer.",
          "Mark conflicting when the evidence contains contradictory values or multiple plausible answers.",
          "Mark unsupported when the answer is absent, legally unsafe to infer, or the field appears not applicable from the evidence.",
          "Be conservative, especially for inadmissibility, criminal, immigration violation, and public charge questions.",
        ].join("\n"),
        userPrompt: JSON.stringify({
          formName: input.formName,
          sectionCode: input.section.code,
          sectionTitle: input.section.title,
          clientProfile: input.evidence.clientProfile,
          canonicalDossier: input.canonicalDossier,
          unresolvedFields: chunk.map((field) => ({
            pdfFieldName: field.pdfFieldName,
            label: field.label,
            dataType: field.dataType,
          })),
          alreadyFilledFields: filledFields.slice(0, 120).map((field) => ({
            pdfFieldName: field.pdfFieldName,
            label: field.label,
            value: field.value,
          })),
          documents: selectedDocuments.map((document) => ({
            title: document.title,
            documentTypeCode: document.document_type_code,
            extractedText: buildSectionAwareTextExcerpt(document, input.section, nonAdminFields),
          })),
          repositoryNotes: selectedNotes.map((note) => ({
            itemTypeCode: note.item_type_code,
            subject: note.subject,
            bodyText: limitText(note.body_text, 1200),
          })),
        }),
        maxCompletionTokens: 2400,
      });

      const rawAssessments = Array.isArray(completion.json.assessments) ? completion.json.assessments : [];
      for (const assessment of rawAssessments) {
        if (!assessment || typeof assessment !== "object") {
          continue;
        }
        const pdfFieldName = String((assessment as { pdfFieldName?: unknown }).pdfFieldName ?? "").trim();
        if (!pdfFieldName) {
          continue;
        }
        const status = String((assessment as { status?: unknown }).status ?? "").trim() as I485CoverageAssessmentStatus;
        if (!["missing_but_supported", "conflicting", "unsupported"].includes(status)) {
          continue;
        }
        assessments.push({
          pdfFieldName,
          status,
          reason: String((assessment as { reason?: unknown }).reason ?? "").trim(),
        });
      }
    } catch {
      for (const field of chunk) {
        assessments.push({
          pdfFieldName: field.pdfFieldName,
          status: "unsupported",
          reason: "Coverage assessment failed for this field chunk.",
        });
      }
    }
  }

  const missingFields = assessments
    .filter((assessment) => assessment.status === "missing_but_supported")
    .map((assessment) => nonAdminFields.find((field) => field.pdfFieldName === assessment.pdfFieldName)?.label ?? assessment.pdfFieldName);
  const conflictingFields = assessments
    .filter((assessment) => assessment.status === "conflicting")
    .map((assessment) => nonAdminFields.find((field) => field.pdfFieldName === assessment.pdfFieldName)?.label ?? assessment.pdfFieldName);
  const unsupportedFields = assessments
    .filter((assessment) => assessment.status === "unsupported")
    .map((assessment) => nonAdminFields.find((field) => field.pdfFieldName === assessment.pdfFieldName)?.label ?? assessment.pdfFieldName);

  const totalFillableFields = filledFields.length + missingFields.length + conflictingFields.length;
  const coverage = totalFillableFields > 0 ? filledFields.length / totalFillableFields : 1;

  return {
    sectionCode: input.section.code,
    title: input.section.title,
    coverage,
    filledValidFields: filledFields.length,
    totalFillableFields,
    missingFields,
    conflictingFields,
    unsupportedFields,
  } satisfies I485SectionCoverageBreakdown;
}

async function calculateI485CoverageReport(input: {
  lawFirmId: string;
  formName: string;
  fields: I485LoopField[];
  canonicalDossier: unknown;
  evidence: I485LoopEvidence;
}) {
  const sectionBreakdown: I485SectionCoverageBreakdown[] = [];

  for (const section of I485_LOOP_SECTION_DEFINITIONS) {
    const sectionFields = input.fields.filter((field) => section.matchesField(field));
    if (!sectionFields.length) {
      continue;
    }
    sectionBreakdown.push(
      await assessSectionCoverage({
        lawFirmId: input.lawFirmId,
        formName: input.formName,
        section,
        fields: sectionFields,
        evidence: input.evidence,
        canonicalDossier: input.canonicalDossier,
      }),
    );
  }

  const filledFields = sectionBreakdown.reduce((sum, section) => sum + section.filledValidFields, 0);
  const totalFields = sectionBreakdown.reduce((sum, section) => sum + section.totalFillableFields, 0);
  const missingFields = sectionBreakdown.flatMap((section) => section.missingFields);
  const conflictingFields = sectionBreakdown.flatMap((section) => section.conflictingFields);
  const unsupportedFields = sectionBreakdown.flatMap((section) => section.unsupportedFields);

  return {
    coverage: totalFields > 0 ? filledFields / totalFields : 1,
    filledFields,
    totalFields,
    missingFields,
    conflictingFields,
    unsupportedFields,
    sectionBreakdown,
  };
}

function buildImprovementPrompt(input: {
  basePrompt: string;
  iteration: number;
  previousIteration?: I485OptimizationIterationSummary | null;
}) {
  const improvementsApplied: string[] = [];

  if (input.iteration === 1) {
    improvementsApplied.push(
      "Enabled section-aware retrieval from long I-485 OCR documents.",
      "Seeded direct answers from prior generated I-485 forms when available.",
      "Persisted a canonical I-485 dossier by section before field filling.",
    );

    return {
      prompt: input.basePrompt,
      improvementsApplied,
    };
  }

  const lowCoverageSections = (input.previousIteration?.sectionBreakdown ?? [])
    .filter((section) => section.coverage < 0.75 && (section.missingFields.length || section.conflictingFields.length))
    .slice(0, 4);

  const missingHighlights = (input.previousIteration?.missingFields ?? []).slice(0, 24);

  improvementsApplied.push(
    "Augmented the specialist prompt with previous-iteration coverage gaps.",
    "Increased attention to prior I-485 evidence for unresolved sections.",
  );

  const addendum = [
    "Iteration improvement instructions:",
    lowCoverageSections.length
      ? `Focus especially on these low-coverage sections: ${lowCoverageSections.map((section) => section.title).join(", ")}.`
      : "Focus on the remaining unresolved sections with conservative evidence matching.",
    missingHighlights.length
      ? `Try again for these still-supported unanswered fields when the evidence is explicit: ${missingHighlights.join(" | ")}.`
      : "Prioritize explicit evidence from official documents, prior I-485s, I-94, passport, DS-160, and USCIS notices.",
    "If a prior I-485 or USCIS filing contains a direct answer for the principal applicant, reuse it only when consistent with official supporting evidence.",
  ].join("\n");

  return {
    prompt: `${input.basePrompt.trim()}\n\n${addendum}`,
    improvementsApplied,
  };
}

async function insertI485AutofillIteration(input: {
  runId: string;
  summary: I485OptimizationIterationSummary;
  detailJson: Record<string, unknown>;
}) {
  await prisma.$executeRaw(
    Prisma.sql`
      INSERT INTO specialized_form_autofill_iterations (
        id,
        run_id,
        iteration_number,
        coverage_ratio,
        filled_valid_fields,
        total_fillable_fields,
        missing_fields_json,
        conflicting_fields_json,
        unsupported_fields_json,
        improvements_applied_json,
        section_breakdown_json,
        generated_document_id,
        repository_item_id,
        file_id,
        file_name,
        runtime_ms,
        detail_json,
        created_at,
        updated_at
      ) VALUES (
        ${createId()},
        ${input.runId},
        ${input.summary.iteration},
        ${input.summary.coverage},
        ${input.summary.filledFields},
        ${input.summary.totalFields},
        ${JSON.stringify(input.summary.missingFields)},
        ${JSON.stringify(input.summary.conflictingFields)},
        ${JSON.stringify(input.summary.unsupportedFields)},
        ${JSON.stringify(input.summary.improvementsApplied)},
        ${JSON.stringify(input.summary.sectionBreakdown)},
        ${input.summary.generatedDocumentId},
        ${input.summary.repositoryItemId},
        ${input.summary.fileId},
        ${input.summary.fileName},
        ${input.summary.runtimeMs},
        ${JSON.stringify(input.detailJson)},
        CURRENT_TIMESTAMP,
        CURRENT_TIMESTAMP
      )
    `,
  );
}

export async function runIterativeI485AutofillLoop(input: {
  lawFirmId: string;
  clientId: string;
  actorUserId: string;
  targetCoverage?: number;
  maxRuntimeMs?: number;
  basePromptOverride?: string | null;
  onIteration?: ((summary: I485OptimizationIterationSummary) => Promise<void> | void) | null;
}) {
  await ensureI485AutofillLoopTables();

  const prepared = await prepareSpecializedFormGeneration({
    lawFirmId: input.lawFirmId,
    actorUserId: input.actorUserId,
    agentCode: "uscis_i_485",
  });

  const runId = createId();
  const startedAt = new Date();
  const targetCoverage = input.targetCoverage ?? 0.8;
  const maxRuntimeMs = input.maxRuntimeMs ?? 2 * 60 * 60 * 1000;
  const effectiveBasePrompt =
    String(input.basePromptOverride ?? "").trim() || prepared.prompt;

  await prisma.$executeRaw(
    Prisma.sql`
      INSERT INTO specialized_form_autofill_runs (
        id,
        law_firm_id,
        client_id,
        form_template_id,
        agent_code,
        created_by_user_id,
        target_coverage,
        max_runtime_seconds,
        status_code,
        iteration_count,
        best_coverage,
        started_at,
        created_at,
        updated_at
      ) VALUES (
        ${runId},
        ${input.lawFirmId},
        ${input.clientId},
        ${prepared.templateId},
        ${prepared.agentCode},
        ${input.actorUserId},
        ${targetCoverage},
        ${Math.round(maxRuntimeMs / 1000)},
        'running',
        0,
        0,
        ${startedAt},
        CURRENT_TIMESTAMP,
        CURRENT_TIMESTAMP
      )
    `,
  );

  const iterationHistory: I485OptimizationIterationSummary[] = [];
  const cumulativeImprovements = new Set<string>();
  let bestIteration: I485OptimizationIterationSummary | null = null;
  let iteration = 0;

  while (true) {
    iteration += 1;
    const iterationStartedAt = Date.now();
    const promptConfig = buildImprovementPrompt({
      basePrompt: effectiveBasePrompt,
      iteration,
      previousIteration: iterationHistory.at(-1) ?? null,
    });
    promptConfig.improvementsApplied.forEach((item) => cumulativeImprovements.add(item));

    const generationResult = await generateClientFormFromTemplate({
      lawFirmId: input.lawFirmId,
      clientId: input.clientId,
      actorUserId: input.actorUserId,
      formTemplateId: prepared.templateId,
      formName: prepared.formName,
      agentCode: prepared.agentCode,
      agentPrompt: promptConfig.prompt,
    });

    const fields = await prisma.$queryRaw<I485LoopField[]>`
      SELECT
        ff.pdf_field_name AS pdfFieldName,
        ff.label,
        ff.data_type AS dataType,
        ff.section_name AS sectionName,
        ff.page_number AS pageNumber
      FROM form_fields ff
      WHERE ff.form_template_id = ${prepared.templateId}
      ORDER BY ff.page_number ASC, ff.created_at ASC
    `;

    const fieldValues = (generationResult as { fieldValues?: Record<string, string | null> }).fieldValues ?? {};
    const coverageEvidence = await loadI485CoverageEvidence({
      lawFirmId: input.lawFirmId,
      clientId: input.clientId,
    });

    const fieldsWithValues = fields.map((field) => ({
      ...field,
      value: fieldValues[field.pdfFieldName] ?? null,
    }));

    const coverageReport = await calculateI485CoverageReport({
      lawFirmId: input.lawFirmId,
      formName: prepared.formName,
      fields: fieldsWithValues,
      canonicalDossier: (generationResult as { canonicalDossier?: unknown }).canonicalDossier ?? null,
      evidence: coverageEvidence,
    });

    const summary: I485OptimizationIterationSummary = {
      iteration,
      coverage: coverageReport.coverage,
      filledFields: coverageReport.filledFields,
      totalFields: coverageReport.totalFields,
      missingFields: coverageReport.missingFields,
      conflictingFields: coverageReport.conflictingFields,
      unsupportedFields: coverageReport.unsupportedFields,
      improvementsApplied: promptConfig.improvementsApplied,
      runtimeMs: Date.now() - iterationStartedAt,
      sectionBreakdown: coverageReport.sectionBreakdown,
      generatedDocumentId: generationResult.generatedDocumentId,
      repositoryItemId: generationResult.repositoryItemId,
      fileId: generationResult.fileId,
      fileName: generationResult.fileName,
    };

    iterationHistory.push(summary);
    if (!bestIteration || summary.coverage > bestIteration.coverage) {
      bestIteration = summary;
    }

    await insertI485AutofillIteration({
      runId,
      summary,
      detailJson: {
        validationWarnings: (generationResult as { validationWarnings?: string[] }).validationWarnings ?? [],
        unresolvedFields: generationResult.unresolvedFields,
      },
    });

    await prisma.$executeRaw(
      Prisma.sql`
        UPDATE specialized_form_autofill_runs
        SET
          iteration_count = ${iteration},
          best_coverage = ${bestIteration.coverage},
          best_generated_document_id = ${bestIteration.generatedDocumentId},
          best_repository_item_id = ${bestIteration.repositoryItemId},
          best_file_id = ${bestIteration.fileId},
          best_file_name = ${bestIteration.fileName},
          updated_at = CURRENT_TIMESTAMP
        WHERE id = ${runId}
      `,
    );

    if (input.onIteration) {
      await input.onIteration(summary);
    }

    if (summary.coverage >= targetCoverage) {
      const finalReport: I485OptimizationFinalReport = {
        runId,
        clientId: input.clientId,
        formTemplateId: prepared.templateId,
        agentCode: prepared.agentCode,
        coverage: summary.coverage,
        filledFields: summary.filledFields,
        totalFields: summary.totalFields,
        iterations: iterationHistory.length,
        runtimeMs: Date.now() - startedAt.getTime(),
        stopReason: "coverage_reached",
        improvementsMade: Array.from(cumulativeImprovements),
        sectionBreakdown: summary.sectionBreakdown,
        remainingGaps: [...summary.missingFields, ...summary.conflictingFields].slice(0, 200),
        latestGeneratedDocumentId: summary.generatedDocumentId,
        latestRepositoryItemId: summary.repositoryItemId,
        latestFileId: summary.fileId,
        latestFileName: summary.fileName,
        iterationHistory,
      };

      await prisma.$executeRaw(
        Prisma.sql`
          UPDATE specialized_form_autofill_runs
          SET
            status_code = 'completed',
            completed_at = NOW(),
            final_report_json = ${JSON.stringify(finalReport)},
            updated_at = CURRENT_TIMESTAMP
          WHERE id = ${runId}
        `,
      );

      return finalReport;
    }

    if (Date.now() - startedAt.getTime() >= maxRuntimeMs) {
      const best = bestIteration ?? summary;
      const finalReport: I485OptimizationFinalReport = {
        runId,
        clientId: input.clientId,
        formTemplateId: prepared.templateId,
        agentCode: prepared.agentCode,
        coverage: best.coverage,
        filledFields: best.filledFields,
        totalFields: best.totalFields,
        iterations: iterationHistory.length,
        runtimeMs: Date.now() - startedAt.getTime(),
        stopReason: "runtime_exceeded",
        improvementsMade: Array.from(cumulativeImprovements),
        sectionBreakdown: best.sectionBreakdown,
        remainingGaps: [...best.missingFields, ...best.conflictingFields].slice(0, 200),
        latestGeneratedDocumentId: best.generatedDocumentId,
        latestRepositoryItemId: best.repositoryItemId,
        latestFileId: best.fileId,
        latestFileName: best.fileName,
        iterationHistory,
      };

      await prisma.$executeRaw(
        Prisma.sql`
          UPDATE specialized_form_autofill_runs
          SET
            status_code = 'completed',
            completed_at = NOW(),
            final_report_json = ${JSON.stringify(finalReport)},
            updated_at = CURRENT_TIMESTAMP
          WHERE id = ${runId}
        `,
      );

      return finalReport;
    }
  }
}
