






















import { randomBytes } from "node:crypto";
import { mkdir, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { bringPageWindowToFront } from "./etrzxtbsmrmtpfpyetfs.mjs";
import {
  INSLY_DASHBOARD_URL,
  INSLY_ORIGIN,
  extractInslyAjaxHtml,
  isAuthenticatedInslyUrl,
  makeInslyPolicyRecordKey,
  mapInslyPolicyRows,
  normalizeInslyPolicyKey,
  parseInslyListPageLinks,
  parseInslyPolicyOidMap,
  parseInslyWorkbookPolicies,
  pickInslyPolicyDocumentPath,
} from "./cuiizeznileiszspulbmnyjrxpuooecwyrymtbbfvyabenblkp.mjs";
import {
  assertPortfolioMappingComplete,
  createPortfolioEmissionResult,
  createPortfolioMappingCollector,
} from "./fgroazrhswhewnaqxvgl.mjs";
import { normalizePortfolioPdfPeriodDate } from "./ldpwrbpjqvuxzylkmjve.mjs";


const INSLY_LOGGED_IN_SELECTOR = 'a[href*="/policy"]';

export const INSLY_IDENTITY_ORIGIN = "https://extranet-identity-prod.superpolisa.pl";




export const INSLY_SSO_ENTRY_URL = "https://app.insly.com/api/v1/identifier/auth/sso/poland/superpolisa?redirect_uri=https://sso.insly.pl/callback/superpolisa";
const INSLY_IDENTITY_LOGIN_PATH = "/account/login";
const INSLY_IDENTITY_USERNAME_SELECTOR = "#username";
const INSLY_IDENTITY_PASSWORD_SELECTOR = "#password";
const INSLY_IDENTITY_SUBMIT_SELECTOR = 'button[name="Input.Button"][value="login"]';
const INSLY_IDENTITY_ERROR_SELECTOR = ".validation-summary-errors, .alert-danger, .field-validation-error";
export const INSLY_LOGIN_REJECTED_CODE = "INSLY_LOGIN_REJECTED";



const INSLY_MAX_LIST_PAGES = 200;
const INSLY_MAX_FETCH_BYTES = 32 * 1024 * 1024;
export const INSLY_PDF_MAP_INCOMPLETE_CODE = "INSLY_PDF_MAP_INCOMPLETE";
export const INSLY_FILTER_CLEAR_FAILED_CODE = "INSLY_FILTER_CLEAR_FAILED";
const INSLY_PDF_TOKEN_BYTES = 24;
const INSLY_POLICY_LIST_MARKER_PATTERN = /\/policy\/view\/policy=\d+\//i;
const INSLY_FILTER_CLEAR_CONTROL_PATTERN = /<(?:button|input)\b[^>]*(?:name|value)=["']filter_clear["'][^>]*>/i;

function normalizeInslyPdfInsurerKey(value) {
  const normalized = String(value || "")
    .toLocaleLowerCase("pl")
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/[^a-z0-9]+/g, " ")
    .trim()
    .slice(0, 80);
  return normalized === "ergo hestia" || normalized === "ergohestia" ? "hestia" : normalized;
}



export function makeInslyPdfPolicyKey(policy) {
  const insurerKey = normalizeInslyPdfInsurerKey(policy?.insurer);
  const policyNumber = normalizeInslyPolicyKey(policy?.policyNumber);
  if (!insurerKey || policyNumber.length < 3 || policyNumber.length > 64) return "";
  let validFrom;
  let validUntil;
  try {
    validFrom = normalizePortfolioPdfPeriodDate(policy?.validFrom);
    validUntil = normalizePortfolioPdfPeriodDate(policy?.validUntil);
  } catch {
    return "";
  }
  return validFrom || validUntil
    ? `${insurerKey}|${policyNumber}|${validFrom}|${validUntil}`
    : `${insurerKey}|${policyNumber}`;
}

export function normalizeInslyPdfPolicyKey(value) {
  const raw = String(value || "").trim();
  const parts = raw.split("|");
  if (parts.length !== 2 && parts.length !== 4) return "";
  return makeInslyPdfPolicyKey({
    insurer: parts[0],
    policyNumber: parts[1],
    validFrom: parts[2] || "",
    validUntil: parts[3] || "",
  });
}

export function bindInslyPolicyOids(policies, oidByPolicyNumber) {
  const source = Array.isArray(policies) ? policies : [];
  const numberCounts = new Map();
  for (const policy of source) {
    const numberKey = normalizeInslyPolicyKey(policy?.policyNumber);
    if (numberKey) numberCounts.set(numberKey, (numberCounts.get(numberKey) || 0) + 1);
  }
  const oidByPolicyKey = new Map();
  const ambiguousPolicyKeys = new Set();
  for (const policy of source) {
    const policyKey = makeInslyPdfPolicyKey(policy);
    const numberKey = normalizeInslyPolicyKey(policy?.policyNumber);
    if (!policyKey || !numberKey) continue;
    const oid = oidByPolicyNumber instanceof Map ? oidByPolicyNumber.get(numberKey) : undefined;
    if ((numberCounts.get(numberKey) || 0) > 1 || oid === null) {
      ambiguousPolicyKeys.add(policyKey);
    } else if (typeof oid === "string" && oid) {
      oidByPolicyKey.set(policyKey, oid);
    }
  }
  return { oidByPolicyKey, ambiguousPolicyKeys };
}

function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}



