import puppeteer from "puppeteer-core";
import { appendFile, mkdir } from "node:fs/promises";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import {
  bringPageWindowToFront,
  buildKnownVehicleAnswerResolver,
  composeKnownAnswerResolvers,
  fillDescribedFields,
  learnOrReplayActionPath,
  learnOrReplayFormPath,
  normalizeBlockedFieldMetadata,
} from "./etrzxtbsmrmtpfpyetfs.mjs";
import { attachClientInfoOverlay } from "./hkwnbsimckiekhsfxcto.mjs";
import { peselToBirthDate } from "./wcjwkazzizmaexpinwryjeopvruz.mjs";
import {
  fetchMtuPortfolioFromApi,
  mapMtuApiPolicyItems,
  warmUpPolisaOnline,
} from "./duymgadbtbrrvujgbqpsldobsrpsttffkvpiesdclzken.mjs";
import {
  prepareMtuPortfolioPdfEntries,
  runMtuPortfolioPdfBatch,
} from "./pueubcpmcchdwvwiqlzg.mjs";
import {
  PORTFOLIO_MAPPING_PDF_BACKLOG_ONLY_COVERAGE,
  assertPortfolioMappingComplete,
  assertPortfolioPdfBacklogOnlyMappingReport,
  createPortfolioEmissionResult,
  createPortfolioMappingCollector,
  createPortfolioPdfBacklogOnlyEmissionResult,
  createPortfolioPdfBacklogOnlyMappingReport,
} from "./fgroazrhswhewnaqxvgl.mjs";
import {
  createPortfolioPdfTransportIdentity,
  restorePortfolioPdfTransportPolicy,
} from "./abywezhrsiqkqfhujfvq.mjs";

const MTU_URL = "https://jupiter.ergohestia.pl/";





const FOREIGN_ERGO_SESSION_HOSTS = new Set(["ihestia.ergohestia.pl", "iagent.ergohestia.pl"]);
const PORTAL_TIMEOUT_MS = 3 * 60 * 1000;
const LOGIN_INPUT_SELECTOR = "[id='ihestialogin' i]";
const PASSWORD_INPUT_SELECTOR = "[id='ihestiapassword' i], input[type='password']";

export function validateMtuCredentials(username, password) {
  const normalizedUsername = String(username || "").trim();
  const normalizedPassword = String(password || "");
  if (!normalizedUsername) throw new Error("Brakuje loginu do MTU.");
  if (!normalizedPassword) throw new Error("Brakuje zapisanego hasła do MTU.");
  if (normalizedPassword === normalizedUsername) {
    throw new Error("Zapisane hasło MTU jest identyczne z loginem. W Ustawieniach użytkownika wpisz ponownie prawidłowe hasło MTU i zapisz zmiany.");
  }
}

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

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

function getFrameHint(frame) {
  try {
    const frameUrl = new URL(frame.url());
    return `${frameUrl.hostname}${frameUrl.pathname}`.slice(0, 220);
  } catch {
    return "";
  }
}

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

function getHostname(page) {
  try {
    return new URL(page.url()).hostname.toLowerCase();
  } catch {
    return "";
  }
}

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 fillVisibleInputAndPressEnter(page, selector, value, expectedKind) {
  const input = await findVisibleElement(page, selector);
  if (!input) return false;
  try {
    const inputKind = await input.evaluate((candidate) => ({
      isInput: candidate instanceof HTMLInputElement,
      type: String(candidate.getAttribute("type") || "text").toLowerCase(),
    }));
    if (!inputKind.isInput) return false;
    if (expectedKind === "login" && inputKind.type === "password") return false;
    if (expectedKind === "password" && inputKind.type !== "password") return false;
    await input.click({ count: 3 });
    await input.press("Backspace");
    await input.type(value, { delay: 18 });
    await input.press("Enter");
    return true;
  } finally {
    await input.dispose().catch(() => {});
  }
}

async function detectAuthenticationInterruption(page) {
  const pageText = normalizeText(await getCombinedPageText(page));
  if (
    pageText.includes("nieprawidlowy login")
    || pageText.includes("nieprawidlowe haslo")
    || pageText.includes("bledny login")
    || pageText.includes("bledne haslo")
    || pageText.includes("niepoprawne dane logowania")
  ) {
    return { type: "error", message: "MTU odrzuciło login lub hasło." };
  }
  if (
    pageText.includes("kod jednorazowy")
    || pageText.includes("kod weryfikacyjny")
    || pageText.includes("uwierzytelnianie dwuskladnikowe")
    || pageText.includes("potwierdz logowanie")
  ) {
    return {
      type: "action",
      title: "Potwierdź logowanie do MTU",
      detail: "Zatwierdź logowanie w otwartym oknie (np. kodem SMS).",
    };
  }
  const captchaElement = await findVisibleElement(page, "iframe[src*='captcha'], [class*='captcha']");
  const captchaVisible = Boolean(captchaElement);
  await captchaElement?.dispose().catch(() => {});
  if (pageText.includes("captcha") || captchaVisible) {
    return {
      type: "action",
      title: "MTU wymaga potwierdzenia CAPTCHA",
      detail: "Rozwiąż CAPTCHA w otwartym oknie.",
    };
  }
  return null;
}

async function waitForPasswordOrPortal(page) {
  const deadline = Date.now() + 60000;
  while (Date.now() < deadline) {
    if (getHostname(page) === "jupiter.ergohestia.pl") return "portal";
    
    
    
    if (FOREIGN_ERGO_SESSION_HOSTS.has(getHostname(page))) return "obca-sesja";
    const interruption = await detectAuthenticationInterruption(page);
    if (interruption?.type === "error") throw new Error(interruption.message);
    const passwordInput = await findVisibleElement(page, PASSWORD_INPUT_SELECTOR);
    if (passwordInput) {
      await passwordInput.dispose().catch(() => {});
      return "password";
    }
    await delay(350);
  }
  
  
  
  console.error(`Automatyzacja mtu: po podaniu loginu brak ekranu hasła (strona: ${getHostname(page) || "nieznana"}).`);
  throw new Error("Nie udało się automatycznie zalogować do MTU. Uruchom kalkulację ponownie albo kliknij Popraw ręcznie.");
}



