import puppeteer from "puppeteer-core";
import {
  bringPageWindowToFront,
  buildKnownVehicleAnswerResolver,
  composeKnownAnswerResolvers,
  fillDescribedFields,
  learnOrReplayActionPath,
  learnOrReplayFormPath,
  normalizeBlockedFieldMetadata,
  waitForUiQuiet,
} from "./etrzxtbsmrmtpfpyetfs.mjs";
import { attachClientInfoOverlay } from "./hkwnbsimckiekhsfxcto.mjs";
import {
  fetchWienerProductionItems,
  downloadWienerPolicyPdfBase64,
  mapWienerProductionItems,
} from "./qkgrpisayfvnamlblogx.mjs";
import { runPortfolioPdfBatch } from "./ylivitleiyuglkyjgkec.mjs";
import {
  countUnskippedPortfolioPdfSelectors,
  createPortfolioPdfPolicySelectorSet,
  portfolioPdfPolicySelectorMatches,
} from "./abywezhrsiqkqfhujfvq.mjs";
import {
  assertPortfolioMappingComplete,
  createPortfolioEmissionResult,
  createPortfolioMappingCollector,
} from "./fgroazrhswhewnaqxvgl.mjs";

const WIENER_PRODUCTION_URL = "https://wienet.pl/#/production";

const WIENER_URL = "https://wienet.pl/";
const PORTAL_TIMEOUT_MS = 3 * 60 * 1000;
const LOGIN_SELECTOR = "#login, input[name='login'], input[autocomplete='username']";
const PASSWORD_SELECTOR = "#password, input[type='password']";

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

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

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

const PESEL_CENTURY_OFFSETS = [
  { offset: 80, century: 1800 },
  { offset: 0, century: 1900 },
  { offset: 20, century: 2000 },
  { offset: 40, century: 2100 },
  { offset: 60, century: 2200 },
];

export function peselToBirthDate(pesel) {
  const digits = String(pesel || "").replace(/\D/g, "");
  if (digits.length !== 11) return null;
  const yearInCentury = Number(digits.slice(0, 2));
  const monthCode = Number(digits.slice(2, 4));
  const day = Number(digits.slice(4, 6));
  const centuryEntry = PESEL_CENTURY_OFFSETS
    .find(({ offset }) => monthCode >= offset + 1 && monthCode <= offset + 12);
  if (!centuryEntry) return null;
  const month = monthCode - centuryEntry.offset;
  const year = centuryEntry.century + yearInCentury;
  const date = new Date(Date.UTC(year, month - 1, day));
  if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) return null;
  const pad = (value) => String(value).padStart(2, "0");
  return `${year}-${pad(month)}-${pad(day)}`;
}

export function isWienerLoginUrl(value) {
  try {
    const url = new URL(value);
    return url.hostname.toLowerCase() === "wienet.pl"
      && (url.hash === "#/login" || url.hash.startsWith("#/login?"));
  } catch {
    return false;
  }
}

