"""
Single source of truth for ALL subprocess spawning.

Every subprocess.Popen / subprocess.run that launches sys.executable
(in frozen mode) MUST go through this module.  Direct calls to
subprocess.Popen(sys.executable, ...) will create visible windows
on packaged builds because our EXE is a GUI-subsystem application
and CREATE_NO_WINDOW alone does NOT work for GUI apps.

Usage:
    from src.subprocess_util import popen, run

    proc = popen([sys.executable, "--auto", "--daemon-pipeline"], **kwargs)
    result = run([sys.executable, "--auto", "--analyze"], **kwargs)

All kwargs are forwarded to subprocess.Popen / subprocess.run.
Window suppression is applied automatically in frozen mode on Windows.
GAT_SUBPROCESS=1 is injected into every child environment to prevent
infinite recursive spawning — this is the SINGLE root-cause fix that
cannot be bypassed by argv manipulation, flag stripping, or code path
changes.
"""
from __future__ import annotations

import subprocess
import sys
import os
from typing import Any

# Canonical list of CLI flags that identify a daemon/worker subprocess.
# Both launcher._is_daemon_subprocess() and gui_backend._spawn_daemon()
# use this list to decide whether to skip the GUI UI / refuse recursive spawn.
# Keep in sync when adding new subprocess entry points.
DAEMON_SUBPROCESS_FLAGS: tuple[str, ...] = (
    "--auto", "--daemon-pipeline", "--daemon", "--watchdog",
    "--train", "--analyze", "--run-macro", "--run-notify",
    "--run-briefing", "--run-evo", "--activate-license",
    "--engine-instance", "--model",
)


def _apply_window_suppression(kwargs: dict[str, Any]) -> None:
    """Mutate kwargs to suppress child-process windows on Windows.

    DETACHED_PROCESS (0x00000008): no console inheritance from parent.
    CREATE_NO_WINDOW (0x08000000): prevents new console window for console apps.
    STARTF_USESHOWWINDOW + SW_HIDE: additional belt-and-suspenders suppression.
    """
    if sys.platform != "win32":
        return
    existing = kwargs.get("creationflags", 0)
    kwargs["creationflags"] = existing | 0x00000008  # DETACHED_PROCESS
    if not getattr(sys, 'frozen', False):
        kwargs["creationflags"] = kwargs["creationflags"] | 0x08000000  # CREATE_NO_WINDOW (console apps only)
    if "startupinfo" not in kwargs:
        si = subprocess.STARTUPINFO()
        si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
        si.wShowWindow = subprocess.SW_HIDE
        kwargs["startupinfo"] = si


def _inject_subprocess_guard(kwargs: dict[str, Any]) -> None:
    """Ensure GAT_SUBPROCESS=1 is in the child's environment.

    This is the SINGLE root-cause fix for infinite recursive window spawning.
    Every subprocess launched through this module inherits the marker, and
    _spawn_daemon() refuses to spawn if the marker is present — regardless
    of argv state, flag stripping, or code path.
    """
    env = kwargs.get("env", None)
    if env is None:
        env = os.environ.copy()
        kwargs["env"] = env
    env["GAT_SUBPROCESS"] = "1"


def popen(args: list[str], **kwargs: Any) -> subprocess.Popen:
    """subprocess.Popen with automatic window suppression on Windows.

    Always use this instead of bare subprocess.Popen when the command
    might be sys.executable (our frozen GUI-subsystem EXE).
    """
    _apply_window_suppression(kwargs)
    _inject_subprocess_guard(kwargs)
    return subprocess.Popen(args, **kwargs)


def run(args: list[str], **kwargs: Any) -> subprocess.CompletedProcess:
    """subprocess.run with automatic window suppression on Windows.

    Always use this instead of bare subprocess.run when the command
    might be sys.executable (our frozen GUI-subsystem EXE).
    """
    _apply_window_suppression(kwargs)
    _inject_subprocess_guard(kwargs)
    return subprocess.run(args, **kwargs)
