from __future__ import annotations

import html
import json
import re
import threading
import time
import traceback
from datetime import datetime
from pathlib import Path
from typing import Callable
from urllib.parse import parse_qs, unquote, urlparse

from .paths import (
    DOWNLOAD_TECH_PACK_CHECKPOINTS_DIR,
    DOWNLOAD_TECH_PACK_DATA_DIR,
    DOWNLOAD_TECH_PACK_DOWNLOADS_DIR,
    DOWNLOAD_TECH_PACK_LOG_PATH,
)

try:
    from playwright.sync_api import TimeoutError as PlaywrightTimeoutError
    from playwright.sync_api import sync_playwright

    PLAYWRIGHT_IMPORT_ERROR = ""
except Exception as exc:  # pragma: no cover - depends on local environment
    PlaywrightTimeoutError = RuntimeError
    sync_playwright = None
    PLAYWRIGHT_IMPORT_ERROR = str(exc)


DOWNLOAD_TECH_PACK_LOGIN_URL = "https://bealls.bamboorose.com/prod/login.do"
DOWNLOAD_TECH_PACK_URL = (
    "https://bealls.bamboorose.com/prod/plm.do?method=plmnavigate"
    "#uid=313b6093-7ec6-4bf1-81a3-3ea9676ec44a&method=Main.loadWorkArea&params="
    "advsearch.do%2CMAIN_LINK_NAVIGATION%26action%3D130%2C%2C__function__"
    "loadNavigationGrid%2C%2C&hasHash=true"
)

StatusCallback = Callable[[str], None]


def parse_style_list(raw_text: str) -> list[str]:
    styles: list[str] = []
    for line in raw_text.replace(",", "\n").splitlines():
        code = line.strip()
        if code and code not in styles:
            styles.append(code)
    return styles


def xpath_literal(value: str) -> str:
    if "'" not in value:
        return f"'{value}'"
    if '"' not in value:
        return f'"{value}"'

    parts = value.split("'")
    return "concat(" + ", \"'\", ".join(f"'{part}'" for part in parts) + ")"


def download_tech_pack_emit_status(callback: StatusCallback | None, message: str) -> None:
    download_tech_pack_append_latest_log(message)
    if callback is not None:
        callback(message)


def download_tech_pack_reset_latest_log() -> None:
    DOWNLOAD_TECH_PACK_DATA_DIR.mkdir(parents=True, exist_ok=True)
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    DOWNLOAD_TECH_PACK_LOG_PATH.write_text(
        f"[{timestamp}] Bat dau phien chay moi\n",
        encoding="utf-8",
    )


def download_tech_pack_append_latest_log(message: str) -> None:
    DOWNLOAD_TECH_PACK_DATA_DIR.mkdir(parents=True, exist_ok=True)
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    with DOWNLOAD_TECH_PACK_LOG_PATH.open("a", encoding="utf-8") as log_file:
        log_file.write(f"[{timestamp}] {message}\n")


def download_tech_pack_log_exception(exc: Exception, context: str = "") -> None:
    if context:
        download_tech_pack_append_latest_log(context)
    download_tech_pack_append_latest_log(f"Exception: {exc}")
    stack = traceback.format_exc()
    with DOWNLOAD_TECH_PACK_LOG_PATH.open("a", encoding="utf-8") as log_file:
        log_file.write(stack)
        if not stack.endswith("\n"):
            log_file.write("\n")


def build_timestamp() -> str:
    return datetime.now().strftime("%Y%m%d-%H%M%S")


def safe_name(value: str) -> str:
    cleaned = re.sub(r"[^A-Za-z0-9._-]+", "-", value.strip())
    return cleaned.strip("-") or "checkpoint"


def download_tech_pack_save_checkpoint(page, label: str, extra: dict | None = None) -> Path:
    DOWNLOAD_TECH_PACK_CHECKPOINTS_DIR.mkdir(parents=True, exist_ok=True)
    checkpoint_dir = DOWNLOAD_TECH_PACK_CHECKPOINTS_DIR / f"{build_timestamp()}-{safe_name(label)}"
    checkpoint_dir.mkdir(parents=True, exist_ok=True)

    metadata = {
        "label": label,
        "captured_at": datetime.now().isoformat(timespec="seconds"),
        "url": page.url,
        "title": page.title(),
    }
    if extra:
        metadata.update(extra)

    (checkpoint_dir / "metadata.json").write_text(
        json.dumps(metadata, ensure_ascii=False, indent=2),
        encoding="utf-8",
    )
    (checkpoint_dir / "page.html").write_text(page.content(), encoding="utf-8")
    page.screenshot(path=str(checkpoint_dir / "page.png"), full_page=True)
    return checkpoint_dir


def download_tech_pack_save_context_checkpoints(context, label_prefix: str, extra: dict | None = None) -> list[Path]:
    saved: list[Path] = []
    for index, page in enumerate(context.pages):
        try:
            page_label = f"{label_prefix}-page-{index + 1}"
            payload = dict(extra or {})
            payload["page_index"] = index + 1
            saved.append(download_tech_pack_save_checkpoint(page, page_label, payload))
        except Exception:
            continue
    return saved


def build_download_tech_pack_debug_summary(page) -> dict:
    summary: dict = {
        "url": "",
        "title": "",
        "page_count": 0,
        "pages": [],
        "selectors": {},
    }
    try:
        summary["url"] = page.url
    except Exception:
        pass
    try:
        summary["title"] = page.title()
    except Exception:
        pass
    try:
        context_pages = list(page.context.pages)
        summary["page_count"] = len(context_pages)
        summary["pages"] = [
            {
                "index": index + 1,
                "url": getattr(item, "url", ""),
                "title": item.title() if hasattr(item, "title") else "",
            }
            for index, item in enumerate(context_pages)
        ]
    except Exception:
        pass

    script = """
    () => {
        const textOf = node => ((node?.innerText || node?.textContent || "") + "").trim();
        const rows = Array.from(document.querySelectorAll("tr[id^='listRow_'], tr"));
        const patternRows = rows.filter(row => textOf(row).toUpperCase().includes("PATTERN"));
        const patternAnchors = patternRows.flatMap(row =>
            Array.from(row.querySelectorAll("a[id^='anchor_img_icon_'][href*='#filename=']"))
        );
        const fitEvalLinks = Array.from(
            document.querySelectorAll("table#MainSection_left_table a[href*='showFitEvaluationOverview']")
        );
        const reportButtons = Array.from(document.querySelectorAll("#FITEVALREPORTbtnCtr"));
        const visibleReportButtons = reportButtons.filter(node => !!(node.offsetParent));
        const dialogs = Array.from(document.querySelectorAll("div, section"))
            .filter(node => {
                const text = textOf(node).toLowerCase();
                return text.includes(".zip") || text.includes(".pdf");
            })
            .slice(0, 10)
            .map(node => textOf(node).slice(0, 200));
        return {
            pattern_row_count: patternRows.length,
            pattern_anchor_count: patternAnchors.length,
            pattern_anchor_ids: patternAnchors.map(node => node.id || ""),
            pattern_filenames: patternAnchors.map(node => {
                const href = node.getAttribute("href") || "";
                return (href.split("#filename=").pop() || "").trim();
            }),
            fit_eval_link_count: fitEvalLinks.length,
            fit_eval_labels: fitEvalLinks.map(node => textOf(node)),
            fit_eval_keyinfos: fitEvalLinks.map(node => node.getAttribute("keyinfo") || ""),
            report_button_count: reportButtons.length,
            visible_report_button_count: visibleReportButtons.length,
            has_fit_eval_detail_title: Array.from(document.querySelectorAll("label, h1, h2, h3, div, span"))
                .some(node => textOf(node) === "Fit Evaluation:"),
            inline_dialog_count: dialogs.length,
            inline_dialog_samples: dialogs,
        };
    }
    """
    try:
        summary["selectors"] = page.evaluate(script) or {}
    except Exception as exc:
        summary["selectors"] = {"error": str(exc)}
    return summary


def save_download_tech_pack_debug_snapshot(
    page,
    label: str,
    *,
    extra: dict | None = None,
    status: StatusCallback | None = None,
) -> Path | None:
    try:
        checkpoint_dir = download_tech_pack_save_checkpoint(page, label, extra)
        (checkpoint_dir / "debug_summary.json").write_text(
            json.dumps(build_download_tech_pack_debug_summary(page), ensure_ascii=False, indent=2),
            encoding="utf-8",
        )
        download_tech_pack_emit_status(status, f"Da luu debug snapshot: {checkpoint_dir}")
        return checkpoint_dir
    except Exception as exc:
        download_tech_pack_emit_status(status, f"Luu debug snapshot that bai ({label}): {exc}")
        return None


def capture_requested_checkpoint(
    page,
    checkpoint_request: dict | None,
    status: StatusCallback | None = None,
) -> None:
    if not checkpoint_request or not checkpoint_request.get("event"):
        return

    request_event = checkpoint_request["event"]
    if not request_event.is_set():
        return

    request_event.clear()
    label = checkpoint_request.get("label", "manual-checkpoint")
    checkpoint_dir = download_tech_pack_save_checkpoint(page, label)
    download_tech_pack_emit_status(status, f"Da luu checkpoint thu cong: {checkpoint_dir}")


def auto_debug_snapshot(
    page,
    checkpoint_request: dict | None,
    label: str,
    *,
    extra: dict | None = None,
    status: StatusCallback | None = None,
) -> Path | None:
    if not checkpoint_request or not checkpoint_request.get("debug_mode"):
        return None
    return save_download_tech_pack_debug_snapshot(page, label, extra=extra, status=status)


def extract_pattern_links_from_html(html_text: str) -> list[dict[str, str]]:
    try:
        html_source = html.unescape(html_text or "")
    except Exception:
        html_source = html_text or ""
    if not html_source:
        return []

    pattern = re.compile(
        r'<a\s+id="(?P<id>anchor_img_icon_[^"]+)"[^>]*href="(?P<href>[^"]+#filename=[^"]+)"[^>]*?(?:onclick="(?P<onclick>[^"]*)")?[^>]*>',
        re.IGNORECASE,
    )
    results: list[dict[str, str]] = []
    seen: set[str] = set()
    for match in pattern.finditer(html_source):
        href = match.group("href") or ""
        onclick = match.group("onclick") or ""
        filename = (href.split("#filename=").pop() or "").strip()
        dedupe_key = href or onclick
        if not dedupe_key or dedupe_key in seen:
            continue
        seen.add(dedupe_key)
        results.append(
            {
                "id": match.group("id") or "",
                "href": href,
                "onclick": onclick,
                "filename": filename,
                "title": filename,
            }
        )
    return results


def ensure_not_stopped(stop_event: threading.Event | None) -> None:
    if stop_event is not None and stop_event.is_set():
        raise RuntimeError("Da dung theo yeu cau nguoi dung.")


def first_visible_locator(locator):
    count = locator.count()
    for index in range(count):
        candidate = locator.nth(index)
        try:
            if candidate.is_visible():
                return candidate
        except Exception:
            continue
    return None


def last_visible_locator(locator):
    try:
        count = locator.count()
    except Exception:
        return None
    for index in range(count - 1, -1, -1):
        candidate = locator.nth(index)
        try:
            if candidate.is_visible():
                return candidate
        except Exception:
            continue
    return None


def iter_page_and_frames(page):
    yield page
    for frame in page.frames:
        yield frame


def click_locator_robust(locator) -> None:
    errors: list[str] = []
    try:
        locator.scroll_into_view_if_needed(timeout=3000)
    except Exception as exc:
        errors.append(str(exc))
    for action in (
        lambda: locator.click(timeout=3000),
        lambda: locator.click(timeout=3000, force=True),
        lambda: (locator.focus(), locator.press("Enter")),
        lambda: locator.evaluate(
            """
            element => {
                const target = element.closest("button, input[type='submit'], input[type='button'], a, [onclick]") || element;
                if (typeof target.click === "function") {
                    target.click();
                } else if (typeof element.click === "function") {
                    element.click();
                }
                element.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
            }
            """
        ),
    ):
        try:
            action()
            return
        except Exception as exc:
            errors.append(str(exc))
    raise RuntimeError(" | ".join(errors) or "Khong the click vao phan tu.")


def get_locator_input_value(locator) -> str:
    try:
        return str(locator.input_value(timeout=1000) or "")
    except Exception:
        try:
            return str(locator.evaluate("element => element.value || element.getAttribute('value') || ''") or "")
        except Exception:
            return ""


