import {
  bringPageWindowToFront,
  buildKnownVehicleAnswerResolver,
  collectBlockedFormFields,
  composeKnownAnswerResolvers,
  fillDescribedFields,
  learnOrReplayActionPath,
  learnOrReplayFormPath,
  waitForUiQuiet,
} from "./etrzxtbsmrmtpfpyetfs.mjs";
import { attachClientInfoOverlay } from "./hkwnbsimckiekhsfxcto.mjs";
import {
  captureCompensaApiContext,
  downloadCompensaRenewalPdfBase64,
  fetchCompensaRenewals,
  mapCompensaRenewals,
} from "./gxhjwqaqbyhzgfnwbljt.mjs";
import { runPortfolioPdfBatch } from "./ylivitleiyuglkyjgkec.mjs";
import {
  createPortfolioPdfPolicySelectorSet,
  portfolioPdfPolicySelectorMatches,
} from "./abywezhrsiqkqfhujfvq.mjs";
import {
  assertPortfolioMappingComplete,
  createPortfolioEmissionResult,
  createPortfolioMappingCollector,
} from "./fgroazrhswhewnaqxvgl.mjs";

const COMPENSA_URL = "https://cportal.compensa.pl/Portal";
const PORTAL_TIMEOUT_MS = 3 * 60 * 1000;
const LOGIN_SELECTOR = "input[name*='login' i], input[id*='login' i], input[autocomplete='username'], form input[type='text']";
const PASSWORD_SELECTOR = "input[type='password']";



const BUSY_DIALOG_SELECTOR = "ux-dialog-container.active, body.ux-dialog-open ux-dialog";
const PREMIUM_CALCULATION_TIMEOUT_MS = 3 * 60 * 1000;

function delay(milliseconds) {
  return new Promise((resolve) => globalThis.setTimeout(resolve, milliseconds));
}

export function prepareCompensaPortfolioPdfEntries({
  policies,
  skipPolicyNumbers = [],
} = {}) {
  const skipSet = createPortfolioPdfPolicySelectorSet(skipPolicyNumbers, {
    insurer: "compensa",
  });
  return (Array.isArray(policies) ? policies : [])
    .filter((policy) => policy?.policyNumber && policy?.policyId)
    .filter((policy) => !portfolioPdfPolicySelectorMatches(skipSet, policy))
    .map((policy) => ({ policy, policyNumber: policy.policyNumber }));
}

function normalizeText(value) {
  return String(value || "")
    .normalize("NFD")
    .replace(/[̀-ͯ]/g, "")
    .replace(/\s+/g, " ")
    .trim()
    .toLowerCase();
}

function manualInteractionRequired(message) {
  const error = new Error(message);
  error.code = "MANUAL_INTERACTION_REQUIRED";
  return error;
}

export function isCompensaPortalUrl(value) {
  try {
    return new URL(value).hostname.toLowerCase() === "cportal.compensa.pl";
  } catch {
    return false;
  }
}

async function getCombinedPageText(page) {
  const parts = [];
  for (const frame of page.frames()) {
    try {
      const text = await frame.evaluate(() => String(document.body?.innerText || "").slice(0, 20000));
      if (text) parts.push(text);
    } catch {
    }
  }
  return parts.join(" ").slice(0, 50000);
}

async function findVisibleElement(page, selector) {
  for (const frame of page.frames()) {
    let elements = [];
    try {
      elements = await frame.$$(selector);
      for (const element of elements) {
        const visible = await element.evaluate((candidate) => candidate instanceof HTMLElement
          && candidate.getClientRects().length > 0
          && getComputedStyle(candidate).visibility !== "hidden");
        if (visible) {
          await Promise.all(elements
            .filter((candidate) => candidate !== element)
            .map((candidate) => candidate.dispose().catch(() => {})));
          return element;
        }
      }
    } catch {
    }
    await Promise.all(elements.map((element) => element.dispose().catch(() => {})));
  }
  return null;
}

async function frameHasSelector(page, selector) {
  const element = await findVisibleElement(page, selector);
  const found = Boolean(element);
  await element?.dispose().catch(() => {});
  return found;
}