export function isInslyIdentityLoginUrl(value) {
  try {
    const url = new URL(String(value || ""));
    return url.origin === INSLY_IDENTITY_ORIGIN
      && url.pathname.toLowerCase().startsWith(INSLY_IDENTITY_LOGIN_PATH);
  } catch {
    return false;
  }
}

export function makeInslyLoginRejectedError() {
  const error = new Error("Superpolisa odrzuciła login lub hasło. Popraw dane Superpolisy w ustawieniach i spróbuj ponownie.");
  error.code = INSLY_LOGIN_REJECTED_CODE;
  return error;
}

function formatDateYmd(date) {
  return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
}

function incompleteInslyPdfMap() {
  const error = new Error("Nie udało się zbudować kompletnej mapy dokumentów. Pobieranie PDF pominięto.");
  error.code = INSLY_PDF_MAP_INCOMPLETE_CODE;
  return error;
}

function hasActiveInslyPolicyFilters(html) {
  for (const match of String(html || "").matchAll(/<input\b[^>]*>/gi)) {
    const tag = match[0];
    const name = /\b(?:name|id)=["']([^"']+)["']/i.exec(tag)?.[1] || "";
    if (!/filter/i.test(name) || /filter_clear/i.test(name)) continue;
    
    
    
    if (/_disp$/i.test(name)) continue;
    const type = /\btype=["']([^"']+)["']/i.exec(tag)?.[1]?.toLowerCase() || "text";
    if (["button", "submit", "reset"].includes(type)) continue;
    const value = /\bvalue=["']([^"']*)["']/i.exec(tag)?.[1]?.trim() || "";
    if (value && !/^-{2,}[\s\S]*-{2,}$/.test(value)) return true;
  }
  for (const match of String(html || "").matchAll(/<select\b([^>]*)>([\s\S]*?)<\/select>/gi)) {
    const attributes = match[1] || "";
    const name = /\b(?:name|id)=["']([^"']+)["']/i.exec(attributes)?.[1] || "";
    if (!/filter/i.test(name)) continue;
    const selected = /<option\b[^>]*\bselected(?:=["'][^"']*["'])?[^>]*\bvalue=["']([^"']+)["'][^>]*>/i.exec(match[2])
      || /<option\b[^>]*\bvalue=["']([^"']+)["'][^>]*\bselected(?:=["'][^"']*["'])?[^>]*>/i.exec(match[2]);
    if (selected?.[1]?.trim()) return true;
  }
  return false;
}

export function requireSuccessfulInslyFilterClear(result) {
  let responseUrl;
  try {
    responseUrl = new URL(result?.url || "");
  } catch {
    responseUrl = null;
  }
  const contentType = String(result?.contentType || "").toLowerCase();
  
  
  
  const html = extractInslyAjaxHtml(String(result?.text || ""));
  const hasEmptyPolicyTable = /<table\b[\s\S]*?<tbody\b[^>]*>\s*<\/tbody>[\s\S]*?<\/table>/i.test(html);
  const hasPolicyListStructure = /<table\b/i.test(html)
    && INSLY_FILTER_CLEAR_CONTROL_PATTERN.test(html)
    && (INSLY_POLICY_LIST_MARKER_PATTERN.test(html) || hasEmptyPolicyTable);
  if (!result?.ok
    || !html
    || !hasPolicyListStructure
    || hasActiveInslyPolicyFilters(html)
    || !responseUrl
    || responseUrl.origin !== INSLY_ORIGIN
    || !/^\/policy\/?$/.test(responseUrl.pathname)
    || !isAuthenticatedInslyUrl(responseUrl.href)
    || (!contentType.includes("json") && !contentType.includes("html"))
    || /<input\b[^>]*type=["']?password|(?:^|[/"'=])login(?:[/"'=?\s]|$)/i.test(html)) {
    const error = new Error("Nie udało się potwierdzić wyczyszczenia filtrów listy polis. Import przerwano.");
    error.code = INSLY_FILTER_CLEAR_FAILED_CODE;
    throw error;
  }
  return html;
}