def fill_input_and_verify(
    locator,
    value: str,
    *,
    status: StatusCallback | None = None,
    stop_event: threading.Event | None = None,
    label: str = "input",
    attempts: int = 4,
) -> None:
    expected = str(value or "").strip()
    sensitive = "password" in str(label or "").lower()
    last_value = ""
    last_error = ""
    for attempt in range(1, max(1, attempts) + 1):
        ensure_not_stopped(stop_event)
        try:
            locator.wait_for(state="visible", timeout=5000)
            locator.click(timeout=3000)
            try:
                locator.fill("", timeout=3000)
            except Exception:
                locator.press("Control+A")
                locator.press("Delete")
            locator.fill(expected, timeout=5000)
            locator.evaluate(
                """
                (element, value) => {
                    element.value = value;
                    element.dispatchEvent(new Event('input', { bubbles: true }));
                    element.dispatchEvent(new Event('change', { bubbles: true }));
                    element.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true }));
                }
                """,
                expected,
            )
            interruptible_sleep(0.25 + attempt * 0.1, stop_event)
            last_value = get_locator_input_value(locator).strip()
            if last_value.upper() == expected.upper():
                if attempt > 1:
                    download_tech_pack_emit_status(status, f"Da xac nhan {label} nhan dung gia tri sau lan thu {attempt}.")
                return
            last_error = f"{label} chua khop gia tri mong doi." if sensitive else f"{label} dang la '{last_value}', can '{expected}'"
            download_tech_pack_emit_status(status, f"Chua xac nhan duoc {label} lan {attempt}: {last_error}")
        except Exception as exc:
            last_error = str(exc)
            download_tech_pack_emit_status(status, f"Nhap {label} lan {attempt} gap loi: {last_error}")
        interruptible_sleep(0.35, stop_event)
    raise RuntimeError(f"Khong xac nhan duoc {label}: {last_error or last_value}")


def click_and_wait(
    locator,
    predicate: Callable[[], bool],
    *,
    status: StatusCallback | None = None,
    stop_event: threading.Event | None = None,
    action_label: str = "button",
    timeout: float = 12.0,
    attempts: int = 3,
) -> None:
    last_error = ""
    for attempt in range(1, max(1, attempts) + 1):
        ensure_not_stopped(stop_event)
        try:
            click_locator_robust(locator)
            download_tech_pack_emit_status(status, f"Da bam {action_label} lan {attempt}. Dang cho man hinh phan hoi...")
        except Exception as exc:
            last_error = str(exc)
            download_tech_pack_emit_status(status, f"Bam {action_label} lan {attempt} that bai: {last_error}")
            interruptible_sleep(0.5, stop_event)
            continue

        deadline = time.monotonic() + max(1.0, timeout)
        while time.monotonic() < deadline:
            ensure_not_stopped(stop_event)
            try:
                if predicate():
                    return
            except Exception as exc:
                last_error = str(exc)
            interruptible_sleep(0.25, stop_event)
        download_tech_pack_emit_status(status, f"Chua thay phan hoi sau khi bam {action_label} lan {attempt}. Dang thu lai...")
    raise RuntimeError(f"Khong xac nhan duoc sau khi bam {action_label}: {last_error}")


def invoke_javascript_link(locator) -> bool:
    try:
        return bool(
            locator.evaluate(
                """
                element => {
                    const target = element.closest("a, td[onclick], [onclick]") || element;
                    try {
                        if (target.tagName === "A") {
                            const href = target.getAttribute("href") || "";
                            if (typeof target.onclick === "function") {
                                try {
                                    target.onclick();
                                } catch (error) {
                                }
                            }
                            if (href.toLowerCase().startsWith("javascript:")) {
                                window.eval(href.replace(/^javascript:/i, ""));
                                return true;
                            }
                        }
                        const onclickCode = target.getAttribute("onclick") || element.getAttribute("onclick") || "";
                        if (typeof target.onclick === "function") {
                            target.onclick();
                            return true;
                        }
                        if (onclickCode) {
                            window.eval(onclickCode.replace(/^javascript:/i, ""));
                            return true;
                        }
                        if (typeof element.click === "function") {
                            element.click();
                            return true;
                        }
                    } catch (error) {
                    }
                    return false;
                }
                """
            )
        )
    except Exception:
        return False


def invoke_generate_report_action(page, locator) -> bool:
    try:
        invoked_from_locator = bool(
            locator.evaluate(
                """
                element => {
                    const container = element.closest("table[onclick], div[onclick], td[onclick], a[onclick]") || element;
                    const onclickCode = container.getAttribute("onclick") || element.getAttribute("onclick") || "";
                    if (!onclickCode) {
                        return false;
                    }
                    try {
                        if (typeof container.onclick === "function") {
                            container.onclick();
                            return true;
                        }
                        window.eval(onclickCode.replace(/^javascript:/i, ""));
                        return true;
                    } catch (error) {
                        return false;
                    }
                }
                """
            )
        )
        if invoked_from_locator:
            return True
    except Exception:
        pass

    try:
        return bool(
            page.evaluate(
                """
                () => {
                    const directCandidates = [
                        document.querySelector("table[title='Generate Report']"),
                        document.querySelector("#_nwcmtPost"),
                        document.querySelector("[onclick*='generateReportForSelectedSize']"),
                    ].filter(Boolean);

                    for (const element of directCandidates) {
                        try {
                            if (typeof element.onclick === "function") {
                                element.onclick();
                                return true;
                            }
                            const onclickCode = element.getAttribute("onclick") || "";
                            if (onclickCode) {
                                window.eval(onclickCode.replace(/^javascript:/i, ""));
                                return true;
                            }
                        } catch (error) {
                        }
                    }

                    const scriptHost = document.querySelector("table[title='Generate Report'], #_nwcmtPost, [onclick*='generateReportForSelectedSize']");
                    if (!scriptHost) {
                        return false;
                    }

                    const onclickCode = scriptHost.getAttribute("onclick") || "";
                    const match = onclickCode.match(/generateReportForSelectedSize\((.*)\)/);
                    if (!match || typeof window.generateReportForSelectedSize !== "function") {
                        return false;
                    }

                    const args = Function("return [" + match[1] + "];")();
                    window.generateReportForSelectedSize.apply(window, args);
                    return true;
                }
                """
            )
        )
    except Exception:
        return False


def is_report_dialog_still_visible(page) -> bool:
    try:
        dialog = locate_report_dialog(page)
        return dialog is not None
    except Exception:
        return False


def close_report_dialog_if_present(
    page,
    style_code: str,
    *,
    status: StatusCallback | None = None,
    stop_event: threading.Event | None = None,
) -> None:
    ensure_not_stopped(stop_event)
    if not is_report_dialog_still_visible(page):
        return

    try:
        page.keyboard.press("Escape")
        interruptible_sleep(0.5, stop_event)
        if not is_report_dialog_still_visible(page):
            download_tech_pack_emit_status(status, f"Da dong popup Report Attributes cho {style_code} bang Escape.")
            return
    except Exception:
        pass

    close_candidates = [
        page.locator("xpath=//*[contains(normalize-space(), 'Report Attributes')]/ancestor::*[self::div or self::td or self::section][1]//*[contains(@class, 'k-i-close') or contains(@class, 'close')]"),
        page.locator("xpath=//*[contains(normalize-space(), 'Report Attributes')]/ancestor::*[self::div or self::td or self::section][1]//*[contains(@title, 'Close')]"),
        page.locator("xpath=//*[contains(normalize-space(), 'Report Attributes')]/ancestor::*[self::div or self::td or self::section][1]//*[normalize-space()='X' or normalize-space()='x']"),
        page.locator("xpath=//*[contains(normalize-space(), 'Report Attributes')]/ancestor::*[self::div or self::td or self::section][1]//*[contains(@onclick, 'close') or contains(@onclick, 'hide')]"),
    ]
    for locator in close_candidates:
        target = first_visible_locator(locator)
        if target is None:
            continue
        try:
            click_locator_robust(target)
            interruptible_sleep(0.8, stop_event)
            if not is_report_dialog_still_visible(page):
                download_tech_pack_emit_status(status, f"Da dong popup Report Attributes cho {style_code}.")
                return
        except Exception:
            continue

    try:
        closed = bool(
            page.evaluate(
                """
                () => {
                    const dialog = Array.from(document.querySelectorAll("div, td, section"))
                        .find(el => (el.innerText || "").includes("Report Attributes"));
                    if (!dialog) {
                        return false;
                    }
                    const closer = dialog.querySelector("[class*='k-i-close'], [class*='close'], [title*='Close']");
                    if (!closer) {
                        return false;
                    }
                    closer.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
                    if (typeof closer.click === "function") {
                        closer.click();
                    }
                    return true;
                }
                """
            )
        )
        if closed:
            interruptible_sleep(0.8, stop_event)
            if not is_report_dialog_still_visible(page):
                download_tech_pack_emit_status(status, f"Da dong popup Report Attributes cho {style_code} bang script.")
                return
    except Exception:
        pass

    try:
        page.mouse.click(120, 120)
        interruptible_sleep(0.6, stop_event)
        if not is_report_dialog_still_visible(page):
            download_tech_pack_emit_status(status, f"Da tra focus ve man hinh chinh cho {style_code}.")
            return
    except Exception:
        pass

    download_tech_pack_emit_status(status, f"Popup Report Attributes van con mo cho {style_code}.")


def locate_download_button(page):
    last_error = "Khong tim thay nut Download."
    for target in iter_page_and_frames(page):
        candidates = [
            target.get_by_role("button", name=re.compile(r"Download|Save", re.I)),
            target.get_by_role("link", name=re.compile(r"Download|Save", re.I)),
            target.locator("css=[title*='Download'], [aria-label*='Download'], [title*='Save'], [aria-label*='Save']"),
            target.locator("xpath=//*[self::button or self::a or self::span or self::div][contains(normalize-space(), 'Download')]"),
            target.locator("xpath=//*[self::button or self::a or self::span or self::div][contains(normalize-space(), 'Save')]"),
        ]
        for locator in candidates:
            try:
                candidate = first_visible_locator(locator)
                if candidate is not None:
                    return candidate
            except Exception as exc:
                last_error = str(exc)
    raise RuntimeError(last_error)


def resolve_download_dir(download_dir: str | Path | None = None) -> Path:
    target = Path(str(download_dir or "").strip()) if str(download_dir or "").strip() else DOWNLOAD_TECH_PACK_DOWNLOADS_DIR
    target.mkdir(parents=True, exist_ok=True)
    return target


def resolve_style_download_dir(download_dir: str | Path | None, style_code: str) -> Path:
    base_dir = resolve_download_dir(download_dir)
    style_dir = base_dir / safe_name(style_code)
    style_dir.mkdir(parents=True, exist_ok=True)
    return style_dir


def build_style_download_path(download_dir: str | Path | None, style_code: str, filename: str, *, unique: bool = False) -> Path:
    style_dir = resolve_style_download_dir(download_dir, style_code)
    base_path = style_dir / Path(filename or "download.bin").name
    if not unique or not base_path.exists():
        return base_path
    stem = base_path.stem
    suffix = base_path.suffix
    counter = 2
    while True:
        candidate = base_path.with_name(f"{stem}_{counter}{suffix}")
        if not candidate.exists():
            return candidate
        counter += 1


def invoke_open_multi_report_fallback(page) -> bool:
    try:
        return bool(
            page.evaluate(
                """
                () => {
                    const trigger = document.querySelector("[onclick*='mfjs.openMultiReport']");
                    if (!trigger) {
                        return false;
                    }
                    const onclickCode = trigger.getAttribute("onclick") || "";
                    if (!onclickCode) {
                        return false;
                    }
                    try {
                        window.eval(onclickCode.replace(/^javascript:/i, ""));
                        return true;
                    } catch (error) {
                        return false;
                    }
                }
                """
            )
        )
    except Exception:
        return False


def click_download_button_and_wait(
    report_page,
    style_code: str,
    *,
    download_dir: str | Path | None = None,
    status: StatusCallback | None = None,
    unique_filename: bool = False,
) -> Path | None:
    try:
        download_button = locate_download_button(report_page)
    except Exception:
        return None

    download_tech_pack_emit_status(status, f"Da tim thay nut Download/Save cho {style_code}. Dang bam tai file...")
    try:
        with report_page.expect_download(timeout=30000) as download_info:
            click_locator_robust(download_button)
        download = download_info.value
        suggested = download.suggested_filename or f"{style_code}.pdf"
        target_path = build_style_download_path(download_dir, style_code, Path(suggested).name, unique=unique_filename)
        download.save_as(str(target_path))
        return target_path
    except Exception:
        return None


def locate_report_dialog(page):
    last_error = "Khong tim thay hop Report Attributes."
    for target in iter_page_and_frames(page):
        candidates = [
            target.locator("xpath=//*[contains(normalize-space(), 'Report Attributes')]/ancestor::*[self::div or self::td or self::section][1]"),
            target.locator("xpath=//*[contains(normalize-space(), 'Tech Pack Tabs')]/ancestor::*[self::div or self::td or self::section][1]"),
            target.locator("xpath=//*[contains(@class, 'window') or contains(@class, 'dialog')][.//*[contains(normalize-space(), 'Report Attributes')]]"),
        ]
        for locator in candidates:
            try:
                candidate = first_visible_locator(locator)
                if candidate is not None:
                    return candidate
            except Exception as exc:
                last_error = str(exc)
    raise RuntimeError(last_error)


