"""Screenshot operation: full-page / element / region, with optional
hide-before / native mask / drawn annotations.
"""
from __future__ import annotations

import json
import sys
from pathlib import Path

from . import scenes
from .bootstrap import ensure_runtime
from .browser import (
    apply_pre_action_waits,
    build_context_options,
    launch_with_browser_install,
)
from .overlay import ensure_overlay


def _apply_hide(page, selectors: list[str]) -> None:
    """display:none every match of every selector (idempotent, scoped to this page)."""
    if not selectors:
        return
    page.evaluate(
        """(sels) => {
          sels.forEach((s) => {
            try {
              document.querySelectorAll(s).forEach((el) => {
                el.style.setProperty('display', 'none', 'important');
              });
            } catch (e) { /* invalid selector — skip */ }
          });
        }""",
        selectors,
    )


def _apply_annotations(page, annotations: list[dict], settle_ms: int) -> None:
    """Reuse scene handlers to draw static overlays before snapshotting."""
    if not annotations:
        return
    ensure_overlay(page)
    for ann in annotations:
        handler = scenes.get_handler(ann.get("type", ""))
        if not handler:
            print(
                f"[annotate] skip unknown type: {ann.get('type')!r} "
                f"(known: {', '.join(scenes.known_types())})",
                file=sys.stderr,
            )
            continue
        # Scene handlers wait on `duration`; for static annotations we want
        # 0-wait so they all stack up before the snapshot fires.
        ann_copy = dict(ann)
        ann_copy.setdefault("duration", 0)
        ann_copy.setdefault("holdMs", 0)
        try:
            handler(page, ann_copy)
        except Exception as e:
            print(f"[annotate] {ann.get('type')} failed: {e}", file=sys.stderr)
    if settle_ms > 0:
        page.wait_for_timeout(settle_ms)


def _load_annotations(spec) -> tuple[list[dict], int]:
    """Accept either a list, a dict with 'annotations' key, or a path to such."""
    if spec is None:
        return [], 0
    settle_ms = 900
    if isinstance(spec, (str, Path)):
        p = Path(spec)
        if p.exists():
            data = json.loads(p.read_text())
        else:
            data = json.loads(str(spec))
    else:
        data = spec
    if isinstance(data, list):
        return data, settle_ms
    if isinstance(data, dict):
        return list(data.get("annotations") or []), int(data.get("settleMs", settle_ms))
    return [], settle_ms


def _build_mask_locators(page, selectors: list[str]):
    out = []
    for s in selectors or []:
        try:
            out.append(page.locator(s))
        except Exception:
            continue
    return out


def do_screenshot(cfg: dict) -> Path:
    """Take a screenshot per cfg. Returns the absolute output path."""
    ensure_runtime()
    from playwright.sync_api import sync_playwright
    from playwright.sync_api import TimeoutError as PlaywrightTimeoutError

    output = Path(cfg["output"]).resolve()
    output.parent.mkdir(parents=True, exist_ok=True)

    annotations, settle_ms = _load_annotations(cfg.get("annotate"))

    with sync_playwright() as p:
        browser = launch_with_browser_install(p, cfg.get("browser"))
        try:
            context = browser.new_context(**build_context_options(p, cfg, None))
            if cfg.get("timeout"):
                context.set_default_timeout(cfg["timeout"])
            page = context.new_page()
            try:
                page.goto(cfg["url"], wait_until="domcontentloaded")
                apply_pre_action_waits(page, cfg)

                _apply_hide(page, cfg.get("hideSelectors") or [])
                _apply_annotations(page, annotations, settle_ms)

                mask_locators = _build_mask_locators(page, cfg.get("maskSelectors") or [])
                mask_kwargs: dict = {}
                if mask_locators:
                    mask_kwargs["mask"] = mask_locators
                    if cfg.get("maskColor"):
                        mask_kwargs["mask_color"] = cfg["maskColor"]

                if cfg.get("selector"):
                    locator = page.locator(cfg["selector"]).first
                    # 先用较短超时确认元素确实存在。否则选择器不匹配时，
                    # scroll_into_view_if_needed() 会干等全局超时（默认 30s）才抛出
                    # 一大段难懂的 Playwright traceback、以 exit 1 崩溃。这里快速失败，
                    # 返回一条模型可读、可据以换选择器的错误信息。
                    probe_timeout = cfg.get("selectorTimeout")
                    if probe_timeout is None:
                        # 页面 domcontentloaded 已触发，元素在则几乎立即命中；
                        # 不在则无需等满 30s。取全局 timeout 与 8s 的较小值。
                        probe_timeout = min(cfg.get("timeout") or 8000, 8000)
                    try:
                        locator.wait_for(state="attached", timeout=probe_timeout)
                    except PlaywrightTimeoutError:
                        raise SystemExit(
                            f'selector "{cfg["selector"]}" 在页面上未找到'
                            f'（{cfg["url"]}）。该元素可能不存在或页面结构已变化。'
                            f'请换用其它选择器，或去掉 --selector 改用整页/区域截屏。'
                        )
                    if cfg.get("scrollIntoView", True):
                        locator.scroll_into_view_if_needed()
                    if isinstance(cfg.get("clip"), (list, tuple)) and len(cfg["clip"]) == 4:
                        box = locator.bounding_box()
                        if not box:
                            raise SystemExit(
                                f'selector "{cfg["selector"]}" has no bounding box'
                            )
                        dx, dy, w, h = cfg["clip"]
                        page.screenshot(
                            path=str(output),
                            clip={
                                "x": box["x"] + dx,
                                "y": box["y"] + dy,
                                "width": w,
                                "height": h,
                            },
                            **mask_kwargs,
                        )
                    else:
                        locator.screenshot(path=str(output), **mask_kwargs)
                elif isinstance(cfg.get("clip"), (list, tuple)) and len(cfg["clip"]) == 4:
                    x, y, w, h = cfg["clip"]
                    page.screenshot(
                        path=str(output),
                        clip={"x": x, "y": y, "width": w, "height": h},
                        **mask_kwargs,
                    )
                else:
                    page.screenshot(
                        path=str(output),
                        full_page=bool(cfg.get("fullPage")),
                        **mask_kwargs,
                    )
            finally:
                context.close()
        finally:
            browser.close()
    return output
