"""Stub modules for heavy ML dependencies that mlx_whisper imports at module
load but never actually CALLS on our (MLX, Apple Silicon) code path.

Without this, py2app pulls ~560 MB of dead code into the .app:

    torch     365 MB   ←  mlx_whisper/torch_whisper.py imports torch.Tensor + nn
                          for type hints — never executes the torch model
    sympy      72 MB   ←  transitive dep of torch
    scipy      94 MB   ←  mlx_whisper/timing.py: from scipy import signal,
                          used only for word-level timestamp alignment which
                          we don't request (transcribe call uses defaults)
    numba      29 MB   ←  mlx_whisper/timing.py: @numba.jit decorator on the
                          same word-alignment functions

Replacing all four with attribute-rich fake modules satisfies the imports
during module load, while never being called at runtime. mlx_whisper.transcribe
returns identical results — verified by smoke-testing with stubs in place
on the live venv before adding `torch`/`sympy`/`scipy`/`numba` to py2app
`excludes` in setup.py.

This module MUST be imported BEFORE mlx_whisper (anything that transitively
loads it). lumo_main.py does this first thing before any other import.
"""

from __future__ import annotations

import sys
import types


def _stub(name: str, **attrs) -> types.ModuleType:
    m = types.ModuleType(name)
    for k, v in attrs.items():
        setattr(m, k, v)
    sys.modules[name] = m
    return m


def _fake_jit(*args, **kwargs):
    """numba.jit / numba.njit decorator stub. Returns the function untouched
    so the @numba.jit-decorated functions inside mlx_whisper.timing just run
    as plain Python — fine because those code paths aren't invoked when we
    transcribe without word-level timestamps."""
    def deco(fn):
        return fn
    if args and callable(args[0]):
        return args[0]
    return deco


def install() -> None:
    """Idempotent. Safe to call multiple times — repeated calls re-bind the
    same fake modules which is a no-op."""
    if "torch" in sys.modules and getattr(sys.modules["torch"], "_lumo_stub", False):
        return  # already installed

    torch = _stub("torch", _lumo_stub=True, Tensor=type("Tensor", (), {}))
    _stub("torch.nn", Module=type("Module", (), {}))
    _stub("torch.nn.functional")
    torch.nn = sys.modules["torch.nn"]
    torch.nn.functional = sys.modules["torch.nn.functional"]

    _stub("sympy", _lumo_stub=True)

    _stub("numba", _lumo_stub=True, jit=_fake_jit, njit=_fake_jit, prange=range)

    scipy = _stub("scipy", _lumo_stub=True)
    scipy.signal = _stub("scipy.signal")