def wait_for_first_visible(candidates, timeout: float = 15.0, poll_ms: int = 300):
    deadline = time.monotonic() + timeout
    last_error = "Khong tim thay phan tu phu hop."

    while time.monotonic() < deadline:
        for locator in candidates:
            try:
                candidate = first_visible_locator(locator)
                if candidate is not None:
                    return candidate
            except Exception as exc:
                last_error = str(exc)

        time.sleep(poll_ms / 1000)

    raise RuntimeError(last_error)


def interruptible_sleep(seconds: float, stop_event: threading.Event | None = None, step: float = 0.2) -> None:
    deadline = time.monotonic() + max(0.0, seconds)
    while time.monotonic() < deadline:
        ensure_not_stopped(stop_event)
        time.sleep(min(step, max(0.0, deadline - time.monotonic())))


def locate_search_controls(page, timeout: float = 45.0, stop_event: threading.Event | None = None):
    deadline = time.monotonic() + timeout
    last_error = "Khong tim thay o Style No va nut Search."

    while time.monotonic() < deadline:
        ensure_not_stopped(stop_event)
        frames = list(page.frames)
        for frame in frames:
            try:
                if frame.url.endswith("/tss.jsp"):
                    continue

                style_candidates = [
                    frame.locator("xpath=//*[@id='2_@100_@4_@0_@0_@0_@_srch']"),
                    frame.get_by_label("Style No", exact=True),
                    frame.get_by_placeholder("Style No", exact=True),
                    frame.locator("input[name*='style' i]"),
                    frame.locator("input[id*='style' i]"),
                    frame.locator("xpath=//*[normalize-space()='Style No']/following::input[1]"),
                    frame.locator("xpath=//label[contains(normalize-space(), 'Style No')]/following::input[1]"),
                ]
                search_candidates = [
                    frame.locator("xpath=//*[@id='_searchBtnbtnCtr']"),
                    frame.locator("css=[id*='_searchBtn'], [title='Search'], [title*='Search']"),
                    frame.get_by_role("button", name="Search", exact=True),
                    frame.locator("xpath=//button[normalize-space()='Search']"),
                    frame.locator("xpath=//input[@type='button' and @value='Search']"),
                    frame.locator("xpath=//*[@role='button' and normalize-space()='Search']"),
                    frame.locator("xpath=//*[self::span or self::a or self::div or self::td or self::table][normalize-space()='Search']"),
                ]

                style_input = None
                search_button = None

                for locator in style_candidates:
                    style_input = first_visible_locator(locator)
                    if style_input is not None:
                        break

                for locator in search_candidates:
                    search_button = first_visible_locator(locator)
                    if search_button is not None:
                        break

                if style_input is not None and search_button is not None:
                    return frame, style_input, search_button
            except Exception as exc:
                last_error = f"{exc} | frame={frame.url}"

        interruptible_sleep(0.3, stop_event)

    raise RuntimeError(last_error)


def open_tech_pack_search_page(
    page,
    *,
    status: StatusCallback | None = None,
    stop_event: threading.Event | None = None,
    checkpoint_request: dict | None = None,
    timeout: float = 60000,
) -> None:
    ensure_not_stopped(stop_event)
    last_error = ""
    for attempt in range(1, 4):
        if attempt == 1:
            download_tech_pack_emit_status(status, "Dang mo URL Tech Pack search...")
        else:
            download_tech_pack_emit_status(status, f"Dang thu mo lai man hinh Tech Pack search lan {attempt}...")
        try:
            if attempt == 2:
                page.reload(wait_until="domcontentloaded", timeout=timeout)
            else:
                page.goto(DOWNLOAD_TECH_PACK_URL, wait_until="domcontentloaded", timeout=timeout)
        except PlaywrightTimeoutError:
            download_tech_pack_emit_status(
                status,
                "Mo URL Tech Pack bi timeout, dang giu nguyen phien hien tai va thu tiep tuc...",
            )
        except Exception as exc:
            download_tech_pack_emit_status(
                status,
                f"Mo URL Tech Pack gap loi: {exc}. Dang thu tiep tuc voi trang hien tai...",
            )

        interruptible_sleep(3.0 + attempt, stop_event)
        capture_requested_checkpoint(page, checkpoint_request, status)
        try:
            frame, _, _ = locate_search_controls(page, timeout=12.0 + attempt * 6.0, stop_event=stop_event)
            download_tech_pack_emit_status(
                status,
                f"Da san sang man hinh Tech Pack search: frame={frame.url or 'main'}",
            )
            return
        except Exception as exc:
            last_error = str(exc)
            download_tech_pack_emit_status(
                status,
                f"Chua bat duoc man hinh search lan {attempt}: {last_error}",
            )
            auto_debug_snapshot(
                page,
                checkpoint_request,
                f"tech-pack-search-not-ready-attempt-{attempt}",
                extra={"step": "tech_pack_search_not_ready", "attempt": attempt, "error": last_error},
                status=status,
            )

    raise RuntimeError(last_error or "Khong tim thay o Style No va nut Search.")


def reset_to_tech_pack_search(
    page,
    *,
    style_code: str = "",
    status: StatusCallback | None = None,
    stop_event: threading.Event | None = None,
    checkpoint_request: dict | None = None,
) -> None:
    ensure_not_stopped(stop_event)
    download_tech_pack_emit_status(status, "Dang quay ve man hinh tim kiem Tech Pack...")
    open_tech_pack_search_page(
        page,
        status=status,
        stop_event=stop_event,
        checkpoint_request=checkpoint_request,
    )


def locate_result_link(frame, style_code: str):
    safe_style = xpath_literal(style_code)
    safe_style_upper = xpath_literal(str(style_code or "").upper())
    upper_expr = "translate(normalize-space(), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"
    candidates = [
        frame.locator(f"xpath=//a[{upper_expr}={safe_style_upper}]"),
        frame.locator(f"xpath=//*[(@role='link' or self::a) and {upper_expr}={safe_style_upper}]"),
        frame.locator(
            f"xpath=//*[contains(@class, 'x-grid3-cell-inner') and {upper_expr}={safe_style_upper}]"
        ),
        frame.locator(f"xpath=//a[contains(normalize-space(), {safe_style})]"),
        frame.locator(f"xpath=//*[contains(normalize-space(), {safe_style}) and (@role='link' or self::a)]"),
        frame.locator(
            f"xpath=//*[contains(@class, 'x-grid3-cell-inner') and contains(normalize-space(), {safe_style})]"
        ),
    ]
    for locator in candidates:
        match = first_visible_locator(locator)
        if match is not None:
            return match
    return None


def safe_locator_attr(locator, attr_name: str) -> str:
    try:
        return locator.get_attribute(attr_name) or ""
    except Exception:
        return ""


def search_style_and_open(
    page,
    style_code: str,
    *,
    status: StatusCallback | None = None,
    stop_event: threading.Event | None = None,
    checkpoint_request: dict | None = None,
) -> str:
    ensure_not_stopped(stop_event)
    download_tech_pack_emit_status(status, f"Dang mo man hinh Tech Pack cho style {style_code}...")
    try:
        frame, style_input, search_button = locate_search_controls(page, stop_event=stop_event)
    except Exception:
        reset_to_tech_pack_search(
            page,
            style_code=style_code,
            status=status,
            stop_event=stop_event,
            checkpoint_request=checkpoint_request,
        )
        frame, style_input, search_button = locate_search_controls(page, stop_event=stop_event)
    capture_requested_checkpoint(page, checkpoint_request, status)
    download_tech_pack_emit_status(
        status,
        f"Da tim thay man hinh tim kiem cho {style_code}: frame={frame.url or 'main'}",
    )

    fill_input_and_verify(
        style_input,
        style_code,
        status=status,
        stop_event=stop_event,
        label=f"Style No {style_code}",
    )
    download_tech_pack_emit_status(status, f"Dang tim style {style_code}...")
    ensure_not_stopped(stop_event)
    click_locator_robust(search_button)
    interruptible_sleep(0.8, stop_event)
    capture_requested_checkpoint(page, checkpoint_request, status)
    download_tech_pack_emit_status(status, f"Da bam Search cho style {style_code}.")

    deadline = time.monotonic() + 30
    result_seen_once = False
    while time.monotonic() < deadline:
        ensure_not_stopped(stop_event)
        capture_requested_checkpoint(page, checkpoint_request, status)
        result_link = locate_result_link(frame, style_code)
        if result_link is not None:
            if not result_seen_once:
                result_seen_once = True
                download_tech_pack_emit_status(status, f"Da thay ket qua cho {style_code}. Dang xac nhan ket qua on dinh...")
                interruptible_sleep(0.6, stop_event)
                continue
            download_tech_pack_emit_status(status, f"Da xac nhan ket qua cho {style_code}. Dang mo detail...")
            click_locator_robust(result_link)
            try:
                page.wait_for_load_state("networkidle", timeout=15000)
            except PlaywrightTimeoutError:
                pass
            wait_for_style_detail_ready(
                page,
                style_code,
                status=status,
                stop_event=stop_event,
                checkpoint_request=checkpoint_request,
                timeout=45.0,
            )
            return page.url

        if int(deadline - time.monotonic()) in {25, 20, 15, 10, 5}:
            download_tech_pack_emit_status(status, f"Dang cho ket qua style {style_code}...")
        interruptible_sleep(0.25, stop_event)

    raise RuntimeError(f"Khong tim thay ket qua style {style_code} sau khi Search.")


def is_style_search_not_found_error(exc: Exception, style_code: str) -> bool:
    message = str(exc or "").strip().lower()
    target = f"khong tim thay ket qua style {style_code} sau khi search.".lower()
    return message == target


def locate_tech_pack_report_button(page):
    candidates = [
        page.get_by_role("button", name="Tech Pack Report", exact=True),
        page.locator("xpath=//button[normalize-space()='Tech Pack Report']"),
        page.locator("xpath=//*[self::span or self::a][normalize-space()='Tech Pack Report']"),
    ]
    return wait_for_first_visible(candidates, timeout=15.0)


def locate_named_tab(page, tab_name: str):
    safe_label = xpath_literal(tab_name)
    candidates = [
        page.get_by_role("tab", name=re.compile(fr"^{re.escape(tab_name)}$", re.I)),
        page.locator(f"xpath=//a[normalize-space()={safe_label}]"),
        page.locator(f"xpath=//button[normalize-space()={safe_label}]"),
        page.locator(f"xpath=//*[self::span or self::div][normalize-space()={safe_label}]"),
    ]
    return wait_for_first_visible(candidates, timeout=10.0)


def open_named_tab(
    page,
    tab_name: str,
    *,
    status: StatusCallback | None = None,
    stop_event: threading.Event | None = None,
) -> None:
    ensure_not_stopped(stop_event)
    try:
        page.mouse.click(160, 120)
        interruptible_sleep(0.3, stop_event)
    except Exception:
        pass

    try:
        tab = locate_named_tab(page, tab_name)
        click_locator_robust(tab)
        interruptible_sleep(1.0, stop_event)
        download_tech_pack_emit_status(status, f"Da mo tab {tab_name}.")
        return
    except Exception:
        pass

    js_function_map = {
        "Constructions": "showConstructions",
        "POM": "showPOM",
        "BOM": "showBillOfMaterials",
        "Instructions": "showInstructions",
        "Stitching Standards": "showStandards",
        "Samples": "showSample",
    }
    js_function = js_function_map.get(tab_name)
    if js_function:
        try:
            invoked = bool(
                page.evaluate(
                    f"""
                    () => {{
                        if (typeof window.{js_function} === "function") {{
                            window.{js_function}();
                            return true;
                        }}
                        return false;
                    }}
                    """
                )
            )
            if invoked:
                interruptible_sleep(1.2, stop_event)
                download_tech_pack_emit_status(status, f"Da mo tab {tab_name} bang ham he thong.")
                return
        except Exception:
            pass

    tab = locate_named_tab(page, tab_name)
    click_locator_robust(tab)
    interruptible_sleep(1.0, stop_event)
    download_tech_pack_emit_status(status, f"Da mo tab {tab_name}.")


def wait_for_style_detail_ready(
    page,
    style_code: str,
    *,
    status: StatusCallback | None = None,
    stop_event: threading.Event | None = None,
    checkpoint_request: dict | None = None,
    timeout: float = 45.0,
) -> None:
    ensure_not_stopped(stop_event)
    download_tech_pack_emit_status(status, f"Dang cho man hinh chi tiet cua {style_code} nap xong...")
    deadline = time.monotonic() + timeout
    last_error = "Khong tim thay man hinh chi tiet style."

    while time.monotonic() < deadline:
        ensure_not_stopped(stop_event)
        capture_requested_checkpoint(page, checkpoint_request, status)
        try:
            button = locate_tech_pack_report_button(page)
            button.wait_for(state="visible", timeout=1000)
            style_markers = [
                page.locator(f"xpath=//*[contains(normalize-space(), {xpath_literal(style_code)})]"),
                page.get_by_text(style_code, exact=False),
            ]
            for marker in style_markers:
                if first_visible_locator(marker) is not None:
                    download_tech_pack_emit_status(status, f"Man hinh chi tiet cua {style_code} da san sang.")
                    return
        except Exception as exc:
            last_error = str(exc)

        interruptible_sleep(0.3, stop_event)

    raise RuntimeError(last_error)