async function findLoginButton(page) {
  for (const frame of page.frames()) {
    let buttons = [];
    try {
      buttons = await frame.$$("button, input[type='submit']");
      for (const button of buttons) {
        const state = await button.evaluate((candidate) => ({
          text: String(candidate.innerText || candidate.value || candidate.textContent || "")
            .normalize("NFD")
            .replace(/[̀-ͯ]/g, "")
            .replace(/\s+/g, " ")
            .trim()
            .toLowerCase(),
          visible: candidate instanceof HTMLElement
            && candidate.getClientRects().length > 0
            && getComputedStyle(candidate).visibility !== "hidden",
        }));
        if (state.visible && /^zaloguj( sie)?$/.test(state.text)) {
          await Promise.all(buttons
            .filter((candidate) => candidate !== button)
            .map((candidate) => candidate.dispose().catch(() => {})));
          return button;
        }
      }
    } catch {
    }
    await Promise.all(buttons.map((button) => button.dispose().catch(() => {})));
  }
  return null;
}

async function isPortalAuthenticated(page) {
  if (!isCompensaPortalUrl(page.url())) return false;
  if (await frameHasSelector(page, PASSWORD_SELECTOR)) return false;
  return page.mainFrame().evaluate(() => {
    const compact = (value) => String(value || "").replace(/\s+/g, " ").trim().toLowerCase();
    return [...document.querySelectorAll("a, button")]
      .some((element) => compact(element.innerText) === "wyloguj");
  }).catch(() => false);
}

async function detectAuthenticationInterruption(page) {
  const pageText = normalizeText(await getCombinedPageText(page));
  if (
    pageText.includes("nieprawidlowy login lub haslo")
    || pageText.includes("nieprawidlowe haslo")
    || pageText.includes("bledny login")
    || pageText.includes("bledne dane logowania")
    || pageText.includes("logowanie nie powiodlo sie")
    || pageText.includes("konto zostalo zablokowane")
  ) {
    return { type: "error", message: "Compensa odrzuciła login lub hasło." };
  }
  if (
    pageText.includes("kod jednorazowy")
    || pageText.includes("kod weryfikacyjny")
    || pageText.includes("kod sms")
    || pageText.includes("uwierzytelnianie dwuskladnikowe")
  ) {
    return {
      type: "action",
      title: "Potwierdź logowanie do Compensy",
      detail: "Zatwierdź logowanie w otwartym oknie (np. kodem SMS).",
    };
  }
  if (pageText.includes("captcha") || await frameHasSelector(page, "iframe[src*='captcha'], [class*='captcha']")) {
    return {
      type: "action",
      title: "Compensa wymaga potwierdzenia CAPTCHA",
      detail: "Rozwiąż CAPTCHA w otwartym oknie.",
    };
  }
  return null;
}

async function fillLoginForm(page, credentials, onProgress) {
  const login = await findVisibleElement(page, LOGIN_SELECTOR);
  const password = await findVisibleElement(page, PASSWORD_SELECTOR);
  const submit = await findLoginButton(page);
  if (!login || !password || !submit) {
    await Promise.all([login, password, submit].filter(Boolean).map((element) => element.dispose().catch(() => {})));
    return false;
  }
  try {
    await login.click({ clickCount: 3 });
    await login.type(credentials.username, { delay: 18 });
    await password.click({ clickCount: 3 });
    await password.type(credentials.password, { delay: 18 });
    const valuesWereEntered = await Promise.all([
      login.evaluate((element, expected) => element instanceof HTMLInputElement && element.value === expected, credentials.username),
      password.evaluate((element, expected) => element instanceof HTMLInputElement && element.value === expected, credentials.password),
    ]);
    if (!valuesWereEntered.every(Boolean)) {
      throw new Error("Nie udało się wpisać loginu lub hasła do formularza Compensy.");
    }
    onProgress({ status: "running", title: "Logowanie do Compensy…", detail: "" });
    await submit.click();
    return true;
  } finally {
    await Promise.all([login, password, submit].map((element) => element.dispose().catch(() => {})));
  }
}

