#!/usr/bin/env python3
"""ctypes loader for inject-shim/libpog422_inject.so (Pi/Linux only)."""

from __future__ import annotations

import ctypes
import os
import sys
from pathlib import Path
from typing import Optional

_LIB: Optional[ctypes.CDLL] = None
_LOAD_ERROR: Optional[str] = None


class InjectOpts(ctypes.Structure):
    _fields_ = [
        ("pre_tx_us", ctypes.c_uint32),
        ("post_hold_us", ctypes.c_uint32),
        ("flush_rx_before_tx", ctypes.c_uint8),
        ("use_realtime", ctypes.c_uint8),
        ("rt_priority", ctypes.c_uint8),
        ("_pad", ctypes.c_uint8),
        ("poll_fn", ctypes.c_void_p),
        ("poll_ctx", ctypes.c_void_p),
    ]


class InjectResult(ctypes.Structure):
    _fields_ = [
        ("tx_ns", ctypes.c_uint64),
        ("total_ns", ctypes.c_uint64),
        ("air_us", ctypes.c_uint32),
        ("err", ctypes.c_int32),
        ("slv_in_before", ctypes.c_uint8),
        ("slv_in_after_on", ctypes.c_uint8),
        ("slv_in_after_body", ctypes.c_uint8),
        ("slv_in_after_drain", ctypes.c_uint8),
        ("slv_in_after_off", ctypes.c_uint8),
        ("gpio1_edges", ctypes.c_uint8),
        ("_pad", ctypes.c_uint8),
        ("gpio1_first_edge_us", ctypes.c_uint32),
        ("gpio1_last_edge_us", ctypes.c_uint32),
    ]


def _shim_dir() -> Path:
    return Path(__file__).resolve().parent / "inject-shim"


def _lib_path() -> Path:
    override = os.environ.get("POG422_INJECT_SHIM_PATH", "").strip()
    if override:
        return Path(override)
    return _shim_dir() / "libpog422_inject.so"


def load_error() -> Optional[str]:
    return _LOAD_ERROR


def available() -> bool:
    if sys.platform != "linux":
        return False
    return _load() is not None


def version() -> str:
    lib = _load()
    if not lib:
        return ""
    lib.pog422_inject_version.restype = ctypes.c_char_p
    raw = lib.pog422_inject_version()
    return raw.decode("ascii") if raw else ""


def _load() -> Optional[ctypes.CDLL]:
    global _LIB, _LOAD_ERROR
    if _LIB is not None:
        return _LIB
    if sys.platform != "linux":
        _LOAD_ERROR = "inject shim requires Linux (Pi)"
        return None
    path = _lib_path()
    if not path.is_file():
        _LOAD_ERROR = f"missing {path} — run: cd inject-shim && make"
        return None
    try:
        lib = ctypes.CDLL(str(path))
    except OSError as exc:
        _LOAD_ERROR = str(exc)
        return None

    lib.pog422_inject_burst.argtypes = [
        ctypes.c_int,
        ctypes.POINTER(ctypes.c_uint8),
        ctypes.c_size_t,
        ctypes.POINTER(InjectOpts),
        ctypes.POINTER(InjectResult),
    ]
    lib.pog422_inject_burst.restype = ctypes.c_int
    lib.pog422_inject_version.restype = ctypes.c_char_p
    _LIB = lib
    return _LIB


def inject_burst(
    fd: int,
    frame: bytes,
    *,
    pre_tx_us: float,
    post_hold_us: float,
    flush_rx_before_tx: bool = False,
    use_realtime: bool = False,
    rt_priority: int = 50,
) -> InjectResult:
    lib = _load()
    if lib is None:
        raise RuntimeError(_LOAD_ERROR or "inject shim not loaded")

    buf = (ctypes.c_uint8 * len(frame)).from_buffer_copy(frame)
    opts = InjectOpts(
        pre_tx_us=int(pre_tx_us),
        post_hold_us=int(post_hold_us),
        flush_rx_before_tx=1 if flush_rx_before_tx else 0,
        use_realtime=1 if use_realtime else 0,
        rt_priority=max(1, min(99, int(rt_priority))),
    )
    result = InjectResult()
    rc = lib.pog422_inject_burst(
        int(fd),
        buf,
        len(frame),
        ctypes.byref(opts),
        ctypes.byref(result),
    )
    if rc != 0 and result.err == 0:
        result.err = -rc
    return result


def env_enabled(default: bool = False) -> bool:
    raw = os.environ.get("POG422_INJECT_SHIM", "").strip().lower()
    if raw in ("0", "false", "no", "off"):
        return False
    if raw in ("1", "true", "yes", "on"):
        return True
    return default


def rt_enabled() -> bool:
    raw = os.environ.get("POG422_INJECT_RT", "").strip().lower()
    return raw in ("1", "true", "yes", "on")