def locate_generate_report_button(page):
    last_error = "Khong tim thay nut Generate Report."
    dialog = None
    try:
        dialog = locate_report_dialog(page)
    except Exception:
        dialog = None

    search_targets = [dialog] if dialog is not None else []
    search_targets.extend(iter_page_and_frames(page))

    for target in search_targets:
        if target is None:
            continue
        candidates = [
            target.get_by_role("button", name=re.compile(r"Generate\s*Report", re.I)),
            target.locator("xpath=//button[contains(normalize-space(), 'Generate Report')]"),
            target.locator("xpath=//input[(@type='button' or @type='submit') and contains(@value, 'Generate Report')]"),
            target.locator("xpath=//*[self::span or self::a or self::div][contains(normalize-space(), 'Generate Report')]"),
            target.locator("xpath=.//*[self::button or self::input or self::span or self::a or self::div][contains(normalize-space(), 'Generate Report')]"),
            target.locator("css=table[title='Generate Report']"),
            target.locator("xpath=//table[contains(@onclick, 'generateReportForSelectedSize')]"),
            target.locator("xpath=.//table[contains(@onclick, 'generateReportForSelectedSize')]"),
        ]
        for locator in candidates:
            try:
                candidate = first_visible_locator(locator)
                if candidate is not None:
                    return candidate
            except Exception as exc:
                last_error = str(exc)
    raise RuntimeError(last_error)


def wait_for_report_dialog_ready(
    page,
    style_code: str,
    *,
    status: StatusCallback | None = None,
    stop_event: threading.Event | None = None,
    checkpoint_request: dict | None = None,
    timeout: float = 45.0,
) -> None:
    ensure_not_stopped(stop_event)
    download_tech_pack_emit_status(status, f"Dang cho hop report cua {style_code} san sang...")
    deadline = time.monotonic() + timeout
    last_error = "Khong tim thay hop report."

    while time.monotonic() < deadline:
        ensure_not_stopped(stop_event)
        capture_requested_checkpoint(page, checkpoint_request, status)
        try:
            dialog_detected = False
            for target in iter_page_and_frames(page):
                dialog_markers = [
                    target.get_by_text("Report Attributes", exact=False),
                    target.locator("xpath=//*[contains(normalize-space(), 'Report Attributes')]"),
                    target.locator("xpath=//*[contains(normalize-space(), 'Tech Pack Tabs')]"),
                ]
                for marker in dialog_markers:
                    candidate = first_visible_locator(marker)
                    if candidate is not None:
                        dialog_detected = True
                        break
                if dialog_detected:
                    break

            if dialog_detected:
                button = locate_generate_report_button(page)
                button.wait_for(state="visible", timeout=1000)
                download_tech_pack_emit_status(status, f"Hop report cua {style_code} da san sang.")
                return
        except Exception as exc:
            last_error = str(exc)
        interruptible_sleep(0.3, stop_event)

    raise RuntimeError(last_error)


def handle_multiple_session_warning(
    page,
    *,
    status: StatusCallback | None = None,
    stop_event: threading.Event | None = None,
    timeout: float = 10.0,
) -> None:
    ensure_not_stopped(stop_event)
    deadline = time.monotonic() + timeout
    warning_candidates = [
        page.get_by_text("End other session", exact=False),
        page.locator("xpath=//*[contains(normalize-space(), 'End other session')]"),
    ]

    while time.monotonic() < deadline:
        ensure_not_stopped(stop_event)
        for locator in warning_candidates:
            try:
                candidate = first_visible_locator(locator)
                if candidate is not None:
                    download_tech_pack_emit_status(status, "Phat hien canh bao phien dang nhap khac. Dang xu ly...")
                    candidate.click()
                    download_tech_pack_emit_status(status, "Da xu ly End other session.")
                    return
            except Exception:
                continue
        interruptible_sleep(0.25, stop_event)


def locate_login_controls(page, timeout: float = 20.0, stop_event: threading.Event | None = None):
    deadline = time.monotonic() + timeout
    last_error = "Khong tim thay form dang nhap."
    while time.monotonic() < deadline:
        ensure_not_stopped(stop_event)
        try:
            user_candidates = [
                page.locator("input[type='text']:visible").first,
                page.locator("input[name*='user' i]:visible").first,
                page.locator("input[id*='user' i]:visible").first,
                page.locator("xpath=//*[contains(normalize-space(), 'User') or contains(normalize-space(), 'Login')]/following::input[1]").first,
            ]
            password_candidates = [
                page.locator("input[type='password']:visible").first,
                page.locator("input[name*='pass' i]:visible").first,
                page.locator("input[id*='pass' i]:visible").first,
            ]
            submit_candidates = [
                page.locator("#formsubmit"),
                page.locator("input[type='submit']:visible").first,
                page.get_by_role("button", name=re.compile(r"Log\s*in|Login|Sign\s*in|Submit", re.I)),
                page.locator("xpath=//*[self::button or self::input or self::a or self::span or self::div][contains(normalize-space(), 'Login') or contains(normalize-space(), 'Log in') or contains(normalize-space(), 'Submit')]").first,
            ]

            user_input = next((candidate for candidate in (first_visible_locator(item) for item in user_candidates) if candidate is not None), None)
            password_input = next((candidate for candidate in (first_visible_locator(item) for item in password_candidates) if candidate is not None), None)
            submit_button = next((candidate for candidate in (first_visible_locator(item) for item in submit_candidates) if candidate is not None), None)
            if user_input is not None and password_input is not None and submit_button is not None:
                return user_input, password_input, submit_button
        except Exception as exc:
            last_error = str(exc)
        interruptible_sleep(0.25, stop_event)
    raise RuntimeError(last_error)


def is_login_form_visible(page) -> bool:
    try:
        locate_login_controls(page, timeout=0.8)
        return True
    except Exception:
        return False


def is_login_error_visible(page) -> bool:
    error_patterns = [
        "invalid",
        "incorrect",
        "failed",
        "try again",
        "not match",
        "required",
        "error",
    ]
    try:
        page_text = str(page.locator("body").inner_text(timeout=1000) or "").lower()
        return any(pattern in page_text for pattern in error_patterns)
    except Exception:
        return False


def is_multiple_session_warning_visible(page) -> bool:
    warning_candidates = [
        page.get_by_text("End other session", exact=False),
        page.locator("xpath=//*[contains(normalize-space(), 'End other session')]"),
    ]
    for locator in warning_candidates:
        try:
            if first_visible_locator(locator) is not None:
                return True
        except Exception:
            continue
    return False


def wait_for_login_accepted(
    page,
    *,
    status: StatusCallback | None = None,
    stop_event: threading.Event | None = None,
    checkpoint_request: dict | None = None,
    timeout: float = 30.0,
) -> None:
    ensure_not_stopped(stop_event)
    deadline = time.monotonic() + timeout
    last_error = "Chua xac nhan duoc dang nhap thanh cong."
    while time.monotonic() < deadline:
        ensure_not_stopped(stop_event)
        capture_requested_checkpoint(page, checkpoint_request, status)
        try:
            if is_multiple_session_warning_visible(page):
                handle_multiple_session_warning(page, status=status, stop_event=stop_event, timeout=6.0)
                interruptible_sleep(1.0, stop_event)
                continue
            if page.url and "login.do" not in page.url.lower() and not is_login_form_visible(page):
                download_tech_pack_emit_status(status, f"Da xac nhan dang nhap thanh cong: {page.url}")
                return
            if is_login_error_visible(page):
                raise RuntimeError("Trang dang nhap bao loi. Vui long kiem tra User ID/Password hoac trang khach.")
            try:
                locate_search_controls(page, timeout=0.8, stop_event=stop_event)
                download_tech_pack_emit_status(status, "Da xac nhan phien dang nhap bang man hinh Tech Pack search.")
                return
            except Exception:
                pass
        except RuntimeError:
            raise
        except Exception as exc:
            last_error = str(exc)
        interruptible_sleep(0.35, stop_event)
    raise RuntimeError(last_error)


def submit_login_action(page, submit_button, password_input, *, status: StatusCallback | None = None) -> None:
    click_errors: list[str] = []
    try:
        click_locator_robust(submit_button)
        return
    except Exception as exc:
        click_errors.append(str(exc))

    try:
        password_input.focus()
        password_input.press("Enter")
        download_tech_pack_emit_status(status, "Da submit dang nhap bang phim Enter.")
        return
    except Exception as exc:
        click_errors.append(str(exc))

    try:
        submitted = bool(
            submit_button.evaluate(
                """
                element => {
                    const button = element.closest("button, input[type='submit'], input[type='button'], a, [onclick]") || element;
                    const form = button.closest("form") || document.querySelector("form");
                    if (typeof button.click === "function") {
                        button.click();
                        return true;
                    }
                    if (form) {
                        if (typeof form.requestSubmit === "function") {
                            form.requestSubmit();
                        } else {
                            form.submit();
                        }
                        return true;
                    }
                    return false;
                }
                """
            )
        )
        if submitted:
            download_tech_pack_emit_status(status, "Da submit dang nhap bang script fallback.")
            return
    except Exception as exc:
        click_errors.append(str(exc))

    raise RuntimeError("Khong bam duoc Submit dang nhap: " + " | ".join(click_errors))


def submit_login_robust(
    page,
    user: str,
    password: str,
    *,
    status: StatusCallback | None = None,
    stop_event: threading.Event | None = None,
    checkpoint_request: dict | None = None,
) -> None:
    last_error = ""
    for attempt in range(1, 4):
        ensure_not_stopped(stop_event)
        download_tech_pack_emit_status(status, f"Dang dien thong tin dang nhap lan {attempt}...")
        try:
            user_input, password_input, submit_button = locate_login_controls(page, timeout=15.0, stop_event=stop_event)
            fill_input_and_verify(
                user_input,
                user,
                status=status,
                stop_event=stop_event,
                label="User ID",
                attempts=3,
            )
            try:
                password_input.evaluate("element => element.readOnly = false")
            except Exception:
                pass
            fill_input_and_verify(
                password_input,
                password,
                status=status,
                stop_event=stop_event,
                label="Password",
                attempts=3,
            )
            capture_requested_checkpoint(page, checkpoint_request, status)
            download_tech_pack_emit_status(status, f"Dang submit dang nhap lan {attempt}...")
            submit_login_action(page, submit_button, password_input, status=status)
            try:
                page.wait_for_load_state("domcontentloaded", timeout=8000)
            except Exception:
                pass
            wait_for_login_accepted(
                page,
                status=status,
                stop_event=stop_event,
                checkpoint_request=checkpoint_request,
                timeout=18.0 + attempt * 6.0,
            )
            return
        except Exception as exc:
            last_error = str(exc)
            download_tech_pack_emit_status(status, f"Dang nhap lan {attempt} chua thanh cong: {last_error}")
            auto_debug_snapshot(
                page,
                checkpoint_request,
                f"download-tech-pack-login-attempt-{attempt}",
                extra={"step": "login_attempt_failed", "attempt": attempt, "error": last_error},
                status=status,
            )
            interruptible_sleep(1.0, stop_event)
    raise RuntimeError(last_error or "Dang nhap that bai sau nhieu lan thu.")


def wait_for_new_page(
    context,
    known_pages: set,
    timeout: float = 30.0,
    stop_event: threading.Event | None = None,
):
    deadline = time.monotonic() + timeout
    while time.monotonic() < deadline:
        ensure_not_stopped(stop_event)
        for page in context.pages:
            if page not in known_pages:
                return page
        interruptible_sleep(0.2, stop_event)
    raise RuntimeError("Khong bat duoc tab moi cho report.")


def is_report_url(url: str) -> bool:
    lowered = (url or "").lower()
    return "report.do" in lowered or lowered.endswith(".pdf") or "content-type=application/pdf" in lowered


def find_report_candidate_page(context, known_pages: set, source_page):
    for page in context.pages:
        try:
            if page not in known_pages and is_report_url(page.url):
                return page, "new-report-page"
        except Exception:
            continue

    for page in context.pages:
        try:
            if page not in known_pages and (page.url or "") not in ("", "about:blank"):
                return page, "new-page-opened"
        except Exception:
            continue

    for page in context.pages:
        try:
            if page is source_page:
                continue
            if is_report_url(page.url):
                return page, "existing-report-page"
        except Exception:
            continue

    try:
        if is_report_url(source_page.url):
            return source_page, "current-page-report-url"
    except Exception:
        pass

    return None, ""


def submit_report_dialog_if_present(
    page,
    style_code: str,
    *,
    status: StatusCallback | None = None,
    stop_event: threading.Event | None = None,
) -> bool:
    ensure_not_stopped(stop_event)
    if not is_report_dialog_still_visible(page):
        return False

    wait_for_report_dialog_ready(page, style_code, status=status, stop_event=stop_event, timeout=10.0)
    generate_button = locate_generate_report_button(page)
    try:
        click_locator_robust(generate_button)
        download_tech_pack_emit_status(
            status,
            f"Da bam Generate Report trong popup Report Attributes cho {style_code}.",
        )
    except Exception:
        invoked = invoke_generate_report_action(page, generate_button)
        if not invoked:
            raise RuntimeError(f"Khong the kich hoat Generate Report trong popup cho {style_code}.")
        download_tech_pack_emit_status(
            status,
            f"Da kich hoat Generate Report trong popup Report Attributes cho {style_code} bang script.",
        )
    return True