async function loginIfNeeded(page, credentials, onProgress) {
  const deadline = Date.now() + 60000;
  while (Date.now() < deadline) {
    if (await isPortalAuthenticated(page)) return;
    if (await frameHasSelector(page, PASSWORD_SELECTOR)) {
      if (!(await fillLoginForm(page, credentials, onProgress))) {
        throw new Error("Nie udało się obsłużyć formularza logowania Compensy.");
      }
      return;
    }
    const interruption = await detectAuthenticationInterruption(page);
    if (interruption?.type === "error") throw new Error(interruption.message);
    await delay(350);
  }
  throw new Error("Nie udało się otworzyć formularza logowania Compensy.");
}

async function waitForPortal(page, onProgress, { allowUserInteraction = true } = {}) {
  const startedAt = Date.now();
  let lastActionTitle = "";
  while (Date.now() - startedAt < PORTAL_TIMEOUT_MS) {
    if (await isPortalAuthenticated(page)) {
      await delay(800);
      return;
    }
    const interruption = await detectAuthenticationInterruption(page);
    if (interruption?.type === "error") throw new Error(interruption.message);
    if (interruption?.type === "action" && !allowUserInteraction) {
      throw manualInteractionRequired(`${interruption.title}. Kalkulacja w tle została wstrzymana - użyj „Popraw ręcznie”, aby otworzyć okno Chrome.`);
    }
    if (interruption?.type === "action" && interruption.title !== lastActionTitle) {
      lastActionTitle = interruption.title;
      onProgress({ status: "action_required", title: interruption.title, detail: interruption.detail });
    }
    await delay(500);
  }
  throw new Error("Panel Compensy nie otworzył się w ciągu 3 minut.");
}

 
export async function waitForCompensaIdle(page, { timeout = PREMIUM_CALCULATION_TIMEOUT_MS } = {}) {
  const deadline = Date.now() + timeout;
  while (Date.now() < deadline) {
    const busy = await page.mainFrame().evaluate((selector) => {
      const dialog = document.querySelector(selector);
      const visible = dialog instanceof HTMLElement && dialog.getClientRects().length > 0;
      return visible || document.body?.classList.contains("ux-dialog-open") === true;
    }, BUSY_DIALOG_SELECTOR).catch(() => false);
    if (!busy) return true;
    await delay(500);
  }
  return false;
}

 
const COMPENSA_AMOUNT_SOURCE = "(?:\\d{1,3}(?:[\\s\\u00a0\\u202f]\\d{3})*|\\d+)(?:,\\d{2})?\\s*(?:zł|PLN)";
const COMPENSA_AMOUNT_PATTERN = new RegExp(COMPENSA_AMOUNT_SOURCE, "i");
const COMPENSA_LEADING_AMOUNT_PATTERN = new RegExp(`^${COMPENSA_AMOUNT_SOURCE}`, "i");

