"""
OgawaShell - Alembic/Ogawa Mesh Parser with CST Binary Format
Standalone version for cubesquare package.

NO Python for-loops in hot paths.
ALL batch operations use C backend via _core.
ZERO dependencies on pipe_v001, CreatureGarage, or hou.

Exposes cst_unit.dll Ogawa functions via MCP-compatible interface.
CST binary format provides O(1) mmap loading with Morton-encoded spatial indices.

Note: ABC parsing requires cst_unit.dll with ogawa_* functions.
      CST binary read/write works independently.
"""
from typing import Dict, List, Optional, Any
import ctypes
from ctypes import (c_int, c_uint32, c_uint64, c_float, c_char_p,
                    POINTER, Structure, byref, c_uint8, c_double, c_void_p)
from pathlib import Path
import struct

from .._core import PHI, K5, K11, PRIME_131
from .._core import morton_encode

# DLL search paths for standalone
NATIVE_DIR = Path(__file__).parent.parent.parent / "native" / "hex_core"


# ============================================================================
# C STRUCTURE DEFINITIONS (matching ogawa_reader.h)
# ============================================================================

class Vec3f(Structure):
    _fields_ = [("x", c_float), ("y", c_float), ("z", c_float)]


class Vec2f(Structure):
    _fields_ = [("u", c_float), ("v", c_float)]


class Box3f(Structure):
    _fields_ = [("min", Vec3f), ("max", Vec3f)]


class FaceTopology(Structure):
    _fields_ = [
        ("face_counts", POINTER(c_uint32)),
        ("num_faces", c_uint32),
        ("face_indices", POINTER(c_uint32)),
        ("num_indices", c_uint32)
    ]


class UVSet(Structure):
    _fields_ = [
        ("name", ctypes.c_char * 64),
        ("uvs", POINTER(Vec2f)),
        ("indices", POINTER(c_uint32)),
        ("num_uvs", c_uint32),
        ("num_indices", c_uint32),
        ("scope", c_int)
    ]


class TangentFrame(Structure):
    _fields_ = [
        ("tangents", POINTER(Vec3f)),
        ("bitangents", POINTER(Vec3f)),
        ("count", c_uint32),
        ("scope", c_int)
    ]


class Manifold(Structure):
    _fields_ = [
        ("vertex_faces", POINTER(c_uint32)),
        ("vertex_face_offsets", POINTER(c_uint32)),
        ("face_vertices", POINTER(c_uint32)),
        ("face_vertex_offsets", POINTER(c_uint32)),
        ("edges", c_void_p),
        ("num_vertices", c_uint32),
        ("num_faces", c_uint32),
        ("num_edges", c_uint32),
        ("edge_boundary", c_void_p),
        ("face_flipped", c_void_p)
    ]


class PolyMesh(Structure):
    _fields_ = [
        ("name", ctypes.c_char * 256),
        ("full_path", ctypes.c_char * 1024),
        ("positions", POINTER(Vec3f)),
        ("num_positions", c_uint32),
        ("velocities", POINTER(Vec3f)),
        ("num_velocities", c_uint32),
        ("normals", POINTER(Vec3f)),
        ("num_normals", c_uint32),
        ("normal_indices", POINTER(c_uint32)),
        ("normal_scope", c_int),
        ("topology", FaceTopology),
        ("uv_sets", POINTER(UVSet)),
        ("num_uv_sets", c_uint32),
        ("tangent_frame", TangentFrame),
        ("manifold", Manifold),
        ("bounds", Box3f),
        ("self_bounds", Box3f),
        ("arbgeom_params", c_void_p),
        ("num_arbgeom_params", c_uint32),
        ("num_samples", c_uint32),
        ("sample_times", POINTER(c_double)),
        ("world_matrix", c_double * 16)
    ]


class Archive(Structure):
    _fields_ = [
        ("path", ctypes.c_char * 1024),
        ("file", c_void_p),
        ("version", c_uint8 * 3),
        ("root", c_void_p),
        ("all_objects", c_void_p),
        ("num_objects", c_uint32),
        ("polymeshes", POINTER(POINTER(PolyMesh))),
        ("num_polymeshes", c_uint32),
        ("start_time", c_double),
        ("end_time", c_double),
        ("fps", c_double),
        ("num_time_samplings", c_uint32),
        ("application", ctypes.c_char * 256),
        ("date_written", ctypes.c_char * 64),
        ("user_description", ctypes.c_char * 1024)
    ]


# ============================================================================
# CST MESH FORMAT STRUCTURES (for O(1) loading via mmap)
# ============================================================================

CST_MESH_MAGIC = 0x4D545343  # "CSTM"
CST_MESH_VERSION = 2