def wait_for_report_target(
    context,
    known_pages: set,
    source_page,
    style_code: str,
    *,
    status: StatusCallback | None = None,
    stop_event: threading.Event | None = None,
    checkpoint_request: dict | None = None,
    timeout: float = 8.0,
):
    deadline = time.monotonic() + timeout
    try:
        dialog_still_visible = is_report_dialog_still_visible(source_page)
    except Exception:
        dialog_still_visible = False

    while time.monotonic() < deadline:
        ensure_not_stopped(stop_event)
        capture_requested_checkpoint(source_page, checkpoint_request, status)

        candidate_page, signal = find_report_candidate_page(context, known_pages, source_page)
        if candidate_page is not None:
            download_tech_pack_emit_status(
                status,
                f"Da nhan dien trang report cho {style_code} qua tin hieu: {signal}.",
            )
            return candidate_page, signal

        try:
            dialog_visible_now = is_report_dialog_still_visible(source_page)
            if dialog_still_visible and not dialog_visible_now:
                download_tech_pack_emit_status(
                    status,
                    f"Popup Report Attributes da dong cho {style_code}. Dang tiep tuc do tim report...",
                )
            dialog_still_visible = dialog_visible_now
        except Exception:
            dialog_still_visible = False

        interruptible_sleep(0.2, stop_event)

    return None, ""


def wait_for_report_page_ready(
    report_page,
    style_code: str,
    *,
    status: StatusCallback | None = None,
    stop_event: threading.Event | None = None,
    checkpoint_request: dict | None = None,
    timeout: float = 30.0,
) -> None:
    ensure_not_stopped(stop_event)
    download_tech_pack_emit_status(status, f"Dang cho tab report cua {style_code} dieu huong xong...")
    deadline = time.monotonic() + timeout
    last_error = "Tab report chua san sang."
    while time.monotonic() < deadline:
        ensure_not_stopped(stop_event)
        capture_requested_checkpoint(report_page, checkpoint_request, status)
        try:
            report_page.wait_for_load_state("domcontentloaded", timeout=1000)
            current_url = report_page.url
            if current_url and current_url != "about:blank":
                download_tech_pack_emit_status(
                    status,
                    f"Tab report cua {style_code} da san sang: {current_url}",
                )
                return
        except Exception as exc:
            last_error = str(exc)
        interruptible_sleep(0.25, stop_event)
    raise RuntimeError(last_error)


def resolve_download_filename(response, url: str) -> str:
    content_disposition = response.headers.get("content-disposition", "")
    if content_disposition:
        match = re.search(r"filename\*?=(?:UTF-8''|\"?)([^\";]+)", content_disposition, re.I)
        if match:
            return Path(unquote(match.group(1).strip().strip('"'))).name

    parsed = urlparse(url)
    query = parse_qs(parsed.query)
    for key in ("filename", "fileName", "name"):
        values = query.get(key)
        if values:
            return Path(unquote(values[0].strip())).name

    path_name = Path(unquote(parsed.path)).name
    return path_name or "tech-pack-report.pdf"


def normalize_filename_by_response(filename: str, response, fallback_stem: str) -> str:
    normalized = Path(filename or "").name or fallback_stem
    content_type = (response.headers.get("content-type", "") or "").lower()
    if "application/pdf" in content_type and not normalized.lower().endswith(".pdf"):
        normalized = f"{Path(normalized).stem or fallback_stem}.pdf"
    elif (
        ("application/zip" in content_type or "application/x-zip" in content_type or "zip" in content_type)
        and not normalized.lower().endswith(".zip")
    ):
        normalized = f"{Path(normalized).stem or fallback_stem}.zip"
    return normalized


def download_report_from_page(
    report_page,
    style_code: str,
    download_dir: str | Path | None = None,
    *,
    unique_filename: bool = False,
) -> Path:
    response = report_page.wait_for_event(
        "response",
        predicate=lambda resp: "application/pdf" in (resp.headers.get("content-type", "") or "").lower()
        or str(resp.url or "").lower().endswith(".pdf"),
        timeout=45000,
    )

    filename = normalize_filename_by_response(
        resolve_download_filename(response, response.url),
        response,
        style_code,
    )
    target_path = build_style_download_path(download_dir, style_code, filename, unique=unique_filename)
    target_path.write_bytes(response.body())
    return target_path


def download_report_from_url(
    context,
    url: str,
    style_code: str,
    download_dir: str | Path | None = None,
    *,
    unique_filename: bool = False,
) -> Path:
    response = context.request.get(url, timeout=45000)
    if not response.ok:
        raise RuntimeError(f"Tai report that bai voi HTTP {response.status}.")

    filename = normalize_filename_by_response(
        resolve_download_filename(response, url),
        response,
        style_code,
    )
    if not filename.lower().endswith(".pdf"):
        filename = f"{Path(filename).stem or style_code}.pdf"

    target_path = build_style_download_path(download_dir, style_code, filename, unique=unique_filename)
    target_path.write_bytes(response.body())
    return target_path


def download_file_from_url(
    context,
    url: str,
    style_code: str,
    download_dir: str | Path | None = None,
    *,
    filename_hint: str | None = None,
) -> Path:
    response = context.request.get(url, timeout=45000)
    if not response.ok:
        raise RuntimeError(f"Tai tep that bai voi HTTP {response.status}.")

    filename = normalize_filename_by_response(
        filename_hint or resolve_download_filename(response, url),
        response,
        style_code,
    )
    target_path = build_style_download_path(download_dir, style_code, filename)
    target_path.write_bytes(response.body())
    return target_path


def locate_inline_file_popup(page):
    last_error = "Khong tim thay popup file noi bo."
    for target in iter_page_and_frames(page):
        candidates = [
            target.locator(
                "xpath=//*[contains(@class, 'window') or contains(@class, 'dialog')][.//*[contains(normalize-space(), '.zip') or contains(normalize-space(), '.pdf')]]"
            ),
            target.locator(
                "xpath=//*[contains(@class, 'window') or contains(@class, 'dialog')][.//*[contains(@class, 'download') or contains(@title, 'Download')]]"
            ),
        ]
        for locator in candidates:
            try:
                candidate = last_visible_locator(locator)
                if candidate is not None:
                    return candidate
            except Exception as exc:
                last_error = str(exc)
    raise RuntimeError(last_error)


def close_inline_popup(dialog, style_code: str, *, status: StatusCallback | None = None, stop_event: threading.Event | None = None) -> None:
    ensure_not_stopped(stop_event)
    close_candidates = [
        dialog.locator("xpath=.//*[contains(@class, 'k-i-close') or contains(@class, 'close') or @title='Close']"),
        dialog.locator("xpath=.//*[normalize-space()='X' or normalize-space()='x' or normalize-space()='✕']"),
    ]
    for locator in close_candidates:
        target = first_visible_locator(locator)
        if target is None:
            continue
        try:
            click_locator_robust(target)
            interruptible_sleep(0.5, stop_event)
            download_tech_pack_emit_status(status, f"Da dong popup file noi bo cho {style_code}.")
            return
        except Exception:
            continue


def restore_constructions_view_after_popup(
    page,
    style_code: str,
    *,
    status: StatusCallback | None = None,
    stop_event: threading.Event | None = None,
) -> None:
    ensure_not_stopped(stop_event)
    try:
        page.keyboard.press("Escape")
    except Exception:
        pass
    interruptible_sleep(0.4, stop_event)

    try:
        close_report_dialog_if_present(page, style_code, status=status, stop_event=stop_event)
    except Exception:
        pass

    try:
        open_named_tab(page, "Constructions", status=status, stop_event=stop_event)
    except Exception:
        pass
    interruptible_sleep(1.0, stop_event)

    try:
        page.wait_for_function(
            """
            () => {
                const rows = Array.from(document.querySelectorAll("tr"));
                return rows.some(row => {
                    const text = ((row.innerText || row.textContent || "") + "").toUpperCase();
                    return text.includes("PATTERN");
                });
            }
            """,
            timeout=8000,
        )
        download_tech_pack_emit_status(
            status,
            f"Da quay ve man hinh Constructions sau popup cho {style_code}.",
        )
    except Exception:
        download_tech_pack_emit_status(
            status,
            f"Chua xac nhan duoc man hinh Constructions sau popup cho {style_code}. Van tiep tuc thu buoc ke tiep.",
        )


def download_from_inline_file_popup(
    page,
    style_code: str,
    *,
    download_dir: str | Path | None = None,
    status: StatusCallback | None = None,
    stop_event: threading.Event | None = None,
    restore_tab_name: str | None = None,
) -> Path | None:
    ensure_not_stopped(stop_event)
    try:
        dialog = locate_inline_file_popup(page)
    except Exception:
        return None

    download_tech_pack_emit_status(status, f"Da nhan dien popup file noi bo cho {style_code}.")
    try:
        download_button = wait_for_first_visible(
            [
                dialog.get_by_role("button", name=re.compile(r"Download|Save", re.I)),
                dialog.locator("xpath=.//*[@title='Download' or @aria-label='Download']"),
                dialog.locator("xpath=.//*[contains(@class, 'download')]"),
                dialog.locator("xpath=.//*[self::a or self::span or self::div][contains(@title, 'Download')]"),
            ],
            timeout=5.0,
        )
        with page.expect_download(timeout=20000) as download_info:
            click_locator_robust(download_button)
        download = download_info.value
        target_path = build_style_download_path(
            download_dir,
            style_code,
            download.suggested_filename or f"{style_code}.bin",
        )
        download.save_as(str(target_path))
        close_inline_popup(dialog, style_code, status=status, stop_event=stop_event)
        if restore_tab_name == "Constructions":
            restore_constructions_view_after_popup(page, style_code, status=status, stop_event=stop_event)
        return target_path
    except Exception:
        pass

    try:
        file_url = page.evaluate(
            """
            () => {
                const roots = Array.from(document.querySelectorAll("div, section"));
                const popup = roots.find(el => {
                    const text = (el.innerText || "").toLowerCase();
                    return text.includes(".zip") || text.includes(".pdf");
                });
                if (!popup) return "";
                const node = popup.querySelector("iframe[src], embed[src], object[data], a[href]");
                if (!node) return "";
                return node.getAttribute("src") || node.getAttribute("data") || node.getAttribute("href") || "";
            }
            """
        ) or ""
    except Exception:
        file_url = ""

    if file_url:
        try:
            target_path = download_file_from_url(page.context, file_url, style_code, download_dir=download_dir)
            close_inline_popup(dialog, style_code, status=status, stop_event=stop_event)
            if restore_tab_name == "Constructions":
                restore_constructions_view_after_popup(page, style_code, status=status, stop_event=stop_event)
            return target_path
        except Exception:
            pass

    return None