function compactCompensaText(value) {
  return String(value || "").replace(/[  ]/g, " ").replace(/\s+/g, " ").trim();
}

 
export function buildCompensaPremiums(snapshot) {
  const variants = [];
  for (const header of Array.isArray(snapshot?.headers) ? snapshot.headers : []) {
    const amount = compactCompensaText(header?.price);
    if (!COMPENSA_AMOUNT_PATTERN.test(amount)) continue;
    const stars = Number(header?.stars) || 0;
    variants.push({
      label: stars > 0 ? `Pakiet ${"★".repeat(stars)}` : `Pakiet ${variants.length + 1}`,
      amount,
      chosen: header?.chosen === true,
      consentRequired: header?.consentRequired === true,
      acVariant: "",
      risks: [],
    });
  }
  if (variants.length === 0) return [];

  
  const rows = new Map();
  for (const cell of Array.isArray(snapshot?.cells) ? snapshot.cells : []) {
    const rowKey = Math.round(Number(cell?.y || 0) / 4);
    if (!rows.has(rowKey)) rows.set(rowKey, []);
    rows.get(rowKey).push({ x: Number(cell?.x || 0), text: compactCompensaText(cell?.text) });
  }
  for (const row of rows.values()) {
    
    if (row.length !== variants.length + 1) continue;
    row.sort((left, right) => left.x - right.x);
    const label = row[0].text;
    if (!label || COMPENSA_LEADING_AMOUNT_PATTERN.test(label)) continue;
    if (/^Wariant$/i.test(label)) {
      row.slice(1).forEach((cell, index) => {
        const selectedVariant = cell.text.match(/(Warsztat Partnerski|Kosztorys|Serwis)\s*$/i)?.[1] || "";
        if (variants[index]) variants[index].acVariant = compactCompensaText(selectedVariant);
      });
      continue;
    }
    row.slice(1).forEach((cell, index) => {
      if (!variants[index] || !COMPENSA_LEADING_AMOUNT_PATTERN.test(cell.text)) return;
      variants[index].risks.push({ label, amount: cell.text.match(COMPENSA_LEADING_AMOUNT_PATTERN)[0] });
    });
  }

  const findRisk = (risks, pattern) => risks.find((risk) => pattern.test(risk.label))?.amount || "";
  return variants.map((variant) => {
    const acRisk = variant.risks.find((risk) => /^AC\b/i.test(risk.label));
    return {
      insurer: "Compensa",
      label: variant.chosen ? `${variant.label} (wybrano)` : variant.label,
      amount: variant.amount,
      details: variant.risks.length > 0
        ? variant.risks.map((risk) => `${risk.label} ${risk.amount}`).join(" • ")
        : "Compensa - zakres ubezpieczenia",
      components: {
        oc: findRisk(variant.risks, /^OC\b/i),
        ac: acRisk?.amount || "",
        acVariant: acRisk ? variant.acVariant : "",
        
        vehicleValue: compactCompensaText(acRisk?.label.match(/SU:\s*(.+)$/i)?.[1] || ""),
        assistance: findRisk(variant.risks, /^Assistance$/i),
        nnw: findRisk(variant.risks, /^NNW$/i),
      },
      componentsList: variant.risks.slice(0, 12),
      consentRequired: variant.consentRequired,
    };
  });
}

async function readCompensaOfferSnapshot(frame) {
  return frame.evaluate(() => {
    const compact = (value) => String(value || "").replace(/[  ]/g, " ").replace(/\s+/g, " ").trim();
    const visible = (element) => element instanceof HTMLElement
      && element.getClientRects().length > 0
      && getComputedStyle(element).visibility !== "hidden";
    const headers = [...document.querySelectorAll("package-header")].filter(visible).map((header) => ({
      price: compact(header.querySelector(".tm-tpPrice")?.textContent),
      stars: header.querySelectorAll(".i-star").length,
      chosen: Boolean(header.querySelector("label.active")),
      consentRequired: Boolean(header.querySelector(".tm-tpPackIsAuthorizationCodeRequired")),
    }));
    const cells = [...document.querySelectorAll("div.item")].filter(visible).map((cell) => {
      const rect = cell.getBoundingClientRect();
      return { x: Math.round(rect.x), y: Math.round(rect.y), text: compact(cell.innerText) };
    });
    return { headers, cells };
  }).catch(() => ({ headers: [], cells: [] }));
}

export async function extractCompensaPremiums(page) {
  const premiums = [];
  for (const frame of page.frames()) {
    const framePremiums = buildCompensaPremiums(await readCompensaOfferSnapshot(frame));
    if (framePremiums.length > 0) premiums.push(...framePremiums);
  }
  return premiums;
}

 
export async function collectCompensaBlockedFields(page) {
  await waitForCompensaIdle(page, { timeout: 60000 });
  return collectBlockedFormFields(page);
}

export async function collectCompensaPremiumDebug(page) {
  return page.mainFrame().evaluate(() => {
    const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
    return {
      url: location.href,
      busy: document.body?.classList.contains("ux-dialog-open") === true,
      packageHeaders: document.querySelectorAll("package-header").length,
      gridCells: document.querySelectorAll("div.item").length,
      dialogText: compact([...document.querySelectorAll("ux-dialog")].map((element) => element.innerText).join(" ")).slice(0, 400),
      bodyText: compact(document.body?.innerText).slice(0, 3000),
    };
  }).catch(() => ({}));
}