async function upgradeForeignErgoSessionToMtu(page, onProgress) {
  onProgress({
    status: "running",
    title: "Przełączanie na MTU…",
    detail: "Na koncie ERGO była otwarta sesja Ergo Hestii.",
  });
  const deadline = Date.now() + 60000;
  await page.goto(MTU_URL, { waitUntil: "domcontentloaded", timeout: 60000 }).catch(() => {});
  while (Date.now() < deadline) {
    if (getHostname(page) === "jupiter.ergohestia.pl") return true;
    await delay(600);
  }
  return false;
}

async function waitForMtuPortal(page, onProgress, { allowUserInteraction = true } = {}) {
  const startedAt = Date.now();
  let lastActionTitle = "";
  while (Date.now() - startedAt < PORTAL_TIMEOUT_MS) {
    if (getHostname(page) === "jupiter.ergohestia.pl") {
      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}. Praca w tle została wstrzymana - potrzebne jest widoczne 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 MTU nie otworzył się w ciągu 3 minut.");
}






async function isStablyOnMtuPortal(page) {
  if (getHostname(page) !== "jupiter.ergohestia.pl") return false;
  await delay(1800);
  return getHostname(page) === "jupiter.ergohestia.pl";
}

async function loginIfNeeded(page, credentials, onProgress, { allowUserInteraction = true } = {}) {
  if (await isStablyOnMtuPortal(page)) return;

  const loginDeadline = Date.now() + 60000;
  let loginReady = false;
  let passwordReady = false;
  while (Date.now() < loginDeadline) {
    const currentHost = getHostname(page);
    if (currentHost === "jupiter.ergohestia.pl") {
      if (await isStablyOnMtuPortal(page)) return;
      continue;
    }
    
    
    
    if (FOREIGN_ERGO_SESSION_HOSTS.has(currentHost)) {
      if (await upgradeForeignErgoSessionToMtu(page, onProgress)) {
        await waitForMtuPortal(page, onProgress, { allowUserInteraction });
        return;
      }
      throw new Error("Na koncie ERGO jest otwarta sesja Ergo Hestii i portal MTU nie chce się na nią przełączyć. Uruchom aplikację ponownie i policz MTU jeszcze raz.");
    }
    const interruption = await detectAuthenticationInterruption(page);
    if (interruption?.type === "error") throw new Error(interruption.message);
    if (interruption?.type === "action" && !allowUserInteraction) {
      throw manualInteractionRequired(`${interruption.title}. Praca w tle została wstrzymana - potrzebne jest widoczne okno Chrome.`);
    }
    if (interruption?.type === "action") {
      onProgress({ status: "action_required", title: interruption.title, detail: interruption.detail });
    }
    const passwordInput = await findVisibleElement(page, PASSWORD_INPUT_SELECTOR);
    if (passwordInput) {
      await passwordInput.dispose().catch(() => {});
      passwordReady = true;
      break;
    }
    const loginInput = await findVisibleElement(page, LOGIN_INPUT_SELECTOR);
    if (loginInput) {
      await loginInput.dispose().catch(() => {});
      loginReady = true;
      break;
    }
    await delay(350);
  }

  if (!loginReady && !passwordReady) {
    throw new Error("Nie udało się otworzyć formularza logowania MTU. Uruchom kalkulację ponownie albo kliknij Popraw ręcznie.");
  }

  if (loginReady) {
    if (!(await fillVisibleInputAndPressEnter(page, LOGIN_INPUT_SELECTOR, credentials.username, "login"))) {
      throw new Error("Nie udało się wpisać loginu MTU.");
    }
    onProgress({
      status: "running",
      title: "Logowanie do MTU…",
      detail: "",
    });
  }

  const afterLogin = await waitForPasswordOrPortal(page);
  if (afterLogin === "portal") return;
  if (afterLogin === "obca-sesja") {
    if (await upgradeForeignErgoSessionToMtu(page, onProgress)) {
      await waitForMtuPortal(page, onProgress, { allowUserInteraction });
      return;
    }
    throw new Error("Na koncie ERGO jest otwarta sesja Ergo Hestii i portal MTU nie chce się na nią przełączyć. Uruchom aplikację ponownie i policz MTU jeszcze raz.");
  }
  if (!(await fillVisibleInputAndPressEnter(page, PASSWORD_INPUT_SELECTOR, credentials.password, "password"))) {
    throw new Error("Nie udało się wpisać hasła MTU.");
  }
  onProgress({
    status: "running",
    title: "Logowanie do MTU…",
    detail: "",
  });
  await waitForMtuPortal(page, onProgress, { allowUserInteraction });
}

async function vehicleValueState(page) {
  for (const frame of page.frames().slice(0, 10)) {
    try {
      const state = await frame.evaluate(() => {
        const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
        const visible = (element) => element instanceof HTMLElement
          && element.getClientRects().length > 0;
        const labels = [...document.querySelectorAll("label")]
          .filter((label) => visible(label) && /suma ubezpieczenia/i.test(compact(label.innerText)));
        for (const label of labels) {
          const cell = label.closest(".input-flex") || label.parentElement;
          const input = cell ? [...cell.querySelectorAll("input")].find(visible) : null;
          if (!input) continue;
          return { found: true, filled: compact(input.value).length > 0 };
        }
        return { found: false, filled: false };
      });
      if (state.found) return state;
    } catch {
    }
  }
  return { found: false, filled: false };
}

async function waitForVehicleValue(page) {
  
  const deadline = Date.now() + 40000;
  while (Date.now() < deadline) {
    const state = await vehicleValueState(page);
    if (!state.found || state.filled) return;
    await delay(500);
  }
}

async function eurotaxRowsState(page) {
  for (const frame of page.frames().slice(0, 10)) {
    try {
      const state = await frame.evaluate(() => {
        const visible = (element) => element instanceof HTMLElement
          && element.getClientRects().length > 0;
        const rows = [...document.querySelectorAll("tr")]
          .filter((row) => visible(row)
            && row.querySelector("input[type='checkbox']")
            && /\b\d{5,7}\b/.test(row.innerText || ""));
        return {
          present: rows.length > 0,
          anyChecked: rows.some((row) => row.querySelector("input[type='checkbox']").checked),
        };
      });
      if (state.present) return state;
    } catch {
    }
  }
  return { present: false, anyChecked: false };
}

async function clickWyliczButton(page) {
  for (const frame of page.frames().slice(0, 10)) {
    try {
      const clicked = await frame.evaluate(() => {
        const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
        const visible = (element) => element instanceof HTMLElement
          && element.getClientRects().length > 0;
        const isWylicz = (candidate) => visible(candidate) && !candidate.disabled
          && /^\s*wylicz\s*$/i.test(candidate.innerText || "");
        const labels = [...document.querySelectorAll("label")]
          .filter((label) => visible(label) && /suma ubezpieczenia/i.test(compact(label.innerText)));
        for (const label of labels) {
          const cell = label.closest(".input-flex") || label.parentElement;
          const button = cell ? [...cell.querySelectorAll("button")].find(isWylicz) : null;
          if (button) {
            button.click();
            return true;
          }
        }
        const fallback = [...document.querySelectorAll("button")].find(isWylicz);
        if (!fallback) return false;
        fallback.click();
        return true;
      });
      if (clicked) return true;
    } catch {
    }
  }
  return false;
}

export async function mtuPrepareBeforeDalej(page, logDecision = null) {
  const log = (decision, extra) => {
    try {
      logDecision?.(`${new Date().toISOString()} ${decision} ${extra || ""}`.trim());
    } catch {
    }
  };
  const deadline = Date.now() + 8000;
  while (true) {
    const request = await collectMtuBlockedFields(page);
    if (request.fields.length > 0) {
      log("skip", `pytania=${request.fields[0]?.label || request.fields.length}`);
      return "skip";
    }
    const rows = await eurotaxRowsState(page);
    if (rows.present && !rows.anyChecked) {
      log("skip", "tabela-bez-wyboru");
      return "skip";
    }
    const value = await vehicleValueState(page);
    if (rows.present || value.found) {
      if (value.found && !value.filled) {
        if (!rows.present) {
          log("skip", "suma-pusta-bez-tabeli");
          return "skip";
        }
        const clicked = await clickWyliczButton(page);
        await waitForVehicleValue(page);
        const after = await vehicleValueState(page);
        if (after.found && !after.filled) {
          log("skip", `wylicz-bez-efektu klik=${clicked}`);
          return "skip";
        }
        log("wylicz-ok", `klik=${clicked}`);
        await delay(800);
        log("dalej", "suma=wyliczona");
        return undefined;
      }
      log("dalej", value.found ? "suma=byla" : "suma-nieznaleziona");
      return undefined;
    }
    if (Date.now() >= deadline) {
      log("dalej", "brak-markerow-mainData");
      return undefined;
    }
    await delay(500);
  }
}

export async function collectMtuBlockedFields(page) {
  for (const frame of page.frames().slice(0, 10)) {
    try {
      const result = await frame.evaluate(({ frameHint }) => {
        const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
        const normalize = (value) => compact(value)
          .normalize("NFD")
          .replace(/[̀-ͯ]/g, "")
          .toLowerCase();
        const visible = (element) => element instanceof HTMLElement
          && element.getClientRects().length > 0
          && getComputedStyle(element).visibility !== "hidden";
        const escapeAttribute = (value) => String(value || "").replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
        const stableSelector = (element) => {
          if (element.id) return `#${CSS.escape(element.id)}`;
          const tag = element.tagName.toLowerCase();
          const name = element.getAttribute("name");
          if (name) {
            const selector = `${tag}[name="${escapeAttribute(name)}"]`;
            try {
              if (document.querySelectorAll(selector).length === 1) return selector;
            } catch {
            }
          }
          const path = [];
          let current = element;
          for (let depth = 0; current instanceof Element && depth < 8; depth += 1, current = current.parentElement) {
            let part = current.tagName.toLowerCase();
            if (current.id) {
              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 placeholderPattern = /^(-?\s*wybierz\s*-?|)$/i;
        const heading = [...document.querySelectorAll("h1, h2, h3, h4")]
          .find((element) => visible(element) && compact(element.innerText).length > 2);
        const title = compact(heading?.innerText).slice(0, 140);
        const cellLabel = (element) => {
          const wrapper = element.closest(".input-wrapper");
          if (!wrapper || !wrapper.parentElement) return "";
          return compact(wrapper.parentElement.querySelector("label")?.innerText || "").slice(0, 160);
        };
        for (const select of document.querySelectorAll("select.form-control")) {
          if (!visible(select) || select.disabled) continue;
          const label = cellLabel(select);
          if (!label) continue;
          const selectedText = compact(select.selectedOptions?.[0]?.innerText || "");
          if (select.value && !placeholderPattern.test(normalize(selectedText))) continue;
          const options = [...select.options]
            .map((option) => ({ value: option.value || "", text: compact(option.innerText).slice(0, 120), selector: "" }))
            .filter((option) => option.text && !placeholderPattern.test(normalize(option.text)));
          if (options.length === 0) continue;
          return {
            title,
            fields: [{
              kind: "select",
              selector: stableSelector(select),
              label,
              options: options.slice(0, 60),
              currentValue: "",
              invalid: false,
              required: true,
              frameHint,
              tag: "select",
              type: "select",
            }],
          };
        }
        const inputCandidates = new Set([
          ...document.querySelectorAll("input.form-control, textarea.form-control"),
          ...document.querySelectorAll("input[type='text'], input[type='tel'], input[type='date'], input[type='number'], input:not([type])"),
        ]);
        for (const input of inputCandidates) {
          if (!visible(input) || input.disabled || input.readOnly) continue;
          
          
          const rawValue = compact(input.value);
          const placeholderText = compact(input.placeholder);
          const maskPattern = /^[_\s.:/-]+$/;
          const maskedEmpty = rawValue !== "" && (rawValue === placeholderText || maskPattern.test(rawValue));
          if (rawValue && !maskedEmpty) continue;
          
          
          
          const sectionLabel = () => {
            let scope = input.closest(".input-wrapper") || input;
            for (let depth = 0; depth < 4 && scope; depth += 1, scope = scope.parentElement) {
              for (const labelElement of scope.querySelectorAll?.("label") || []) {
                const owned = labelElement.htmlFor
                  ? document.getElementById(labelElement.htmlFor)
                  : labelElement.querySelector("input");
                if (owned && /^(checkbox|radio)$/i.test(owned.type || "")) continue;
                const text = compact(labelElement.innerText);
                if (text.length >= 3) return text.slice(0, 160);
              }
            }
            return "";
          };
          const label = cellLabel(input) || sectionLabel()
            || (placeholderText && !maskPattern.test(placeholderText) ? placeholderText.slice(0, 160) : "");
          if (!label) continue;
          
          
          
          
          
          const cell = input.closest(".input-wrapper")?.parentElement || input.parentElement;
          const markedInvalid = /ng-invalid/.test(String(input.className))
            || Boolean(cell && cell.querySelector(".ng-invalid, .has-error"));
          const markedRequired = input.required
            || input.getAttribute("aria-required") === "true"
            || /\*/.test(String(cell?.querySelector?.("label")?.innerText || ""));
          const noticeMentionsField = (() => {
            if (markedInvalid || markedRequired) return false;
            const reddishBackground = (element) => {
              const match = (getComputedStyle(element).backgroundColor || "")
                .match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([0-9.]+))?\)/);
              if (!match) return false;
              if (match[4] !== undefined && Number(match[4]) === 0) return false;
              return Number(match[1]) >= 150
                && Number(match[1]) - Number(match[2]) >= 30
                && Number(match[1]) - Number(match[3]) >= 30;
            };
            let noticeText = "";
            for (const element of document.querySelectorAll("body *")) {
              if (!visible(element) || element.children.length > 3) continue;
              const text = compact(element.innerText || element.textContent);
              if (text.length < 15 || text.length > 300) continue;
              const role = element.getAttribute("role") || "";
              const cls = (typeof element.className === "string" ? element.className : "").toLowerCase();
              if (role === "alert" || role === "status"
                || /(error|alert|danger|warning|notification|notice)/.test(cls)
                || reddishBackground(element)) {
                noticeText = text;
                break;
              }
            }
            if (!noticeText) return false;
            const stems = (value) => normalize(value).split(/[^a-z0-9]+/)
              .filter((token) => token.length >= 4)
              .map((token) => token.slice(0, 3));
            const labelStems = new Set(stems(label));
            return stems(noticeText).filter((token) => labelStems.has(token)).length >= 2;
          })();
          if (!markedInvalid && !markedRequired && !noticeMentionsField) continue;
          return {
            title,
            fields: [{
              kind: input.type === "number" ? "number" : "text",
              selector: stableSelector(input),
              label: label.replace(/\s*\*+\s*$/, ""),
              options: [],
              currentValue: "",
              invalid: markedInvalid || noticeMentionsField,
              required: true,
              frameHint,
              tag: input.tagName.toLowerCase(),
              type: input.type || "text",
            }],
          };
        }
        const rows = [...document.querySelectorAll("tr")]
          .filter((row) => visible(row)
            && row.querySelector("input[type='checkbox']")
            && /\b\d{5,7}\b/.test(row.innerText || ""));
        if (rows.length > 0 && !rows.some((row) => row.querySelector("input[type='checkbox']").checked)) {
          const options = rows.slice(0, 40).map((row) => {
            const text = compact(row.innerText).slice(0, 120);
            return { value: text, text, selector: stableSelector(row.querySelector("input[type='checkbox']")) };
          });
          return {
            title,
            fields: [{
              kind: "radio",
              selector: options[0].selector,
              label: "Wybierz konkretny wariant pojazdu (Eurotax)",
              options,
              currentValue: "",
              invalid: false,
              required: true,
              frameHint,
              tag: "input",
              type: "radio",
            }],
          };
        }
        return { title: "", fields: [] };
      }, { frameHint: getFrameHint(frame) });
      if (result?.fields?.length) {
        return {
          title: result.title,
          fields: [{
            ...normalizeBlockedFieldMetadata(result.fields[0]),
            id: "pole-1",
          }],
        };
      }
    } catch {
    }
  }
  return { title: "", fields: [] };
}

export async function collectMtuPortalNotices(page) {
  const collected = [];
  for (const frame of page.frames().slice(0, 10)) {
    try {
      const frameNotices = await frame.evaluate(() => {
        const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
        const visible = (element) => element instanceof HTMLElement
          && element.getClientRects().length > 0
          && getComputedStyle(element).visibility !== "hidden";
        const reddishBackground = (element) => {
          const match = (getComputedStyle(element).backgroundColor || "")
            .match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([0-9.]+))?\)/);
          if (!match) return false;
          if (match[4] !== undefined && Number(match[4]) === 0) return false;
          const red = Number(match[1]);
          const green = Number(match[2]);
          const blue = Number(match[3]);
          return red >= 150 && red - green >= 30 && red - blue >= 30;
        };
        const looksLikeNotice = (element) => {
          const role = element.getAttribute("role") || "";
          if (role === "alert" || role === "status") return true;
          const cls = (typeof element.className === "string" ? element.className : "").toLowerCase();
          if (/(error|alert|danger|warning|notification|notice)/.test(cls)) return true;
          return reddishBackground(element);
        };
        const results = [];
        for (const element of document.querySelectorAll("body *")) {
          if (!visible(element) || element.children.length > 3) continue;
          const text = compact(element.innerText || element.textContent);
          if (text.length < 15 || text.length > 300) continue;
          if (!/[a-ząćęłńóśźż]{3}/i.test(text)) continue;
          let node = element;
          let notice = false;
          for (let level = 0; level < 3 && node; level += 1, node = node.parentElement) {
            if (looksLikeNotice(node)) {
              notice = true;
              break;
            }
          }
          if (notice) results.push(text);
        }
        return results.slice(0, 6);
      });
      collected.push(...frameNotices);
    } catch {
    }
  }
  const unique = [...new Set(collected)]
    .filter((text, _, all) => !all.some((other) => other !== text && other.includes(text)));
  return unique.slice(0, 2).join(" | ").slice(0, 400);
}

