import {
  ActaFieldsSchema,
  ActaLookupDictionarySchema,
  type ActaAdministrationType,
  type ActaFields,
  type ActaForeignersClause,
  type ActaLookupCompanyType,
  type ActaLookupDictionary,
} from "@/lib/extraction/schema";

const LABEL_HINTS = [
  "DENOMINACION",
  "RAZON SOCIAL",
  "NOMBRE COMERCIAL",
  "RFC",
  "REPRESENTANTE LEGAL",
  "APODERADO LEGAL",
  "FECHA DE CONSTITUCION",
  "CONSTITUIDA",
  "ESCRITURA",
  "INSTRUMENTO",
  "NOTARIO",
  "NOTARIA",
  "CORREDOR PUBLICO",
  "DOMICILIO FISCAL",
  "DOMICILIO SOCIAL",
  "OBJETO SOCIAL",
  "ENTIDAD FEDERATIVA",
  "ESTADO",
  "CLAUSULA",
  "PRIMERA",
  "SEGUNDA",
  "TERCERA",
  "CUARTA",
  "QUINTA",
  "SEXTA",
  "SEPTIMA",
  "OCTAVA",
  "NOVENA",
  "DECIMA",
];

const CLAUSE_START_PATTERN = /^(CL[AÁ]USULA|PRIMERA|SEGUNDA|TERCERA|CUARTA|QUINTA|SEXTA|S[EÉ]PTIMA|OCTAVA|NOVENA|D[EÉ]CIMA|UND[EÉ]CIMA)\b/i;

function cleanValue(value: string | null | undefined, maxLength = 420) {
  if (!value) {
    return null;
  }

  const compact = value
    .replace(/^[\s:;,\-.]+/, "")
    .replace(/\s+/g, " ")
    .trim();

  if (!compact) {
    return null;
  }

  return compact.length > maxLength ? compact.slice(0, maxLength) : compact;
}

function normalizeSearchText(value: string) {
  return value
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .toUpperCase()
    .replace(/[^A-Z0-9 ]+/g, " ")
    .replace(/\s+/g, " ")
    .trim();
}

function extractByPatterns(text: string, patterns: RegExp[]) {
  for (const pattern of patterns) {
    const match = text.match(pattern);
    if (!match?.[1]) {
      continue;
    }

    const value = cleanValue(match[1]);
    if (value) {
      return value;
    }
  }

  return null;
}