export function createCompensaAutomation({ browserPool, pdfRetryStore }) {
  const INSURER_KEY = "compensa";
  let lastVisiblePage = null;

  async function openPage(headless) {
    return browserPool.openPage({ insurerKey: INSURER_KEY, headless });
  }

  
  
  function isVisibleBrowserActive() {
    return browserPool.isHeadless(INSURER_KEY) === false;
  }

  function isHeadlessBrowserActive() {
    return browserPool.isHeadless(INSURER_KEY) === true;
  }

  async function calculateQuoteAttempt({
    registration,
    pesel,
    vin,
    username,
    password,
    vehicleInfo,
    navigationPath,
    formFields,
    resultNavigationPath,
    onPathLearned,
    onDiagnosticsCaptured,
    onResultPathLearned,
    onInputRequested,
    extraKnownAnswerResolver,
    onProgress,
    manualStartOnly = false,
    onOverlayAction,
  }, { headless = false } = {}) {
    const page = await openPage(headless);
    page.setDefaultTimeout(12000);
    page.setDefaultNavigationTimeout(60000);
    if (!headless) {
      lastVisiblePage = page;
      await bringPageWindowToFront(page);
      await attachClientInfoOverlay(page, vehicleInfo, { onAction: onOverlayAction }).catch(() => {});
    }
    try {
      
      
      const targetFields = [
        {
          key: "pesel",
          patterns: ["regon lub pesel", "pesel lub regon", "pesel", "regon", "identyfikator klienta"],
          value: pesel,
        },
        {
          key: "registration",
          patterns: ["numer rejestracyjny", "nr rejestracyjny", "nr rej", "rejestracj", "registrationnumber", "registration-number", "registration"],
          value: registration,
        },
      ];

      onProgress({
        status: "running",
        title: headless ? "Kalkulacja Compensa działa w tle…" : "Otwieranie kalkulatora Compensy…",
        detail: "",
      });
      await page.goto(COMPENSA_URL, { waitUntil: "domcontentloaded", timeout: 60000 });
      await loginIfNeeded(page, { username, password }, onProgress);
      await waitForPortal(page, onProgress, { allowUserInteraction: !headless });

      onProgress({ status: "running", title: "Zalogowano do Compensy", detail: "" });
      await waitForUiQuiet(page, { timeout: 6000 });
      await waitForCompensaIdle(page, { timeout: 20000 });

      const knownAnswerResolver = composeKnownAnswerResolvers(
        extraKnownAnswerResolver,
        buildKnownVehicleAnswerResolver({ vin, vehicleInfo, pesel }),
      );
      let formNavigation = null;
      try {
        formNavigation = await learnOrReplayFormPath({
          page,
          savedPath: navigationPath,
          savedFields: formFields,
          fields: targetFields,
          insurerName: "Compensa",
          manualInstruction: "Logowanie jest zakończone. Kliknij „Rozpocznij nagrywanie”, wejdź w kafel „Compensa Komunikacja” i zakończ nagrywanie na oknie z polami PESEL/REGON oraz numeru rejestracyjnego.",
          onPathLearned,
          onDiagnosticsCaptured,
          onProgress,
          requestInput: onInputRequested,
          knownAnswerResolver,
          allowManual: !headless && !manualStartOnly,
          patientReplay: !manualStartOnly,
        });
      } catch (error) {
        if (!manualStartOnly) throw error;
      }
      if (manualStartOnly) {
        onProgress({
          status: "running",
          title: "Karta Compensy jest gotowa do ręcznego liczenia",
          detail: "Wpisz dane w formularzu samodzielnie.",
        });
        return [];
      }

      onProgress({ status: "running", title: "Uzupełnianie danych w Compensie…", detail: "" });
      if (!(await fillDescribedFields(page, targetFields, formNavigation?.formFields))) {
        throw new Error("Nie znaleziono pól PESEL i numeru rejestracyjnego w kalkulatorze Compensy.");
      }

      let premiums = [];
      const resultsReady = async () => {
        
        
        if (!(await waitForCompensaIdle(page, { timeout: 45000 }))) return false;
        premiums = await extractCompensaPremiums(page);
        return premiums.length > 0;
      };
      await learnOrReplayActionPath({
        page,
        savedPath: resultNavigationPath,
        isComplete: resultsReady,
        requestInput: onInputRequested,
        knownAnswerResolver,
        collectFields: collectCompensaBlockedFields,
        inputMaxRounds: 10,
        async beforeStep() {
          await waitForCompensaIdle(page, { timeout: 60000 });
        },
        stageTitle: "od formularza do składek Compensy",
        manualInstruction: "PESEL i numer rejestracyjny są już wpisane. Klikaj dalej jak zwykle (Weryfikacja UFG, Przejdź do oferty), aż pojawią się składki. Aplikacja zapamięta te czynności. Nie klikaj „Zatwierdź”.",
        retryInstruction: "Zapamiętana droga do wyników Compensy przestała pasować. Klikaj ręcznie aż pojawią się składki, aby zapisać nową sekwencję.",
        onPathLearned: onResultPathLearned,
        onProgress,
        allowManual: !headless,
      });
      await waitForCompensaIdle(page, { timeout: 60000 });
      premiums = await extractCompensaPremiums(page);
      if (premiums.length === 0) {
        try {
          await onDiagnosticsCaptured?.(await collectCompensaPremiumDebug(page));
        } catch {
        }
        throw new Error("Nie udało się odczytać składek z kalkulatora Compensy w ciągu 3 minut.");
      }
      return premiums;
    } finally {
      if (headless) await page.close().catch(() => {});
    }
  }

  async function calculateQuote(options) {
    const formPathReady = options?.runInBackground === true
      && Array.isArray(options?.navigationPath)
      && options.navigationPath.length > 0;
    const resultPathReady = Array.isArray(options?.resultNavigationPath)
      && options.resultNavigationPath.length > 0;
    
    
    
    const canRunInBackground = formPathReady && resultPathReady;
    if (formPathReady && !resultPathReady) {
      options?.onProgress?.({
        status: "running",
        title: "Nagranie trasy Compensy jest niedokończone…",
        detail: "W otwartym oknie doklikaj do składek. Aplikacja zapisze drogę do wyników i następne kalkulacje pójdą w tle.",
      });
    }
    const visibleBrowserActive = isVisibleBrowserActive();
    if (!canRunInBackground || visibleBrowserActive) return calculateQuoteAttempt(options, { headless: false });
    return calculateQuoteAttempt(options, { headless: true });
  }

  
  
  
  async function downloadCompensaPortfolioPdfs(page, {
    bearer,
    policies,
    pdfDirectory,
    pdfPolicyLimit,
    skipPdfPolicyNumbers = [],
    onProgress,
  }) {
    const targets = prepareCompensaPortfolioPdfEntries({
      policies,
      skipPolicyNumbers: skipPdfPolicyNumbers,
    });
    if (targets.length === 0) {
      return {
        attempted: 0,
        pdfSaved: 0,
        pdfRemaining: 0,
        pdfRetry: null,
        pdfRetryIdentities: [],
      };
    }

    const planned = Math.min(targets.length, Number(pdfPolicyLimit) || targets.length);
    let processed = 0;
    let batch;
    try {
      batch = await runPortfolioPdfBatch({
        retryStore: pdfRetryStore,
        insurer: "compensa",
        pdfDirectory,
        entries: targets,
        limit: planned,
        concurrency: 1,
        async fetchDocument({ entry }) {
          const result = await downloadCompensaRenewalPdfBase64(page, {
            bearer,
            renewalId: entry.policy?.policyId,
          });
          processed += 1;
          if (processed % 5 === 0 || processed === planned) {
            onProgress({
              status: "running",
              title: `Pobieranie PDF-ów… (${Math.min(processed, planned)}/${planned})`,
              detail: "",
            });
          }
          await delay(150);
          if (result?.type === "success" && typeof result.base64 === "string") {
            entry.policy.pdfAvailable = true;
            return { type: "success", bytes: Buffer.from(result.base64, "base64") };
          }
          if (result?.type === "document_failure" || result?.type === "unsupported" || result?.type === "global_failure") {
            return { type: result.type, code: result.code };
          }
          return { type: "global_failure", code: "unknown" };
        },
      });
    } catch {
      return {
        attempted: processed,
        pdfSaved: 0,
        pdfRemaining: Math.max(0, targets.length - processed),
        pdfRetry: { circuitOpen: true, circuitCode: "store_failed", manualRetryAvailable: false },
        pdfRetryIdentities: [],
      };
    }
    const pdfRetry = batch.publicResult;
    
    
    console.error(
      `Compensa PDF partia: rozmiar=${processed} attempted=${pdfRetry.attempted} ok=${pdfRetry.succeeded}`
      + ` failed=${pdfRetry.failed} unsupported=${pdfRetry.unsupported}`
      + ` obwod=${pdfRetry.circuitOpen ? pdfRetry.circuitCode || "unknown" : "-"} pozostalo=${pdfRetry.remaining}`,
    );
    onProgress({
      status: "running",
      title: pdfRetry.circuitOpen ? "Pobieranie PDF-ów przerwane" : "PDF-y pobrane",
      detail: pdfRetry.circuitOpen
        ? "Faza dokumentów została bezpiecznie zatrzymana; polisy nadal zostaną zaimportowane."
        : `Zapisano ${pdfRetry.succeeded} z ${pdfRetry.attempted} rozpoczętych plików.`,
    });
    return {
      attempted: pdfRetry.attempted,
      pdfSaved: pdfRetry.succeeded,
      pdfRemaining: pdfRetry.remaining,
      pdfRetry,
      pdfRetryIdentities: batch.privateResult.manualRetryIdentities,
    };
  }

  async function importPortfolioAttempt({
    username,
    password,
    sinceDate = "",
    downloadPdfs = false,
    pdfDirectory = "",
    pdfPolicyLimit = 50,
    skipPdfPolicyNumbers = [],
    onCompletion,
    onProgress,
  }, { headless = false } = {}) {
    const page = await openPage(headless);
    page.setDefaultTimeout(12000);
    page.setDefaultNavigationTimeout(60000);
    if (!headless) {
      lastVisiblePage = page;
      await bringPageWindowToFront(page);
    }
    try {
      onProgress({
        status: "running",
        title: headless ? "Import polis Compensa działa w tle…" : "Otwieranie portalu Compensy…",
        detail: "",
      });
      await page.goto(COMPENSA_URL, { waitUntil: "domcontentloaded", timeout: 60000 });
      await loginIfNeeded(page, { username, password }, onProgress);
      await waitForPortal(page, onProgress, { allowUserInteraction: !headless });

      onProgress({ status: "running", title: "Odczytywanie listy polis…", detail: "" });
      const { bearer, template } = await captureCompensaApiContext(page);
      if (!bearer || !template) {
        throw manualInteractionRequired("Nie udało się odczytać listy polis Compensy. Otwórz okno i wejdź w Narzędzia > Kontynuacje.");
      }
      const items = await fetchCompensaRenewals(page, {
        bearer,
        template,
        onProgress({ collected, total }) {
          onProgress({
            status: "running",
            title: "Pobieranie polis z Compensy…",
            detail: total ? `${collected} z ${total}` : "",
          });
        },
      });
      
      
      
      const mappingCollector = createPortfolioMappingCollector(items.length);
      const policies = mapCompensaRenewals(items, { mappingCollector });
      const mappingReport = assertPortfolioMappingComplete(mappingCollector.createReport());
      let pdfOutcome = {
        pdfSaved: 0,
        pdfRemaining: 0,
        pdfRetry: null,
        pdfRetryIdentities: [],
      };
      if (downloadPdfs && pdfDirectory && policies.length > 0) {
        pdfOutcome = await downloadCompensaPortfolioPdfs(page, {
          bearer,
          policies,
          pdfDirectory,
          pdfPolicyLimit,
          skipPdfPolicyNumbers,
          onProgress,
        });
      }
      
      
      
      const { emittedPolicies, emissionReport } = createPortfolioEmissionResult(policies, {
        sinceDate,
        sourceAlreadyFilteredToCheckpoint: true,
      });
      onCompletion?.({
        ...pdfOutcome,
        freshCount: emissionReport.freshCount,
        mappingReport,
        emissionReport,
      });
      return emittedPolicies;
    } finally {
      if (headless) await page.close().catch(() => {});
    }
  }

  async function importPortfolio(options) {
    const startHeadless = isVisibleBrowserActive() ? false : 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: "Dokończ logowanie w oknie, które się otworzy.",
      });
      return importPortfolioAttempt(options, { headless: false });
    }
  }

  async function focusWindow() {
    if (!lastVisiblePage || lastVisiblePage.isClosed?.()) return false;
    return bringPageWindowToFront(lastVisiblePage).then(() => true).catch(() => false);
  }

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

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