def generate_report_for_style(
    page,
    style_code: str,
    *,
    download_dir: str | Path | None = None,
    status: StatusCallback | None = None,
    stop_event: threading.Event | None = None,
    checkpoint_request: dict | None = None,
) -> Path:
    ensure_not_stopped(stop_event)
    download_tech_pack_emit_status(status, f"Dang mo Tech Pack Report cho style {style_code}...")
    wait_for_style_detail_ready(
        page,
        style_code,
        status=status,
        stop_event=stop_event,
        checkpoint_request=checkpoint_request,
    )

    report_dialog_ready = False
    last_report_button_error = ""
    for attempt in range(1, 4):
        ensure_not_stopped(stop_event)
        try:
            tech_pack_button = locate_tech_pack_report_button(page)
            click_locator_robust(tech_pack_button)
            download_tech_pack_emit_status(status, f"Da bam Tech Pack Report cho {style_code} lan {attempt}.")
            wait_for_report_dialog_ready(
                page,
                style_code,
                status=status,
                stop_event=stop_event,
                checkpoint_request=checkpoint_request,
                timeout=12.0 + attempt * 4.0,
            )
            report_dialog_ready = True
            break
        except Exception as exc:
            last_report_button_error = str(exc)
            download_tech_pack_emit_status(
                status,
                f"Chua mo duoc popup report cho {style_code} lan {attempt}: {last_report_button_error}",
            )
            auto_debug_snapshot(
                page,
                checkpoint_request,
                f"tech-pack-report-dialog-not-ready-{style_code}-attempt-{attempt}",
                extra={"style_code": style_code, "step": "report_dialog_not_ready", "attempt": attempt, "error": last_report_button_error},
                status=status,
            )
            close_report_dialog_if_present(page, style_code, status=status, stop_event=stop_event)
            interruptible_sleep(0.8, stop_event)
    if not report_dialog_ready:
        raise RuntimeError(last_report_button_error or f"Khong mo duoc popup report cho {style_code}.")

    download_tech_pack_emit_status(status, f"Dang bam Generate Report cho style {style_code}...")
    known_pages = set(page.context.pages)
    generate_button = locate_generate_report_button(page)
    clicked = False
    try:
        click_locator_robust(generate_button)
        clicked = True
        download_tech_pack_emit_status(status, f"Da click Generate Report cho style {style_code} bang locator UI.")
    except Exception as exc:
        download_tech_pack_emit_status(status, f"Click UI cho Generate Report that bai: {exc}")

    if clicked:
        interruptible_sleep(1.2, stop_event)
        if is_report_dialog_still_visible(page):
            download_tech_pack_emit_status(
                status,
                f"Popup Report Attributes van con mo sau click UI cho {style_code}. Dang chuyen sang onclick script...",
            )
            clicked = False

    if not clicked:
        invoked = invoke_generate_report_action(page, generate_button)
        if not invoked:
            raise RuntimeError("Khong the kich hoat Generate Report tu popup Report Attributes.")
        download_tech_pack_emit_status(status, f"Da kich hoat Generate Report cho style {style_code} bang onclick script.")
    try:
        download_tech_pack_save_checkpoint(
            page,
            f"after-generate-report-{style_code}",
            {"style_code": style_code, "step": "after_generate_report_click"},
        )
    except Exception:
        pass

    report_page, report_signal = wait_for_report_target(
        page.context,
        known_pages,
        page,
        style_code,
        status=status,
        stop_event=stop_event,
        checkpoint_request=checkpoint_request,
        timeout=8.0,
    )

    if report_page is None:
        download_tech_pack_emit_status(
            status,
            f"Chua thay popup report cho {style_code}. Dang thu duong fallback openMultiReport...",
        )
        if invoke_open_multi_report_fallback(page):
            download_tech_pack_emit_status(status, f"Da goi fallback openMultiReport cho {style_code}.")
            report_page, report_signal = wait_for_report_target(
                page.context,
                known_pages,
                page,
                style_code,
                status=status,
                stop_event=stop_event,
                checkpoint_request=checkpoint_request,
                timeout=6.0,
            )

    if report_page is not None:
        download_tech_pack_emit_status(
            status,
            f"Da bat duoc report cho style {style_code} ({report_signal or 'unknown-signal'}).",
        )
        try:
            download_tech_pack_save_checkpoint(
                report_page,
                f"report-page-opened-{style_code}",
                {"style_code": style_code, "step": "report_page_opened", "signal": report_signal},
            )
        except Exception:
            pass
        wait_for_report_page_ready(
            report_page,
            style_code,
            status=status,
            stop_event=stop_event,
            checkpoint_request=checkpoint_request,
        )
        download_tech_pack_emit_status(status, f"Da mo tab report cho {style_code}: {report_page.url}")
        if report_signal == "new-page-opened" and not is_report_url(report_page.url):
            download_tech_pack_emit_status(
                status,
                f"Cua so moi cho {style_code} da mo nhung chua vao report URL. Dang cho dieu huong them...",
            )
            report_page = (
                wait_for_new_page(page.context, known_pages, timeout=1.0, stop_event=stop_event)
                if report_page in known_pages
                else report_page
            )
            deadline = time.monotonic() + 12.0
            while time.monotonic() < deadline and not is_report_url(report_page.url):
                ensure_not_stopped(stop_event)
                capture_requested_checkpoint(report_page, checkpoint_request, status)
                try:
                    report_page.wait_for_load_state("domcontentloaded", timeout=1000)
                except Exception:
                    pass
                interruptible_sleep(0.2, stop_event)
            download_tech_pack_emit_status(status, f"URL sau khi cho them cua {style_code}: {report_page.url}")
        if is_report_url(report_page.url):
            download_tech_pack_emit_status(
                status,
                f"Da nhan dien URL report/PDF cho {style_code}. Dang tai truc tiep bang session hien tai...",
            )
            saved_path = download_report_from_url(page.context, report_page.url, style_code, download_dir=download_dir)
        else:
            saved_path = click_download_button_and_wait(report_page, style_code, download_dir=download_dir, status=status)
            if saved_path is None:
                download_tech_pack_emit_status(
                    status,
                    f"Khong tim thay nut Download/Save ro rang cho {style_code}. Dang doi PDF response tu tab report...",
                )
                saved_path = download_report_from_page(report_page, style_code, download_dir=download_dir)
    else:
        download_tech_pack_emit_status(status, f"Khong thay popup moi cho {style_code}. Dang kiem tra nut Download tren tab hien tai...")
        saved_path = click_download_button_and_wait(page, style_code, download_dir=download_dir, status=status)
        if saved_path is None:
            download_tech_pack_emit_status(
                status,
                f"Khong tim thay nut Download/Save tren tab hien tai cho {style_code}. Dang theo doi PDF response...",
            )
            try:
                download_tech_pack_save_context_checkpoints(
                    page.context,
                    f"no-popup-before-response-{style_code}",
                    {"style_code": style_code, "step": "before_waiting_pdf_response"},
                )
            except Exception:
                pass
            saved_path = download_report_from_page(page, style_code, download_dir=download_dir)

    if report_page is not None and report_page is not page:
        try:
            report_page.close()
            download_tech_pack_emit_status(status, f"Da dong cua so report cho {style_code}.")
        except Exception:
            pass

    close_report_dialog_if_present(
        page,
        style_code,
        status=status,
        stop_event=stop_event,
    )

    download_tech_pack_emit_status(status, f"Da luu report cho {style_code}: {saved_path}")
    return saved_path