function sanitizeLegalNameCandidate(value: string | null) {
  if (!value) {
    return null;
  }

  let cleaned = value
    .replace(/^[A-Z]{2,6}\d{4,}\s+/i, "")
    .replace(/^(LA\s+SOCIEDAD\s+SE\s+DENOMINAR[ÁA]\s*)/i, "")
    .replace(/^DENOMINACI[OÓ]N\s*[-:]\s*/i, "")
    .replace(/^(DENOMINACI[OÓ]N|RAZ[OÓ]N\s+SOCIAL)\s*[:\-]?\s*/i, "")
    .replace(/["'“”]+/g, "")
    .replace(/\s+/g, " ")
    .trim();

  const stopMatch = cleaned.match(
    /\b(CONTRATO\s+SOCIAL|DECLARACIONES|CLAUSULA|CL[AÁ]USULA|PRIMERA|SEGUNDA|TERCERA|CUARTA|QUINTA|SEXTA|S[EÉ]PTIMA|OCTAVA|NOVENA|D[EÉ]CIMA|UND[EÉ]CIMA|QUE\s+CELEBRAN)\b/i,
  );

  if (stopMatch && typeof stopMatch.index === "number" && stopMatch.index > 0) {
    cleaned = cleaned.slice(0, stopMatch.index).trim();
  }

  cleaned = cleaned.replace(/^[\s:;,\-.]+/, "").replace(/[\s:;,\-.]+$/, "");

  if (!cleaned || cleaned.length < 2 || cleaned.length > 120) {
    return null;
  }

  const letters = cleaned.match(/[A-Za-zÁÉÍÓÚÑáéíóúñ]/g)?.length ?? 0;
  if (letters < 2) {
    return null;
  }

  return cleanValue(cleaned, 120);
}

function isLikelyLabelLine(line: string) {
  const normalized = normalizeSearchText(line);
  if (!normalized || normalized.length > 120) {
    return false;
  }

  return LABEL_HINTS.some((hint) => normalized.includes(hint));
}

function hasStrongLabelMatch(line: string, normalizedLine: string, labels: string[]) {
  for (const label of labels) {
    const normalizedLabel = normalizeSearchText(label);
    if (!normalizedLabel) {
      continue;
    }

    if (normalizedLine.startsWith(normalizedLabel)) {
      return true;
    }

    const escapedLabel = label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
    const rawPattern = new RegExp(`\\b${escapedLabel}\\b\\s*[:\\-]`, "i");
    if (rawPattern.test(line)) {
      return true;
    }
  }

  return false;
}

function findValueNearLabel(
  lines: string[],
  labels: string[],
  options?: { maxLinesForward?: number; joinLines?: boolean; requireLineStart?: boolean; maxLineLength?: number },
) {
  const maxLinesForward = options?.maxLinesForward ?? 1;
  const joinLines = options?.joinLines ?? false;
  const requireLineStart = options?.requireLineStart ?? false;
  const maxLineLength = options?.maxLineLength ?? 180;
  const normalizedLabels = labels.map((label) => normalizeSearchText(label));

  for (let index = 0; index < lines.length; index += 1) {
    const currentLine = cleanValue(lines[index] ?? "");
    if (!currentLine) {
      continue;
    }

    const normalizedLine = normalizeSearchText(currentLine);
    const hasLabel = normalizedLabels.some((label) => normalizedLine.includes(label));
    const hasStrongLabel = hasStrongLabelMatch(currentLine, normalizedLine, labels);

    if (!hasLabel) {
      continue;
    }

    if (requireLineStart && !hasStrongLabel) {
      continue;
    }

    if (!hasStrongLabel && currentLine.length > maxLineLength) {
      continue;
    }

    const inline = currentLine.split(/[:\-]/).slice(1).join("-").trim();
    const inlineValue = cleanValue(inline);

    if (inlineValue) {
      return inlineValue;
    }

    const collected: string[] = [];

    for (let step = 1; step <= maxLinesForward; step += 1) {
      const nextLine = cleanValue(lines[index + step] ?? "");
      if (!nextLine) {
        break;
      }

      if (isLikelyLabelLine(nextLine) || CLAUSE_START_PATTERN.test(nextLine)) {
        break;
      }

      collected.push(nextLine);

      if (!joinLines) {
        break;
      }
    }

    if (collected.length > 0) {
      return cleanValue(joinLines ? collected.join(" ") : collected[0]);
    }
  }

  return null;
}

function findClauseBlock(lines: string[], labels: string[], options?: { maxLines?: number }) {
  const maxLines = options?.maxLines ?? 10;
  const normalizedLabels = labels.map((label) => normalizeSearchText(label));

  for (let index = 0; index < lines.length; index += 1) {
    const currentLine = cleanValue(lines[index] ?? "");
    if (!currentLine) {
      continue;
    }

    const normalizedLine = normalizeSearchText(currentLine);
    const hasLabel = normalizedLabels.some((label) => normalizedLine.includes(label));

    if (!hasLabel) {
      continue;
    }

    const inline = currentLine.split(/[:\-]/).slice(1).join("-").trim();
    const collected = inline ? [inline] : [];

    for (let step = 1; step <= maxLines; step += 1) {
      const nextLine = cleanValue(lines[index + step] ?? "");
      if (!nextLine) {
        break;
      }

      if (CLAUSE_START_PATTERN.test(nextLine)) {
        break;
      }

      if (isLikelyLabelLine(nextLine) && step > 1) {
        break;
      }

      collected.push(nextLine);
    }

    return cleanValue(collected.join(" "), 1200);
  }

  return null;
}

function normalizeCompanyName(value: string | null) {
  if (!value) {
    return null;
  }

  return cleanValue(
    value
      .replace(/\bS\.?\s*A\.?\s*P\.?\s*I\.?\s*DE\s*C\.?\s*V\.?\b/gi, "")
      .replace(/\bS\.?\s*A\.?\s*DE\s*C\.?\s*V\.?\b/gi, "")
      .replace(/\bS\.?\s*A\.?\s*S\.?\b/gi, "")
      .replace(/\bS\.?\s*DE\s*R\.?\s*L\.?\s*DE\s*C\.?\s*V\.?\b/gi, "")
      .replace(/\bS\.?\s*DE\s*R\.?\s*L\.?\b/gi, "")
      .replace(/\bS\.?\s*A\.?\b/gi, "")
      .replace(/\bS\.?\s*C\.?\b/gi, "")
      .replace(/["'“”]+/g, "")
      .replace(/\s+/g, " "),
  );
}

function normalizeDocumentNumber(value: string | null) {
  if (!value) {
    return null;
  }

  return cleanValue(value.replace(/\bO\b/g, "0").replace(/(?<=\d)O/g, "0").replace(/O(?=\d)/g, "0"));
}

function extractLegalName(compactText: string, lines: string[]) {
  const byLabel = findValueNearLabel(lines, [
    "DENOMINACION SOCIAL",
    "DENOMINACION",
    "RAZON SOCIAL",
    "NOMBRE DE LA SOCIEDAD",
    "NOMBRE O DENOMINACION",
  ]);
  const sanitizedByLabel = sanitizeLegalNameCandidate(byLabel);
  if (sanitizedByLabel) {
    return sanitizedByLabel;
  }

  const byPattern = extractByPatterns(compactText, [
    /(?:DENOMINACI[OÓ]N|RAZ[OÓ]N\s+SOCIAL)\s*(?:SER[AÁ]|ES|DE)?\s*[:\-"“”]?\s*([A-Z0-9&.,'"()\- ]{4,220}?)(?=\s+(?:CONTRATO\s+SOCIAL|DECLARACIONES|CLAUSULA|PRIMERA|SEGUNDA|TERCERA|CUARTA|QUINTA|SEXTA|S[EÉ]PTIMA|OCTAVA|NOVENA|D[EÉ]CIMA|QUE\s+CELEBRAN)\b|$)/i,
    /SE\s+DENOMINAR[ÁA]\s*[:\-"“”]?\s*([A-Z0-9&.,'"()\- ]{4,220}?)(?=\s+(?:CONTRATO\s+SOCIAL|DECLARACIONES|CLAUSULA|PRIMERA|SEGUNDA|TERCERA|CUARTA|QUINTA|SEXTA|S[EÉ]PTIMA|OCTAVA|NOVENA|D[EÉ]CIMA|QUE\s+CELEBRAN)\b|$)/i,
    /SOCIEDAD\s+DENOMINADA\s*[:\-]?\s*([A-Z0-9&.,'"()\- ]{4,220}?)(?=\s+(?:CONTRATO\s+SOCIAL|DECLARACIONES|CLAUSULA|PRIMERA|SEGUNDA|TERCERA|CUARTA|QUINTA|SEXTA|S[EÉ]PTIMA|OCTAVA|NOVENA|D[EÉ]CIMA|QUE\s+CELEBRAN)\b|$)/i,
    /BAJO\s+LA\s+DENOMINACI[OÓ]N\s+DE\s*[:\-]?\s*([A-Z0-9&.,'"()\- ]{4,220}?)(?=\s+(?:CONTRATO\s+SOCIAL|DECLARACIONES|CLAUSULA|PRIMERA|SEGUNDA|TERCERA|CUARTA|QUINTA|SEXTA|S[EÉ]PTIMA|OCTAVA|NOVENA|D[EÉ]CIMA|QUE\s+CELEBRAN)\b|$)/i,
  ]);
  const sanitizedByPattern = sanitizeLegalNameCandidate(byPattern);
  if (sanitizedByPattern) {
    return sanitizedByPattern;
  }

  const corporateLine = lines.find(
    (line) =>
      /\b(S\.?\s*A\.?\s*DE\s*C\.?\s*V\.?|S\.?\s*DE\s*R\.?\s*L\.?\s*DE\s*C\.?\s*V\.?|S\.?\s*A\.?\s*S\.?|SOCIEDAD)\b/i.test(
        line,
      ) && !/\b(CONTRATO\s+SOCIAL|DECLARACIONES|CLAUSULA|PRIMERA|SEGUNDA|TERCERA|CUARTA|QUINTA|SEXTA)\b/i.test(line),
  );

  return sanitizeLegalNameCandidate(corporateLine ?? null);
}

function detectCompanyType(compactText: string): ActaLookupCompanyType | null {
  const text = normalizeSearchText(compactText);

  if (/SOCIEDAD ANONIMA PROMOTORA DE INVERSION DE CAPITAL VARIABLE|\bS A P I DE C V\b/.test(text)) {
    return "SAPI_DE_CV";
  }

  if (/SOCIEDAD DE RESPONSABILIDAD LIMITADA DE CAPITAL VARIABLE|\bS DE R L DE C V\b/.test(text)) {
    return "S_DE_RL_DE_CV";
  }

  if (/SOCIEDAD DE RESPONSABILIDAD LIMITADA|\bS DE R L\b/.test(text)) {
    return "S_DE_RL";
  }

  if (/SOCIEDAD POR ACCIONES SIMPLIFICADA|\bS A S\b/.test(text)) {
    return "SAS";
  }

  if (/SOCIEDAD ANONIMA DE CAPITAL VARIABLE|\bS A DE C V\b/.test(text)) {
    return "SA_DE_CV";
  }

  if (/SOCIEDAD ANONIMA|\bS A\b/.test(text)) {
    return "SA";
  }

  if (/SOCIEDAD CIVIL|\bS C\b/.test(text)) {
    return "SC";
  }

  if (/SOCIEDAD\s+/.test(text)) {
    return "OTHER";
  }

  return null;
}

function detectAdministrationType(compactText: string): ActaAdministrationType | null {
  const text = normalizeSearchText(compactText);

  if (/ADMINISTRADOR UNICO/.test(text)) {
    return "ADMIN_UNICO";
  }

  if (/CONSEJO DE ADMINISTRACION/.test(text)) {
    return "CONSEJO_ADMIN";
  }

  if (/GERENTE UNICO/.test(text)) {
    return "GERENTE_UNICO";
  }

  if (/\bGERENTES\b|\bGERENTE\b/.test(text)) {
    return "GERENTES";
  }

  if (/ADMINISTRACION/.test(text)) {
    return "OTHER";
  }

  return null;
}

function detectForeignersClause(compactText: string): ActaForeignersClause | null {
  const text = normalizeSearchText(compactText);

  if (
    /CLAUSULA DE EXTRANJEROS|CONVENCION DE EXTRANJEROS|SE CONSIDERARAN COMO NACIONALES|NO INVOCAR LA PROTECCION DE SU GOBIERNO/.test(
      text,
    )
  ) {
    return "CLAUSULA_CALVO";
  }

  if (/ADMITE EXTRANJEROS|SIN CLAUSULA DE EXCLUSION DE EXTRANJEROS/.test(text)) {
    return "ADMITE_EXTRANJEROS";
  }

  return null;
}

function extractInstrumentType(compactText: string) {
  return extractByPatterns(compactText, [
    /(ESCRITURA\s+P[UÚ]BLICA|ACTA\s+CONSTITUTIVA|CONSTITUCI[OÓ]N\s+DE\s+LA\s+SOCIEDAD|INSTRUMENTO\s+NOTARIAL)/i,
  ]);
}

function extractInstrumentNumber(compactText: string) {
  const raw = extractByPatterns(compactText, [
    /FOLIO\s+DE\s+CONSTITUCI[OÓ]N\s*[:#\-]?\s*([A-Z]{2,6}\d{4,})/i,
    /(?:^|\s)(SAS\d{6,})\s+DENOMINACI[OÓ]N/i,
    /(?:ESCRITURA(?:\s+P[UÚ]BLICA)?|INSTRUMENTO)\s*(?:N[UÚ]MERO|NO\.?|N\.?|NRO\.?)?[\s:#\-]*([A-Z0-9\-\/]*\d[A-Z0-9\-\/]*)/i,
    /N[UÚ]MERO\s+DE\s+ESCRITURA\s*[:#\-]?\s*([A-Z0-9\-\/]*\d[A-Z0-9\-\/]*)/i,
  ]);

  return normalizeDocumentNumber(raw);
}

function extractProtocolVolumeBook(compactText: string) {
  const value = extractByPatterns(compactText, [
    /(?:TOMO|LIBRO|VOLUMEN|PROTOCOLO)\s*[:#]?\s*([A-Z0-9\-\/\.]*\d[A-Z0-9\-\/\.]*)/i,
  ]);

  return cleanValue(value?.replace(/[.,;:]+$/, "") ?? null);
}

function extractInstrumentDate(compactText: string, lines: string[]) {
  const byLabel = findValueNearLabel(lines, [
    "FECHA DE CONSTITUCION",
    "CONSTITUIDA EL",
    "SE CONSTITUYO EL",
    "FECHA DEL INSTRUMENTO",
  ]);

  if (byLabel) {
    return byLabel;
  }

  const byPattern = extractByPatterns(compactText, [
    /([0-3]?\d\s+DE\s+(?:ENERO|FEBRERO|MARZO|ABRIL|MAYO|JUNIO|JULIO|AGOSTO|SEPTIEMBRE|SETIEMBRE|OCTUBRE|NOVIEMBRE|DICIEMBRE)\s+DE\s+\d{4})/i,
    /(A\s+LOS?\s+[^.]{8,120}?\s+DEL\s+MES\s+DE\s+[^.]{4,80}?\s+DEL\s+A[NÑ]O\s+[^.]{2,80})/i,
    /(\d{1,2}[\/\.\-]\d{1,2}[\/\.\-]\d{2,4})/i,
  ]);

  return byPattern;
}

function extractPlaceOfGranting(compactText: string) {
  return extractByPatterns(compactText, [
    /EN\s+LA\s+CIUDAD\s+DE\s+([^,\n\.]{3,120})/i,
    /MUNICIPIO\s+DE\s+([^,\n\.]{3,120})/i,
  ]);
}

function extractNotaryNumber(compactText: string) {
  const raw = extractByPatterns(compactText, [
    /(?:NOTAR[IÍ]A|NOTARIO\s+P[UÚ]BLICO)\s*(?:N[UÚ]MERO|NO\.?|N\.)\s*([0-9O]{1,4})/i,
  ]);

  return normalizeDocumentNumber(raw);
}

function extractNotaryName(compactText: string, lines: string[]) {
  const byLabel = findValueNearLabel(lines, ["NOTARIO PUBLICO", "NOTARIO", "CORREDOR PUBLICO", "TITULAR DE LA NOTARIA"], {
    maxLinesForward: 2,
    joinLines: true,
    requireLineStart: true,
    maxLineLength: 120,
  });

  if (byLabel) {
    return byLabel;
  }

  return extractByPatterns(compactText, [
    /NOTARIO\s+P[UÚ]BLICO\s*(?:N[UÚ]MERO|NO\.?|N\.)?\s*[0-9O]{0,4}\s*[^,\n]{0,40},?\s*(?:LIC\.?|LICENCIADO)?\s*([A-ZÁÉÍÓÚÑ][A-ZÁÉÍÓÚÑ\s]{4,140})/i,
  ]);
}

function extractNotaryState(compactText: string) {
  return extractByPatterns(compactText, [
    /(?:DEL\s+ESTADO\s+DE|ESTADO\s+DE)\s+([A-ZÁÉÍÓÚÑ][A-ZÁÉÍÓÚÑ\s]{2,80})/i,
  ]);
}

function extractMoney(compactText: string, patterns: RegExp[]) {
  const value = extractByPatterns(compactText, patterns);
  if (!value) {
    return null;
  }

  return cleanValue(value.replace(/\s+/g, " "));
}

function parseMoneyAmount(value: string | null) {
  if (!value) {
    return null;
  }

  const match = value.match(/[0-9][0-9\.,]*/);
  if (!match) {
    return null;
  }

  const normalized = match[0].replace(/,/g, "");
  const amount = Number.parseFloat(normalized);

  return Number.isFinite(amount) ? amount : null;
}

function normalizeCountryCodeToName(code: string | null) {
  if (!code) {
    return null;
  }

  const normalized = code.trim().toUpperCase();

  if (normalized === "MEX" || normalized === "MX") {
    return "Mexico";
  }

  return null;
}

function extractIndustry(compactText: string) {
  return extractByPatterns(compactText, [
    /ACTIVIDAD\s+PRINCIPAL\s+(.+?)(?=\s+(?:DE\s+FORMA|CUARTA\.|QUINTA\.|SEXTA\.|S[EÉ]PTIMA\.|OCTAVA\.|NOVENA\.|D[EÉ]CIMA\.|P[ÁA]GINA))/i,
    /INDUSTRIA\s*[:\-]?\s*([A-ZÁÉÍÓÚÑa-záéíóúñ0-9 ,.\-]{3,180})/i,
  ]);
}

function extractCountryOfOperation(compactText: string) {
  const byTag = normalizeCountryCodeToName(
    extractByPatterns(compactText, [/<PAIS>\s*([A-Z]{2,3})\s*<\/PAIS>/i, /<PA[IÍ]S>\s*([A-Z]{2,3})\s*<\/PA[IÍ]S>/i]),
  );

  if (byTag) {
    return byTag;
  }

  const byNationality = extractByPatterns(compactText, [
    /NACIONALIDAD\s+DE\s+LA\s+SOCIEDAD\.\s+LA\s+SOCIEDAD\s+SER[ÁA]\s+DE\s+NACIONALIDAD\s+([A-ZÁÉÍÓÚÑa-záéíóúñ]+)/i,
  ]);

  if (byNationality) {
    if (/MEXICAN/i.test(byNationality)) {
      return "Mexico";
    }

    return cleanValue(byNationality);
  }

  if (/REP[ÚU]BLICA\s+MEXICANA|CIUDAD\s+DE\s+M[ÉE]XICO|GOBIERNO\s+FEDERAL/i.test(compactText)) {
    return "Mexico";
  }

  return null;
}

function extractRpcReference(compactText: string) {
  const folioMercantil = extractByPatterns(compactText, [/(?:FOLIO\s+MERCANTIL)\s*[:#]?\s*([A-Z0-9\-\/]{2,60})/i]);
  const registroPublico = /REGISTRO\s+P[UÚ]BLICO\s+DE\s+COMERCIO/i.test(compactText)
    ? "Registro Publico de Comercio"
    : null;
  const inscriptionDate = extractByPatterns(compactText, [
    /INSCRIPCI[OÓ]N\s*(?:DE\s+FECHA)?\s*[:\-]?\s*([0-3]?\d\s+DE\s+[A-ZÁÉÍÓÚÑ]+\s+DE\s+\d{4})/i,
    /INSCRITA\s+EL\s*[:\-]?\s*([0-3]?\d\s+DE\s+[A-ZÁÉÍÓÚÑ]+\s+DE\s+\d{4})/i,
  ]);

  if (!folioMercantil && !registroPublico && !inscriptionDate) {
    return null;
  }

  return {
    folioMercantil,
    registroPublico,
    inscriptionDate,
  };
}

function uniqueStrings(values: Array<string | null>) {
  return [...new Set(values.map((value) => cleanValue(value)).filter((value): value is string => Boolean(value)))];
}

export function extractActaLookupDictionary(fullText: string): ActaLookupDictionary {
  const normalizedText = fullText.replace(/\u0000/g, " ").trim();
  const compactText = normalizedText.replace(/\s+/g, " ");
  const lines = normalizedText
    .split(/\r?\n/)
    .map((line) => line.trim())
    .filter(Boolean);

  const legalName = extractLegalName(compactText, lines);
  const instrumentType = extractInstrumentType(compactText);
  const instrumentNumber = extractInstrumentNumber(compactText);
  const protocolVolumeBook = extractProtocolVolumeBook(compactText);
  const instrumentDate = extractInstrumentDate(compactText, lines);
  const placeOfGranting = extractPlaceOfGranting(compactText);
  const companyType = detectCompanyType(compactText);

  const hasNotaryReference = /NOTAR[IÍ]A|NOTARIO\s+P[UÚ]BLICO|ESCRITURA\s+P[UÚ]BLICA|INSTRUMENTO\s+NOTARIAL/i.test(compactText);
  const notaryName = hasNotaryReference ? extractNotaryName(compactText, lines) : null;
  const notaryNumber = hasNotaryReference ? extractNotaryNumber(compactText) : null;
  const notaryState = hasNotaryReference ? extractNotaryState(compactText) : null;

  const domicileFromClause = extractByPatterns(compactText, [
    /TERCERA\.\s*DOMICILIO\.\s*EL\s+DOMICILIO\s+DE\s+LA\s+SOCIEDAD\s+SER[ÁA]\s+EL\s+UBICADO\s+EN\s+(.+?)(?=\s+CUARTA\.)/i,
  ]);

  const domicileLabels =
    companyType === "SAS"
      ? ["DOMICILIO SOCIAL", "DOMICILIO DE LA SOCIEDAD", "TENDRA SU DOMICILIO EN"]
      : ["DOMICILIO FISCAL", "DOMICILIO SOCIAL", "DOMICILIO DE LA SOCIEDAD", "TENDRA SU DOMICILIO EN", "DOMICILIO"];

  const domicileRaw =
    domicileFromClause ??
    findValueNearLabel(lines, domicileLabels, {
      maxLinesForward: 4,
      joinLines: true,
      requireLineStart: companyType === "SAS",
      maxLineLength: companyType === "SAS" ? 140 : 220,
    });

  const domicile = cleanValue(domicileRaw?.replace(/,\s*pudiendo\s+establecer[\s\S]*$/i, "") ?? null, 600);

  const durationFromClause = extractByPatterns(compactText, [
    /CUARTA\.\s*DURACI[OÓ]N\.\s*LA\s+DURACI[OÓ]N\s+DE\s+LA\s+SOCIEDAD\s+SER[ÁA]\s*(INDEFINIDA|[0-9]{1,3}\s+A[NÑ]OS?)/i,
  ]);

  const duration = durationFromClause ?? extractByPatterns(compactText, [
    /DURACI[OÓ]N\s*(?:SER[AÁ]|ES)?\s*[:\-]?\s*(INDEFINIDA|[0-9]{1,3}\s+A[NÑ]OS?)/i,
  ]);
  const corporatePurpose =
    extractByPatterns(compactText, [
      /LA\s+SOCIEDAD\s+TIENE\s+COMO\s+ACTIVIDAD\s+PRINCIPAL\s+(.+?)(?=\s+CUARTA\.)/i,
      /OBJETO\s+SOCIAL\s*[:\-]?\s*(.+?)(?=\s+(?:CUARTA|QUINTA|SEXTA|S[EÉ]PTIMA|OCTAVA|NOVENA|D[EÉ]CIMA)\.)/i,
      /TIENE\s+COMO\s+ACTIVIDAD\s+PRINCIPAL\s+(.+?)(?=\s+(?:CUARTA|QUINTA|SEXTA|S[EÉ]PTIMA|OCTAVA|NOVENA|D[EÉ]CIMA)\.)/i,
    ]) ??
    findClauseBlock(lines, ["OBJETO SOCIAL", "LA SOCIEDAD TENDRA POR OBJETO", "TENDRA POR OBJETO"], {
      maxLines: 12,
    }) ?? findValueNearLabel(lines, ["OBJETO SOCIAL", "OBJETO"], { maxLinesForward: 8, joinLines: true });
  const stateFromDomicile = extractByPatterns(domicile ?? "", [
    /ENTIDAD\s+FEDERATIVA\s+([A-ZÁÉÍÓÚÑ ]{3,80})/i,
  ]);

  const stateOfIncorporation =
    stateFromDomicile ??
    extractByPatterns(compactText, [
      /TERCERA\.\s*DOMICILIO\.[^]*?ENTIDAD\s+FEDERATIVA\s+([A-ZÁÉÍÓÚÑ ]{3,80})(?=\s+[A-ZÁÉÍÓÚÑ][a-záéíóúñ]+\.|\s+CUARTA\.)/i,
    ]) ??
    findValueNearLabel(lines, ["ENTIDAD FEDERATIVA"], {
      maxLinesForward: 1,
      joinLines: false,
      requireLineStart: true,
      maxLineLength: 90,
    });
  const legalRepresentative = findValueNearLabel(
    lines,
    ["REPRESENTANTE LEGAL", "APODERADO LEGAL"],
    {
      maxLinesForward: 2,
      joinLines: true,
      requireLineStart: true,
      maxLineLength: 120,
    },
  ) ??
    extractByPatterns(compactText, [
      /ADMINISTRADOR(?:A)?\s+UNICO\s*[:\-]?\s*([A-ZÁÉÍÓÚÑ][A-ZÁÉÍÓÚÑ\s]{4,120})/i,
      /SE\s+DESIGNA\s+COMO\s+ADMINISTRADOR(?:A)?\s+UNICO\s+A\s+([A-ZÁÉÍÓÚÑ][A-ZÁÉÍÓÚÑ\s]{4,120})/i,
      /CARGO\s+DE\s+ADMINISTRADOR(?:A)?\s+UNICO\s+RECAER[ÁA]\s+EN\s+([A-ZÁÉÍÓÚÑ][A-ZÁÉÍÓÚÑ\s]{4,120})/i,
      /FUNCI[OÓ]N\s+QUE\s+DESEMPE[ÑN]AR[ÁA]\s+([A-ZÁÉÍÓÚÑ][A-ZÁÉÍÓÚÑ\s]{4,120}?)\s+Y\s+DE\s+CONFORMIDAD/i,
      /ESTAR[ÁA]\s+A\s+CARGO\s+DE\s+UN\s+ADMINISTRADOR[^.]{0,220}?\s+([A-ZÁÉÍÓÚÑ][A-ZÁÉÍÓÚÑ\s]{4,120}?)\s+Y\s+DE\s+CONFORMIDAD/i,
    ]);

  const industry = extractIndustry(compactText);
  const countryOfOperation = extractCountryOfOperation(compactText);

  // RFC must be captured manually by user during onboarding (acta often contains personal RFCs).
  const rfcCompany = null;
  const rpcReference = extractRpcReference(compactText);

  const capitalTotalDetected = extractMoney(compactText, [
    /CAPITAL\s+SOCIAL(?:\s+TOTAL)?\s*(?:ASCIENDE\s+A|SERA\s+DE|ES\s+DE|POR\s+LA\s+CANTIDAD\s+DE)\s*\$?\s*([0-9][0-9\.,]*(?:\s*[A-Z.]+)?)/i,
  ]);
  const capitalFixed = extractMoney(compactText, [
    /(?:CAPITAL\s+SOCIAL\s+FIJO|CAPITAL\s+FIJO|PARTE\s+FIJA)\s*(?:ES\s+LA\s+CANTIDAD\s+DE|:|\-)?\s*\$?\s*([0-9][0-9\.,]*(?:\s*[A-Z.]+)?)/i,
  ]);
  const capitalVariable = extractMoney(compactText, [
    /(?:PORCI[OÓ]N\s+VARIABLE\s+DEL\s+CAPITAL\s+SOCIAL|CAPITAL\s+VARIABLE|PARTE\s+VARIABLE)\s*(?:ES\s+LA\s+CANTIDAD\s+DE|:|\-)?\s*\$?\s*([0-9][0-9\.,]*(?:\s*[A-Z.]+)?|ILIMITAD[AO])/i,
  ]);
  const shareNominalValue = extractMoney(compactText, [
    /VALOR\s+NOMINAL(?:\s+DE)?\s*[:\-]?\s*\$?\s*([0-9][0-9\.,]*(?:\s*[A-Z.]+)?)/i,
    /CADA\s+(?:ACCI[OÓ]N|PARTE\s+SOCIAL)\s+DE\s*\$?\s*([0-9\.,]+(?:\s*[A-Z.]+)?)/i,
  ]);

  const derivedCapitalTotal = (() => {
    const fixed = parseMoneyAmount(capitalFixed);
    const variable = parseMoneyAmount(capitalVariable);

    if (fixed === null || variable === null) {
      return null;
    }

    return cleanValue(`${fixed + variable} pesos`);
  })();

  const capitalTotal = capitalTotalDetected ?? derivedCapitalTotal;

  const administrationType = detectAdministrationType(compactText);
  const foreignersClause = detectForeignersClause(compactText);

  const sreNameAuthorization = findValueNearLabel(
    lines,
    ["SECRETARIA DE RELACIONES EXTERIORES", "SRE", "USO DE DENOMINACION", "AUTORIZACION"],
    {
      maxLinesForward: 2,
      joinLines: true,
    },
  );

  const keySignersCandidates = uniqueStrings([legalRepresentative]);

  return ActaLookupDictionarySchema.parse({
    version: "mx_acta_constitutiva_reference_v1",
    instrumentType,
    instrumentNumber,
    protocolVolumeBook,
    instrumentDate,
    placeOfGranting,
    notaryName,
    notaryNumber,
    notaryState,
    legalName,
    normalizedName: normalizeCompanyName(legalName),
    companyType,
    domicile,
    duration,
    corporatePurpose,
    industry,
    countryOfOperation,
    stateOfIncorporation,
    legalRepresentative,
    rpcReference,
    rfcCompany,
    capitalTotal,
    capitalFixed,
    capitalVariable,
    shareNominalValue,
    administrationType,
    foreignersClause: foreignersClause ?? "NO_DETECTADO",
    sreNameAuthorization,
    derivedFields: {
      isCompanyValidlyConstituted: legalName && companyType && instrumentNumber && notaryNumber ? true : null,
      keySignersCandidates: keySignersCandidates.length ? keySignersCandidates : null,
    },
  });
}

export function mapLookupDictionaryToActaFields(dictionary: ActaLookupDictionary): ActaFields {
  const notaryName = dictionary.notaryName
    ? dictionary.notaryNumber
      ? `${dictionary.notaryName} (Notario No. ${dictionary.notaryNumber})`
      : dictionary.notaryName
    : dictionary.notaryNumber
      ? `Notario Publico No. ${dictionary.notaryNumber}`
      : null;

  return ActaFieldsSchema.parse({
    name: dictionary.legalName,
    rfc: null,
    legalRepresentative: dictionary.legalRepresentative,
    incorporationDate: dictionary.instrumentDate,
    deedNumber: dictionary.instrumentNumber,
    notaryName,
    fiscalAddress: dictionary.domicile,
    businessPurpose: dictionary.corporatePurpose,
    stateOfIncorporation: dictionary.stateOfIncorporation ?? dictionary.notaryState,
  });
}

export function extractFields(fullText: string): ActaFields {
  const dictionary = extractActaLookupDictionary(fullText);
  return mapLookupDictionaryToActaFields(dictionary);
}
