"""
Post-compile mtime stabilization for DLL outputs.

Problem:
    When zccache caches link results, it restores cached outputs with their
    ORIGINAL mtime (from when the link first ran). This can produce DLLs with
    mtimes OLDER than the fastled.dll.symbols file, causing ninja to think the
    DLLs need relinking on the next build. Since zccache returns the same cached
    result each time, the DLLs never get a newer mtime — creating an infinite
    relink loop of ~190 DLLs on every build.

Solution:
    After ninja compile completes, check if any DLL outputs are older than
    the symbols file (their key input dependency). If so, touch them to NOW
    so ninja sees them as up-to-date on the next run.

    This is a lightweight post-build fixup (~5ms) that breaks the relink loop
    and saves ~1-2 seconds per build (190 unnecessary link operations).

Usage:
    Called automatically after stream_compile_only() completes in streaming.py.
"""

import os
import time
from pathlib import Path

from ci.util.timestamp_print import ts_print as _ts_print


# Glob patterns for DLL outputs relative to the build directory
_DLL_PATTERNS = ["tests/*.dll", "examples/*.dll"]

# Key input files whose mtime triggers DLL relinking
_KEY_INPUTS = [
    # fastled.dll.symbols — generated by SHSYM from fastled.dll
    Path("ci") / "meson" / "native" / "fastled.dll.p" / "fastled.dll.symbols",
    # libcrash_handler.a — static library linked into runner.exe and DLLs
    Path("ci") / "meson" / "native" / "libcrash_handler.a",
]

# Runner executables that also depend on these inputs
_RUNNER_PATTERNS = [
    "tests/runner.exe",
    "tests/runner",
    "examples/example_runner.exe",
    "examples/example_runner",
]


def stabilize_dll_mtimes(build_dir: Path, verbose: bool = False) -> int:
    """
    Ensure all DLL outputs have mtimes newer than their key input dependencies.

    When zccache caches link results, it may restore outputs with old mtimes
    that are older than the symbols file, causing ninja to relink on every build.
    This function touches any such stale outputs to break the relink loop.

    Args:
        build_dir: Meson build directory (e.g., .build/meson-quick)
        verbose: Print details about touched files

    Returns:
        Number of files touched (0 if all mtimes were already stable)
    """
    # Find the maximum mtime across all key input files
    max_input_mtime: float = 0
    for rel_path in _KEY_INPUTS:
        abs_path = build_dir / rel_path
        if abs_path.exists():
            try:
                mtime = abs_path.stat().st_mtime
                if mtime > max_input_mtime:
                    max_input_mtime = mtime
            except OSError:
                pass

    if max_input_mtime == 0:
        return 0  # No input files found, nothing to stabilize

    # Collect all DLL and runner outputs
    all_outputs: list[Path] = []
    for pattern in _DLL_PATTERNS:
        all_outputs.extend(build_dir.glob(pattern))
    for pattern in _RUNNER_PATTERNS:
        candidate = build_dir / pattern
        if candidate.exists():
            all_outputs.append(candidate)

    if not all_outputs:
        return 0

    # Touch any outputs that are older than the key inputs
    now = time.time()
    # Use a timestamp slightly after the max input mtime to ensure outputs
    # are newer. Using NOW is safest since it's guaranteed to be >= max_input_mtime.
    touch_time = max(now, max_input_mtime + 1)
    touched = 0

    for output_path in all_outputs:
        try:
            output_mtime = output_path.stat().st_mtime
            if output_mtime <= max_input_mtime:
                os.utime(str(output_path), (touch_time, touch_time))
                touched += 1
        except OSError:
            pass

    if touched > 0 and verbose:
        _ts_print(
            f"[BUILD] ⚡ Stabilized {touched} DLL mtimes"
            f" (were older than input dependencies)"
        )
    elif touched > 0:
        _ts_print(f"[BUILD] ⚡ Stabilized {touched} output mtimes (zccache mtime fix)")

    return touched