export function buildMtuCommunicationPremium(riskResults = []) {
  const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
  const normalize = (value) => compact(value)
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .toLowerCase();
  const describeRisk = (raw) => {
    const lowered = normalize(raw);
    if (/^oc\b|oc posiadaczy/.test(lowered)) return { key: "oc", label: "OC" };
    if (lowered.includes("minicasco")) return { key: "minicasco", label: "Minicasco" };
    if (lowered.includes("autocasco") || /\bac\b/.test(lowered)) return { key: "ac", label: "AC" };
    if (lowered.includes("nnw")) return { key: "nnw", label: "NNW" };
    if (lowered.includes("assistance")) {
      const name = compact(raw)
        .replace(/^.*?\bassistance\b/i, "")
        .replace(/^[\s:;,./+–—-]+|[\s:;,./+–—-]+$/g, "");
      return {
        key: "assistance",
        label: name ? `Assistance (${name.slice(0, 36)})` : "Assistance",
      };
    }
    if (lowered.includes("szyb")) return { key: "szyby", label: "Szyby" };
    return null;
  };
  const componentsList = [];
  const seenRisks = new Set();
  for (const risk of Array.isArray(riskResults) ? riskResults : []) {
    const description = describeRisk(risk?.label);
    if (!description || seenRisks.has(description.key)) continue;
    const amount = compact(risk?.amount);
    if (!amount) continue;
    seenRisks.add(description.key);
    componentsList.push({
      label: `${description.label}${risk?.insuranceSum ? ` (${compact(risk.insuranceSum)})` : ""}`,
      amount,
    });
  }
  if (componentsList.length === 0) return null;
  const total = componentsList.reduce((sum, risk) => {
    const parsed = Number(String(risk.amount).replace(/\s+|zł/gi, "").replace(",", "."));
    return Number.isFinite(parsed) ? sum + parsed : sum;
  }, 0);
  const totalParts = (Math.round(total * 100) / 100).toFixed(2).split(".");
  const grouped = totalParts[0].replace(/\B(?=(\d{3})+(?!\d))/g, " ");
  const totalAmount = totalParts[1] === "00" ? `${grouped} zł` : `${grouped},${totalParts[1]} zł`;
  return {
    label: "Kalkulacja komunikacyjna",
    amount: totalAmount,
    details: componentsList.map((component) => `${component.label} ${component.amount}`).join(" • ").slice(0, 180),
    components: { oc: "", ac: "", vehicleValue: "", assistance: "", nnw: "" },
    componentsList,
  };
}

