"""Resilient xdotool windowactivate — sync retry + nonsync fallback (P0 GNOME/Wayland)."""

from __future__ import annotations

import logging
import subprocess
import time
from dataclasses import dataclass, field
from typing import Any

logger = logging.getLogger(__name__)

DEFAULT_ACTIVATE_TIMEOUT = 12.0
RETRY_DELAY_SEC = 0.5


@dataclass
class WindowActivateResult:
    ok: bool
    returncode: int
    stdout: str
    stderr: str
    window_id: str = ""
    command: list[str] = field(default_factory=list)
    timeout_sec: float = 0.0
    attempts: list[dict[str, Any]] = field(default_factory=list)
    used_fallback: bool = False
    degraded: bool = False

    @property
    def detail(self) -> str:
        if self.ok:
            return self.window_id or self.stdout or "activated"
        return self.stderr or "windowactivate_failed"

    def as_evidence(self) -> dict[str, Any]:
        return {
            "window_id": self.window_id,
            "focus_command": " ".join(self.command),
            "focus_timeout_sec": self.timeout_sec,
            "focus_attempts": list(self.attempts),
            "focus_fallback": self.used_fallback,
            "focus_degraded": self.degraded,
        }


def _infer_window_id(cmd: list[str]) -> str:
    if not cmd:
        return ""
    if "windowactivate" not in cmd:
        return cmd[-1] if cmd else ""
    idx = cmd.index("windowactivate")
    tail = cmd[idx + 1 :]
    if tail and tail[0] == "--sync":
        return tail[1] if len(tail) > 1 else ""
    return tail[0] if tail else ""


def _without_sync(cmd: list[str]) -> list[str] | None:
    if "--sync" not in cmd:
        return None
    return [part for part in cmd if part != "--sync"]


def _run_once(cmd: list[str], *, timeout: float) -> tuple[int, str, str, bool]:
    try:
        proc = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=timeout,
            check=False,
        )
        return (
            int(proc.returncode),
            str(proc.stdout or "").strip(),
            str(proc.stderr or "").strip(),
            False,
        )
    except subprocess.TimeoutExpired as exc:
        return 1, "", f"timeout after {timeout}s: {exc}", True
    except FileNotFoundError as exc:
        return 1, "", str(exc), False


def run_windowactivate(
    cmd: list[str],
    *,
    timeout: float = DEFAULT_ACTIVATE_TIMEOUT,
    retry_delay: float = RETRY_DELAY_SEC,
    window_id: str = "",
) -> WindowActivateResult:
    """
    Run xdotool windowactivate with --sync retry and nonsync fallback.

  1. Try --sync (catch TimeoutExpired)
    2. Retry --sync once after short delay
    3. Fallback to windowactivate without --sync
    """
    target_id = str(window_id or _infer_window_id(cmd) or "").strip()
    attempts: list[dict[str, Any]] = []
    had_sync = "--sync" in cmd

    def _attempt(command: list[str], label: str) -> tuple[int, str, str, bool]:
        code, out, err, timed_out = _run_once(command, timeout=timeout)
        entry = {
            "label": label,
            "command": " ".join(command),
            "window_id": target_id,
            "timeout_sec": timeout,
            "returncode": code,
            "timed_out": timed_out,
            "stderr": err,
        }
        attempts.append(entry)
        logger.info(
            "xdotool windowactivate %s window_id=%s timeout=%.1fs rc=%s timed_out=%s",
            label,
            target_id or "?",
            timeout,
            code,
            timed_out,
        )
        return code, out, err, timed_out

    code, out, err, _ = _attempt(cmd, "sync")
    if code != 0 and had_sync:
        time.sleep(retry_delay)
        code, out, err, _ = _attempt(cmd, "sync_retry")

    used_fallback = False
    degraded = False
    if code != 0 and had_sync:
        fallback_cmd = _without_sync(cmd)
        if fallback_cmd is not None:
            used_fallback = True
            code, out, err, _ = _attempt(fallback_cmd, "nonsync_fallback")
            if code == 0:
                degraded = True

    ok = code == 0
    if ok and used_fallback:
        logger.info(
            "xdotool focus degraded (nonsync fallback) window_id=%s",
            target_id or "?",
        )
    elif not ok:
        logger.warning(
            "xdotool windowactivate failed window_id=%s attempts=%s",
            target_id or "?",
            attempts,
        )

    return WindowActivateResult(
        ok=ok,
        returncode=code,
        stdout=out,
        stderr=err,
        window_id=target_id,
        command=list(cmd),
        timeout_sec=timeout,
        attempts=attempts,
        used_fallback=used_fallback,
        degraded=degraded,
    )