function isAuthenticatedWienerPortalUrl(value) {
  try {
    const url = new URL(value);
    return url.hostname.toLowerCase() === "wienet.pl"
      && !isWienerLoginUrl(url.href);
  } 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(/[\u0300-\u036f]/g, "")
            .replace(/\s+/g, " ")
            .trim()
            .toLowerCase(),
          visible: candidate instanceof HTMLElement
            && candidate.getClientRects().length > 0
            && getComputedStyle(candidate).visibility !== "hidden",
        }));
        if (state.visible && state.text === "zaloguj sie") {
          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 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")
  ) {
    return { type: "error", message: "Wiener odrzucił login lub hasło." };
  }
  if (
    pageText.includes("kod jednorazowy")
    || pageText.includes("kod weryfikacyjny")
    || pageText.includes("uwierzytelnianie dwuskladnikowe")
  ) {
    return {
      type: "action",
      title: "Potwierdź logowanie do Wiener",
      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: "Wiener 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 Wiener.");
    }
    onProgress({
      status: "running",
      title: "Logowanie do Wiener…",
      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 (isAuthenticatedWienerPortalUrl(page.url()) && !(await frameHasSelector(page, LOGIN_SELECTOR))) return;
    if (await frameHasSelector(page, LOGIN_SELECTOR) && await frameHasSelector(page, PASSWORD_SELECTOR)) {
      if (!(await fillLoginForm(page, credentials, onProgress))) {
        throw new Error("Nie udało się obsłużyć formularza logowania Wiener.");
      }
      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 Wiener.");
}

async function waitForPortal(page, onProgress, { allowUserInteraction = true } = {}) {
  const startedAt = Date.now();
  let lastActionTitle = "";
  while (Date.now() - startedAt < PORTAL_TIMEOUT_MS) {
    if (isAuthenticatedWienerPortalUrl(page.url()) && !(await frameHasSelector(page, LOGIN_SELECTOR))) {
      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 Wiener nie otworzył się w ciągu 3 minut.");
}

async function setSelectOptionByText(frame, selector, wantedText) {
  return frame.evaluate(({ targetSelector, wanted }) => {
    const normalize = (value) => String(value || "")
      .normalize("NFD")
      .replace(/[̀-ͯ]/g, "")
      .replace(/\s+/g, " ")
      .trim()
      .toLowerCase();
    const element = document.querySelector(targetSelector);
    if (!(element instanceof HTMLSelectElement)) return false;
    const target = normalize(wanted);
    const option = [...element.options].find((candidate) => normalize(candidate.textContent) === target)
      || [...element.options].find((candidate) => normalize(candidate.textContent).includes(target));
    if (!option) return false;
    element.value = option.value;
    element.dispatchEvent(new Event("input", { bubbles: true }));
    element.dispatchEvent(new Event("change", { bubbles: true }));
    element.dispatchEvent(new Event("blur", { bubbles: true }));
    return true;
  }, { targetSelector: selector, wanted: wantedText }).catch(() => false);
}

export async function ensureWienerVehicleUsage(page) {
  const frame = page.mainFrame();
  const found = await frame.evaluate(() => {
    const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
    const normalize = (value) => compact(value)
      .normalize("NFD")
      .replace(/[̀-ͯ]/g, "")
      .toLowerCase();
    const visibleElement = (element) => element instanceof HTMLElement
      && element.getClientRects().length > 0
      && getComputedStyle(element).visibility !== "hidden";
    const pathSelector = (element) => {
      const path = [];
      let current = element;
      for (let depth = 0; current instanceof Element && depth < 9; depth += 1, current = current.parentElement) {
        let part = current.tagName.toLowerCase();
        if (current.id && current.id !== "undefined") {
          path.unshift(`#${CSS.escape(current.id)}`);
          break;
        }
        const siblings = current.parentElement
          ? [...current.parentElement.children].filter((sibling) => sibling.tagName === current.tagName)
          : [];
        if (siblings.length > 1) part += `:nth-of-type(${siblings.indexOf(current) + 1})`;
        path.unshift(part);
      }
      return path.join(" > ");
    };
    let holder = null;
    for (const element of document.querySelectorAll("label, span, div, legend")) {
      const ownText = normalize([...element.childNodes]
        .filter((node) => node.nodeType === 3)
        .map((node) => node.textContent)
        .join(" "));
      if (!ownText.includes("wykorzystanie pojazdu") || ownText.length > 90) continue;
      holder = element;
      break;
    }
    if (!holder) return null;
    let select = null;
    let container = holder;
    for (let depth = 0; depth < 5 && container.parentElement; depth += 1) {
      container = container.parentElement;
      const selects = [...container.querySelectorAll("select")].filter((candidate) => !candidate.disabled);
      if (selects.length > 0) {
        select = selects[0];
        break;
      }
    }
    if (!select) return null;
    if (!visibleElement(select) && !visibleElement(select.closest(".bootstrap-select"))) return null;
    const selectedText = compact(select.selectedOptions?.[0]?.textContent);
    const empty = !select.value || /^(wybierz|select)$/i.test(selectedText);
    return { selector: pathSelector(select), empty };
  }).catch(() => null);
  if (!found) return "absent";
  if (!found.empty) return "already";
  return (await setSelectOptionByText(frame, found.selector, "Użytek własny")) ? "filled" : "absent";
}

export async function collectWienerBlockedFields(page) {
  
  
  
  let lastResult = { title: "", fields: [] };
  for (let attempt = 0; attempt < 3; attempt += 1) {
    lastResult = await collectWienerBlockedFieldsOnce(page);
    const versionMentioned = /wersj/i.test(String(lastResult.title || ""));
    const versionCollected = (lastResult.fields || []).some((field) => /wersj/i.test(String(field.label || "")));
    if (!versionMentioned || versionCollected) return lastResult;
    await delay(1300);
  }
  return lastResult;
}




export function normalizeWienerFieldLabel(value) {
  const compact = String(value || "").replace(/\s+/g, " ").trim();
  const technicalName = compact.replace(/[:*]+$/g, "").trim().toLowerCase();
  if (technicalName === "datetimes" || technicalName === "datetime") return "Data urodzenia";
  return compact.replace(/\bdatetimes?\b/gi, "Data urodzenia");
}

async function collectWienerBlockedFieldsOnce(page) {
  const fields = [];
  let screenTitle = "";
  for (const frame of page.frames().slice(0, 10)) {
    let frameHint = "";
    try {
      const frameUrl = new URL(frame.url());
      frameHint = `${frameUrl.hostname}${frameUrl.pathname}`.slice(0, 220);
    } catch {
    }
    try {
      const frameResult = await frame.evaluate((currentFrameHint) => {
        const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
        const visible = (element) => element instanceof HTMLElement
          && element.getClientRects().length > 0
          && getComputedStyle(element).visibility !== "hidden";
        const pathSelector = (element) => {
          const path = [];
          let current = element;
          for (let depth = 0; current instanceof Element && depth < 9; depth += 1, current = current.parentElement) {
            let part = current.tagName.toLowerCase();
            if (current.id && current.id !== "undefined") {
              path.unshift(`#${CSS.escape(current.id)}`);
              break;
            }
            const siblings = current.parentElement
              ? [...current.parentElement.children].filter((sibling) => sibling.tagName === current.tagName)
              : [];
            if (siblings.length > 1) part += `:nth-of-type(${siblings.indexOf(current) + 1})`;
            path.unshift(part);
          }
          return path.join(" > ");
        };
        const labelForRow = (element) => {
          const row = element.closest(".row, .form-group");
          const labelElement = row?.querySelector("label");
          const labelText = compact(labelElement?.innerText);
          if (labelText) return labelText.slice(0, 120);
          return compact(row?.innerText).slice(0, 120);
        };
        const stickyErrors = [...document.querySelectorAll("[class*='sticky']")]
          .map((element) => compact(element.innerText))
          .find((text) => /błędy formularza|bledy formularza/i.test(text)) || "";
        const results = [];
        let blockedDisabledVersion = false;
        for (const element of document.querySelectorAll("select.is-invalid, input.is-invalid, textarea.is-invalid")) {
          if (!(element instanceof HTMLElement)) continue;
          if (element.disabled) {
            
            
            if (element.tagName.toLowerCase() === "select" && /wersj/i.test(labelForRow(element))) {
              blockedDisabledVersion = true;
            }
            continue;
          }
          if (!visible(element) && !visible(element.closest(".bootstrap-select"))) continue;
          const tag = element.tagName.toLowerCase();
          if (tag === "select") {
            results.push({
              kind: "select",
              selector: pathSelector(element),
              label: labelForRow(element),
              options: [...element.options].slice(0, 60)
                .filter((option) => !/^(wybierz|select)$/i.test(compact(option.textContent)))
                .map((option) => ({
                  value: option.value || "",
                  text: compact(option.textContent).slice(0, 120),
                  selector: "",
                })),
              currentValue: "",
              invalid: true,
              required: true,
              tag: "select",
              type: "select",
            });
          } else {
            const inputType = (element.getAttribute("type") || "text").toLowerCase();
            if (inputType === "radio" || inputType === "checkbox") continue;
            results.push({
              kind: inputType === "number" ? "number" : "text",
              selector: pathSelector(element),
              label: labelForRow(element),
              options: [],
              currentValue: compact(element.value).slice(0, 120),
              invalid: true,
              required: true,
              tag,
              type: inputType,
            });
          }
          if (results.length >= 15) break;
        }
        
        
        
        
        const stemsOf = (value) => compact(value)
          .normalize("NFD")
          .replace(/[̀-ͯ]/g, "")
          .toLowerCase()
          .split(/[^a-z0-9]+/)
          .filter((token) => token.length >= 4)
          .map((token) => token.slice(0, 4));
        const errorPhrases = stickyErrors
          ? [...stickyErrors.matchAll(/prosz[eę][^]*?(?=prosz[eę]|$)/gi)].map((match) => match[0])
          : [];
        if (errorPhrases.length > 0) {
          for (const element of document.querySelectorAll("select, input, textarea")) {
            if (!(element instanceof HTMLElement) || element.disabled) continue;
            const tag = element.tagName.toLowerCase();
            if (tag === "input" && /^(checkbox|radio|hidden)$/i.test(element.getAttribute("type") || "text")) continue;
            if (!visible(element) && !visible(element.closest(".bootstrap-select"))) continue;
            const selector = pathSelector(element);
            if (results.some((entry) => entry.selector === selector)) continue;
            const label = labelForRow(element);
            if (!label) continue;
            const labelStems = stemsOf(label).slice(0, 4);
            if (labelStems.length === 0 || labelStems.length > 3) continue;
            const matchedPhrase = errorPhrases.find((candidate) => {
              const phraseStems = new Set(stemsOf(candidate));
              return labelStems.every((token) => phraseStems.has(token));
            });
            if (!matchedPhrase) continue;
            if (tag === "select") {
              const chosenText = compact(element.selectedOptions?.[0]?.textContent);
              if (chosenText && !/^(wybierz|select)$/i.test(chosenText)) continue;
              const options = [...element.options].slice(0, 60)
                .filter((option) => !/^(wybierz|select)$/i.test(compact(option.textContent)))
                .map((option) => ({ value: option.value || "", text: compact(option.textContent).slice(0, 120), selector: "" }));
              if (options.length === 0) continue;
              results.push({
                kind: "select",
                selector,
                label,
                options,
                currentValue: "",
                invalid: true,
                required: true,
                tag: "select",
                type: "select",
              });
            } else {
              if (compact(element.value)) continue;
              const inputType = (element.getAttribute("type") || "text").toLowerCase();
              results.push({
                kind: inputType === "number" ? "number" : "text",
                selector,
                label,
                options: [],
                currentValue: compact(element.value).slice(0, 120),
                invalid: true,
                required: true,
                tag,
                type: inputType,
              });
            }
            if (results.length >= 15) break;
          }
        }
        
        
        
        
        const supplemental = [];
        const versionEntry = results.find((entry) => entry.kind === "select" && /wersj/i.test(entry.label));
        
        
        
        const versionNeedsHelp = Boolean(versionEntry) || blockedDisabledVersion || /wersj/i.test(stickyErrors);
        if (versionNeedsHelp) {
          let scope = document;
          if (versionEntry) {
            try {
              scope = document.querySelector(versionEntry.selector)?.closest("form") || document;
            } catch {
            }
          }
          for (const pattern of [/\bmark[aię]/i, /\bmodel/i]) {
            for (const element of scope.querySelectorAll("select")) {
              if (!(element instanceof HTMLSelectElement) || element.disabled) continue;
              if (element.classList.contains("is-invalid")) continue;
              if (!visible(element) && !visible(element.closest(".bootstrap-select"))) continue;
              const label = labelForRow(element);
              if (!pattern.test(label)) continue;
              const selector = pathSelector(element);
              if (results.some((entry) => entry.selector === selector)
                || supplemental.some((entry) => entry.selector === selector)) continue;
              const chosen = element.selectedOptions?.[0];
              const chosenText = compact(chosen?.textContent);
              supplemental.push({
                kind: "select",
                selector,
                label,
                options: [...element.options].slice(0, 60)
                  .filter((option) => !/^(wybierz|select)$/i.test(compact(option.textContent)))
                  .map((option) => ({ value: option.value || "", text: compact(option.textContent).slice(0, 120), selector: "" })),
                currentValue: chosenText && !/^(wybierz|select)$/i.test(chosenText) ? (chosen.value || "") : "",
                invalid: false,
                required: false,
                tag: "select",
                type: "select",
                
                
                keepForUser: true,
              });
              break;
            }
          }
        }
        return {
          heading: stickyErrors.replace(/.*błędy formularza/i, "Błędy formularza").slice(0, 140),
          fields: results,
          supplemental,
        };
      }, frameHint);
      if (!screenTitle && frameResult?.heading) {
        screenTitle = normalizeWienerFieldLabel(frameResult.heading);
      }
      const frameFields = [...(frameResult?.fields || [])]
        .map((field) => normalizeBlockedFieldMetadata({
          ...field,
          label: normalizeWienerFieldLabel(field.label),
        }));
      const supplementalFields = (frameResult?.supplemental || [])
        .map((field) => normalizeBlockedFieldMetadata({
          ...field,
          label: normalizeWienerFieldLabel(field.label),
        }));
      if (supplementalFields.length > 0) {
        const versionIndex = frameFields.findIndex((field) => field.kind === "select" && /wersj/i.test(field.label));
        frameFields.splice(versionIndex < 0 ? 0 : versionIndex, 0, ...supplementalFields);
      }
      for (const field of frameFields) fields.push({ ...field, frameHint });
    } catch {
    }
  }
  if (fields.length === 0) {
    const mainFrame = page.mainFrame();
    let mainFrameHint = "";
    try {
      const frameUrl = new URL(mainFrame.url());
      mainFrameHint = `${frameUrl.hostname}${frameUrl.pathname}`.slice(0, 220);
    } catch {
    }
    try {
      const missingNames = await mainFrame.evaluate(() => {
        const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
        const names = new Set();
        for (const element of document.querySelectorAll("li, p, span, div")) {
          if (element.children.length > 1) continue;
          const text = compact(element.innerText);
          if (text.length < 12 || text.length > 140) continue;
          const match = text.match(/^prosz[eę] (?:dokona[cć] wyboru w polu|uzupe[lł]ni[cć] pole|wybra[cć] w polu)\s+(.{2,80})$/i);
          if (match) names.add(compact(match[1]));
        }
        return [...names].slice(0, 10);
      });
      if (missingNames.length > 0) {
        const wentBack = await mainFrame.evaluate(() => {
          const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
          const tab = [...document.querySelectorAll("a, button, [role='tab'], div, span")]
            .filter((element) => element instanceof HTMLElement
              && element.getClientRects().length > 0
              && compact(element.innerText) === "Dane podstawowe");
          if (tab.length === 0) return false;
          tab[0].click();
          return true;
        });
        const recoveryDeadline = Date.now() + (wentBack ? 8000 : 2500);
        let recovered = [];
        while (Date.now() < recoveryDeadline && recovered.length === 0) {
          await delay(500);
          recovered = await mainFrame.evaluate((wantedNames) => {
            const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
            const normalize = (value) => compact(value)
              .normalize("NFD")
              .replace(/[̀-ͯ]/g, "")
              .toLowerCase();
            const pathSelector = (element) => {
              const path = [];
              let current = element;
              for (let depth = 0; current instanceof Element && depth < 9; depth += 1, current = current.parentElement) {
                let part = current.tagName.toLowerCase();
                if (current.id && current.id !== "undefined") {
                  path.unshift(`#${CSS.escape(current.id)}`);
                  break;
                }
                const siblings = current.parentElement
                  ? [...current.parentElement.children].filter((sibling) => sibling.tagName === current.tagName)
                  : [];
                if (siblings.length > 1) part += `:nth-of-type(${siblings.indexOf(current) + 1})`;
                path.unshift(part);
              }
              return path.join(" > ");
            };
            const controlSelector = "select, input:not([type='hidden']):not([type='radio']):not([type='checkbox'])";
            const visibleElement = (element) => element instanceof HTMLElement
              && element.getClientRects().length > 0
              && getComputedStyle(element).visibility !== "hidden";
            const results = [];
            for (const wantedName of wantedNames) {
              const wanted = normalize(wantedName);
              let holder = null;
              let control = [...document.querySelectorAll(controlSelector)].find((candidate) => {
                if (candidate.disabled || candidate.readOnly) return false;
                const technicalNames = [
                  candidate.getAttribute("name"),
                  candidate.id,
                  candidate.getAttribute("aria-label"),
                  candidate.getAttribute("placeholder"),
                ].map(normalize).filter(Boolean);
                return technicalNames.some((name) => name === wanted || name.includes(wanted));
              }) || null;
              for (const element of document.querySelectorAll("label, span, div, legend, th, td")) {
                const ownText = normalize([...element.childNodes]
                  .filter((node) => node.nodeType === 3)
                  .map((node) => node.textContent)
                  .join(" "));
                if (!ownText.includes(wanted) || ownText.length > 90) continue;
                holder = element;
                break;
              }
              if (!control && holder) {
                let container = holder;
                for (let depth = 0; depth < 5 && container.parentElement; depth += 1) {
                  container = container.parentElement;
                  const controls = [...container.querySelectorAll(controlSelector)]
                    .filter((candidate) => !candidate.disabled && !candidate.readOnly);
                  if (controls.length === 1) {
                    control = controls[0];
                    break;
                  }
                  if (controls.length > 1) {
                    control = controls[0];
                    break;
                  }
                }
              }
              if (!control) continue;
              if (!visibleElement(control) && !visibleElement(control.closest(".bootstrap-select"))) continue;
              if (control.tagName === "SELECT") {
                results.push({
                  kind: "select",
                  selector: pathSelector(control),
                  label: wantedName,
                  options: [...control.options].slice(0, 60)
                    .filter((option) => !/^(wybierz|select)$/i.test(compact(option.textContent)))
                    .map((option) => ({ value: option.value || "", text: compact(option.textContent).slice(0, 120), selector: "" })),
                  currentValue: "",
                  invalid: true,
                  required: true,
                  tag: "select",
                  type: "select",
                });
              } else {
                results.push({
                  kind: "text",
                  selector: pathSelector(control),
                  label: wantedName,
                  options: [],
                  currentValue: compact(control.value).slice(0, 120),
                  invalid: true,
                  required: true,
                  tag: "input",
                  type: "text",
                });
              }
            }
            return results;
          }, missingNames).catch(() => []);
        }
        let recoveredForUser = 0;
        for (const field of recovered) {
          const normalizedField = normalizeBlockedFieldMetadata({
            ...field,
            label: normalizeWienerFieldLabel(field.label),
          });
          if (normalizedField.kind === "select" && /wykorzystanie pojazdu/i.test(normalizedField.label)) {
            await setSelectOptionByText(mainFrame, field.selector, "Użytek własny");
            continue;
          }
          fields.push({ ...normalizedField, frameHint: mainFrameHint });
          recoveredForUser += 1;
        }
        if (recoveredForUser > 0 && !screenTitle) {
          const displayNames = missingNames.map(normalizeWienerFieldLabel);
          screenTitle = `Błędy kalkulacji - ${displayNames.join(", ")}`.slice(0, 140);
        }
      }
    } catch {
    }
  }
  const seenSelectors = new Set();
  const unique = [];
  for (const field of fields) {
    const key = `${field.frameHint}|${field.selector}`;
    if (seenSelectors.has(key)) continue;
    seenSelectors.add(key);
    unique.push(field);
  }
  return {
    title: screenTitle || "Formularz Wiener",
    fields: unique.slice(0, 15).map((field, index) => ({ ...field, id: `pole-${index + 1}` })),
  };
}

const WIENER_AMOUNT_SOURCE = "(?:\\d{1,3}(?:[ .]\\d{3})+|\\d+)(?:,\\d{2})?\\s*zł";


const WIENER_PACKAGE_NAME_SOURCE = "Pakiet\\s+OC(?:\\s*\\+\\s*AC(?:\\s+[A-Za-ząćęłńóśźż]+)?)?|OC\\s*\\+\\s*AC(?:\\s+[A-Za-ząćęłńóśźż]+)?|AC\\s+(?:Serwis|Kosztorys)|Mini\\s*[Cc]asco";




export function parseWienerPackagePanelText(rawText) {
  const compact = (value) => String(value || "").replace(/ /g, " ").replace(/\s+/g, " ").trim();
  const text = compact(rawText);
  if (!text) return [];
  const headerPattern = new RegExp(`(${WIENER_PACKAGE_NAME_SOURCE})\\s+(${WIENER_AMOUNT_SOURCE}|---)\\s+(Wybierz|Wybrano)\\b`, "gi");
  const packages = [];
  let riskRegionStart = -1;
  for (let match; (match = headerPattern.exec(text));) {
    packages.push({
      name: compact(match[1]),
      amount: match[2] === "---" ? "" : compact(match[2]),
      chosen: /wybrano/i.test(match[3]),
      risks: [],
    });
    riskRegionStart = headerPattern.lastIndex;
  }
  if (!packages.some((entry) => entry.amount)) return [];
  const riskRegion = text.slice(riskRegionStart)
    .split(/Udział własny|Katalog dokumentów|Okres ubezpieczenia/i)[0]
    .slice(0, 1200);
  const cellPattern = new RegExp(`(${WIENER_AMOUNT_SOURCE}|---)`, "gi");
  const rows = [];
  let cursor = 0;
  for (let match; (match = cellPattern.exec(riskRegion));) {
    const label = compact(riskRegion.slice(cursor, match.index));
    if (label) rows.push({ label, cells: [] });
    if (rows.length > 0) rows[rows.length - 1].cells.push(match[1] === "---" ? "" : compact(match[1]));
    cursor = cellPattern.lastIndex;
  }
  for (const row of rows) {
    if (row.label.length > 70 || row.cells.length === 0 || row.cells.length > packages.length) continue;
    const offset = packages.length - row.cells.length;
    row.cells.forEach((cell, index) => {
      if (cell) packages[offset + index].risks.push({ label: row.label, amount: cell });
    });
  }
  const nnwMatch = text.match(new RegExp(`(?:^|\\s)NNW\\s+(${WIENER_AMOUNT_SOURCE})`, "i"));
  if (nnwMatch) {
    for (const entry of packages) {
      if (!entry.amount || entry.risks.some((risk) => /^NNW\b/i.test(risk.label))) continue;
      entry.risks.push({ label: "NNW", amount: compact(nnwMatch[1]) });
    }
  }
  const singlePayment = text.match(new RegExp(`1\\s+rata\\s+1\\s+rata\\s+(${WIENER_AMOUNT_SOURCE})`, "i"));
  const twoPayments = text.match(new RegExp(`2\\s+raty\\s+1\\s+rata\\s+(${WIENER_AMOUNT_SOURCE})\\s+2\\s+rata\\s+(${WIENER_AMOUNT_SOURCE})\\s+Składka:\\s+(${WIENER_AMOUNT_SOURCE})`, "i"));
  const paymentParts = [];
  if (singlePayment) paymentParts.push(`1 rata ${compact(singlePayment[1])}`);
  if (twoPayments) paymentParts.push(`2 raty ${compact(twoPayments[1])} + ${compact(twoPayments[2])} (łącznie ${compact(twoPayments[3])})`);
  const paymentNote = paymentParts.length > 0 ? `Płatność: ${paymentParts.join(" lub ")}` : "";
  return packages.filter((entry) => entry.amount).map((entry) => {
    const baseName = /^AC\s/i.test(entry.name) ? `OC + ${entry.name}` : entry.name;
    const findRisk = (pattern) => entry.risks.find((risk) => pattern.test(risk.label))?.amount || "";
    const componentsList = entry.risks.slice(0, 10)
      .map((risk) => ({ label: risk.label.slice(0, 40), amount: risk.amount }));
    if (entry.chosen && twoPayments) {
      componentsList.push({
        label: "2 raty",
        amount: `${compact(twoPayments[1])} + ${compact(twoPayments[2])} (łącznie ${compact(twoPayments[3])})`,
      });
    }
    const summary = entry.risks.map((risk) => `${risk.label} ${risk.amount}`).join(" • ");
    const details = [summary, entry.chosen ? paymentNote : ""].filter(Boolean).join(" • ")
      || "Wiener - zakres ubezpieczenia";
    return {
      insurer: "Wiener",
      label: entry.chosen ? `${baseName} (wybrano)` : baseName,
      amount: entry.amount,
      details: details.slice(0, 180),
      components: {
        oc: findRisk(/^OC\b/i),
        ac: findRisk(/^AC\b|\bAC$/i),
        vehicleValue: "",
        assistance: findRisk(/assistance/i),
        nnw: findRisk(/^NNW\b/i),
      },
      componentsList,
    };
  });
}

export async function extractWienerPremiums(page) {
  const structured = [];
  const generic = [];
  for (const frame of page.frames()) {
    try {
      const packagePremiums = await frame.evaluate(() => {
        const compact = (value) => String(value || "").replace(/ /g, " ").replace(/\s+/g, " ").trim();
        const amountPattern = /(\d{1,3}(?:[ . ]\d{3})*|\d+)(?:,([0-9]{2}))?\s*zł/i;
        const visible = (element) => element instanceof HTMLElement
          && element.getClientRects().length > 0
          && getComputedStyle(element).visibility !== "hidden";
        const variantRow = document.querySelector(".tm-calMotoVariantB");
        if (!variantRow || !visible(variantRow)) return [];
        const headers = [...variantRow.querySelectorAll("label.tm-header")].filter(visible);
        const packages = [];
        for (const header of headers) {
          const priceText = compact(header.querySelector(".tm-price")?.textContent);
          const match = priceText.match(amountPattern);
          if (!match) continue;
          const nameSpan = [...header.querySelectorAll("span")]
            .find((span) => !span.classList.contains("tm-price") && compact(span.textContent));
          const buttonText = compact(header.querySelector("button")?.textContent);
          packages.push({
            name: compact(nameSpan?.textContent) || `Wariant ${packages.length + 1}`,
            amount: priceText,
            chosen: /wybrano/i.test(buttonText),
            risks: [],
          });
        }
        if (packages.length === 0) return [];
        const riskRows = [...variantRow.querySelectorAll(".tm-flex")]
          .filter((row) => visible(row) && !/sticky/i.test(String(row.className)));
        for (const row of riskRows) {
          const cells = [...row.children].filter(visible).map((cell) => compact(cell.innerText));
          if (cells.length < 2) continue;
          const riskName = cells[0];
          if (!riskName || riskName.length > 70 || amountPattern.test(riskName)) continue;
          for (let packageIndex = 0; packageIndex < packages.length; packageIndex += 1) {
            const cellText = cells[packageIndex + 1] || "";
            const cellMatch = cellText.match(amountPattern);
            if (cellMatch) packages[packageIndex].risks.push({ label: riskName, amount: cellMatch[0] });
          }
        }
        return packages.map((entry) => ({
          insurer: "Wiener",
          label: entry.chosen ? `${entry.name} (wybrano)` : entry.name,
          amount: entry.amount,
          details: entry.risks.length > 0
            ? entry.risks.map((risk) => `${risk.label} ${risk.amount}`).join(" • ")
            : "Wiener - zakres ubezpieczenia",
          components: { oc: "", ac: "", vehicleValue: "", assistance: "", nnw: "" },
          componentsList: entry.risks.slice(0, 12),
        }));
      }).catch(() => []);
      if (packagePremiums.length > 0) {
        structured.push(...packagePremiums);
        continue;
      }
      const frameText = await frame.evaluate(() => String(document.body?.innerText || "").slice(0, 30000)).catch(() => "");
      const panelPremiums = parseWienerPackagePanelText(frameText);
      if (panelPremiums.length > 0) {
        structured.push(...panelPremiums);
        continue;
      }
      const framePremiums = await frame.evaluate(() => {
        const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
        const normalize = (value) => compact(value)
          .normalize("NFD")
          .replace(/[\u0300-\u036f]/g, "")
          .toLowerCase();
        const amountPattern = /(\d{1,3}(?:[ .\u00a0]\d{3})*|\d+)(?:,([0-9]{2}))?\s*(?:zł|PLN)/i;
        const visible = (element) => element instanceof HTMLElement
          && element.getClientRects().length > 0
          && getComputedStyle(element).visibility !== "hidden";
        const pageText = normalize(document.body?.innerText || "");
        if (!/(skladk|ofert|wariant|pakiet|rata|roczn|do zaplaty)/.test(pageText)) return [];
        const excludedContext = /suma ubezpieczenia|wartosc pojazdu|limit|franszyza|udzial wlasny|znizk/i;
        const premiumContext = /skladk|do zaplaty|cena|koszt|lacznie|rocznie|rata|pakiet|wariant|ofert/i;
        const candidates = [...document.querySelectorAll("[class*='price'], [class*='premium'], [class*='amount'], [class*='sklad'], strong, b, td, span, div, p, h1, h2, h3, h4")]
          .filter(visible)
          .map((element, index) => ({ element, index, text: compact(element.innerText || element.textContent) }))
          .filter(({ text }) => text.length > 0 && text.length <= 150 && amountPattern.test(text))
          .sort((left, right) => left.text.length - right.text.length)
          .slice(0, 60);
        const entries = [];
        const used = new Set();
        const usedContainers = new Set();
        const scrubNoise = (value) => compact(String(value || "")
          .replace(/\b\d+\s+kolumn[ay]?\b/gi, " ")
          .replace(/Nr kalkulacji(?:\s+PW)?:\s*\S+/gi, " ")
          .replace(/Katalog dokumentów|Karta IDD|\bOWU\b|Dane podstawowe|Dane do umowy|Wystawiona polisa|Okres ubezpieczenia|Zakres ubezpieczenia|Pokaż informacje o szkodach|Drukuj informację o [A-Za-ząćęłńóśźżĄĆĘŁŃÓŚŹŻ ]+|dostosuj zakres|Wybierz(?:\s+\d{3,4})+\s*Wybierz/gi, " "));
        for (const { element, index, text } of candidates) {
          const match = text.match(amountPattern);
          if (!match) continue;
          const numericAmount = Number(match[1].replace(/[ .\u00a0]/g, "")) + Number(match[2] || "0") / 100;
          if (!Number.isFinite(numericAmount) || numericAmount < 10 || numericAmount > 100000) continue;
          if ([...used].some((saved) => saved.contains(element) || element.contains(saved))) continue;
          let container = element;
          for (let level = 0; level < 5 && container.parentElement; level += 1) {
            const parentText = compact(container.parentElement.innerText || container.parentElement.textContent);
            if (parentText.length > 900) break;
            container = container.parentElement;
            if (/(offer|ofert|variant|wariant|package|pakiet|product|produkt|tile|card|summary|podsum)/i.test(container.className || "")) break;
          }
          if (usedContainers.has(container)) continue;
          const context = compact(container.innerText || container.textContent).slice(0, 500);
          const normalizedContext = normalize(context);
          if (excludedContext.test(normalizedContext) && !premiumContext.test(normalizedContext)) continue;
          if (/^\d+\s*rat[ay]\b/i.test(context)) continue;
          used.add(element);
          usedContainers.add(container);
          const heading = [...container.querySelectorAll("h1, h2, h3, h4, [class*='title'], [class*='name'], strong, b")]
            .find((candidate) => visible(candidate) && !amountPattern.test(compact(candidate.textContent)));
          const cleanContext = scrubNoise(context);
          let label = compact(heading?.innerText || heading?.textContent || "");
          if (/^KOM\d|^[A-Z]{2,5}\d{5,}/.test(label)) label = "";
          if (!label) label = cleanContext.replace(amountPattern, "").replace(/^[\s–—|:•-]+|[\s–—|:•-]+$/g, "").slice(0, 100);
          entries.push({
            index,
            insurer: "Wiener",
            label: label || `Wariant ${entries.length + 1}`,
            amount: match[0].replace(/PLN/i, "zł").replace(/\u00a0/g, " ").replace(/\s+/g, " ").trim(),
            details: cleanContext.slice(0, 160) || "Wiener - kalkulacja komunikacyjna",
            components: { oc: "", ac: "", vehicleValue: "", assistance: "", nnw: "" },
          });
        }
        return entries.sort((left, right) => left.index - right.index).map(({ index, ...entry }) => entry);
      });
      generic.push(...framePremiums);
    } catch {
    }
  }
  
  
  const collected = structured.length > 0 ? structured : generic;
  const unique = [];
  const seen = new Set();
  for (const premium of collected) {
    const key = `${normalizeText(premium.label)}|${premium.amount}`;
    if (seen.has(key)) continue;
    seen.add(key);
    unique.push(premium);
  }
  return unique.slice(0, 12);
}

async function collectWienerPremiumDebug(page) {
  let frameHint = "";
  try {
    frameHint = new URL(page.url()).hostname;
  } catch {
  }
  return {
    capturedAt: new Date().toISOString(),
    reason: "wiener-skladki-nieodczytane",
    frameHint,
    frameCount: page.frames().length,
  };
}

function normalizeWienerPortfolioPdfKey(value) {
  return String(value || "").replace(/[^0-9A-Za-z]/g, "").toUpperCase();
}




export function planWienerPortfolioPdfEntries(policies, {
  limit = 50,
  skipPolicyNumbers = [],
  knownPolicyNumbers = [],
} = {}) {
  const skipSet = createPortfolioPdfPolicySelectorSet(skipPolicyNumbers, {
    insurer: "wiener",
  });
  const knownSet = createPortfolioPdfPolicySelectorSet(knownPolicyNumbers, {
    insurer: "wiener",
  });
  const eligible = [];
  for (const policy of Array.isArray(policies) ? policies : []) {
    const policyNumber = normalizeWienerPortfolioPdfKey(policy?.policyNumber);
    const identitySource = { ...policy, policyNumber };
    if (!policyNumber || portfolioPdfPolicySelectorMatches(skipSet, identitySource)) continue;
    eligible.push({
      policy,
      policyNumber,
      known: portfolioPdfPolicySelectorMatches(knownSet, identitySource),
    });
  }
  eligible.sort(
    (left, right) => (
      Number(left.known) - Number(right.known)
    ),
  );
  const effectiveLimit = Number.isSafeInteger(limit) && limit > 0
    ? limit
    : eligible.length;
  const entries = eligible.slice(0, effectiveLimit);
  return {
    entries,
    eligibleCount: eligible.length,
    deferredByBusinessLimit: eligible.length - entries.length,
  };
}

export function includeWienerPortfolioPdfDeferred(publicResult, deferredCount) {
  const deferred = Number.isSafeInteger(deferredCount) && deferredCount > 0
    ? deferredCount
    : 0;
  return {
    ...publicResult,
    deferred: Number(publicResult?.deferred || 0) + deferred,
    remaining: Number(publicResult?.remaining || 0) + deferred,
    batchComplete: publicResult?.batchComplete === true && deferred === 0,
  };
}

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

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

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

  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 birthDate = peselToBirthDate(pesel);
      if (!birthDate) {
        throw new Error("Nie udało się odczytać daty urodzenia z numeru PESEL. Sprawdź numer PESEL w formularzu kalkulacji.");
      }
      const targetFields = [
        {
          key: "registration",
          patterns: ["numer rejestracyjny", "nr rejestracyjny", "nr rej", "rejestracj", "registrationnumber", "registration-number", "registration"],
          value: registration,
        },
        {
          key: "birthDate",
          patterns: ["data urodzenia", "urodzen", "birth", "date-of-birth", "dateofbirth"],
          value: birthDate,
          commitWithKeyboard: true,
        },
      ];

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

      onProgress({
        status: "running",
        title: "Zalogowano do Wiener",
        detail: "",
      });
      
      await waitForUiQuiet(page, { timeout: 6000 });
      const knownAnswerResolver = composeKnownAnswerResolvers(
        extraKnownAnswerResolver,
        buildKnownVehicleAnswerResolver({ vin, vehicleInfo, pesel }),
      );
      let formNavigation = null;
      try {
        formNavigation = await learnOrReplayFormPath({
          page,
          savedPath: navigationPath,
          savedFields: formFields,
          fields: targetFields,
          insurerName: "Wiener",
          manualInstruction: "Logowanie jest zakończone. Kliknij „Rozpocznij nagrywanie”, przejdź przez Analizę potrzeb Klienta (APK) do kalkulatora komunikacyjnego i zakończ nagrywanie na ekranie z polami numeru rejestracyjnego i daty urodzenia.",
          onPathLearned,
          onDiagnosticsCaptured,
          onProgress,
          requestInput: onInputRequested,
          knownAnswerResolver,
          allowManual: !headless && !manualStartOnly,
          patientReplay: !manualStartOnly,
        });
      } catch (error) {
        if (!manualStartOnly) throw error;
      }
      if (manualStartOnly) {
        onProgress({
          status: "running",
          title: "Karta Wiener jest gotowa do ręcznego liczenia",
          detail: "Wpisz dane w formularzu samodzielnie.",
        });
        return [];
      }

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

      let premiums = await extractWienerPremiums(page);
      const resultsReady = async () => {
        premiums = await extractWienerPremiums(page);
        return premiums.length > 0;
      };
      await learnOrReplayActionPath({
        page,
        savedPath: resultNavigationPath,
        isComplete: resultsReady,
        requestInput: onInputRequested,
        knownAnswerResolver,
        collectFields: collectWienerBlockedFields,
        inputMaxRounds: 10,
        async beforeStep(step) {
          if (/nast[eę]pny krok|wyszukaj/i.test(String(step?.text || ""))) {
            await ensureWienerVehicleUsage(page);
          }
        },
        stageTitle: "od formularza do składek Wiener",
        manualInstruction: "Numer rejestracyjny i data urodzenia są już wpisane. Klikaj dalej jak zwykle, aż pojawią się składki Wiener. Aplikacja zapamięta te czynności.",
        retryInstruction: "Zapamiętana droga do wyników Wiener przestała pasować. Klikaj ręcznie aż pojawią się składki, aby zapisać nową sekwencję.",
        onPathLearned: onResultPathLearned,
        onProgress,
        allowManual: !headless,
      });
      premiums = await extractWienerPremiums(page);
      if (premiums.length === 0) {
        try {
          await onDiagnosticsCaptured?.(await collectWienerPremiumDebug(page));
        } catch {
        }
        throw new Error("Nie udało się odczytać składek z kalkulatora Wiener 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 Wiener 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 captureWienerApiContext(page, { timeoutMs = 30000 } = {}) {
    let bearer = "";
    let template = null;
    const onRequest = (request) => {
      try {
        const requestUrl = request.url();
        if (!requestUrl.includes("wienet.pl/api/")) return;
        const auth = request.headers().authorization || request.headers().Authorization;
        if (auth && /^Bearer\s+\S/i.test(auth) && !bearer) bearer = auth;
        if (requestUrl.includes("SearchProductionInfo") && request.method() === "POST" && !template) {
          const postData = request.postData();
          if (postData) {
            try {
              const parsed = JSON.parse(postData);
              if (parsed && parsed.Filter) template = parsed;
            } catch {
            }
          }
        }
      } catch {
      }
    };
    page.on("request", onRequest);
    try {
      
      await page.goto(WIENER_PRODUCTION_URL, { waitUntil: "domcontentloaded", timeout: 60000 }).catch(() => {});
      const deadline = Date.now() + timeoutMs;
      while (Date.now() < deadline && (!bearer || !template)) {
        await delay(400);
      }
    } finally {
      page.off("request", onRequest);
    }
    return { bearer, template };
  }

  async function downloadWienerPortfolioPdfs(page, bearer, policies, { pdfDirectory, onProgress, limit = 50, skipPolicyNumbers = [], knownPolicyNumbers = [] }) {
    const selection = planWienerPortfolioPdfEntries(policies, {
      limit,
      skipPolicyNumbers,
      knownPolicyNumbers,
    });
    const targets = selection.entries;
    if (selection.eligibleCount === 0) {
      onProgress({
        status: "running",
        title: "Pobieranie PDF-ów pominięte",
        detail: "Wszystkie polisy mają już podpięte pliki.",
      });
      return {
        attempted: 0,
        saved: 0,
        remaining: 0,
        pdfRetry: {
          attempted: 0,
          succeeded: 0,
          failed: 0,
          backoff: 0,
          quarantined: 0,
          unsupported: 0,
          deferred: 0,
          remaining: 0,
          batchComplete: true,
          circuitOpen: false,
          circuitCode: null,
          manualRetryAvailable: false,
        },
        pdfRetryIdentities: [],
      };
    }
    const planned = targets.length;
    onProgress({
      status: "running",
      title: "Pobieranie PDF-ów…",
      detail: `Do pobrania w tej partii: ${planned} plików.`,
    });

    let processed = 0;
    let batch;
    try {
      batch = await runPortfolioPdfBatch({
        retryStore: pdfRetryStore,
        insurer: "wiener",
        pdfDirectory,
        entries: targets,
        limit: targets.length,
        concurrency: 1,
        fetchDocument: async ({ entry }) => {
          const result = await downloadWienerPolicyPdfBase64(page, {
            bearer,
            number: entry.policy?.policyNumber,
            policyId: 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") {
            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 {
      batch = {
        publicResult: {
          attempted: processed,
          succeeded: 0,
          failed: 0,
          backoff: 0,
          quarantined: 0,
          unsupported: 0,
          deferred: Math.max(0, targets.length - processed),
          remaining: targets.length,
          batchComplete: false,
          circuitOpen: true,
          circuitCode: "store_failed",
          manualRetryAvailable: false,
        },
        privateResult: { manualRetryIdentities: [] },
      };
    }
    const pdfRetry = includeWienerPortfolioPdfDeferred(
      batch.publicResult,
      selection.deferredByBusinessLimit,
    );
    
    
    console.error(
      `Wiener PDF partia: rozmiar=${processed} attempted=${pdfRetry.attempted} ok=${pdfRetry.succeeded}`
      + ` failed=${pdfRetry.failed} backoff=${pdfRetry.backoff} unsupported=${pdfRetry.unsupported}`
      + ` obwod=${pdfRetry.circuitOpen ? pdfRetry.circuitCode || "unknown" : "-"}`
      + ` pozostalo=${pdfRetry.remaining}`,
    );
    if (processed === 0 && pdfRetry.remaining > 0) {
      onProgress({
        status: "running",
        title: "Pobieranie PDF-ów odłożone",
        detail: pdfRetry.quarantined > 0
          ? "Część dokumentów wymaga ręcznego wznowienia."
          : "Dokumenty oczekują na termin kolejnej bezpiecznej próby.",
      });
    } else {
      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,
      saved: pdfRetry.succeeded,
      remaining: pdfRetry.remaining,
      pdfRetry,
      pdfRetryIdentities: batch.privateResult.manualRetryIdentities,
    };
  }

  
  async function importPortfolioAttempt({
    username,
    password,
    downloadPdfs = false,
    pdfDirectory = "",
    pdfPolicyLimit = 50,
    skipPdfPolicyNumbers = [],
    knownPdfPolicyNumbers = [],
    sinceDate = "",
    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 Wiener działa w tle…" : "Otwieranie portalu Wiener…",
        detail: "",
      });
      await page.goto(WIENER_URL, { waitUntil: "domcontentloaded", timeout: 60000 });
      await loginIfNeeded(page, { username, password }, onProgress);
      await waitForPortal(page, onProgress, { allowUserInteraction: !headless });

      onProgress({
        status: "running",
        title: "Zalogowano do Wiener",
        detail: "Pobieranie polis…",
      });
      const { bearer, template } = await captureWienerApiContext(page);
      if (!bearer) {
        throw new Error("Nie udało się rozpocząć pobierania polis. Spróbuj ponownie.");
      }

      
      
      
      const pdfBacklogCount = countUnskippedPortfolioPdfSelectors({
        known: knownPdfPolicyNumbers,
        skipped: skipPdfPolicyNumbers,
        insurer: "wiener",
      });
      const fetchFullForPdfBacklog = downloadPdfs === true && pdfBacklogCount > 0;
      const items = await fetchWienerProductionItems(page, {
        bearer,
        template,
        
        
        issueDateFrom: fetchFullForPdfBacklog ? "" : sinceDate,
        onProgress: ({ collected, total }) => {
          onProgress({
            status: "running",
            title: "Pobieranie polis…",
            detail: `Pobrano ${collected} z ${total} pozycji.`,
          });
        },
      });

      
      const cutoff = new Date();
      cutoff.setFullYear(cutoff.getFullYear() - 1);
      const minEndDate = `${cutoff.getFullYear()}-${String(cutoff.getMonth() + 1).padStart(2, "0")}-${String(cutoff.getDate()).padStart(2, "0")}`;
      const mappingCollector = createPortfolioMappingCollector(items.length);
      const policies = mapWienerProductionItems(items, {
        minEndDate,
        mappingCollector,
      });
      const mappingReport = assertPortfolioMappingComplete(mappingCollector.createReport());
      const backlogMode = fetchFullForPdfBacklog && Boolean(sinceDate);
      const emissionOptions = {
        sinceDate,
        sourceAlreadyFilteredToCheckpoint: Boolean(sinceDate) && !backlogMode,
        getFreshnessDates: (policy) => policy?.issueDate,
        isBacklogPolicy: (policy) => policy?.pdfAvailable === true,
      };
      const previewEmission = createPortfolioEmissionResult(policies, emissionOptions);
      if (policies.length === 0) {
        if (sinceDate) {
          onCompletion?.({
            freshCount: previewEmission.emissionReport.freshCount,
            pdfSaved: null,
            pdfRemaining: null,
            mappingReport,
            emissionReport: previewEmission.emissionReport,
          });
          return previewEmission.emittedPolicies;
        }
        throw new Error("Nie znaleziono żadnej polisy do pobrania.");
      }
      onProgress({
        status: "running",
        title: "Porządkowanie pobranych polis…",
        detail: sinceDate
          ? `Nowych polis: ${previewEmission.emissionReport.freshCount}.`
          : `Pobrano ${policies.length} polis.`,
      });

      let pdfOutcome = null;
      if (downloadPdfs && pdfDirectory) {
        pdfOutcome = await downloadWienerPortfolioPdfs(page, bearer, policies, {
          pdfDirectory,
          onProgress,
          limit: pdfPolicyLimit,
          skipPolicyNumbers: skipPdfPolicyNumbers,
          knownPolicyNumbers: knownPdfPolicyNumbers,
        });
      }
      const { emittedPolicies, emissionReport } = createPortfolioEmissionResult(
        policies,
        emissionOptions,
      );
      onCompletion?.({
        freshCount: emissionReport.freshCount,
        pdfSaved: pdfOutcome ? pdfOutcome.saved : null,
        pdfRemaining: pdfOutcome ? pdfOutcome.remaining : null,
        pdfRetry: pdfOutcome?.pdfRetry || null,
        pdfRetryIdentities: pdfOutcome?.pdfRetryIdentities || [],
        mappingReport,
        emissionReport,
      });
      return emittedPolicies;
    } finally {
      if (headless) await page.close().catch(() => {});
      else lastVisiblePage = page;
    }
  }

  
  
  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() {
    const candidates = [];
    if (lastVisiblePage && !lastVisiblePage.isClosed()) candidates.push(lastVisiblePage);
    
    
    if (browserPool.isHeadless(INSURER_KEY) === false) {
      for (const page of [...browserPool.listPages(INSURER_KEY)].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(INSURER_KEY);
  }

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