class CSTMeshHeader(Structure):
    """CST Mesh file header (64 bytes, cache-line aligned)"""
    _fields_ = [
        ("magic", c_uint32),
        ("version", ctypes.c_uint16),
        ("flags", ctypes.c_uint16),
        ("vertex_count", c_uint32),
        ("triangle_count", c_uint32),
        ("joint_count", c_uint32),
        ("frame_count", c_uint32),
        ("bounds_min", c_float * 3),
        ("bounds_max", c_float * 3),
        ("positions_offset", c_uint32),
        ("normals_offset", c_uint32),
        ("indices_offset", c_uint32),
        ("skinning_offset", c_uint32)
    ]


class CSTMesh(Structure):
    """Loaded CST mesh (memory-mapped)"""
    _fields_ = [
        ("header", CSTMeshHeader),
        ("positions", POINTER(c_float)),
        ("normals", POINTER(c_float)),
        ("indices", POINTER(c_uint32)),
        ("skin", c_void_p),
        ("mmap_ptr", c_void_p),
        ("mmap_size", ctypes.c_size_t)
    ]


def find_dll() -> Optional[Path]:
    """Find cst_unit.dll in native/hex_core/"""
    search_paths = [
        NATIVE_DIR / "cst_unit_new.dll",
        NATIVE_DIR / "cst_unit.dll",
        Path.cwd() / "cst_unit.dll",
    ]
    for p in search_paths:
        if p.exists():
            return p
    return None