export async function extractMtuPremiums(page) {
  const collected = [];
  for (const frame of page.frames()) {
    try {
      const framePremiums = await frame.evaluate(() => {
        const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
        const amountPattern = /(\d{1,3}(?:[ .]\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 riskItems = [...document.querySelectorAll(".risk-item")].filter(visible);
        const riskResults = [];
        for (const item of riskItems) {
          const selectionControls = [
            ...item.querySelectorAll(
              'input[type="checkbox"], input[type="radio"], [role="checkbox"], [role="radio"], [aria-checked]',
            ),
          ];
          if (selectionControls.length > 0 && !selectionControls.some((control) => (
            control.checked === true || control.getAttribute("aria-checked") === "true"
          ))) {
            continue;
          }
          const priceElement = [...item.querySelectorAll(".item-price")].find(visible);
          if (!priceElement) continue;
          const rawPrice = compact(priceElement.textContent);
          if (!/^\d[\d\s]{0,9}(?:,\d{2})?$/.test(rawPrice)) continue;
          const clone = item.cloneNode(true);
          for (const priceNode of clone.querySelectorAll(".item-price")) priceNode.remove();
          const rawLabel = compact(clone.textContent || "");
          const rawInsuranceSum = rawLabel.match(
            /\bSU\s*:?\s*((?:\d{1,3}(?:[ .\u00a0]\d{3})+|\d+)(?:,\d{2})?\s*(?:zł|PLN)?)/i,
          )?.[1] || "";
          const insuranceSum = rawInsuranceSum && !/(?:zł|PLN)$/i.test(rawInsuranceSum.trim())
            ? `${rawInsuranceSum.trim()} zł`
            : rawInsuranceSum;
          const label = compact(rawLabel
            .replace(/SU\s*:?\s*[\d .\u00a0,]+\s*(?:zł|PLN)?/gi, " "))
            .slice(0, 120);
          riskResults.push({
            label: label || `Ryzyko MTU ${riskResults.length + 1}`,
            amount: `${rawPrice} zł`,
            insuranceSum: compact(insuranceSum).replace(/PLN/i, "zł"),
          });
        }
        if (riskResults.length > 0) {
          return { riskResults, premiums: [] };
        }
        const candidates = [...document.querySelectorAll("body *")]
          .filter((element) => visible(element) && element.children.length === 0)
          .filter((element) => amountPattern.test(compact(element.textContent)))
          .slice(0, 80);
        const results = [];
        for (const element of candidates) {
          const amount = compact(element.textContent).match(amountPattern)?.[0]
            ?.replace(/\s+/g, " ")
            .replace(/pln/i, "zł")
            .trim();
          if (!amount) continue;
          let context = element;
          for (let level = 0; level < 5 && context.parentElement; level += 1) {
            const parentText = compact(context.parentElement.innerText || context.parentElement.textContent);
            if (parentText.length > 1200) break;
            context = context.parentElement;
            if (/składk|wariant|pakiet|ofert|ubezpieczen|\boc\b/i.test(parentText) && parentText.length >= 20) break;
          }
          const contextText = compact(context.innerText || context.textContent);
          if (!/składk|wariant|pakiet|ofert|ubezpieczen|\boc\b/i.test(contextText)) continue;
          const heading = [...context.querySelectorAll("h1, h2, h3, h4, h5, strong, [class*='title'], [class*='name']")]
            .find((candidate) => visible(candidate) && !amountPattern.test(compact(candidate.textContent)));
          const label = compact(heading?.textContent || "").slice(0, 120);
          results.push({
            label,
            amount,
            details: contextText.replace(amount, "").slice(0, 180),
            components: { oc: "", ac: "", vehicleValue: "", assistance: "", nnw: "" },
          });
        }
        return { riskResults: [], premiums: results };
      });
      const communicationPremium = buildMtuCommunicationPremium(framePremiums?.riskResults);
      collected.push(...(communicationPremium ? [communicationPremium] : (framePremiums?.premiums || [])));
    } catch {
    }
  }
  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,
      label: premium.label || `Wariant MTU ${unique.length + 1}`,
    });
  }
  return unique.slice(0, 12);
}