export function buildInslyCompletionOutcome(freshCount, pdfOutcome = null) {
  return {
    freshCount: Number.isFinite(freshCount) ? freshCount : 0,
    pdfSaved: Number.isFinite(pdfOutcome?.saved) ? pdfOutcome.saved : null,
    pdfRemaining: Number.isFinite(pdfOutcome?.remaining) ? pdfOutcome.remaining : null,
    globalError: typeof pdfOutcome?.globalError === "string" ? pdfOutcome.globalError : null,
    ambiguousCount: Number.isFinite(pdfOutcome?.ambiguousCount) ? pdfOutcome.ambiguousCount : 0,
  };
}

export async function buildInslyPolicyOidMap(fetchPage, firstListHtml, {
  maxPages = INSLY_MAX_LIST_PAGES,
  pause = delay,
  
  
  
  
  targetPolicyNumbers = null,
} = {}) {
  const targets = targetPolicyNumbers instanceof Set && targetPolicyNumbers.size > 0
    ? targetPolicyNumbers
    : null;
  const oidMap = parseInslyPolicyOidMap(firstListHtml);
  const hasAllTargets = () => {
    if (!targets) return false;
    for (const policyNumber of targets) {
      if (!oidMap.has(policyNumber)) return false;
    }
    return true;
  };
  if (hasAllTargets()) return oidMap;
  const visitedPaths = new Set();
  const pendingPaths = [...parseInslyListPageLinks(firstListHtml)];
  let fetchedPages = 0;
  while (pendingPaths.length > 0) {
    const path = pendingPaths.shift();
    if (!path || visitedPaths.has(path)) continue;
    if (fetchedPages >= maxPages) throw incompleteInslyPdfMap();
    visitedPaths.add(path);
    fetchedPages += 1;
    const listPage = await fetchPage(path);
    if (!listPage?.ok || !listPage.text) throw incompleteInslyPdfMap();
    if (listPage.url && !isAuthenticatedInslyUrl(listPage.url)) throw incompleteInslyPdfMap();
    const contentType = String(listPage.contentType || "").toLowerCase();
    if (contentType && !contentType.includes("html") && !contentType.includes("json")) {
      throw incompleteInslyPdfMap();
    }
    const listHtml = extractInslyAjaxHtml(listPage.text);
    if (!listHtml
      || !/<(?:table|a|form)\b|\/policy(?:[/"'?=\s]|$)/i.test(listHtml)
      || /<input\b[^>]*type=["']?password|(?:^|[/"'=])login(?:[/"'=?\s]|$)/i.test(listHtml)) {
      throw incompleteInslyPdfMap();
    }
    parseInslyPolicyOidMap(listHtml, oidMap);
    if (hasAllTargets()) return oidMap;
    for (const nextPath of parseInslyListPageLinks(listHtml)) {
      if (!visitedPaths.has(nextPath) && !pendingPaths.includes(nextPath)) pendingPaths.push(nextPath);
    }
    await pause(150);
  }
  return oidMap;
}

function assertInslyOidMapCoverage(oidByPolicyKey, policies, ambiguousPolicyKeys) {
  for (const policy of policies) {
    const policyKey = makeInslyPdfPolicyKey(policy);
    if (!policyKey || ambiguousPolicyKeys.has(policyKey)) continue;
    if (!oidByPolicyKey.has(policyKey)) throw incompleteInslyPdfMap();
  }
}




export async function performInslyFetchInPage({
  requestPath,
  requestMethod,
  requestBody,
  wantBase64,
  allowedOrigin,
  maxBytes,
}) {
  try {
    const requestUrl = new URL(requestPath, allowedOrigin);
    if (requestUrl.origin !== allowedOrigin) return { ok: false, status: 0, errorCode: "ORIGIN_REJECTED" };
    const response = await fetch(requestUrl.href, {
      method: requestMethod,
      credentials: "include",
      headers: requestBody ? { "Content-Type": "application/x-www-form-urlencoded" } : { Accept: "text/html,application/xhtml+xml,*/*" },
      body: requestBody || undefined,
    });
    if (!response.ok) return { ok: false, status: response.status };
    const responseUrl = new URL(response.url);
    if (responseUrl.origin !== allowedOrigin) return { ok: false, status: 0, errorCode: "ORIGIN_REJECTED" };
    const advertisedHeader = response.headers.get("content-length");
    const advertisedLength = advertisedHeader === null ? null : Number(advertisedHeader);
    if (Number.isFinite(advertisedLength) && advertisedLength > maxBytes) {
      await response.body?.cancel?.().catch(() => {});
      return { ok: false, status: 0, errorCode: "RESPONSE_TOO_LARGE" };
    }

    const chunks = [];
    let totalBytes = 0;
    if (response.body?.getReader) {
      const reader = response.body.getReader();
      try {
        while (true) {
          const { done, value } = await reader.read();
          if (done) break;
          const chunk = value instanceof Uint8Array ? value : new Uint8Array(value || 0);
          if (totalBytes + chunk.byteLength > maxBytes) {
            await reader.cancel().catch(() => {});
            return { ok: false, status: 0, errorCode: "RESPONSE_TOO_LARGE" };
          }
          chunks.push(chunk);
          totalBytes += chunk.byteLength;
        }
      } finally {
        reader.releaseLock();
      }
    } else {
      const fallbackBuffer = await response.arrayBuffer();
      if (fallbackBuffer.byteLength > maxBytes) {
        return { ok: false, status: 0, errorCode: "RESPONSE_TOO_LARGE" };
      }
      chunks.push(new Uint8Array(fallbackBuffer));
      totalBytes = fallbackBuffer.byteLength;
    }

    const bytes = new Uint8Array(totalBytes);
    let outputOffset = 0;
    for (const chunk of chunks) {
      bytes.set(chunk, outputOffset);
      outputOffset += chunk.byteLength;
    }
    const contentType = String(response.headers.get("content-type") || "").slice(0, 120);
    if (wantBase64) {
      let binary = "";
      const chunkSize = 0x8000;
      for (let offset = 0; offset < bytes.length; offset += chunkSize) {
        binary += String.fromCharCode(...bytes.subarray(offset, offset + chunkSize));
      }
      return {
        ok: true,
        status: response.status,
        base64: btoa(binary),
        bytes: bytes.length,
        contentType,
        url: responseUrl.href,
      };
    }
    const text = new TextDecoder("utf-8").decode(bytes);
    return {
      ok: true,
      status: response.status,
      text: text.slice(0, 12000000),
      contentType,
      url: responseUrl.href,
    };
  } catch {
    return { ok: false, status: 0, errorCode: "FETCH_FAILED" };
  }
}

async function writeInslyPdfToOpaqueFile(pdfDirectory, base64) {
  const contents = Buffer.from(base64, "base64");
  for (let attempt = 0; attempt < 3; attempt += 1) {
    const pdfKey = randomBytes(INSLY_PDF_TOKEN_BYTES).toString("base64url");
    try {
      await writeFile(join(pdfDirectory, `${pdfKey}.pdf`), contents, { flag: "wx" });
      return pdfKey;
    } catch (error) {
      if (error?.code !== "EEXIST") throw error;
    }
  }
  const error = new Error("Nie udało się utworzyć bezpiecznego tokenu pliku PDF.");
  error.code = "INSLY_PDF_TOKEN_FAILED";
  throw error;
}

export function createInslyAutomation({ browserPool }) {
  let lastVisiblePage = null;

  async function getPage({ focus = true, headless = false } = {}) {
    
    
    
    
    
    let page = lastVisiblePage && !lastVisiblePage.isClosed() && lastVisiblePage.browser?.()?.connected
      ? lastVisiblePage
      : null;
    if (!page) {
      
      
      
      const activeBrowser = await browserPool.getBrowser({ insurerKey: "insly", headless });
      const inheritedPage = (await activeBrowser.pages().catch(() => []))
        .filter((candidate) => !candidate.isClosed())
        .reverse()
        .find((candidate) => {
          try {
            const hostname = new URL(candidate.url()).hostname;
            return hostname.endsWith("insly.pl") || hostname.endsWith("superpolisa.pl");
          } catch {
            return false;
          }
        });
      if (inheritedPage) {
        browserPool.adoptPage("insly", inheritedPage);
        page = inheritedPage;
      }
    }
    if (!page) {
      page = await browserPool.openPage({ insurerKey: "insly", headless });
    }
    page.setDefaultTimeout(15000);
    page.setDefaultNavigationTimeout(60000);
    lastVisiblePage = page;
    
    for (const other of browserPool.listPages("insly")) {
      if (other !== page && !other.isClosed() && other.url() === "about:blank") {
        await other.close().catch(() => {});
      }
    }
    if (focus) await page.bringToFront().catch(() => {});
    return page;
  }

  
  
  async function minimizeWindow(page) {
    try {
      const session = await page.createCDPSession();
      try {
        const { windowId } = await session.send("Browser.getWindowForTarget");
        await session.send("Browser.setWindowBounds", { windowId, bounds: { windowState: "minimized" } });
      } finally {
        await session.detach().catch(() => {});
      }
      return true;
    } catch {
      return false;
    }
  }

  async function isLoggedIn(page) {
    if (!isAuthenticatedInslyUrl(page.url())) return false;
    return Boolean(await page.$(INSLY_LOGGED_IN_SELECTOR).catch(() => null));
  }

  async function isIdentityLoginFormReady(page) {
    if (!isInslyIdentityLoginUrl(page.url())) return false;
    const usernameField = await page.$(INSLY_IDENTITY_USERNAME_SELECTOR).catch(() => null);
    const passwordField = await page.$(INSLY_IDENTITY_PASSWORD_SELECTOR).catch(() => null);
    return Boolean(usernameField && passwordField);
  }

  async function readIdentityLoginError(page) {
    return page.evaluate((selector) => {
      const node = document.querySelector(selector);
      return (node?.textContent || "").replace(/\s+/g, " ").trim().slice(0, 200);
    }, INSLY_IDENTITY_ERROR_SELECTOR).catch(() => "");
  }

  
  
  
  
  async function waitForLoginOutcome(page, timeoutMs) {
    const deadline = Date.now() + timeoutMs;
    while (Date.now() < deadline) {
      if (await isLoggedIn(page)) return "logged-in";
      if (await isIdentityLoginFormReady(page)) return "login-form";
      await delay(400);
    }
    return "timeout";
  }

  
  
  
  
  async function tryAutomaticLogin(page, { username = "", password = "" } = {}, onProgress = () => {}) {
    if (!username || !password) return false;
    if (!(await isIdentityLoginFormReady(page))) return false;
    onProgress({ status: "running", title: "Logowanie do Superpolisy…", detail: "" });
    
    
    await page.waitForFunction(
      () => Boolean(document.getElementById("DeviceFingerprint")?.value),
      { timeout: 5000 },
    ).catch(() => {});
    const usernameField = await page.$(INSLY_IDENTITY_USERNAME_SELECTOR).catch(() => null);
    const passwordField = await page.$(INSLY_IDENTITY_PASSWORD_SELECTOR).catch(() => null);
    const submitButton = await page.$(INSLY_IDENTITY_SUBMIT_SELECTOR).catch(() => null);
    if (!usernameField || !passwordField || !submitButton) return false;
    
    
    
    await bringPageWindowToFront(page).catch(() => {});
    
    onProgress({ status: "running", title: "Logowanie do Superpolisy…", detail: "Wpisuję login." });
    await usernameField.click({ count: 3 });
    await usernameField.type(username, { delay: 15 });
    onProgress({ status: "running", title: "Logowanie do Superpolisy…", detail: "Wpisuję hasło." });
    await passwordField.click({ count: 3 });
    await passwordField.type(password, { delay: 15 });
    onProgress({ status: "running", title: "Logowanie do Superpolisy…", detail: "Zatwierdzam logowanie." });
    await submitButton.click();
    onProgress({ status: "running", title: "Logowanie do Superpolisy…", detail: "Czekam na wynik logowania." });
    const deadline = Date.now() + 60000;
    while (Date.now() < deadline) {
      await delay(400);
      if (await isLoggedIn(page)) return true;
      if (await isIdentityLoginFormReady(page)) {
        
        
        
        const passwordValue = await page.$eval(INSLY_IDENTITY_PASSWORD_SELECTOR, (input) => input.value || "").catch(() => null);
        if (passwordValue === "") {
          const errorText = await readIdentityLoginError(page);
          if (errorText) throw makeInslyLoginRejectedError();
        }
      }
    }
    return false;
  }

  
  
  
  
  async function reachLoginOutcome(page) {
    await page.goto(INSLY_DASHBOARD_URL, { waitUntil: "domcontentloaded", timeout: 60000 }).catch(() => {});
    const outcome = await waitForLoginOutcome(page, 10000);
    if (outcome !== "timeout") return outcome;
    await page.goto(INSLY_SSO_ENTRY_URL, { waitUntil: "domcontentloaded", timeout: 60000 }).catch(() => {});
    return waitForLoginOutcome(page, 30000);
  }

  async function ensureLoggedIn(page, onProgress, credentials = {}, { headless = false } = {}) {
    if (await isLoggedIn(page)) return;
    onProgress({ status: "running", title: "Sprawdzanie logowania…", detail: "" });
    
    
    const outcome = await reachLoginOutcome(page);
    if (outcome === "login-form" && await tryAutomaticLogin(page, credentials, onProgress)) return;
    if (await isLoggedIn(page)) return;
    if (headless) {
      
      
      
      const error = new Error("Logowanie do Superpolisy wymaga okna przeglądarki.");
      error.code = "MANUAL_INTERACTION_REQUIRED";
      throw error;
    }
    throw new Error("Nie jesteś zalogowany do Superpolisy. Zapisz login i hasło Superpolisy w ustawieniach albo kliknij „Otwórz okno Superpolisy i zaloguj się”, zaloguj się w otwartym oknie i uruchom import jeszcze raz.");
  }

  
  
  
  async function openLogin({ onProgress = () => {}, username = "", password = "" } = {}) {
    const page = await getPage();
    if (await isLoggedIn(page)) {
      await bringPageWindowToFront(page).catch(() => {});
      return { loggedIn: true };
    }
    onProgress({
      status: "running",
      title: "Otwieranie okna Superpolisy…",
      detail: username && password
        ? "Program spróbuje zalogować się zapisanymi danymi."
        : "Zaloguj się w otwartym oknie.",
    });
    try {
      const outcome = await reachLoginOutcome(page);
      if (outcome === "login-form") await tryAutomaticLogin(page, { username, password }, onProgress);
    } finally {
      await bringPageWindowToFront(page).catch(() => {});
    }
    return { loggedIn: await isLoggedIn(page) };
  }

  
  async function inslyFetch(page, path, { method = "GET", body = "", asBase64 = false } = {}) {
    return page.evaluate(performInslyFetchInPage, {
      requestPath: path,
      requestMethod: method,
      requestBody: body,
      wantBase64: asBase64,
      allowedOrigin: INSLY_ORIGIN,
      maxBytes: INSLY_MAX_FETCH_BYTES,
    });
  }

  
  
  
  
  
  
  async function clearFiltersAndFetchListHtml(page) {
    const cleared = await inslyFetch(page, "/policy", { method: "POST", body: "filter_clear=filter_clear" });
    return requireSuccessfulInslyFilterClear(cleared);
  }

  
  
  
  
  async function downloadInslyPolicyPdfBase64(page, policyOid) {
    const fragment = await inslyFetch(page, `/policy/view/policy=${encodeURIComponent(policyOid)}/get=document/`);
    if (!fragment.ok || !fragment.text) return null;
    const documentPath = pickInslyPolicyDocumentPath(extractInslyAjaxHtml(fragment.text));
    if (!documentPath) return null;
    const file = await inslyFetch(page, documentPath, { asBase64: true });
    if (!file.ok || !file.base64 || Number(file.bytes) < 1000) return null;
    
    if (!file.base64.startsWith("JVBERi")) return null;
    return { base64: file.base64, bytes: file.bytes };
  }

  
  
  async function downloadInslyPortfolioPdfs(page, policies, oidByPolicyKey, ambiguousPolicyKeys, {
    pdfDirectory,
    onProgress,
    limit = 50,
    skipPolicyKeys = [],
    knownPolicyKeys = [],
  }) {
    const skipSet = new Set(
      (Array.isArray(skipPolicyKeys) ? skipPolicyKeys : []).map(normalizeInslyPdfPolicyKey).filter(Boolean),
    );
    const knownSet = new Set(
      (Array.isArray(knownPolicyKeys) ? knownPolicyKeys : []).map(normalizeInslyPdfPolicyKey).filter(Boolean),
    );
    const eligible = [];
    for (const policy of policies) {
      const policyKey = makeInslyPdfPolicyKey(policy);
      if (!policyKey
        || skipSet.has(policyKey)
        || ambiguousPolicyKeys.has(policyKey)
        || typeof oidByPolicyKey.get(policyKey) !== "string") {
        continue;
      }
      eligible.push({ policy, policyKey });
    }
    eligible.sort((a, b) => (knownSet.has(a.policyKey) ? 1 : 0) - (knownSet.has(b.policyKey) ? 1 : 0));
    const targets = eligible.slice(0, limit);
    const deferred = eligible.length - targets.length;
    if (targets.length === 0) {
      onProgress({
        status: "running",
        title: "Pobieranie PDF-ów pominięte",
        detail: ambiguousPolicyKeys.size > 0
          ? "Niejednoznaczne numery polis pominięto bez zapisywania plików."
          : "Wszystkie polisy mają już podpięte pliki.",
      });
      return {
        attempted: 0,
        saved: 0,
        remaining: 0,
        ambiguousCount: ambiguousPolicyKeys.size,
      };
    }
    onProgress({
      status: "running",
      title: "Pobieranie PDF-ów…",
      detail: `Do pobrania: ${targets.length} plików.`,
    });
    await mkdir(pdfDirectory, { recursive: true });
    let processed = 0;
    let saved = 0;
    for (const { policy, policyKey } of targets) {
      processed += 1;
      try {
        const policyOid = oidByPolicyKey.get(policyKey);
        if (policyOid) {
          const result = await downloadInslyPolicyPdfBase64(page, policyOid);
          if (result?.base64) {
            policy.pdfKey = await writeInslyPdfToOpaqueFile(pdfDirectory, result.base64);
            policy.pdfAvailable = true;
            saved += 1;
          }
        }
      } catch (error) {
        const code = error?.code === "INSLY_PDF_TOKEN_FAILED"
          ? "INSLY_PDF_TOKEN_FAILED"
          : "INSLY_PDF_DOWNLOAD_FAILED";
        console.error(`Insly PDF: ${code}`);
      }
      if (processed % 5 === 0 || processed === targets.length) {
        onProgress({
          status: "running",
          title: `Pobieranie PDF-ów… (${processed}/${targets.length})`,
          detail: "",
        });
      }
      await delay(200);
    }
    onProgress({
      status: "running",
      title: "PDF-y pobrane",
      detail: `Zapisano ${saved} z ${targets.length} plików.`,
    });
    return {
      attempted: targets.length,
      saved,
      remaining: deferred + (targets.length - saved),
      ambiguousCount: ambiguousPolicyKeys.size,
    };
  }

  
  
  
  
  async function importPortfolioAttempt({
    onProgress = () => {},
    username = "",
    password = "",
    sinceDate = "",
    downloadPdfs = false,
    pdfDirectory = "",
    pdfPolicyLimit = 50,
    skipPdfPolicyKeys = [],
    knownPdfPolicyKeys = [],
    onCompletion,
  } = {}, { headless = false } = {}) {
    const page = await getPage({ focus: false, headless });
    
    
    let hidden = false;
    if (!headless) {
      hidden = await minimizeWindow(page);
      if (hidden) {
        onProgress({
          status: "running",
          title: "Import polis z Superpolisy działa w tle…",
          detail: "Okno zostało zminimalizowane na czas importu.",
        });
      }
    }
    try {
      await ensureLoggedIn(page, onProgress, { username, password }, { headless });
      onProgress({ status: "running", title: "Pobieranie listy polis…", detail: "" });
      const listHtml = await clearFiltersAndFetchListHtml(page);

      const workbook = await inslyFetch(page, "/policy/get=xlsx/", { asBase64: true });
      if (!workbook.ok || !workbook.base64) {
        console.error(`Import Insly: eksport listy odpowiedział błędem (status ${workbook.status || "sieciowy"}).`);
        throw new Error("Nie udało się pobrać listy polis. Spróbuj ponownie za chwilę.");
      }
      const rows = parseInslyWorkbookPolicies(Buffer.from(workbook.base64, "base64"));

      
      const cutoff = new Date();
      cutoff.setFullYear(cutoff.getFullYear() - 1);
      const mappingCollector = createPortfolioMappingCollector(rows.length);
      const allPolicies = mapInslyPolicyRows(rows, {
        minEndDate: formatDateYmd(cutoff),
        mappingCollector,
      });
      const mappingReport = assertPortfolioMappingComplete(mappingCollector.createReport());
      const emissionOptions = {
        sinceDate,
        getFreshnessDates: (policy) => [policy?.issueDate, policy?.addedDate],
        isBacklogPolicy: (policy) => policy?.pdfAvailable === true,
      };
      const previewEmission = createPortfolioEmissionResult(allPolicies, emissionOptions);
      if (allPolicies.length === 0) {
        if (sinceDate) {
          onCompletion?.({
            ...buildInslyCompletionOutcome(previewEmission.emissionReport.freshCount),
            mappingReport,
            emissionReport: previewEmission.emissionReport,
          });
          return previewEmission.emittedPolicies;
        }
        throw new Error("Nie znaleziono żadnej polisy. Sprawdź, czy konto ma dostęp do listy polis.");
      }
      onProgress({
        status: "running",
        title: "Pobieranie polis…",
        detail: `Pobrano ${allPolicies.length} polis.`,
      });

      let pdfOutcome = null;
      if (downloadPdfs && pdfDirectory) {
        let { ambiguousPolicyKeys } = bindInslyPolicyOids(allPolicies, new Map());
        try {
          
          
          
          
          const skipKeySet = new Set(
            (Array.isArray(skipPdfPolicyKeys) ? skipPdfPolicyKeys : []).map(normalizeInslyPdfPolicyKey).filter(Boolean),
          );
          const pdfTargetPolicies = allPolicies.filter((policy) => {
            const policyKey = makeInslyPdfPolicyKey(policy);
            return policyKey && !skipKeySet.has(policyKey) && !ambiguousPolicyKeys.has(policyKey);
          });
          const targetPolicyNumbers = new Set(
            pdfTargetPolicies.map((policy) => normalizeInslyPolicyKey(policy.policyNumber)).filter(Boolean),
          );
          const oidByPolicyNumber = await buildInslyPolicyOidMap(
            (path) => inslyFetch(page, path),
            listHtml,
            { targetPolicyNumbers },
          );
          const binding = bindInslyPolicyOids(allPolicies, oidByPolicyNumber);
          ambiguousPolicyKeys = binding.ambiguousPolicyKeys;
          assertInslyOidMapCoverage(binding.oidByPolicyKey, pdfTargetPolicies, ambiguousPolicyKeys);
          pdfOutcome = await downloadInslyPortfolioPdfs(
            page,
            allPolicies,
            binding.oidByPolicyKey,
            ambiguousPolicyKeys,
            {
              pdfDirectory,
              onProgress,
              limit: pdfPolicyLimit,
              skipPolicyKeys: skipPdfPolicyKeys,
              knownPolicyKeys: knownPdfPolicyKeys,
            },
          );
        } catch (error) {
          const code = error?.code === INSLY_PDF_MAP_INCOMPLETE_CODE
            ? INSLY_PDF_MAP_INCOMPLETE_CODE
            : "INSLY_PDF_PHASE_FAILED";
          console.error(`Insly PDF-y: ${code}`);
          pdfOutcome = {
            saved: 0,
            remaining: allPolicies.filter((policy) => {
              const policyKey = makeInslyPdfPolicyKey(policy);
              return policyKey && !ambiguousPolicyKeys.has(policyKey);
            }).length,
            globalError: code,
            ambiguousCount: ambiguousPolicyKeys.size,
          };
          onProgress({
            status: "running",
            title: "Pobieranie PDF-ów pominięte",
            detail: "Mapa dokumentów była niekompletna. Polisy nie zostały oznaczone jako błędne.",
          });
        }
      }

      onProgress({
        status: "running",
        title: "Porządkowanie pobranych polis…",
        detail: sinceDate
          ? `Nowych polis: ${previewEmission.emissionReport.freshCount}.`
          : `Pobrano ${allPolicies.length} polis.`,
      });
      const { emittedPolicies, emissionReport } = createPortfolioEmissionResult(
        allPolicies,
        emissionOptions,
      );
      onCompletion?.({
        ...buildInslyCompletionOutcome(emissionReport.freshCount, pdfOutcome),
        mappingReport,
        emissionReport,
      });
      return emittedPolicies;
    } catch (error) {
      if (hidden) await bringPageWindowToFront(page).catch(() => {});
      throw error;
    } finally {
      
      
      
      if (headless) await page.close().catch(() => {});
    }
  }

  
  
  
  
  async function importPortfolio(options) {
    const groupHeadless = browserPool.isHeadless("insly");
    const startHeadless = groupHeadless === null ? true : groupHeadless === true;
    if (!startHeadless) return importPortfolioAttempt(options, { headless: false });
    try {
      return await importPortfolioAttempt(options, { headless: true });
    } catch (error) {
      if (error?.code !== "MANUAL_INTERACTION_REQUIRED") throw error;
      options?.onProgress?.({
        status: "running",
        title: "Potrzebne jest okno przeglądarki…",
        detail: "Program otworzy okno Superpolisy - dokończ w nim logowanie, jeśli poprosi.",
      });
      return importPortfolioAttempt(options, { headless: false });
    }
  }

  async function focusWindow() {
    const candidates = [];
    if (lastVisiblePage && !lastVisiblePage.isClosed()) candidates.push(lastVisiblePage);
    for (const page of [...browserPool.listPages("insly")].reverse()) {
      if (page && !page.isClosed() && !candidates.includes(page)) candidates.push(page);
    }
    for (const candidate of candidates) {
      if (await bringPageWindowToFront(candidate)) return true;
    }
    return false;
  }

  async function close() {
    lastVisiblePage = null;
    
    
    await browserPool.closeInsurer("insly");
  }

  return { openLogin, importPortfolio, focusWindow, close };
}