def download_pattern_zip_for_style(
    page,
    style_code: str,
    *,
    download_dir: str | Path | None = None,
    status: StatusCallback | None = None,
    stop_event: threading.Event | None = None,
    checkpoint_request: dict | None = None,
) -> list[Path]:
    ensure_not_stopped(stop_event)
    close_report_dialog_if_present(page, style_code, status=status, stop_event=stop_event)
    download_tech_pack_emit_status(status, f"Dang tai pattern cho style {style_code}...")
    open_named_tab(page, "Constructions", status=status, stop_event=stop_event)
    interruptible_sleep(2.0, stop_event)
    auto_debug_snapshot(
        page,
        checkpoint_request,
        f"debug-pattern-constructions-{style_code}",
        extra={"style_code": style_code, "step": "pattern_constructions"},
        status=status,
    )

    extracted_pattern_links: list[dict] = []
    try:
        extracted_pattern_links = page.evaluate(
            """
            () => {
                const rows = Array.from(document.querySelectorAll("tr[id^='listRow_'], tr"));
                const seen = new Set();
                const results = [];
                for (const row of rows) {
                    const text = ((row.innerText || row.textContent || "") + "").toUpperCase();
                    if (!text.includes("PATTERN")) continue;
                    const anchors = row.querySelectorAll(
                        "a[id^='anchor_img_icon_'][href*='#filename='], a[href*='.zip'][href*='#filename='], a[onclick*='showFullScreenImg']"
                    );
                    for (const anchor of anchors) {
                        const href = anchor.getAttribute("href") || "";
                        const onclick = anchor.getAttribute("onclick") || "";
                        const key = href || onclick;
                        if (!key || seen.has(key)) continue;
                        seen.add(key);
                        results.push({
                            href,
                            onclick,
                            title: anchor.getAttribute("title") || "",
                            filename: (href.split("#filename=").pop() || "").trim(),
                        });
                    }
                }
                return results;
            }
            """
        ) or []
    except Exception:
        extracted_pattern_links = []

    if not extracted_pattern_links:
        try:
            extracted_pattern_links = extract_pattern_links_from_html(page.content())
        except Exception:
            extracted_pattern_links = []

    if extracted_pattern_links:
        download_tech_pack_emit_status(
            status,
            f"Da phat hien {len(extracted_pattern_links)} link pattern truc tiep qua href cho {style_code}.",
        )
        auto_debug_snapshot(
            page,
            checkpoint_request,
            f"debug-pattern-links-{style_code}",
            extra={
                "style_code": style_code,
                "step": "pattern_links_detected",
                "pattern_link_count": len(extracted_pattern_links),
            },
            status=status,
        )
        downloaded_paths: list[Path] = []
        for index, item in enumerate(extracted_pattern_links, start=1):
            ensure_not_stopped(stop_event)
            href = html.unescape(str(item.get("href") or ""))
            onclick = html.unescape(str(item.get("onclick") or ""))
            raw_name = str(item.get("filename") or item.get("title") or "").strip()
            if "plm.do?preview=" not in href or "#filename=" not in href:
                download_tech_pack_emit_status(
                    status,
                    f"Bo qua pattern {index}/{len(extracted_pattern_links)} cho {style_code} vi href khong dung dinh dang zip.",
                )
                continue
            try:
                file_url = href.split("plm.do?preview=", 1)[-1].split(",", 1)[0].strip()
                if not file_url:
                    download_tech_pack_emit_status(
                        status,
                        f"Khong tach duoc file URL tu href pattern {index}/{len(extracted_pattern_links)} cho {style_code}.",
                    )
                    continue
                target_path = download_file_from_url(
                    page.context,
                    unquote(file_url),
                    style_code,
                    download_dir=download_dir,
                    filename_hint=raw_name or None,
                )
                downloaded_paths.append(target_path)
                download_tech_pack_emit_status(
                    status,
                    f"Da tai pattern {index}/{len(extracted_pattern_links)} cho {style_code}: {target_path}",
                )
            except Exception:
                if onclick and "showFullScreenImg" in onclick:
                    try:
                        match = re.search(r"showFullScreenImg\('([^']+)'(?:,\s*'[^']*')?(?:,\s*'([^']+)')?\)", onclick)
                        file_url = match.group(1) if match else ""
                        if not file_url:
                            download_tech_pack_emit_status(
                                status,
                                f"Khong tach duoc file URL tu onclick pattern {index}/{len(extracted_pattern_links)} cho {style_code}.",
                            )
                            continue
                        target_path = download_file_from_url(
                            page.context,
                            unquote(file_url),
                            style_code,
                            download_dir=download_dir,
                            filename_hint=raw_name or (match.group(2) if match and match.lastindex and match.lastindex >= 2 else None),
                        )
                        downloaded_paths.append(target_path)
                        download_tech_pack_emit_status(
                            status,
                            f"Da tai pattern {index}/{len(extracted_pattern_links)} cho {style_code}: {target_path}",
                        )
                    except Exception as exc:
                        download_tech_pack_emit_status(
                            status,
                            f"Tai truc tiep pattern {index}/{len(extracted_pattern_links)} qua onclick that bai cho {style_code}: {exc}",
                        )
                        continue
                else:
                    download_tech_pack_emit_status(
                        status,
                        f"Tai truc tiep pattern {index}/{len(extracted_pattern_links)} qua href that bai cho {style_code}.",
                    )
                    continue
        if downloaded_paths:
            return downloaded_paths
        download_tech_pack_emit_status(
            status,
            f"Khong tai duoc pattern nao qua href truc tiep cho {style_code}. Dang chuyen sang fallback UI.",
        )

    pattern_row = page.locator(
        "xpath=//*[contains(normalize-space(), 'PATTERN') or contains(normalize-space(), 'PATTERNS') or contains(normalize-space(), 'USE ATTACHED PATTERN')]/ancestor::tr[1]"
    ).last
    try:
        pattern_row.wait_for(state="attached", timeout=12000)
        try:
            pattern_row.scroll_into_view_if_needed(timeout=2000)
        except Exception:
            pass
    except Exception:
        download_tech_pack_emit_status(status, f"Khong tim thay dong PATTERNS cho {style_code}.")
        return []

    download_tech_pack_emit_status(status, f"Da tim thay dong PATTERNS cho {style_code}.")
    primary_candidates = pattern_row.locator(
        "xpath=.//a[(starts-with(@id, 'anchor_img_icon_') and contains(@href, '#filename=')) or (.//img[contains(@src, 'L.gif') or @title='Large View'])]"
    )
    triggers: list = []
    seen_hrefs: set[str] = set()
    try:
        count = primary_candidates.count()
    except Exception:
        count = 0
    for index in range(count):
        candidate = primary_candidates.nth(index)
        try:
            if not candidate.is_visible():
                continue
        except Exception:
            continue
        href = safe_locator_attr(candidate, "href")
        dedupe_key = href or f"index:{index}"
        if dedupe_key in seen_hrefs:
            continue
        seen_hrefs.add(dedupe_key)
        triggers.append(candidate)

    if not triggers:
        fallback_locators = [
            pattern_row.locator("xpath=.//a[contains(@href, '#filename=')]"),
            pattern_row.locator("xpath=.//a[.//img[contains(@src, 'L.gif') or @title='Large View']]"),
            pattern_row.locator("xpath=.//*[contains(@onclick, 'showFullScreenImg')]"),
            pattern_row.locator("xpath=.//img[contains(@src, 'L.gif') or @title='Large View']"),
            pattern_row.locator("xpath=.//td[last()]//*[self::a or self::img or self::input or self::span or self::div or self::button]"),
        ]
        for locator in fallback_locators:
            try:
                fallback_count = locator.count()
            except Exception:
                fallback_count = 0
            for fallback_index in range(fallback_count):
                candidate = locator.nth(fallback_index)
                try:
                    if not candidate.is_visible():
                        continue
                except Exception:
                    continue
                try:
                    anchor_candidate = first_visible_locator(candidate.locator("xpath=ancestor-or-self::a[1]"))
                    if anchor_candidate is not None:
                        candidate = anchor_candidate
                except Exception:
                    pass
                href = safe_locator_attr(candidate, "href")
                onclick = safe_locator_attr(candidate, "onclick")
                dedupe_key = href or onclick or f"fallback:{fallback_index}"
                if dedupe_key in seen_hrefs:
                    continue
                seen_hrefs.add(dedupe_key)
                triggers.append(candidate)

    if not triggers:
        download_tech_pack_emit_status(status, f"Khong tim thay file pattern de tai cho {style_code}.")
        return []

    downloaded_paths: list[Path] = []
    seen_saved_paths: set[str] = set()

    for index, trigger in enumerate(triggers, start=1):
        ensure_not_stopped(stop_event)
        download_tech_pack_emit_status(status, f"Da tim thay trigger pattern {index}/{len(triggers)} cho {style_code}.")
        try:
            href = safe_locator_attr(trigger, "href")
            if "plm.do?preview=" in href and "#filename=" in href:
                file_url = href.split("plm.do?preview=", 1)[-1].split(",", 1)[0].strip()
                if file_url and file_url != href:
                    target_path = download_file_from_url(
                        page.context,
                        unquote(file_url),
                        style_code,
                        download_dir=download_dir,
                    )
                    path_key = str(target_path).lower()
                    if path_key not in seen_saved_paths:
                        seen_saved_paths.add(path_key)
                        downloaded_paths.append(target_path)
                        download_tech_pack_emit_status(status, f"Da tai pattern {index}/{len(triggers)} cho {style_code}: {target_path}")
                    continue
        except Exception:
            pass

        try:
            known_pattern_pages = set(page.context.pages)
            invoked = bool(
                trigger.evaluate(
                    """
                    element => {
                        const host = element.closest("a[onclick]") || element;
                        const onclickCode = host.getAttribute("onclick") || "";
                        try {
                            if (typeof host.onclick === "function") {
                                host.onclick();
                                return true;
                            }
                            if (onclickCode) {
                                window.eval(onclickCode.replace(/^javascript:/i, ""));
                                return true;
                            }
                        } catch (error) {
                        }
                        return false;
                    }
                    """
                )
            )
            if invoked:
                interruptible_sleep(1.0, stop_event)
                target_path = download_from_inline_file_popup(
                    page,
                    style_code,
                    download_dir=download_dir,
                    status=status,
                    stop_event=stop_event,
                    restore_tab_name="Constructions",
                )
                if target_path is not None:
                    path_key = str(target_path).lower()
                    if path_key not in seen_saved_paths:
                        seen_saved_paths.add(path_key)
                        downloaded_paths.append(target_path)
                        download_tech_pack_emit_status(status, f"Da tai pattern {index}/{len(triggers)} cho {style_code}: {target_path}")
                    continue
                report_page, _ = wait_for_report_target(
                    page.context,
                    known_pattern_pages,
                    page,
                    style_code,
                    status=status,
                    stop_event=stop_event,
                    timeout=6.0,
                )
                if report_page is not None:
                    try:
                        target_path = download_file_from_url(
                            page.context,
                            report_page.url,
                            style_code,
                            download_dir=download_dir,
                        )
                        try:
                            if report_page is not page:
                                report_page.close()
                        except Exception:
                            pass
                        path_key = str(target_path).lower()
                        if path_key not in seen_saved_paths:
                            seen_saved_paths.add(path_key)
                            downloaded_paths.append(target_path)
                            download_tech_pack_emit_status(status, f"Da tai pattern {index}/{len(triggers)} cho {style_code}: {target_path}")
                        continue
                    except Exception:
                        pass
        except Exception:
            pass

        known_pages = set(page.context.pages)
        try:
            download_tech_pack_save_checkpoint(
                page,
                f"pattern-before-click-{style_code}-{index}",
                {"style_code": style_code, "step": "pattern_before_click", "index": index},
            )
        except Exception:
            pass
        try:
            with page.expect_download(timeout=20000) as download_info:
                click_locator_robust(trigger)
            download = download_info.value
            target_path = build_style_download_path(
                download_dir,
                style_code,
                download.suggested_filename or f"{style_code}-pattern-{index}.zip",
            )
            download.save_as(str(target_path))
            path_key = str(target_path).lower()
            if path_key not in seen_saved_paths:
                seen_saved_paths.add(path_key)
                downloaded_paths.append(target_path)
                download_tech_pack_emit_status(status, f"Da tai pattern {index}/{len(triggers)} cho {style_code}: {target_path}")
            continue
        except Exception:
            pass

        try:
            click_locator_robust(trigger)
        except Exception:
            pass

        interruptible_sleep(1.0, stop_event)
        try:
            download_tech_pack_save_checkpoint(
                page,
                f"pattern-after-click-{style_code}-{index}",
                {"style_code": style_code, "step": "pattern_after_click", "index": index},
            )
        except Exception:
            pass

        try:
            submit_report_dialog_if_present(page, style_code, status=status, stop_event=stop_event)
        except Exception as exc:
            download_tech_pack_emit_status(status, f"Xu ly popup pattern {index}/{len(triggers)} cho {style_code} that bai: {exc}")

        target_path = download_from_inline_file_popup(
            page,
            style_code,
            download_dir=download_dir,
            status=status,
            stop_event=stop_event,
            restore_tab_name="Constructions",
        )
        if target_path is not None:
            path_key = str(target_path).lower()
            if path_key not in seen_saved_paths:
                seen_saved_paths.add(path_key)
                downloaded_paths.append(target_path)
                download_tech_pack_emit_status(status, f"Da tai pattern {index}/{len(triggers)} cho {style_code}: {target_path}")
            continue

        report_page, _ = wait_for_report_target(
            page.context,
            known_pages,
            page,
            style_code,
            status=status,
            stop_event=stop_event,
            timeout=10.0,
        )
        if report_page is not None:
            try:
                wait_for_report_page_ready(report_page, style_code, status=status, stop_event=stop_event, timeout=15.0)
            except Exception:
                pass
            interruptible_sleep(1.0, stop_event)
            try:
                if is_report_url(report_page.url):
                    target_path = download_report_from_url(page.context, report_page.url, style_code, download_dir=download_dir)
                else:
                    target_path = download_file_from_url(page.context, report_page.url, style_code, download_dir=download_dir)
                try:
                    if report_page is not page:
                        report_page.close()
                except Exception:
                    pass
                path_key = str(target_path).lower()
                if path_key not in seen_saved_paths:
                    seen_saved_paths.add(path_key)
                    downloaded_paths.append(target_path)
                    download_tech_pack_emit_status(status, f"Da tai pattern {index}/{len(triggers)} cho {style_code}: {target_path}")
                continue
            except Exception:
                pass

            target_path = click_download_button_and_wait(report_page, style_code, download_dir=download_dir, status=status)
            if target_path is not None:
                path_key = str(target_path).lower()
                if path_key not in seen_saved_paths:
                    seen_saved_paths.add(path_key)
                    downloaded_paths.append(target_path)
                    download_tech_pack_emit_status(status, f"Da tai pattern {index}/{len(triggers)} cho {style_code}: {target_path}")
                continue

        download_tech_pack_emit_status(status, f"Khong bat duoc pattern {index}/{len(triggers)} cho {style_code}.")
        auto_debug_snapshot(
            page,
            checkpoint_request,
            f"debug-pattern-miss-{style_code}-{index}",
            extra={
                "style_code": style_code,
                "step": "pattern_miss",
                "index": index,
                "trigger_count": len(triggers),
            },
            status=status,
        )

    if not downloaded_paths:
        try:
            download_tech_pack_save_context_checkpoints(
                page.context,
                f"pattern-no-download-{style_code}",
                {"style_code": style_code, "step": "pattern_no_download"},
            )
        except Exception:
            pass
        download_tech_pack_emit_status(status, f"Khong bat duoc file pattern cho {style_code}.")

    return downloaded_paths