function buildMtuKnownAnswerResolver({ pesel, vin, vehicleInfo, extraResolver }) {
  const vehicleResolver = buildKnownVehicleAnswerResolver({ vin, vehicleInfo, pesel });
  const isoBirthDate = peselToBirthDate(pesel) || "";
  const polishBirthDate = isoBirthDate ? isoBirthDate.split("-").reverse().join(".") : "";
  return composeKnownAnswerResolvers(
    extraResolver,
    (field) => {
      const label = String(field?.label || "").toLowerCase();
      if (polishBirthDate && /data\s+urodzenia|data\s+ur\b/.test(label)) return polishBirthDate;
      return vehicleResolver(field);
    },
  );
}

function emptyMtuPdfRetry() {
  return {
    attempted: 0,
    succeeded: 0,
    failed: 0,
    backoff: 0,
    quarantined: 0,
    unsupported: 0,
    deferred: 0,
    remaining: 0,
    batchComplete: true,
    circuitOpen: false,
    circuitCode: null,
    manualRetryAvailable: false,
  };
}

function failedMtuPdfRetry(remaining, circuitCode = "unknown", deferred = 0) {
  return {
    ...emptyMtuPdfRetry(),
    deferred: Math.max(0, deferred),
    remaining: Math.max(0, remaining),
    batchComplete: false,
    circuitOpen: true,
    circuitCode,
  };
}