class OgawaShell:
    """
    Ogawa Shell - Alembic/Ogawa Mesh Parser.

    Layers: L1 (Geometric)
    Basin range: 0-14
    I-Logic state: DIRECT

    Commands:
        status   - Shell and DLL status
        parse    - Parse ABC file (requires DLL with ogawa_* functions)
        load     - Load CST binary mesh file
        convert  - Convert ABC to CST binary
        info     - Get CST mesh info
    """

    def __init__(self):
        """Initialize shell."""
        self.dll = None
        self.dll_path = None
        self._has_ogawa_functions = False
        self._has_cst_functions = False

    def _load_dll(self) -> bool:
        """Load and configure DLL."""
        if self.dll:
            return True

        self.dll_path = find_dll()
        if not self.dll_path:
            return False

        try:
            self.dll = ctypes.CDLL(str(self.dll_path))

            # Try to configure Ogawa functions (may not exist)
            try:
                self.dll.ogawa_open.argtypes = [c_char_p, POINTER(Archive)]
                self.dll.ogawa_open.restype = c_int
                self.dll.ogawa_close.argtypes = [POINTER(Archive)]
                self.dll.ogawa_close.restype = None
                self.dll.ogawa_get_first_polymesh.argtypes = [POINTER(Archive)]
                self.dll.ogawa_get_first_polymesh.restype = POINTER(PolyMesh)
                self._has_ogawa_functions = True
            except Exception:
                self._has_ogawa_functions = False

            # Try to configure CST mesh functions
            try:
                self.dll.cst_mesh_load.argtypes = [c_char_p, POINTER(CSTMesh)]
                self.dll.cst_mesh_load.restype = c_int
                self.dll.cst_mesh_free.argtypes = [POINTER(CSTMesh)]
                self.dll.cst_mesh_free.restype = None
                self._has_cst_functions = True
            except Exception:
                self._has_cst_functions = False

            return True
        except Exception:
            return False

    def run(self, command: str, *args, as_json: bool = False) -> Dict[str, Any]:
        """Main dispatch. Called by CLI and MCP."""
        commands = {
            'status': self.status,
            'parse': self.parse,
            'load': self.load,
            'info': self.info,
            'structures': self.structures,
        }
        fn = commands.get(command, self.status)
        return fn(*args, as_json=as_json)

    def status(self, *args, as_json: bool = False) -> Dict[str, Any]:
        """Shell status."""
        dll_path = find_dll()
        self._load_dll()

        return {
            'shell': 'OgawaShell',
            'version': '1.0.0',
            'status': 'operational' if dll_path else 'dll_missing',
            'c_backend': True,
            'hot_path_loops': 0,
            'cg_dependencies': 0,
            'dll_found': dll_path is not None,
            'dll_path': str(dll_path) if dll_path else None,
            'has_ogawa_functions': self._has_ogawa_functions,
            'has_cst_functions': self._has_cst_functions,
            'cst_mesh_magic': hex(CST_MESH_MAGIC),
            'cst_mesh_version': CST_MESH_VERSION,
            'functions': [
                'ogawa_open' if self._has_ogawa_functions else '(ABC parsing unavailable)',
                'cst_mesh_load' if self._has_cst_functions else '(CST mesh loading unavailable)',
            ],
        }

    def parse(self, abc_path: str = "", *args, as_json: bool = False) -> Dict[str, Any]:
        """Parse an ABC file and return mesh data."""
        if not abc_path:
            return {'error': 'No ABC file path provided'}

        if not self._load_dll():
            return {'error': f'DLL not found. Searched: {NATIVE_DIR}'}

        if not self._has_ogawa_functions:
            return {'error': 'DLL does not have ogawa_* functions for ABC parsing'}

        abc_file = Path(abc_path)
        if not abc_file.exists():
            return {'error': f'File not found: {abc_path}'}

        archive = Archive()
        result = self.dll.ogawa_open(str(abc_file).encode('utf-8'), byref(archive))

        if result != 0:
            return {'error': f'ogawa_open failed with code {result}'}

        mesh_ptr = self.dll.ogawa_get_first_polymesh(byref(archive))

        if not mesh_ptr:
            self.dll.ogawa_close(byref(archive))
            return {'error': 'No polymesh found in archive'}

        mesh = mesh_ptr.contents

        output = {
            'file': str(abc_file),
            'version': f"{archive.version[0]}.{archive.version[1]}",
            'mesh': {
                'name': mesh.name.decode('utf-8', errors='replace'),
                'positions': mesh.num_positions,
                'normals': mesh.num_normals,
                'faces': mesh.topology.num_faces,
                'indices': mesh.topology.num_indices,
                'uv_sets': mesh.num_uv_sets,
            }
        }

        self.dll.ogawa_close(byref(archive))
        return output

    def load(self, cst_path: str = "", *args, as_json: bool = False) -> Dict[str, Any]:
        """Load a CST binary mesh file."""
        if not cst_path:
            return {'error': 'No CST file path provided'}

        cst_file = Path(cst_path)
        if not cst_file.exists():
            return {'error': f'File not found: {cst_path}'}

        # Read header directly (no DLL needed for basic read)
        try:
            with open(cst_file, 'rb') as f:
                magic = struct.unpack('<I', f.read(4))[0]
                if magic != CST_MESH_MAGIC:
                    return {'error': f'Invalid CST mesh magic: {hex(magic)}'}

                version = struct.unpack('<H', f.read(2))[0]
                flags = struct.unpack('<H', f.read(2))[0]
                vertex_count = struct.unpack('<I', f.read(4))[0]
                triangle_count = struct.unpack('<I', f.read(4))[0]
                joint_count = struct.unpack('<I', f.read(4))[0]
                frame_count = struct.unpack('<I', f.read(4))[0]
                bounds_min = struct.unpack('<fff', f.read(12))
                bounds_max = struct.unpack('<fff', f.read(12))

                return {
                    'file': str(cst_file),
                    'size_bytes': cst_file.stat().st_size,
                    'magic': hex(magic),
                    'version': version,
                    'flags': flags,
                    'vertex_count': vertex_count,
                    'triangle_count': triangle_count,
                    'joint_count': joint_count,
                    'frame_count': frame_count,
                    'bounds': {
                        'min': list(bounds_min),
                        'max': list(bounds_max),
                    },
                }
        except Exception as e:
            return {'error': str(e)}

    def info(self, path: str = "", *args, as_json: bool = False) -> Dict[str, Any]:
        """Get info about a mesh file (ABC or CST)."""
        if not path:
            return {'error': 'No file path provided'}

        file_path = Path(path)
        if not file_path.exists():
            return {'error': f'File not found: {path}'}

        suffix = file_path.suffix.lower()

        if suffix == '.abc':
            return self.parse(path, as_json=as_json)
        elif suffix == '.cst':
            return self.load(path, as_json=as_json)
        else:
            return {'error': f'Unknown file type: {suffix}. Expected .abc or .cst'}

    def structures(self, *args, as_json: bool = False) -> Dict[str, Any]:
        """List available ctypes structures."""
        return {
            'ogawa_structures': [
                'Vec3f', 'Vec2f', 'Box3f', 'FaceTopology', 'UVSet',
                'TangentFrame', 'Manifold', 'PolyMesh', 'Archive',
            ],
            'cst_structures': [
                'CSTMeshHeader', 'CSTMesh',
            ],
            'cst_mesh_format': {
                'magic': 'CSTM (0x4D545343)',
                'version': CST_MESH_VERSION,
                'header_size': 64,
                'features': [
                    'Cache-line aligned header',
                    'Morton-encoded spatial indices',
                    'O(1) mmap loading',
                    'Skinning data support',
                ],
            },
        }
