(function exposePolicyPdfImporter(globalScope) {
  "use strict";

  const PDF_WORKER_URL = "pliki/vendor/pdf.worker.min.js";
  const registeredParsers = [];

  function normalizeLine(value) {
    return String(value ?? "")
      
      
      
      .normalize("NFC")
      .replace(/\u00a0/g, " ")
      
      
      .replace(/[\u2010-\u2015]/g, "-")
      .replace(/\s+/g, " ")
      .trim();
  }

  function normalizeForDetection(value) {
    return normalizeLine(value)
      .toLocaleUpperCase("pl")
      .normalize("NFD")
      .replace(/[\u0300-\u036f]/g, "")
      .replace(/Ł/g, "L");
  }

  function firstMatch(value, patterns) {
    const text = String(value ?? "");
    for (const pattern of patterns) {
      const match = text.match(pattern);
      if (match?.[1]) return normalizeLine(match[1]);
    }
    return "";
  }

  function toIsoDate(value) {
    const dateText = normalizeLine(value);
    let match = dateText.match(/^(\d{4})[.\-/](\d{1,2})[.\-/](\d{1,2})$/);
    let year = match?.[1];
    let month = match?.[2];
    let day = match?.[3];
    if (!match) {
      match = dateText.match(/^(\d{1,2})[.\-/](\d{1,2})[.\-/](\d{4})$/);
      year = match?.[3];
      month = match?.[2];
      day = match?.[1];
    }
    if (!year || !month || !day) return "";
    const candidate = new Date(Date.UTC(Number(year), Number(month) - 1, Number(day)));
    if (
      candidate.getUTCFullYear() !== Number(year)
      || candidate.getUTCMonth() !== Number(month) - 1
      || candidate.getUTCDate() !== Number(day)
    ) return "";
    return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
  }

  
  
  const POLISH_MONTH_NUMBERS = {
    stycznia: 1,
    lutego: 2,
    marca: 3,
    kwietnia: 4,
    maja: 5,
    czerwca: 6,
    lipca: 7,
    sierpnia: 8,
    "września": 9,
    wrzesnia: 9,
    "października": 10,
    pazdziernika: 10,
    listopada: 11,
    grudnia: 12,
  };

  function toIsoIssueDate(value) {
    const numeric = toIsoDate(value);
    if (numeric) return numeric;
    const worded = normalizeLine(value)
      .toLocaleLowerCase("pl")
      .match(/^(\d{1,2})\s+(\p{L}+)\s+((?:19|20)\d{2})/u);
    const month = worded ? POLISH_MONTH_NUMBERS[worded[2]] : null;
    if (!month) return "";
    return toIsoDate(`${worded[1]}.${month}.${worded[3]}`);
  }

  
  const ISSUE_DATE_TOKEN = "(\\d{1,4}[.\\-/]\\d{1,2}[.\\-/]\\d{1,4}|\\d{1,2}\\s+\\p{L}+\\s+(?:19|20)\\d{2})";

  function buildIssueDatePattern(labelSource, flags = "giu") {
    return new RegExp(labelSource.replace("DATA", ISSUE_DATE_TOKEN), flags);
  }

  
  
  
  
  
  
  const ISSUE_DATE_PATTERNS = [
    
    /dat[aęy](?:\s+i\s+godzin[aęy])?\s+wystawienia(?:\s+(?:polisy|dokumentu|umowy|oferty))?[^\n:]{0,20}:?\s*DATA/.source,
    /(?:polis[aęy]|dokument|umow[aęy])\s+(?:została\s+|zostal\s+)?wystawion[aoy](?:\s+(?:w\s+dniu|dnia))?\s*:?\s*DATA/.source,
    /wystawiono(?:\s+(?:w\s+dniu|dnia))?\s*:?\s*DATA/.source,
    
    
    /DATA\s*,?\s*\d{1,2}:\d{2}[^\n]*\n\s*data\s*,\s*godzina\s*,\s*miejsce\s+wystawienia/.source,
    
    /DATA[^\n]*\n\s*miejscowość\s*,\s*data\s+wystawienia/.source,
    
    /polisa\s+pakiet[^\n|]{0,40}?\bz\s+dnia\s+DATA/.source,
    
    /orygina[łl]\s+DATA\s+\d{1,2}:\d{2}/.source,
    
    
    /miejsce\s+i\s+data\s+(?:wystawienia|zawarcia)[^\n:]{0,30}:?[^\n0-9]{0,40}?DATA/.source,
    /miejsce\s+i\s+data\s+(?:wystawienia|zawarcia)[^\n]{0,40}\n[^\n0-9]{0,40}?,?\s*DATA/.source,
    
    /DATA\s+\p{L}[^\n]{0,120}\n[^\n]{0,80}?miejsce\s+i\s+data\s+zawarcia/.source,
    /dat[aęy](?:\s+i\s+miejsce)?\s+zawarcia(?:\s+umowy)?(?:\s+ubezpieczenia)?[^\n:]{0,20}:?[^\n0-9]{0,40}?DATA/.source,
    
    /dat[aęy]\s+zawarcia\s+umowy(?:\s+ubezpieczenia)?[^\n]{0,20}\n\s*DATA/.source,
    /umow[aęy]\s+(?:ubezpieczenia\s+)?(?:została\s+)?zawart[aoą][^\n]{0,30}?(?:w\s+dniu|dnia)\s*:?\s*DATA/.source,
    /zawarto(?:\s+(?:w\s+dniu|dnia))?\s*:?\s*DATA/.source,
    /dat[aęy]\s+sporządzenia(?:\s+(?:dokumentu|polisy))?\s*:?\s*DATA/.source,
    /sporządzono(?:\s+(?:w\s+dniu|dnia))?\s*:?\s*DATA/.source,
    /dat[aęy]\s+podpisania(?:\s+umowy)?\s*:?\s*DATA/.source,
    
    /podpis\s+ubezpieczaj[aą]cego\s*,\s*DATA/.source,
    
    
    /dat[ay][^\n]{0,40}?podpis[^\n]{0,80}\n\s*DATA/.source,
    
    /DATA\s*,?\s+czytelny\s+podpis/.source,
    
    /w\s+dniu\s+DATA\s*r?\.?\s*o\s+godzinie\s+\d{1,2}:\d{2}/.source,
    
    /\p{Lu}\p{L}{2,},\s*(?:dnia|dn\.)\s*DATA/.source,
    
    /na\s+podstawie\s+wniosku\s+z\s+dnia\s+DATA/.source,
  ].map((source) => buildIssueDatePattern(source)).concat([
    
    
    
    buildIssueDatePattern(/\p{Lu}{2}[\p{Lu} -]{0,28}\s?,\s*DATA(?!\s*(?:-|do\b))/.source, "gu"),
  ], [
    
    buildIssueDatePattern(/wydruk\s+wygenerowany\s+z\s+systemu[^\n]{0,60}?DATA/.source),
  ]);

  
  
  function isPlausibleIssueDate(issueDate, validFrom) {
    if (!validFrom) return true;
    const issue = Date.parse(`${issueDate}T00:00:00Z`);
    const from = Date.parse(`${validFrom}T00:00:00Z`);
    if (!Number.isFinite(issue) || !Number.isFinite(from)) return true;
    const dayDifference = Math.round((issue - from) / 86400000);
    return dayDifference >= -730 && dayDifference <= 370;
  }

  function findPolicyIssueDate(value, { validFrom = "" } = {}) {
    const text = String(value ?? "");
    for (const pattern of ISSUE_DATE_PATTERNS) {
      for (const match of text.matchAll(pattern)) {
        const issueDate = toIsoIssueDate(match[1]);
        if (issueDate && isPlausibleIssueDate(issueDate, validFrom)) return issueDate;
      }
    }
    return "";
  }

  function normalizeVehicleIdentifier(value) {
    return normalizeLine(value)
      .replace(/[^A-Z0-9]/gi, "")
      .toLocaleUpperCase("pl");
  }

  function normalizeVehicleDetail(value) {
    return normalizeLine(value)
      .replace(/^[,;:/\s]+|[,;:/\s]+$/g, "")
      .slice(0, 120);
  }

  function splitVehicleMakeAndModel(value) {
    const description = normalizeVehicleDetail(value)
      .replace(/^(?:marka\s*(?:i|,)\s*model|marka\s*\/\s*model(?:\s*\/\s*typ)?|make\s*[,/]\s*model)\s*:?\s*/i, "")
      .replace(/,\s*(?:rodzaj|rok produkcji|numer|nr rejestracyjny|VIN|pojemność|liczba)\b.*$/i, "")
      .trim();
    if (!description) return { vehicleMake: "", vehicleModel: "" };

    const separated = description.match(/^([^,;/]{1,60})\s*[,;/]\s*(.+)$/);
    if (separated) {
      return {
        vehicleMake: normalizeVehicleDetail(separated[1]),
        vehicleModel: normalizeVehicleDetail(separated[2]),
      };
    }

    const multiwordMakePatterns = [
      /^ALFA(?:\s+|-)ROMEO\b/i,
      /^ASTON(?:\s+|-)MARTIN\b/i,
      /^DS(?:\s+|-)AUTOMOBILES\b/i,
      /^GREAT(?:\s+|-)WALL\b/i,
      /^LAND(?:\s+|-)ROVER\b/i,
      /^MERCEDES(?:\s+|-)BENZ\b/i,
      /^ROLLS(?:\s+|-)ROYCE\b/i,
    ];
    const multiwordMake = multiwordMakePatterns
      .map((pattern) => description.match(pattern))
      .find(Boolean);
    if (multiwordMake) {
      return {
        vehicleMake: normalizeVehicleDetail(multiwordMake[0]),
        vehicleModel: normalizeVehicleDetail(description.slice(multiwordMake[0].length)),
      };
    }

    const firstSpaceIndex = description.indexOf(" ");
    if (firstSpaceIndex < 0) return { vehicleMake: description, vehicleModel: "" };
    return {
      vehicleMake: normalizeVehicleDetail(description.slice(0, firstSpaceIndex)),
      vehicleModel: normalizeVehicleDetail(description.slice(firstSpaceIndex + 1)),
    };
  }

  function findVehicleMakeAndModel(value) {
    const text = String(value ?? "");
    const separateFieldPatterns = [
      /Marka(?:\s+pojazdu)?\s*:?\s*([^\n,;/]{1,60})\s*\n\s*Model(?:\s+pojazdu)?\s*:?\s*([^\n]{1,120})/i,
      /marka\s*:\s*([^,\n]{1,60})\s*,\s*model\s*:\s*([^,\n]{1,120})(?=\s*,\s*(?:typ|rodzaj|rok|VIN|nr|numer)\b|\n|$)/i,
      /Marka\s*\/\s*Model\s+([^/\n]{1,60})\s*\/\s*([^/\n]{1,120})(?=\s*\/)/i,
      /Marka\s*,?\s*model\s*:\s*\/\s*Make\s*:\s*([^,\n]{1,60})\s*,\s*([^\n]{1,120})/i,
      /marka\s*\/\s*model\s*\/\s*typ\s*:\s*\n?\s*([^/\n]{1,60})\s*\/\s*([^/\n]{1,120})/i,
    ];
    for (const pattern of separateFieldPatterns) {
      const match = text.match(pattern);
      if (!match) continue;
      return {
        vehicleMake: normalizeVehicleDetail(match[1]),
        vehicleModel: normalizeVehicleDetail(match[2]),
      };
    }

    const describedVehicle = text.match(
      /Ubezpieczony pojazd\s*\n[^,\n]+,\s*([^,\n]+),\s*[A-Z]{1,3}[A-Z0-9]{3,8}\s*,\s*ROK PRODUKCJI\s*:?/i,
    );
    if (describedVehicle) return splitVehicleMakeAndModel(describedVehicle[1]);

    const combined = firstMatch(text, [
      /marka\s*(?:i|,)\s*model\s*:?\s*([^\n]{2,160})/i,
      /Marka\s*\/\s*Model\s*\/\s*Typ\s*:\s*([^\n]{2,160})/i,
    ]);
    return splitVehicleMakeAndModel(combined);
  }

  function findFirstRegistrationDate(value) {
    const date = firstMatch(value, [
      /(?:data\s+)?pierwszej\s+rejestracji(?:\s+pojazdu)?\s*:?\s*(\d{1,4}[.\-/]\d{1,2}[.\-/]\d{1,4})/i,
      /pierwsza\s+rejestracja(?:\s+pojazdu)?\s*:?\s*(\d{1,4}[.\-/]\d{1,2}[.\-/]\d{1,4})/i,
      /date\s+of\s+first\s+registration\s*:?\s*(\d{1,4}[.\-/]\d{1,2}[.\-/]\d{1,4})/i,
    ]);
    return toIsoDate(date);
  }

  function parsePositiveInteger(value) {
    const digits = String(value ?? "").replace(/[^0-9]/g, "");
    if (!digits) return null;
    const parsed = Number(digits);
    return Number.isFinite(parsed) ? parsed : null;
  }

  function findEngineCapacity(value) {
    const text = String(value ?? "");
    const patterns = [
      
      /pojemno\s*[śs]\s*[ćc](?:\s+(?:silnika|skokowa))?\s*(?:\(\s*cm\s*3?\s*³?\s*\))?\s*:?\s*([0-9][0-9  ]{1,7})(?=\s*(?:cm|ccm|,|;|\n|$))/i,
      
      /pojemno[^\n]{0,60}?:\s*([0-9][0-9  ]{1,7})\s*(?:cm|ccm)/i,
      
      /\/\s*([0-9][0-9  ]{1,7})\s*cm\s*3?\b/i,
    ];
    for (const pattern of patterns) {
      const capacity = parsePositiveInteger(firstMatch(text, [pattern]));
      if (capacity !== null && capacity >= 49 && capacity <= 20000) return `${capacity} cm³`;
    }
    return "";
  }

  function normalizePowerUnit(unit) {
    return /^k\s*w$/i.test(String(unit ?? "").trim()) ? "kW" : "KM";
  }

  function findEnginePower(value) {
    const text = String(value ?? "");
    const isPlausible = (amount, unit) => (unit === "kW"
      ? amount >= 3 && amount <= 1500
      : amount >= 4 && amount <= 2100);

    
    const combined = text.match(/moc(?:\s+silnika)?\s*:?\s*([0-9]{1,4})\s*kW\s*\(\s*([0-9]{1,4})\s*KM\s*\)/i);
    if (combined) {
      const kilowatts = parsePositiveInteger(combined[1]);
      const horsepower = parsePositiveInteger(combined[2]);
      if (kilowatts !== null && horsepower !== null
        && isPlausible(kilowatts, "kW") && isPlausible(horsepower, "KM")) {
        return `${kilowatts} kW (${horsepower} KM)`;
      }
    }

    
    const bracketUnit = text.match(/moc(?:\s+silnika)?\s*[([]\s*(k\s*W|KM)\s*[)\]]\s*:?\s*([0-9]{1,4}(?:[.,][0-9]{1,2})?)/i);
    if (bracketUnit) {
      const unit = normalizePowerUnit(bracketUnit[1]);
      const amount = parsePositiveInteger(bracketUnit[2].replace(/[.,][0-9]+$/, ""));
      if (amount !== null && isPlausible(amount, unit)) return `${amount} ${unit}`;
    }

    
    const labelled = text.match(/moc(?:\s+silnika)?\s*:?\s*([0-9]{1,4}(?:[.,][0-9]{1,2})?)\s*(k\s*W|KW|KM|km)\b/i);
    if (labelled) {
      const unit = normalizePowerUnit(labelled[2]);
      const amount = parsePositiveInteger(labelled[1].replace(/[.,][0-9]+$/, ""));
      if (amount !== null && isPlausible(amount, unit)) return `${amount} ${unit}`;
    }

    
    const slashKilowatts = text.match(/\/\s*([0-9]{1,4})\s*(?:kW|KW|kw)\b/);
    if (slashKilowatts) {
      const amount = parsePositiveInteger(slashKilowatts[1]);
      if (amount !== null && isPlausible(amount, "kW")) return `${amount} kW`;
    }
    const slashHorsepower = text.match(/\/\s*([0-9]{1,4})\s*KM\b/);
    if (slashHorsepower) {
      const amount = parsePositiveInteger(slashHorsepower[1]);
      if (amount !== null && isPlausible(amount, "KM")) return `${amount} KM`;
    }
    return "";
  }

  function findMileage(value) {
    const text = String(value ?? "");
    const match = text.match(
      /przebieg(?!\p{L})(?!\s+ubezpiecz)(?!\s+roczn)(?:\s+pojazdu)?\s*(?:[([]\s*km\s*[)\]])?\s*:?\s*([0-9][0-9  ]{0,9})(\s*tys\.?)?\s*km\b/iu,
    ) || text.match(
      /stan\s+licznika\s*:?\s*([0-9][0-9  ]{0,9})(\s*tys\.?)?\s*(?:km\b)?/i,
    );
    if (!match) return "";
    let mileage = parsePositiveInteger(match[1]);
    if (mileage === null) return "";
    if (match[2]) mileage *= 1000;
    if (mileage < 1 || mileage > 2000000) return "";
    return `${mileage} km`;
  }

  function findDriverLicenseDate(value) {
    const text = String(value ?? "");
    const fullDate = toIsoDate(firstMatch(text, [
      /dat[aey]\s+(?:uzyskania|wydania|zrobienia|otrzymania)\s+(?:prawa\s+jazdy|uprawnie[ńn])[^\n:]{0,30}:?\s*(\d{1,4}[.\-/]\d{1,2}[.\-/]\d{1,4})/i,
      /prawo\s+jazdy\s+(?:od|wydane|uzyskane)(?:\s+dnia)?\s*:?\s*(\d{1,4}[.\-/]\d{1,2}[.\-/]\d{1,4})/i,
      /uprawnienia\s+do\s+kierowania[^\n]{0,40}?\bod\b\s*:?\s*(\d{1,4}[.\-/]\d{1,2}[.\-/]\d{1,4})/i,
    ]));
    if (fullDate) return fullDate;
    return firstMatch(text, [
      /rok\s+(?:uzyskania|wydania|zrobienia)\s+(?:prawa\s+jazdy|uprawnie[ńn])[^\n:]{0,30}:?\s*((?:19|20)\d{2})\b/i,
      /prawo\s+jazdy\s+od\s+(?:roku\s+)?((?:19|20)\d{2})\b(?!\s*[.\-/]\d)/i,
      /(?:dat[aey]|rok)\s+uzyskania\s+uprawnie[ńn][^\n:]{0,40}:?\s*((?:19|20)\d{2})\b(?!\s*[.\-/]\d)/i,
    ]);
  }

  function cleanClientAddress(value) {
    return normalizeLine(value)
      .replace(/[,;\s]*(?:e-?mail|email)\s*:?\s*[^\s,;]*@[\s\S]*$/i, "")
      .replace(/[,;\s]*[^\s,;]+@[\s\S]*$/, "")
      
      
      .replace(/[,;\s]*(?:PESEL|REGON|NIP)\s*:?\s*[0-9][\s\S]*$/i, "")
      .replace(/[,;\s]*(?:e-?mail|telefon|tel)\s*\.?\s*:\s*(?:nieustalon\p{L}*|klient odm[óo]wi\p{L}*|brak)[\s\S]*$/iu, "")
      
      
      .replace(/[,;\s]*(?:numer telefonu|adres e-?mail)\b[\s\S]*$/iu, "")
      .replace(/[,;\s]*tel(?:efon)?(?:\s+komórkowy)?\s*\.?\s*:?\s*[+0-9(][\s\S]*$/i, "")
      .replace(/\b(ul|al|os|pl)\.\s+\1\.\s+/gi, "$1. ")
      .replace(/\s*,\s*/g, ", ")
      .replace(/^[,;:.\s]+|[,;\s]+$/g, "")
      .slice(0, 180);
  }

  function findClientPostalCode(value) {
    
    
    
    const text = normalizeLine(value);
    for (const match of text.matchAll(/\b(\d{2})([- ])(\d{3})\b/gu)) {
      const prefix = text.slice(Math.max(0, match.index - 16), match.index);
      const suffix = text.slice(match.index + match[0].length);
      const currencyBefore = /(?:PLN|zł)\s*$/iu.test(prefix);
      const currencyAfter = /^\s*(?:[,.]\s*(?:\d{1,2}|-)\s*)?(?:PLN|zł)(?!\p{L})/iu.test(suffix);
      const decimalAmountAfter = /^\s*[,.]\s*(?:\d{1,2}|-)(?=\s|$)/u.test(suffix);
      const missingHyphenWithoutLocality = match[2] !== "-" && !/^\s+\p{L}/u.test(suffix);
      if (currencyBefore || currencyAfter || decimalAmountAfter || missingHyphenWithoutLocality) continue;
      return `${match[1]}-${match[3]}`;
    }
    return "";
  }

  function looksLikeAddress(value) {
    const address = String(value ?? "");
    if (address.length < 6) return false;
    return /\d{2}-\d{3}/.test(address) || (/\d/.test(address) && /\p{L}{3,}/u.test(address));
  }

  function findClientAddress(value, { extraLines = 2 } = {}) {
    const lines = String(value ?? "").split("\n");
    const addressLabelPattern = /(?:dane\s+adresowe|adres(?!\p{L})(?!\s+(?:e-?mail|mailow|poczt|elektron|strony|www|do\s+dor[ęe]cze[ńn]))(?:\s+(?:zamieszkania|zameldowania|siedziby|g[łl][óo]wny|korespondencyjny|do\s+korespondencji))?)\s*:?[ \t]*(.*)$/iu;
    for (let index = 0; index < lines.length; index += 1) {
      const match = lines[index].match(addressLabelPattern);
      if (!match) continue;
      let address = match[1] || "";
      let appendedLines = 0;
      while (!/\d{2}-\d{3}/.test(address) && appendedLines < extraLines && index + 1 + appendedLines < lines.length) {
        address = `${address} ${lines[index + 1 + appendedLines]}`;
        appendedLines += 1;
      }
      const cleaned = cleanClientAddress(address);
      if (looksLikeAddress(cleaned)) return cleaned;
    }
    
    for (const line of lines) {
      if (/^\d{2}-\d{3}\s+\p{L}/u.test(line.trim())) {
        const cleaned = cleanClientAddress(line);
        if (looksLikeAddress(cleaned)) return cleaned;
      }
    }
    return "";
  }

  function findVehicleExtraDetails(value) {
    return {
      engineCapacity: findEngineCapacity(value),
      enginePower: findEnginePower(value),
      mileage: findMileage(value),
      driverLicenseDate: findDriverLicenseDate(value),
    };
  }

  function cleanPremium(value) {
    return normalizeLine(value)
      .replace(/\s*zł\s*$/i, "")
      .replace(/[\s\u00a0']/g, "");
  }

  
  
  
  
  
  function findRowTrailingAmount(rowText, maxAmount = null) {
    const trailing = String(rowText ?? "")
      .match(/((?:[0-9]{1,3}[\s ])*[0-9]+(?:[.,][0-9]{1,2})?)\s*$/);
    if (!trailing) return "";
    const groups = trailing[1].split(/[\s ]/).filter(Boolean);
    const toNumber = (value) => Number(String(value).replace(/[\s ]/g, "").replace(",", "."));
    let best = groups.at(-1);
    for (let count = 2; count <= groups.length; count += 1) {
      const candidate = groups.slice(-count).join(" ");
      const value = toNumber(candidate);
      if (!Number.isFinite(value) || value === toNumber(best)) continue;
      if (maxAmount !== null && value > maxAmount) break;
      best = candidate;
    }
    return best;
  }

  function findRiskSummary(value) {
    const allLines = String(value ?? "")
      .split(/\r?\n/)
      .map(normalizeLine)
      .filter(Boolean);
    const normalizedLines = allLines.map(normalizeForDetection);
    const sectionStartPatterns = [
      /^(?:\d+[.)]?\s*)?ZAKRES UBEZPIECZENIA\b/,
      /^ZAKRES OCHRONY\b/,
      /^PRODUKT UBEZPIECZENIOWY\b/,
      /^UBEZPIECZENIA I SKLADKI\b/,
      /^PRZEDMIOT I ZAKRES UBEZPIECZENIA\b/,
      /^PRZEDMIOT UBEZPIECZENIA\b/,
      /^UBEZPIECZENIE MIENIA OD WSZYSTKICH RYZYK\b/,
    ];
    const startIndex = normalizedLines.findIndex((line) => (
      sectionStartPatterns.some((pattern) => pattern.test(line))
    ));
    if (startIndex < 0) return "";
    const isSectionEndLine = (line) => (
      /^(?:PLATNOSC|PLATNOSCI|SPOSOB PLATNOSCI|OSWIADCZENIA|KLAUZULE PRODUKTOWE)\b/.test(line)
      || /\b(?:RAZEM DO ZAPLATY|SKLADKA LACZNA|SKLADKA LACZNIE|SKLADKA OGOL(?:EM|NA))\b/.test(line)
      || /^SKLADKA I (?:TERMINY|SPOSOB) PLATNOSCI\b/.test(line)
      || /\bTOTAL PREMIUM\b/.test(line)
    );
    const endIndex = normalizedLines.findIndex((line, index) => (
      index > startIndex && isSectionEndLine(line)
    ));
    const sectionEnd = endIndex > startIndex ? endIndex : Math.min(allLines.length, startIndex + 180);

    const riskDefinitions = [
      { label: "OC rolnika", pattern: /(?:OC ROLNIK|ODPOWIEDZIALNOSCI CYWILNEJ ROLNIK)/ },
      { label: "Budynki rolnicze", pattern: /BUDYNK(?:I|OW) ROLNICZ/ },
      { label: "Agrocasco", pattern: /AGROCASCO/ },
      { label: "OC w życiu prywatnym", pattern: /ODPOWIEDZIALNOSC CYWILNA W ZYCIU PRYWATNYM|OC W ZYCIU PRYWATNYM/ },
      { label: "OC działalności", pattern: /OC DZIALALNOSCI|ODPOWIEDZIALNOSC CYWILNA.*DZIALALNOSC/ },
      { label: "OC", pattern: /ODPOWIEDZIALNOSC CYWILNA POSIADACZ(?:A|Y) POJAZD|OC POSIADACZ(?:A|Y) POJAZD|OC ZGODNIE Z USTAWA|OC KOMUNIKACYJNE|PZU AUTO OC|WARTA OC|HDI OC|\bOC TURBO\b|\bOC\s*\/\s*ZIELONA KARTA/ },
      { label: "AC", pattern: /\bAUTOCASCO\b|\bAC KOMUNIKACYJNE\b/ },
      { label: "Mini Casco", pattern: /\bMINI\s*CASCO\b|\bMINICASCO\b/ },
      
      
      { label: "NNW Bezpieczna Rodzina", pattern: /NNW BEZPIECZNA RODZINA/ },
      { label: "Następstwa nieszczęśliwych wypadków", pattern: /NASTEPSTW(?:A)? NIESZCZESLIWYCH WYPADKOW|\bNNW\b/ },
      { label: "Moje Auto Ekstra", pattern: /MOJE AUTO EKSTRA/ },
      { label: "Szyby", pattern: /UBEZPIECZENIE SZYB|AUTO SZYBA|\bSZYBY\b|AUTOSZYBY/ },
      { label: "Opony", pattern: /UBEZPIECZENIE OPON|\bOPONY\b/ },
      { label: "Koszty leczenia", pattern: /KOSZTY LECZENIA/ },
      { label: "Kontynuacja leczenia powypadkowego", pattern: /KONTYNUACJA LECZENIA POWYPADKOWEGO/ },
      { label: "Pakiet narciarza/snowboardzisty", pattern: /PAKIET KORZYSCI DLA NARCIARZA|PAKIET NARCIARZA/ },
      
      
      { label: "Assistance", pattern: /\b(?:AUTO|CAR|HOME|DOM I FIRMA)?\s*ASSISTANCE\b|PZU POMOC W DRODZE|PZU AUTO POMOC|\bPOMOC W DOMU\b/ },
      { label: "Holowanie pojazdu", pattern: /HOLOWANIE POJAZDU/ },
      { label: "Zielona Karta", pattern: /ZIELONA KARTA/ },
      { label: "Ochrona prawna", pattern: /OCHRONA PRAWNA|ASYSTENT PRAWNY/ },
      { label: "GAP", pattern: /\bGAP\b/ },
      { label: "Bagaż", pattern: /\bBAGAZ\b/ },
      { label: "Ruchomości domowe", pattern: /RUCHOMOSCI DOMOWE/ },
      { label: "Przedmioty od stłuczenia", pattern: /PRZEDMIOTY OD STLUCZENIA/ },
      { label: "Budynek niemieszkalny", pattern: /BUDYNEK NIEMIESZKALNY/ },
      { label: "Garaż/inny budynek", pattern: /GARAZ\s*\/\s*INNY BUDYNEK|GARAZ WRAZ ZE STALYMI ELEMENTAMI/ },
      
      
      
      { label: "Budynek/lokal", pattern: /DOM JEDNORODZINNY|DOM LETNISKOWY|BUDYNEK MIESZKALNY|LOKAL MIESZKALNY|MIESZKANIE \(?WRAZ ZE STALYMI ELEMENTAMI\)?/ },
      { label: "Budowle", pattern: /^(?:L\s+)?BUDOWLE\b/ },
      { label: "Elementy zewnętrzne", pattern: /ELEMENTY ZEWNETRZNE/ },
      { label: "Siłowniki i napędy bram", pattern: /NAPEDY BRAM/ },
      { label: "Stałe elementy", pattern: /STALE ELEMENTY/ },
      { label: "Wyposażenie posesji", pattern: /WYPOSAZENIE POSESJI/ },
      { label: "Mała architektura", pattern: /MALA ARCHITEKTURA/ },
      { label: "Utrata czynszu", pattern: /UTRATA CZYNSZU/ },
      { label: "Koszty usunięcia awarii", pattern: /KOSZTY POSZUKIWANIA I USUNIECIA AWARII/ },
      { label: "Mienie All Risk", pattern: /MIENIE ALL RISK/ },
      { label: "Mienie", pattern: /MIENIE OD|UBEZPIECZENIE MIENIA/ },
      { label: "Sprzęt", pattern: /UBEZPIECZENIE SPRZETU|SPRZET OD SZKOD/ },
    ];
    const risks = new Map();

    function parseMoneyNumber(value) {
      const normalized = String(value ?? "")
        .replace(/[\s\u00a0']/g, "")
        .replace(",", ".");
      const amount = Number(normalized);
      return Number.isFinite(amount) && amount >= 0 ? amount : null;
    }

    function findTotalPremium() {
      for (let index = 0; index < allLines.length; index += 1) {
        if (!/(?:RAZEM DO ZAPLATY|SKLADKA LACZNA|SKLADKA LACZNIE|SKLADKA OGOL(?:EM|NA)|SKLADKA I (?:TERMINY|SPOSOB) PLATNOSCI|DO ZAPLATY)/.test(normalizedLines[index])) continue;
        const explicitAmounts = [...allLines[index].matchAll(/([0-9][0-9\s\u00a0']*(?:[.,][0-9]{1,2})?)\s*(?:zł|PLN)(?!\p{L})/giu)];
        if (explicitAmounts.length > 0) {
          const parsed = parseMoneyNumber(explicitAmounts.at(-1)[1]);
          if (parsed !== null) return parsed;
        }
        const labelledAmount = normalizedLines[index].match(
          /(?:RAZEM DO ZAPLATY|SKLADKA LACZNA|SKLADKA LACZNIE|SKLADKA OGOL(?:EM|NA)|SKLADKA I (?:TERMINY|SPOSOB) PLATNOSCI|DO ZAPLATY)[^0-9]{0,30}([0-9][0-9\s\u00a0']*(?:[.,][0-9]{1,2})?)/,
        );
        const parsed = parseMoneyNumber(labelledAmount?.[1]);
        if (parsed !== null) return parsed;
      }
      return null;
    }

    const totalPremium = findTotalPremium();

    function findLinePremium(lineIndex) {
      const moneyPattern = /([0-9][0-9\s\u00a0]*(?:[.,][0-9]{1,2})?)\s*(?:zł|PLN)(?!\p{L})/giu;
      const currentNormalized = normalizedLines[lineIndex];
      
      
      if (/\b(?:TAK|NIE|W CENIE(?: PAKIETU)?)\s*$/.test(currentNormalized)) return "";

      let amounts = [...allLines[lineIndex].matchAll(moneyPattern)];
      let sourceLine = allLines[lineIndex];
      const isUsablePremium = (candidateAmounts, candidateLine) => {
        if (candidateAmounts.length === 0) return false;
        const numericAmount = parseMoneyNumber(candidateAmounts.at(-1)[1]);
        const explicitlyPremium = /SKŁADKA|SKLADKA|PZU\s+AUTO\s+(?:OC|AC|NNW)/i.test(candidateLine);
        if (numericAmount === null) return false;
        if (candidateAmounts.length === 1 && !explicitlyPremium && totalPremium !== null && numericAmount > totalPremium * 1.02) {
          return false;
        }
        if (candidateAmounts.length === 1 && !explicitlyPremium && totalPremium === null) return false;
        return true;
      };

      
      
      if (!isUsablePremium(amounts, sourceLine)) {
        amounts = [];
        
        
        
        for (let offset = 1; offset <= 16 && lineIndex + offset < sectionEnd; offset += 1) {
          const nearbyIndex = lineIndex + offset;
          const nearbyNormalized = normalizedLines[nearbyIndex];
          if (isSectionEndLine(nearbyNormalized)) break;
          if (riskDefinitions.some(({ pattern }) => pattern.test(nearbyNormalized))) break;
          if (/^(?:SUMY? GWARANCYJNE|BONUS MALUS|ZNIZK|SZKODY NA)\b/.test(nearbyNormalized)) break;
          
          
          
          
          
          if (/^(?:MIEJSCE UBEZPIECZENIA|[IVX]+\.\s)/.test(nearbyNormalized)
            || /^SUMA UBEZPIECZENIA(?:\s+SKLADKA)?$/.test(nearbyNormalized)) break;
          const nearbyAmounts = [...allLines[nearbyIndex].matchAll(moneyPattern)];
          if (nearbyAmounts.length === 0) continue;
          if (!isUsablePremium(nearbyAmounts, allLines[nearbyIndex])) continue;
          amounts = nearbyAmounts;
          sourceLine = allLines[nearbyIndex];
          break;
        }
      }
      if (amounts.length === 0) return "";
      const candidate = amounts.at(-1)[1];
      const numericAmount = parseMoneyNumber(candidate);
      if (numericAmount === null) return "";
      const amount = normalizeLine(candidate).replace(/\s+/g, " ");
      return amount ? `${amount} zł` : "";
    }

    for (let index = startIndex; index < sectionEnd; index += 1) {
      const normalized = normalizedLines[index];
      if (!normalized || normalized.length > 260) continue;
      let definition = riskDefinitions.find(({ pattern }) => pattern.test(normalized));
      
      
      
      const looksLikeWrappedLabel = normalized.length <= 26 && !/[0-9]/.test(normalized);
      if (!definition
        && (looksLikeWrappedLabel || /^ODPOWIEDZIALNOSC CYWILNA\b|\(WRAZ ZE STALYMI$/.test(normalized))
        && index + 1 < sectionEnd) {
        const combined = `${normalized} ${normalizedLines[index + 1]}`;
        definition = riskDefinitions.find(({ pattern }) => pattern.test(combined));
      }
      if (!definition) continue;
      const amount = findLinePremium(index);
      const existingAmount = risks.get(definition.label) || "";
      if (!risks.has(definition.label) || (!existingAmount && amount)) {
        risks.set(definition.label, amount);
      }
    }
    return [...risks]
      .map(([label, amount]) => (amount ? `${label} ${amount}` : label))
      .join(" • ")
      .slice(0, 400);
  }

  function buildLines(textItems) {
    const lines = [];
    let currentLine = "";

    textItems.forEach((item) => {
      const fragment = normalizeLine(item?.str);
      if (fragment) currentLine = currentLine ? `${currentLine} ${fragment}` : fragment;
      if (item?.hasEOL) {
        const completedLine = normalizeLine(currentLine);
        if (completedLine) lines.push(completedLine);
        currentLine = "";
      }
    });

    const completedLine = normalizeLine(currentLine);
    if (completedLine) lines.push(completedLine);
    return lines;
  }

  async function extractPdfPages(source) {
    const pdfjs = globalScope.pdfjsLib;
    if (!pdfjs?.getDocument) {
      const error = new Error("Brakuje lokalnego modułu PDF.js do odczytu polisy.");
      error.code = "PDF_ENGINE_MISSING";
      throw error;
    }

    if (pdfjs.GlobalWorkerOptions && !pdfjs.GlobalWorkerOptions.workerSrc) {
      pdfjs.GlobalWorkerOptions.workerSrc = PDF_WORKER_URL;
    }

    const bytes = source instanceof Uint8Array
      ? source
      : new Uint8Array(await source.arrayBuffer());
    const loadingTask = pdfjs.getDocument({
      data: bytes,
      isEvalSupported: false,
      stopAtErrors: false,
    });
    let documentProxy = null;

    try {
      documentProxy = await loadingTask.promise;
      const pages = [];
      for (let pageNumber = 1; pageNumber <= documentProxy.numPages; pageNumber += 1) {
        const page = await documentProxy.getPage(pageNumber);
        const textContent = await page.getTextContent({
          normalizeWhitespace: true,
          disableCombineTextItems: false,
        });
        const lines = buildLines(textContent.items);
        pages.push({ pageNumber, lines, text: lines.join("\n") });
        page.cleanup();
      }
      return pages;
    } finally {
      if (documentProxy) await documentProxy.destroy();
      else if (typeof loadingTask.destroy === "function") await loadingTask.destroy();
    }
  }

  function buildParserContext(pages) {
    const lines = pages.flatMap((page) => page.lines).map(normalizeLine).filter(Boolean);
    if (lines.length < 8) {
      const error = new Error("PDF nie ma wystarczającej warstwy tekstowej. To może być skan wymagający OCR.");
      error.code = "PDF_TEXT_MISSING";
      throw error;
    }
    const allText = lines.join("\n");
    return {
      pages,
      lines,
      allText,
      detectionText: normalizeForDetection(allText),
    };
  }

  function registerParser(parser) {
    if (!parser?.id || !parser?.insurer || typeof parser.detect !== "function" || typeof parser.parse !== "function") {
      throw new Error("Nieprawidłowa definicja parsera polisy.");
    }
    const existingIndex = registeredParsers.findIndex((item) => item.id === parser.id);
    const normalizedParser = Object.freeze({ ...parser });
    if (existingIndex >= 0) registeredParsers.splice(existingIndex, 1, normalizedParser);
    else registeredParsers.push(normalizedParser);
  }

  function isInsuranceOffer(context) {
    const openingText = normalizeForDetection(context.lines.slice(0, 80).join(" "));
    return /\b(?:PONIZSZA|NINIEJSZA)\s+OFERTA\s+WAZNA\s+DO\s+DNIA\b/.test(openingText)
      || /\bOFERTA\s+UBEZPIECZENIA\s+(?:MAJATKOWEGO|KOMUNIKACYJNEGO)\s+ERGO\s+BIZNES\s+NR\b/.test(openingText)
      || /\bOFERTA\s+E7(?:\s|_)/.test(openingText)
      || (/\bOFERTA\s+NR\s*:\s*[0-9A-Z/-]+/.test(openingText)
        && /NIE\s+STANOWI\s+OFERTY\s+ZAWARCIA\s+UMOWY\s+UBEZPIECZENIA/.test(openingText));
  }

  
  
  function isRenewalConfirmationLetter(context) {
    const openingText = normalizeForDetection(context.lines.slice(0, 20).join(" "));
    return /\bJEST JUZ UBEZPIECZONY\b/.test(openingText)
      && /W ZALACZENIU PRZESYLAMY/.test(openingText);
  }

  function parsePolicyPages(pages) {
    const context = buildParserContext(pages);
    if (isInsuranceOffer(context)) {
      const error = new Error("Dokument jest ofertą ubezpieczenia, a nie zawartą polisą. Oferty nie są importowane.");
      error.code = "POLICY_OFFER";
      throw error;
    }
    if (isRenewalConfirmationLetter(context)) {
      const error = new Error("Dokument to list potwierdzający wznowienie OC, a nie polisa. Dane wznowienia przyjdą z właściwym dokumentem polisy.");
      error.code = "POLICY_OFFER";
      throw error;
    }
    const parser = registeredParsers.find((candidate) => candidate.detect(context));
    if (!parser) {
      const supportedProducts = registeredParsers
        .map((candidate) => [candidate.insurer, candidate.product].filter(Boolean).join(" - "))
        .join(", ");
      const error = new Error(`Nie rozpoznano obsługiwanego wzoru polisy. Obsługiwane produkty: ${supportedProducts}.`);
      error.code = "UNSUPPORTED_POLICY";
      throw error;
    }

    const parsed = parser.parse(context);
    
    
    
    const insurer = normalizeLine(parsed?.insurer || parser.insurer);
    const product = normalizeLine(parsed?.product || parser.product);
    const clientAddress = cleanClientAddress(parsed?.policy?.clientAddress);
    
    
    const issueDate = toIsoIssueDate(parsed?.policy?.issueDate)
      || findPolicyIssueDate(context.allText, { validFrom: parsed?.policy?.validFrom });
    return {
      insurer,
      product,
      policy: {
        ...parsed.policy,
        insurer,
        issueDate,
        clientAddress,
        clientPostalCode: findClientPostalCode(parsed?.policy?.clientPostalCode)
          || findClientPostalCode(clientAddress),
      },
      pageCount: pages.length,
      hasTextLayer: true,
      missingOptionalFields: Array.isArray(parsed.missingOptionalFields)
        ? parsed.missingOptionalFields
        : [],
      parserId: parser.id,
    };
  }

  async function importPolicyPdf(source) {
    return parsePolicyPages(await extractPdfPages(source));
  }

  globalScope.PolicyPdfParserHelpers = Object.freeze({
    normalizeLine,
    normalizeForDetection,
    firstMatch,
    toIsoDate,
    toIsoIssueDate,
    findPolicyIssueDate,
    normalizeVehicleIdentifier,
    normalizeVehicleDetail,
    splitVehicleMakeAndModel,
    findVehicleMakeAndModel,
    findFirstRegistrationDate,
    findEngineCapacity,
    findEnginePower,
    findMileage,
    findDriverLicenseDate,
    findClientAddress,
    findClientPostalCode,
    findVehicleExtraDetails,
    findRiskSummary,
    findRowTrailingAmount,
    cleanPremium,
  });
  globalScope.PolicyPdfParserRegistry = Object.freeze({
    register: registerParser,
    list: () => registeredParsers.map(({ id, insurer, product }) => ({ id, insurer, product: product || "" })),
  });
  globalScope.PolicyPdfImporter = Object.freeze({
    importPolicyPdf,
    parsePolicyPages,
  });
})(globalThis);