export function buildMtuImportCompletion({
  freshCount = 0,
  downloadPdfs = false,
  pdfOutcome = null,
  pdfExpectedCount = freshCount,
} = {}) {
  const normalizedFreshCount = Number.isSafeInteger(freshCount) && freshCount >= 0
    ? freshCount
    : 0;
  const normalizedExpectedCount = Number.isSafeInteger(pdfExpectedCount) && pdfExpectedCount >= 0
    ? pdfExpectedCount
    : normalizedFreshCount;
  if (!downloadPdfs) {
    return {
      freshCount: normalizedFreshCount,
      pdfSaved: null,
      pdfRemaining: null,
      pdfRetry: null,
      pdfRetryIdentities: [],
    };
  }
  const hasPdfOutcome = pdfOutcome
    && typeof pdfOutcome === "object"
    && pdfOutcome.pdfRetry
    && typeof pdfOutcome.pdfRetry === "object";
  if (!hasPdfOutcome) {
    return {
      freshCount: normalizedFreshCount,
      pdfSaved: 0,
      pdfRemaining: normalizedExpectedCount,
      pdfRetry: failedMtuPdfRetry(normalizedExpectedCount, "write_failed"),
      pdfRetryIdentities: [],
    };
  }
  return {
    freshCount: normalizedFreshCount,
    pdfSaved: Number.isSafeInteger(pdfOutcome?.saved) ? pdfOutcome.saved : 0,
    pdfRemaining: Number.isSafeInteger(pdfOutcome?.remaining) ? pdfOutcome.remaining : 0,
    pdfRetry: pdfOutcome?.pdfRetry || emptyMtuPdfRetry(),
    pdfRetryIdentities: Array.isArray(pdfOutcome?.pdfRetryIdentities)
      ? pdfOutcome.pdfRetryIdentities
      : [],
  };
}

