"""Linux keyboard text injection — IBUS-safe paste vs xdotool type (0.2.2.70)."""

from __future__ import annotations

import logging
import os
import shutil
import subprocess
import sys
import time
from typing import Any

logger = logging.getLogger(__name__)

PASTE_CHAR_THRESHOLD = 50
SHORT_TYPE_DELAY_MS = 5
LEGACY_TYPE_DELAY_MS = 12
TYPE_CHUNK_SIZE = 400

_VALID_STRATEGIES = frozenset({"paste", "type"})


def is_linux_desktop() -> bool:
    return sys.platform.startswith("linux")


def is_ibus_active() -> bool:
    """Best-effort detect IBUS input method daemon (common freeze source with xdotool)."""
    if not is_linux_desktop():
        return False
    if shutil.which("pgrep"):
        try:
            proc = subprocess.run(
                ["pgrep", "-x", "ibus-daemon"],
                capture_output=True,
                text=True,
                timeout=2,
                check=False,
            )
            if proc.returncode == 0 and str(proc.stdout or "").strip():
                return True
        except Exception:
            pass
    return bool(str(os.environ.get("IBUS_ADDRESS", "")).strip())


def select_text_injection_strategy(text: str, *, ibus_active: bool | None = None) -> str:
    """Choose paste (clipboard) vs char-by-char xdotool type."""
    payload = str(text or "")
    if not payload:
        return "type"
    active = is_ibus_active() if ibus_active is None else bool(ibus_active)
    if active:
        return "paste"
    if len(payload) > PASTE_CHAR_THRESHOLD:
        return "paste"
    return "type"


def subprocess_env_with_ibus_bypass(base: dict[str, str] | None = None) -> dict[str, str]:
    """Disable IBUS/GTK IM modules for short-lived automation subprocesses."""
    env = dict(os.environ)
    if base:
        env.update(base)
    env["IBUS_DISABLE"] = "1"
    env["GTK_IM_MODULE"] = "xim"
    env["QT_IM_MODULE"] = "xim"
    env["XMODIFIERS"] = ""
    return env


def copy_text_to_clipboard(text: str, *, env: dict[str, str] | None = None) -> tuple[bool, str]:
    payload = str(text or "")
    if not payload:
        return True, "empty"
    run_env = subprocess_env_with_ibus_bypass(env)
    if str(os.environ.get("WAYLAND_DISPLAY", "")).strip():
        wl_copy = shutil.which("wl-copy")
        if wl_copy:
            try:
                proc = subprocess.run(
                    [wl_copy],
                    input=payload,
                    capture_output=True,
                    text=True,
                    timeout=8,
                    check=False,
                    env=run_env,
                )
                if proc.returncode == 0:
                    return True, "wl-copy"
                return False, (proc.stderr or proc.stdout or "wl_copy_failed").strip()
            except Exception as exc:
                return False, str(exc)
    xclip = shutil.which("xclip")
    if xclip:
        try:
            proc = subprocess.run(
                [xclip, "-selection", "clipboard"],
                input=payload,
                capture_output=True,
                text=True,
                timeout=8,
                check=False,
                env=run_env,
            )
            if proc.returncode == 0:
                return True, "xclip"
            return False, (proc.stderr or proc.stdout or "xclip_failed").strip()
        except Exception as exc:
            return False, str(exc)
    wl_copy = shutil.which("wl-copy")
    if wl_copy:
        try:
            proc = subprocess.run(
                [wl_copy],
                input=payload,
                capture_output=True,
                text=True,
                timeout=8,
                check=False,
                env=run_env,
            )
            if proc.returncode == 0:
                return True, "wl-copy"
        except Exception:
            pass
    return False, "clipboard_tool_missing: install xclip or wl-copy"


def inject_text_with_xdotool(
    xdotool: str,
    text: str,
    *,
    strategy: str | None = None,
    ibus_active: bool | None = None,
) -> tuple[bool, str, dict[str, Any]]:
    """Inject text into focused field; returns (ok, detail, evidence)."""
    payload = str(text or "")
    if not payload:
        return True, "empty", {"strategy": "noop", "chars": 0}

    chosen = str(strategy or select_text_injection_strategy(payload, ibus_active=ibus_active)).strip().lower()
    if chosen not in _VALID_STRATEGIES:
        chosen = select_text_injection_strategy(payload, ibus_active=ibus_active)

    run_env = subprocess_env_with_ibus_bypass()
    ibus = is_ibus_active() if ibus_active is None else bool(ibus_active)

    if chosen == "paste":
        copied, copy_detail = copy_text_to_clipboard(payload, env=run_env)
        if not copied:
            logger.warning("linux_text_injection paste fallback to type: %s", copy_detail)
            chosen = "type"
        else:
            pasted = subprocess.run(
                [xdotool, "key", "--clearmodifiers", "ctrl+v"],
                capture_output=True,
                text=True,
                timeout=15,
                check=False,
                env=run_env,
            )
            if pasted.returncode == 0:
                return True, "pasted", {
                    "strategy": "paste",
                    "clipboard_tool": copy_detail,
                    "chars": len(payload),
                    "ibus_active": ibus,
                    "ibus_bypass": True,
                }
            detail = (pasted.stderr or pasted.stdout or "xdotool_paste_failed").strip()
            logger.warning("linux_text_injection paste failed, fallback type: %s", detail)
            chosen = "type"

    delay_ms = str(SHORT_TYPE_DELAY_MS if len(payload) <= PASTE_CHAR_THRESHOLD else LEGACY_TYPE_DELAY_MS)
    chunks = [payload[i : i + TYPE_CHUNK_SIZE] for i in range(0, len(payload), TYPE_CHUNK_SIZE)] or [payload]
    for index, chunk in enumerate(chunks):
        if index > 0:
            time.sleep(0.05)
        typed = subprocess.run(
            [xdotool, "type", "--delay", delay_ms, "--", chunk],
            capture_output=True,
            text=True,
            timeout=max(30, min(120, len(chunk) // 5)),
            check=False,
            env=run_env,
        )
        if typed.returncode != 0:
            return False, (typed.stderr or typed.stdout or "xdotool_type_failed").strip(), {
                "strategy": "type",
                "type_delay_ms": delay_ms,
                "ibus_active": ibus,
                "ibus_bypass": True,
            }
    return True, "typed", {
        "strategy": "type",
        "type_delay_ms": delay_ms,
        "chunks": len(chunks),
        "chars": len(payload),
        "ibus_active": ibus,
        "ibus_bypass": True,
    }