def download_comment_report_for_style(
    page,
    style_code: str,
    *,
    download_dir: str | Path | None = None,
    status: StatusCallback | None = None,
    stop_event: threading.Event | None = None,
    checkpoint_request: dict | None = None,
) -> list[Path]:
    ensure_not_stopped(stop_event)
    close_report_dialog_if_present(page, style_code, status=status, stop_event=stop_event)
    download_tech_pack_emit_status(status, f"Dang tai comment report cho style {style_code}...")
    open_named_tab(page, "POM", status=status, stop_event=stop_event)
    interruptible_sleep(2.0, stop_event)

    known_pages = set(page.context.pages)
    fit_eval_button = wait_for_first_visible(
        [
            page.locator(
                "xpath=//button[@type='button' and contains(@onclick, '_plmpom_viewFitEval') and contains(normalize-space(), 'View Fit Evals')]"
            ),
            page.get_by_role("button", name=re.compile(r"View\s*Fit\s*Evals", re.I)),
            page.locator("xpath=//*[self::button or self::a or self::span][contains(normalize-space(), 'View Fit Evals')]"),
            page.locator("xpath=//*[contains(@onclick, 'FitEval') or contains(@onclick, 'fitEval')]"),
        ],
        timeout=15.0,
    )
    download_tech_pack_emit_status(status, f"Da tim thay nut View Fit Evals cho {style_code}.")
    fit_eval_opened = False
    try:
        fit_eval_opened = bool(
            fit_eval_button.evaluate(
                """
                element => {
                    const onclickCode = element.getAttribute("onclick") || "";
                    try {
                        if (typeof window._plmpom_viewFitEval === "function") {
                            window._plmpom_viewFitEval();
                            return true;
                        }
                        if (onclickCode) {
                            window.eval(onclickCode.replace(/^javascript:/i, ""));
                            return true;
                        }
                        if (typeof element.click === "function") {
                            element.click();
                            return true;
                        }
                    } catch (error) {
                    }
                    return false;
                }
                """
            )
        )
    except Exception:
        fit_eval_opened = False
    if not fit_eval_opened:
        click_locator_robust(fit_eval_button)
    interruptible_sleep(2.0, stop_event)

    fit_eval_page = page
    try:
        fit_eval_page = wait_for_new_page(page.context, known_pages, timeout=3.0, stop_event=stop_event)
    except Exception:
        fit_eval_page = page
    try:
        fit_eval_page.wait_for_load_state("domcontentloaded", timeout=5000)
    except Exception:
        pass

    download_tech_pack_emit_status(status, f"Da vao man hinh Fit Eval cho {style_code}: {fit_eval_page.url}")
    auto_debug_snapshot(
        fit_eval_page,
        checkpoint_request,
        f"debug-fit-eval-list-{style_code}",
        extra={"style_code": style_code, "step": "fit_eval_list"},
        status=status,
    )
    try:
        download_tech_pack_save_checkpoint(
            fit_eval_page,
            f"comment-fit-eval-page-{style_code}",
            {"style_code": style_code, "step": "comment_fit_eval_page"},
        )
    except Exception:
        pass

    fit_eval_entries: list[dict[str, str | int]] = []
    try:
        fit_eval_entries = fit_eval_page.evaluate(
            """
            () => Array.from(
                document.querySelectorAll("table#MainSection_left_table a[href*='showFitEvaluationOverview']")
            ).map((anchor, index) => ({
                id: anchor.id || "",
                keyinfo: anchor.getAttribute("keyinfo") || "",
                label: (anchor.textContent || "").trim(),
                row_index: index,
            })).filter(item => item.id || item.keyinfo)
            """
        ) or []
    except Exception:
        fit_eval_entries = []

    if not fit_eval_entries:
        report_links = fit_eval_page.locator(
            "xpath=//table[@id='MainSection_left_table']//a[contains(@href, 'showFitEvaluationOverview') and normalize-space() != '']"
        )
        try:
            report_count = report_links.count()
        except Exception:
            report_count = 0
        fit_eval_entries = [{"id": "", "keyinfo": "", "label": "", "row_index": idx} for idx in range(max(report_count, 1))]

    downloaded_paths: list[Path] = []
    report_count = max(len(fit_eval_entries), 1)
    for report_index, fit_eval_entry in enumerate(fit_eval_entries):
        ensure_not_stopped(stop_event)
        download_tech_pack_emit_status(
            status,
            f"Dang mo Fit Eval {report_index + 1}/{report_count} cho {style_code}..."
        )
        try:
            row_index = int(fit_eval_entry.get("row_index", report_index))
            keyinfo = str(fit_eval_entry.get("keyinfo") or "")
            anchor_id = str(fit_eval_entry.get("id") or "")
            opened = bool(
                fit_eval_page.evaluate(
                    """
                    payload => {
                        const { rowIndex, keyinfo, anchorId } = payload;
                        const escaped = value => {
                            try {
                                return CSS.escape(value);
                            } catch (error) {
                                return value;
                            }
                        };
                        const anchor =
                            (anchorId && document.querySelector(`#${escaped(anchorId)}`)) ||
                            (keyinfo && document.querySelector(`table#MainSection_left_table a[keyinfo="${keyinfo}"]`)) ||
                            document.querySelectorAll("table#MainSection_left_table a[href*='showFitEvaluationOverview']")[rowIndex] ||
                            null;
                        if (!anchor) return false;
                        try {
                            if (typeof window.setRowInd === "function") {
                                window.setRowInd(rowIndex);
                            }
                        } catch (error) {
                        }
                        try {
                            if (typeof window.setComponentKeyInfo === "function") {
                                window.setComponentKeyInfo(anchor);
                            }
                        } catch (error) {
                        }
                        try {
                            if (typeof window.showFitEvaluationOverview === "function") {
                                window.showFitEvaluationOverview();
                                return true;
                            }
                        } catch (error) {
                        }
                        try {
                            const href = anchor.getAttribute("href") || "";
                            if (href.toLowerCase().startsWith("javascript:")) {
                                window.eval(href.replace(/^javascript:/i, ""));
                                return true;
                            }
                        } catch (error) {
                        }
                        try {
                            if (typeof anchor.click === "function") {
                                anchor.click();
                                return true;
                            }
                        } catch (error) {
                        }
                        return false;
                    }
                    """,
                    {"rowIndex": row_index, "keyinfo": keyinfo, "anchorId": anchor_id},
                )
            )
            if not opened:
                link_to_open = None
                if anchor_id:
                    link_to_open = fit_eval_page.locator(f"#{anchor_id}")
                elif keyinfo:
                    safe_keyinfo = keyinfo.replace("'", "\\'")
                    link_to_open = fit_eval_page.locator(
                        f"xpath=//table[@id='MainSection_left_table']//a[@keyinfo='{safe_keyinfo}']"
                    )
                if link_to_open is not None:
                    opened = invoke_javascript_link(link_to_open)
                    if not opened:
                        click_locator_robust(link_to_open)
            try:
                fit_eval_page.wait_for_function(
                    """
                    () => {
                        const button = document.querySelector("#FITEVALREPORTbtnCtr");
                        if (button && button.offsetParent !== null) return true;
                        const title = Array.from(document.querySelectorAll("label, h1, h2, h3, div, span"))
                            .some(node => ((node.innerText || node.textContent || "").trim() === "Fit Evaluation:"));
                        return title;
                    }
                    """,
                    timeout=15000,
                )
            except Exception:
                interruptible_sleep(2.0, stop_event)
            try:
                fit_eval_page.wait_for_load_state("domcontentloaded", timeout=5000)
            except Exception:
                pass
        except Exception:
            pass
        auto_debug_snapshot(
            fit_eval_page,
            checkpoint_request,
            f"debug-fit-eval-opened-{style_code}-{report_index + 1}",
            extra={
                "style_code": style_code,
                "step": "fit_eval_opened",
                "index": report_index + 1,
                "report_count": report_count,
                "fit_eval_entry": fit_eval_entry,
            },
            status=status,
        )

        try:
            report_button = wait_for_first_visible(
                [
                    fit_eval_page.locator(
                        "xpath=//td[@id='FITEVALREPORTbtnCtr' and contains(@onclick, 'printReports') and contains(@onclick, 'fiteval')]"
                    ),
                    fit_eval_page.locator(
                        "xpath=(//td[@id='FITEVALREPORTbtnCtr' and contains(@onclick, 'printReports') and contains(@onclick, 'fiteval')])[last()]"
                    ),
                    fit_eval_page.get_by_role("button", name=re.compile(r"Fit\s*Evaluation\s*Report", re.I)),
                ],
                timeout=20.0,
            )
            known_report_pages = set(page.context.pages)
            download_tech_pack_emit_status(status, f"Da tim thay nut Fit Evaluation Report {report_index + 1}/{report_count} cho {style_code}.")
            auto_debug_snapshot(
                fit_eval_page,
                checkpoint_request,
                f"debug-fit-eval-report-button-{style_code}-{report_index + 1}",
                extra={
                    "style_code": style_code,
                    "step": "fit_eval_report_button",
                    "index": report_index + 1,
                },
                status=status,
            )
            target_path = None

            def invoke_report_action() -> None:
                invoked_local = bool(
                    report_button.evaluate(
                        """
                        element => {
                            const host = element.closest("td[onclick], [onclick], a, button, div, span") || element;
                            const onclickCode = host.getAttribute("onclick") || element.getAttribute("onclick") || "";
                            try {
                                if (typeof window.printReports === "function") {
                                    const match = onclickCode.match(/printReports\((\d+)\s*,\s*(\d+)\s*,\s*['"]([^'"]+)['"]\)/i);
                                    if (match) {
                                        window.printReports(Number(match[1]), Number(match[2]), match[3]);
                                        return true;
                                    }
                                }
                                if (typeof host.onclick === "function") {
                                    host.onclick();
                                    return true;
                                }
                                if (onclickCode) {
                                    window.eval(onclickCode.replace(/^javascript:/i, ""));
                                    return true;
                                }
                            } catch (error) {
                            }
                            if (typeof element.click === "function") {
                                element.click();
                                return true;
                            }
                            return false;
                        }
                        """
                    )
                )
                if not invoked_local:
                    click_locator_robust(report_button)

            try:
                with fit_eval_page.expect_download(timeout=8000) as download_info:
                    invoke_report_action()
                download = download_info.value
                target_path = build_style_download_path(
                    download_dir,
                    style_code,
                    download.suggested_filename or f"{style_code}-fit-eval.pdf",
                    unique=True,
                )
                download.save_as(str(target_path))
            except Exception:
                target_path = None

            if target_path is None:
                try:
                    with fit_eval_page.expect_event(
                        "response",
                        predicate=lambda resp: "application/pdf" in (resp.headers.get("content-type", "") or "").lower()
                        or str(resp.url or "").lower().endswith(".pdf"),
                        timeout=8000,
                    ) as response_info:
                        invoke_report_action()
                    response = response_info.value
                    filename = normalize_filename_by_response(
                        resolve_download_filename(response, response.url),
                        response,
                        f"{style_code}-fit-eval",
                    )
                    target_path = build_style_download_path(download_dir, style_code, filename, unique=True)
                    target_path.write_bytes(response.body())
                except Exception:
                    target_path = None

            if target_path is not None:
                downloaded_paths.append(target_path)
                download_tech_pack_emit_status(
                    status,
                    f"Da tai comment report {report_index + 1}/{report_count} cho {style_code}: {target_path}",
                )
                continue
        except Exception:
            try:
                download_tech_pack_save_context_checkpoints(
                    page.context,
                    f"comment-no-report-{style_code}-{report_index + 1}",
                    {"style_code": style_code, "step": "comment_no_report", "index": report_index + 1},
                )
            except Exception:
                pass
            continue

        interruptible_sleep(1.5, stop_event)
        try:
            download_tech_pack_save_checkpoint(
                fit_eval_page,
                f"comment-after-report-click-{style_code}-{report_index + 1}",
                {"style_code": style_code, "step": "comment_after_report_click", "index": report_index + 1},
            )
        except Exception:
            pass

        try:
            submit_report_dialog_if_present(fit_eval_page, style_code, status=status, stop_event=stop_event)
        except Exception:
            pass

        target_path = download_from_inline_file_popup(
            fit_eval_page,
            style_code,
            download_dir=download_dir,
            status=status,
            stop_event=stop_event,
        )
        if target_path is None:
            report_page, _ = wait_for_report_target(
                page.context,
                known_report_pages,
                fit_eval_page,
                style_code,
                status=status,
                stop_event=stop_event,
                timeout=8.0,
            )
            if report_page is None:
                continue

            wait_for_report_page_ready(report_page, style_code, status=status, stop_event=stop_event)
            if is_report_url(report_page.url):
                target_path = download_report_from_url(
                    page.context,
                    report_page.url,
                    style_code,
                    download_dir=download_dir,
                    unique_filename=True,
                )
            else:
                try:
                    target_path = download_report_from_url(
                        page.context,
                        report_page.url,
                        style_code,
                        download_dir=download_dir,
                        unique_filename=True,
                    )
                except Exception:
                    target_path = click_download_button_and_wait(
                        report_page,
                        style_code,
                        download_dir=download_dir,
                        status=status,
                        unique_filename=True,
                    )
                    if target_path is None:
                        target_path = download_report_from_page(
                            report_page,
                            style_code,
                            download_dir=download_dir,
                            unique_filename=True,
                        )
            try:
                if report_page is not fit_eval_page:
                    report_page.close()
            except Exception:
                pass

        if target_path is not None:
            downloaded_paths.append(target_path)
            download_tech_pack_emit_status(
                status,
                f"Da tai comment report {report_index + 1}/{report_count} cho {style_code}: {target_path}",
            )

    try:
        if fit_eval_page is not page:
            fit_eval_page.close()
    except Exception:
        pass

    return downloaded_paths


def run_download_tech_pack(
    user: str,
    password: str,
    *,
    styles: list[str] | None = None,
    download_dir: str | Path | None = None,
    headless: bool = False,
    debug_mode: bool = False,
    include_pattern_zip: bool = False,
    include_comment_report: bool = False,
    status_callback: StatusCallback | None = None,
    stop_event: threading.Event | None = None,
    checkpoint_request: dict | None = None,
) -> str:
    if sync_playwright is None:
        raise RuntimeError(
            "Playwright chua san sang trong moi truong hien tai. "
            f"Chi tiet: {PLAYWRIGHT_IMPORT_ERROR or 'missing playwright package'}"
        )
    if not user.strip():
        raise ValueError("User ID dang rong.")
    if not password:
        raise ValueError("Password dang rong.")

    checkpoint_request = dict(checkpoint_request or {})
    checkpoint_request["debug_mode"] = bool(debug_mode or checkpoint_request.get("debug_mode"))

    with sync_playwright() as playwright:
        browser = playwright.chromium.launch(channel="chrome", headless=headless)
        context = browser.new_context(accept_downloads=True)
        page = context.new_page()

        try:
            ensure_not_stopped(stop_event)
            download_tech_pack_emit_status(status_callback, "Dang mo trang dang nhap...")
            page.goto(DOWNLOAD_TECH_PACK_LOGIN_URL, wait_until="domcontentloaded", timeout=60000)
            capture_requested_checkpoint(page, checkpoint_request, status_callback)

            submit_login_robust(
                page,
                user,
                password,
                status=status_callback,
                stop_event=stop_event,
                checkpoint_request=checkpoint_request,
            )

            try:
                page.wait_for_load_state("networkidle", timeout=15000)
            except PlaywrightTimeoutError:
                pass

            results: list[str] = [f"Dang submit form thanh cong.\nTrang hien tai: {page.url}"]

            ensure_not_stopped(stop_event)
            download_tech_pack_emit_status(status_callback, "Dang vao man hinh Tech Pack...")
            open_tech_pack_search_page(
                page,
                status=status_callback,
                stop_event=stop_event,
                checkpoint_request=checkpoint_request,
            )

            if styles:
                for style_code in styles:
                    ensure_not_stopped(stop_event)
                    try:
                        reset_to_tech_pack_search(
                            page,
                            style_code=style_code,
                            status=status_callback,
                            stop_event=stop_event,
                            checkpoint_request=checkpoint_request,
                        )
                        opened_url = search_style_and_open(
                            page,
                            style_code,
                            status=status_callback,
                            stop_event=stop_event,
                            checkpoint_request=checkpoint_request,
                        )
                        downloaded_items: list[str] = []
                        report_path = generate_report_for_style(
                            page,
                            style_code,
                            download_dir=download_dir,
                            status=status_callback,
                            stop_event=stop_event,
                            checkpoint_request=checkpoint_request,
                        )
                        downloaded_items.append(str(report_path))

                        if include_pattern_zip:
                            pattern_paths = download_pattern_zip_for_style(
                                page,
                                style_code,
                                download_dir=download_dir,
                                status=status_callback,
                                stop_event=stop_event,
                                checkpoint_request=checkpoint_request,
                            )
                            for pattern_path in pattern_paths:
                                downloaded_items.append(str(pattern_path))

                        if include_comment_report:
                            comment_paths = download_comment_report_for_style(
                                page,
                                style_code,
                                download_dir=download_dir,
                                status=status_callback,
                                stop_event=stop_event,
                                checkpoint_request=checkpoint_request,
                            )
                            for comment_path in comment_paths:
                                downloaded_items.append(str(comment_path))

                        results.append(f"{style_code}: {opened_url} -> {' | '.join(downloaded_items)}")
                    except Exception as exc:
                        if is_style_search_not_found_error(exc, style_code):
                            download_tech_pack_emit_status(
                                status_callback,
                                f"Khong tim thay style {style_code}. Bo qua va chuyen sang style tiep theo.",
                            )
                            results.append(f"{style_code}: SKIPPED -> style not found")
                            continue
                        raise
            else:
                results.append("Khong co Style list, da dung tai man hinh Tech Pack.")

            result = "\n".join(results)

            if headless:
                context.close()
                browser.close()

            return result
        except Exception:
            context.close()
            browser.close()
            raise