export async function runMtuPortfolioPdfPhase({
  page,
  retryStore,
  pdfDirectory,
  policies,
  onProgress = () => {},
  limit = 50,
  skipPolicyNumbers = [],
  knownPolicyNumbers = [],
} = {}) {
  if (!Number.isSafeInteger(limit) || limit <= 0) {
    throw new TypeError("limit musi byc dodatnia liczba calkowita.");
  }
  const eligible = prepareMtuPortfolioPdfEntries({
    policies,
    skipPolicyNumbers,
    knownPolicyNumbers,
  });
  const pdfDirectoryReady = typeof pdfDirectory === "string"
    && pdfDirectory.trim().length > 0
    && !pdfDirectory.includes("\0");
  if (!pdfDirectoryReady) {
    const pdfRetry = failedMtuPdfRetry(
      eligible.length,
      "write_failed",
      Math.max(0, eligible.length - Math.min(eligible.length, limit)),
    );
    console.error(
      `MTU PDF partia: brak katalogu docelowego (write_failed), pozostalo=${pdfRetry.remaining}`,
    );
    onProgress({
      status: "running",
      title: "Pobieranie PDF-ów przerwane",
      detail: "Faza dokumentów została bezpiecznie zatrzymana; polisy nadal zostaną zaimportowane.",
    });
    return {
      attempted: 0,
      saved: 0,
      remaining: pdfRetry.remaining,
      pdfRetry,
      pdfRetryIdentities: [],
    };
  }
  if (eligible.length === 0) {
    onProgress({
      status: "running",
      title: "Pobieranie PDF-ów pominięte",
      detail: "Wszystkie polisy MTU mają już podpięte pliki.",
    });
    return {
      attempted: 0,
      saved: 0,
      remaining: 0,
      pdfRetry: emptyMtuPdfRetry(),
      pdfRetryIdentities: [],
    };
  }

  const planned = Math.min(eligible.length, limit);
  onProgress({
    status: "running",
    title: "Pobieranie PDF-ów…",
    detail: `Do rozpatrzenia w tej partii: ${planned} plików.`,
  });

  const retryStoreReady = retryStore
    && typeof retryStore.getDecisions === "function"
    && typeof retryStore.recordFailures === "function"
    && typeof retryStore.recordSuccesses === "function"
    && typeof retryStore.markUnsupported === "function";
  let batch;
  if (!retryStoreReady) {
    batch = {
      publicResult: {
        ...failedMtuPdfRetry(
          eligible.length,
          "store_failed",
          Math.max(0, eligible.length - planned),
        ),
      },
      privateResult: {
        manualRetryIdentities: [],
      },
    };
  } else {
    try {
      const transportReady = Boolean(
        page
        && typeof page.evaluate === "function"
        && typeof page.goto === "function"
        && await warmUpPolisaOnline(page),
      );
      batch = await runMtuPortfolioPdfBatch({
        page: transportReady ? page : null,
        retryStore,
        pdfDirectory,
        policies,
        limit,
        skipPolicyNumbers,
        knownPolicyNumbers,
        onProgress({ processed, planned: batchPlanned }) {
          if (processed % 5 === 0 || processed === batchPlanned) {
            onProgress({
              status: "running",
              title: `Pobieranie PDF-ów… (${processed}/${batchPlanned})`,
              detail: "",
            });
          }
        },
      });
    } catch {
      
      
      batch = {
        publicResult: failedMtuPdfRetry(
          eligible.length,
          "unknown",
          Math.max(0, eligible.length - planned),
        ),
        privateResult: {
          manualRetryIdentities: [],
        },
      };
    }
  }

  const pdfRetry = batch.publicResult;
  
  
  console.error(
    `MTU PDF partia: rozmiar=${planned} 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 (pdfRetry.attempted === 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."
        : pdfRetry.circuitOpen
          ? "Faza dokumentów została bezpiecznie zatrzymana; polisy nadal zostaną zaimportowane."
          : "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,
  };
}

export async function finalizeMtuPortfolioApiImport({
  apiPolicies,
  mappingReport = null,
  sinceDate = "",
  downloadPdfs = false,
  page,
  retryStore,
  pdfDirectory = "",
  pdfPolicyLimit = 50,
  skipPdfPolicyNumbers = [],
  knownPdfPolicyNumbers = [],
  onProgress = () => {},
  onCompletion,
} = {}) {
  if (!Array.isArray(apiPolicies)) {
    throw new TypeError("apiPolicies musi byc tablica.");
  }
  const pdfEligibleEntries = downloadPdfs
    ? prepareMtuPortfolioPdfEntries({
      policies: apiPolicies,
      skipPolicyNumbers: skipPdfPolicyNumbers,
      knownPolicyNumbers: knownPdfPolicyNumbers,
    })
    : [];
  const pdfExpectedCount = pdfEligibleEntries.length;
  const pdfOutcome = downloadPdfs
    ? await runMtuPortfolioPdfPhase({
      page,
      retryStore,
      pdfDirectory,
      policies: apiPolicies,
      onProgress,
      limit: pdfPolicyLimit,
      skipPolicyNumbers: skipPdfPolicyNumbers,
      knownPolicyNumbers: knownPdfPolicyNumbers,
    })
    : null;
  
  
  const pdfQueuePolicyNumbers = downloadPdfs
    ? pdfEligibleEntries
      .filter((entry) => entry?.policy?.pdfAvailable !== true)
      .map((entry) => createPortfolioPdfTransportIdentity(entry.policy, {
        insurer: "mtu",
      }))
      .slice(0, 10000)
    : [];
  const completionMappingReport = mappingReport?.coverage
    === PORTFOLIO_MAPPING_PDF_BACKLOG_ONLY_COVERAGE
    ? assertPortfolioPdfBacklogOnlyMappingReport(mappingReport)
    : assertPortfolioMappingComplete(mappingReport);
  const { emittedPolicies, emissionReport } = completionMappingReport.coverage
    === PORTFOLIO_MAPPING_PDF_BACKLOG_ONLY_COVERAGE
    ? createPortfolioPdfBacklogOnlyEmissionResult(
        apiPolicies.filter((policy) => (
          String(policy?.insurer || "").trim().toUpperCase() === "MTU"
          && policy.pdfAvailable === true
        )),
      )
    : createPortfolioEmissionResult(apiPolicies, {
        sinceDate,
        getFreshnessDates: (policy) => [policy?.issueDate, policy?.validFrom],
        isBacklogPolicy: (policy) => (
          String(policy?.insurer || "").trim().toUpperCase() === "MTU"
          && policy?.pdfAvailable === true
        ),
      });
  const completion = buildMtuImportCompletion({
    freshCount: emissionReport.freshCount,
    downloadPdfs,
    pdfOutcome,
    pdfExpectedCount,
  });
  onCompletion?.({
    ...completion,
    pdfQueuePolicyNumbers,
    mappingReport: completionMappingReport,
    emissionReport,
  });
  return emittedPolicies;
}

export function restoreMtuPortfolioPdfQueue(values) {
  return (Array.isArray(values) ? values : [])
    .map((value) => restorePortfolioPdfTransportPolicy(value, {
      insurer: "mtu",
      insurerLabel: "MTU",
    }))
    .filter(Boolean);
}

export function createMtuAutomation({ browserPool, pdfRetryStore }) {
  const INSURER_KEY = "mtu";
  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 } = {}) {
    validateMtuCredentials(username, password);
    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: ["pesel", "identity", "identyfikator klienta", "numer klienta"],
        value: pesel,
      },
      {
        key: "registration",
        patterns: [
          "numer rejestracyjny",
          "nr rejestracyjny",
          "nr rej",
          "rejestracj",
          "registrationnumber",
          "registration",
        ],
        value: registration,
      },
    ];

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

    onProgress({
      status: "running",
      title: "Zalogowano do MTU",
      detail: "",
    });
    const knownAnswerResolver = buildMtuKnownAnswerResolver({
      pesel,
      vin,
      vehicleInfo,
      extraResolver: extraKnownAnswerResolver,
    });
    let formNavigation = null;
    try {
      formNavigation = await learnOrReplayFormPath({
        page,
        savedPath: navigationPath,
        savedFields: formFields,
        fields: targetFields,
        insurerName: "MTU",
        manualInstruction: "Logowanie jest już zakończone. Kliknij „Rozpocznij nagrywanie”, przejdź do kalkulatora komunikacyjnego i zakończ nagrywanie na ekranie z polami PESEL i 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 MTU jest gotowa do ręcznego liczenia",
        detail: "Wpisz dane w formularzu samodzielnie.",
      });
      return [];
    }

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

    let premiums = await extractMtuPremiums(page);
    let lastPortalNotice = "";
    let noticeProbeCounter = 0;
    const resultsReady = async () => {
      premiums = await extractMtuPremiums(page);
      if (premiums.length === 0 && noticeProbeCounter % 5 === 0) {
        const notice = await collectMtuPortalNotices(page).catch(() => "");
        if (notice) lastPortalNotice = notice;
      }
      noticeProbeCounter += 1;
      return premiums.length > 0;
    };
    const buttonStepsOnly = (path) => (Array.isArray(path) ? path : [])
      .filter((step) => String(step?.tag || "").toLowerCase() === "button");
    const settleAfterResultStep = async (step) => {
      if (!/wylicz/i.test(String(step?.text || ""))) return;
      await waitForVehicleValue(page);
    };
    const gateLogPath = join(dirname(fileURLToPath(import.meta.url)), "..", "tmp", "mtu-bramka-log.txt");
    const logGateDecision = (line) => {
      appendFile(gateLogPath, `${line}\n`).catch(() => {});
    };
    const prepareBeforeResultStep = async (step) => {
      if (!/dalej/i.test(String(step?.text || ""))) return undefined;
      return mtuPrepareBeforeDalej(page, logGateDecision);
    };
    let actionPathError = null;
    try {
      await learnOrReplayActionPath({
        page,
        savedPath: buttonStepsOnly(resultNavigationPath),
        isComplete: resultsReady,
        requestInput: onInputRequested,
        knownAnswerResolver,
        collectFields: collectMtuBlockedFields,
        inputMaxRounds: 24,
        afterStep: settleAfterResultStep,
        beforeStep: prepareBeforeResultStep,
        stageTitle: "od formularza do składek MTU",
        manualInstruction: "PESEL i numer rejestracyjny są już wpisane. Klikaj dalej jak zwykle, aż pojawią się składki MTU. Aplikacja zapamięta te czynności.",
        retryInstruction: "Zapamiętana droga do wyników MTU przestała pasować. Klikaj ręcznie aż pojawią się składki, aby zapisać nową sekwencję.",
        onPathLearned: async (path) => onResultPathLearned?.(buttonStepsOnly(path)),
        onProgress,
        allowManual: !headless,
      });
    } catch (error) {
      actionPathError = error;
    }
    premiums = await extractMtuPremiums(page).catch(() => []);
    if (premiums.length === 0) {
      const portalNotice = (await collectMtuPortalNotices(page).catch(() => "")) || lastPortalNotice;
      if (portalNotice) throw new Error(`Kalkulator MTU zgłasza: ${portalNotice}`.slice(0, 480));
      if (actionPathError) throw actionPathError;
      throw new Error("Nie udało się odczytać składek z kalkulatora MTU 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 MTU 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 importPortfolioAttempt({
    username,
    password,
    navigationPath,
    downloadPdfs = false,
    pdfDirectory = "",
    pdfPolicyLimit = 50,
    skipPdfPolicyNumbers = [],
    knownPdfPolicyNumbers = [],
    pdfBacklogOnly = false,
    pdfBacklogPolicyNumbers = [],
    sinceDate = "",
    onCompletion,
    onPathLearned,
    onDiagnosticsCaptured,
    onProgress,
  }, { headless = false } = {}) {
    validateMtuCredentials(username, password);
    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 MTU działa w tle…" : "Otwieranie portalu MTU…",
        detail: "",
      });
      try {
        await page.goto(MTU_URL, { waitUntil: "domcontentloaded", timeout: 60000 });
      } catch {
        
        
        await page.goto(MTU_URL, { waitUntil: "domcontentloaded", timeout: 60000 }).catch(() => {});
      }
      await loginIfNeeded(page, { username, password }, onProgress, { allowUserInteraction: !headless });

      
      
      
      
      if (pdfBacklogOnly) {
        const backlogPolicies = restoreMtuPortfolioPdfQueue(pdfBacklogPolicyNumbers);
        if (backlogPolicies.length === 0) {
          throw new Error("Brak zaległych dokumentów do pobrania.");
        }
        onProgress({
          status: "running",
          title: "Pobieranie zaległych PDF-ów…",
          detail: `W kolejce: ${backlogPolicies.length} plików.`,
        });
        return await finalizeMtuPortfolioApiImport({
          apiPolicies: backlogPolicies,
          mappingReport: createPortfolioPdfBacklogOnlyMappingReport(),
          downloadPdfs: true,
          page,
          retryStore: pdfRetryStore,
          pdfDirectory,
          pdfPolicyLimit,
          skipPdfPolicyNumbers,
          knownPdfPolicyNumbers,
          onProgress,
          onCompletion,
        });
      }

      onProgress({
        status: "running",
        title: "Zalogowano do MTU",
        detail: "Pobieranie polis…",
      });
      let apiError = null;
      for (let apiAttempt = 0; apiAttempt < 2; apiAttempt += 1) {
        if (apiAttempt === 1) {
          
          
          
          
          await page.goto(MTU_URL, { waitUntil: "domcontentloaded", timeout: 60000 }).catch(() => {});
          await loginIfNeeded(page, { username, password }, onProgress, { allowUserInteraction: !headless });
          await page.goto(`${MTU_URL}#/policies`, { waitUntil: "domcontentloaded", timeout: 60000 }).catch(() => {});
          await delay(2500);
        }
        try {
          const apiItems = await fetchMtuPortfolioFromApi(page, {
            onProgress: ({ rangeFrom, rangeTo, total }) => {
              onProgress({
                status: "running",
                title: "Pobieranie polis…",
                detail: `Sprawdzono ${total} polis.`,
              });
            },
          });
          const mappingCollector = createPortfolioMappingCollector(apiItems.length);
          const apiPolicies = mapMtuApiPolicyItems(apiItems, { mappingCollector });
          const mappingReport = assertPortfolioMappingComplete(mappingCollector.createReport());
          if (apiPolicies.length > 0) {
            const previewEmission = createPortfolioEmissionResult(apiPolicies, {
              sinceDate,
              getFreshnessDates: (policy) => [policy?.issueDate, policy?.validFrom],
            });
            onProgress({
              status: "running",
              title: "Porządkowanie pobranych polis…",
              detail: sinceDate
                ? `Nowych polis: ${previewEmission.emissionReport.freshCount}.`
                : `Sprawdzono ${previewEmission.emissionReport.freshCount} polis.`,
            });
            
            
            return await finalizeMtuPortfolioApiImport({
              apiPolicies,
              mappingReport,
              sinceDate,
              downloadPdfs,
              page,
              retryStore: pdfRetryStore,
              pdfDirectory,
              pdfPolicyLimit,
              skipPdfPolicyNumbers,
              knownPdfPolicyNumbers,
              onProgress,
              onCompletion,
            });
          }
          apiError = new Error("API Jupitera nie zwróciło żadnej aktywnej polisy.");
        } catch (error) {
          apiError = error;
        }
      }
      
      
      
      
      console.error("Import MTU: API portfela niedostępne; niebezpieczny fallback ekranowy został zablokowany.", apiError?.message || apiError);
      throw new Error(
        "Nie udało się pobrać kompletnej listy polis z API Jupitera. Import został zatrzymany bez zmiany daty ostatniego importu.",
      );
    } finally {
      if (headless) await page.close().catch(() => {});
    }
  }

  
  
  
  async function importPortfolio(options) {
    const preferHeadless = options?.runInBackground === true;
    
    
    
    
    const groupHeadless = browserPool.isHeadless(INSURER_KEY);
    const startHeadless = groupHeadless === null ? preferHeadless : groupHeadless === true;
    if (!startHeadless) return importPortfolioAttempt(options, { headless: false });
    try {
      return await importPortfolioAttempt(options, { headless: true });
    } catch (error) {
      if (error?.code !== "MANUAL_INTERACTION_REQUIRED") throw error;
      options?.onProgress?.({
        status: "running",
        title: "Potrzebne jest okno przeglądarki…",
        detail: "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 };
}
