"""
Nexa Language — FFI Bridge (v23)
Call C/C++ shared libraries directly from Nexa.

Usage in Nexa:
    import ffi
    let lib = ffi.load("libm.so.6")          // load shared lib
    let result = ffi.call(lib, "sqrt", 16.0) // call C function
    let s = ffi.struct(lib, "Point", ["x: double", "y: double"])

Supported types: int, double, string (char*), void
Platform: Linux (.so), Windows (.dll), macOS (.dylib)
"""
from __future__ import annotations
import ctypes, ctypes.util, sys, os
from typing import Any


# ── Type helpers ──────────────────────────────────────────────────────────────
_CTYPE_MAP = {
    "int":    ctypes.c_int,
    "long":   ctypes.c_long,
    "float":  ctypes.c_float,
    "double": ctypes.c_double,
    "str":    ctypes.c_char_p,
    "string": ctypes.c_char_p,
    "void":   None,
    "bool":   ctypes.c_bool,
    "size_t": ctypes.c_size_t,
}


def _to_ctype(type_hint: str):
    return _CTYPE_MAP.get(type_hint.strip(), ctypes.c_int)


def _coerce_arg(val: Any):
    """Convert Nexa value to ctypes-compatible Python value."""
    if isinstance(val, float): return ctypes.c_double(val)
    if isinstance(val, int):   return ctypes.c_int(val)
    if isinstance(val, str):   return val.encode("utf-8")
    if isinstance(val, bool):  return ctypes.c_bool(val)
    return val


# ── FFI Module ────────────────────────────────────────────────────────────────
class FFIModule:
    """The `ffi` built-in module accessible from Nexa."""

    def load(self, name: str):
        """
        Load a shared library by name or path.
        ffi.load("m")        → finds libm automatically
        ffi.load("/usr/lib/libssl.so")  → absolute path
        """
        # Try as absolute/relative path first
        if os.path.exists(name):
            return ctypes.CDLL(name)
        # Platform-specific name resolution
        found = ctypes.util.find_library(name)
        if found:
            return ctypes.CDLL(found)
        # Try common patterns
        for pattern in [f"lib{name}.so", f"lib{name}.so.6",
                        f"lib{name}.dylib", f"{name}.dll"]:
            try: return ctypes.CDLL(pattern)
            except OSError: pass
        raise RuntimeError(f"FFI: Cannot find library '{name}'")

    def call(self, lib, fn_name: str, *args,
             restype: str = "int", argtypes: list = None):
        """
        Call a C function from a loaded library.
        restype and argtypes are optional type hint strings.

        Example: ffi.call(lib, "add", 1, 2, restype="int")
        """
        try:
            fn = getattr(lib, fn_name)
        except AttributeError:
            raise RuntimeError(f"FFI: Function '{fn_name}' not found in library")

        fn.restype  = _to_ctype(restype)
        if argtypes:
            fn.argtypes = [_to_ctype(t) for t in argtypes]

        coerced = [_coerce_arg(a) for a in args]
        result = fn(*coerced)

        # Convert bytes back to string
        if isinstance(result, bytes): return result.decode("utf-8", errors="replace")
        return result

    def struct(self, field_defs: list):
        """
        Create a ctypes Structure class from a field definition list.
        field_defs: [("x", "double"), ("y", "double")] or ["x: double", "y: double"]

        Returns an instance factory function.
        """
        fields = []
        for fd in field_defs:
            if isinstance(fd, str):
                parts = fd.split(":")
                fname = parts[0].strip()
                ftype = parts[1].strip() if len(parts) > 1 else "int"
            else:
                fname, ftype = fd[0], fd[1]
            ct = _to_ctype(ftype)
            if ct: fields.append((fname, ct))

        class NexaStruct(ctypes.Structure):
            _fields_ = fields

        def make_struct(**kwargs):
            s = NexaStruct()
            for k, v in kwargs.items():
                setattr(s, k, v)
            return s

        make_struct.__struct_class__ = NexaStruct
        make_struct.__fields__ = [f[0] for f in fields]
        return make_struct

    def ptr(self, val):
        """Get ctypes pointer to a value."""
        return ctypes.byref(val) if hasattr(val, "_obj") else ctypes.cast(id(val), ctypes.c_void_p)

    def null(self):
        """Return a null pointer."""
        return None

    def sizeof(self, type_name: str) -> int:
        """Return byte size of a C type."""
        ct = _to_ctype(type_name)
        return ctypes.sizeof(ct) if ct else 0

    def array(self, type_name: str, size: int):
        """Create a C array of given type and size."""
        ct = _to_ctype(type_name)
        if ct is None: raise ValueError(f"Cannot create array of void")
        arr_type = ct * size
        return arr_type()

    # Dict-like interface for Nexa module access
    def __getitem__(self, key): return getattr(self, key)
    def get(self, key, default=None): return getattr(self, key, default)


# Singleton module instance registered as 'ffi' builtin
FFI_MODULE = FFIModule()
