import os
import sys
import json
import hashlib
import warnings
import platform
from pathlib import Path
from typing import Optional, Any, Dict
from .exceptions import (
    CppApiError,
    CppBuildError,
    CppModuleNotFoundError,
    CppModuleOutdatedError,
    CppReloadWarning,
    CppValidationError
)
from ..cli.config_parser import CppProjectConfig
class ModuleWrapper:
    def __init__(self, module, info: Dict[str, Any]):
        object.__setattr__(self, '_module', module)
        object.__setattr__(self, '_info', info)
        self._expose_cpp_types()
    def _expose_cpp_types(self):
        module = object.__getattribute__(self, '_module')
        if module:
            for attr_name in dir(module):
                if not attr_name.startswith('_'):
                    try:
                        attr_value = getattr(module, attr_name)
                        object.__setattr__(self, attr_name, attr_value)
                    except Exception:
                        pass
    def getInfo(self) -> Dict[str, Any]:
        return object.__getattribute__(self, '_info')
    def __getattr__(self, name):
        if name.startswith('_'):
            raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
        module = object.__getattribute__(self, '_module')
        return getattr(module, name)
    def __dir__(self):
        module = object.__getattribute__(self, '_module')
        wrapper_attrs = ['getInfo']
        if module:
            module_attrs = [attr for attr in dir(module) if not attr.startswith('__')]
            return sorted(set(wrapper_attrs + module_attrs))
        return wrapper_attrs
class CppApi:
    FileSystem = "filesystem"
    Crypto = "crypto"
    JSON = "json"
    StringUtils = "string_utils"
    Networking = "networking"
    DataStructures = "data_structures"
    Threading = "threading"
    StandardMenus = "standard_menus"
    def __init__(self, verbose: bool = False, auto_update: bool = False, config_path: Optional[Path] = None):
        self.project_root = Path.cwd()
        self.config = CppProjectConfig(config_path or (self.project_root / "cpp.proj"))
        self.base_dir = self.config.base_dir
        self.registry_file = self.base_dir / ".module_registry.json"
        self.bindings_dir = self.base_dir / "bindings"
        self.verbose = verbose
        self.auto_update = auto_update
        self.silent = False
        package_root = Path(__file__).parent.parent
        self.inbuilds_dir = package_root / "inbuilds"
        if str(self.bindings_dir) not in sys.path:
            sys.path.insert(0, str(self.bindings_dir))
        self.registry = self._load_registry()
        self.loaded_modules = {}
        self.api_module = None
        self._import_api()
    @property
    def Inbuilds(self):
        if not self.inbuilds_dir.exists():
            return []
        inbuilds = []
        for item in self.inbuilds_dir.iterdir():
            if item.is_dir() and (item / f"{item.name}.cp").exists():
                inbuilds.append(item.name)
        return sorted(inbuilds)
    def _load_registry(self):
        if not self.registry_file.exists():
            if self.verbose:
                print(f"[WARNING] Registry file not found: {self.registry_file}")
                print(f"[INFO] Run 'python -m includecpp rebuild' in project directory")
            return {}
        try:
            with open(self.registry_file, 'r') as f:
                data = json.load(f)
            if "schema_version" in data and data["schema_version"] == "2.0":
                return data.get("modules", {})
            else:
                return data
        except Exception as e:
            if self.verbose:
                print(f"[WARNING] Failed to load registry: {e}")
            return {}
    def _save_registry(self):
        try:
            with open(self.registry_file, 'w') as f:
                json.dump(self.registry, f, indent=2)
        except Exception:
            pass
    def _import_api(self):
        try:
            if 'api' in sys.modules:
                self.api_module = sys.modules['api']
            else:
                import api
                self.api_module = api
        except ImportError:
            if self.verbose:
                print(f"[WARNING] api module not found in {self.bindings_dir}")
                print(f"[INFO] Run 'python -m includecpp rebuild' to build C++ modules")
            self.api_module = None
    def _compute_hash(self, filepath):
        if not os.path.exists(filepath):
            return "0"
        try:
            with open(filepath, 'rb') as f:
                return hashlib.sha256(f.read()).hexdigest()
        except Exception:
            return "0"
    def exists(self, module_name):
        if module_name not in self.registry:
            return False
        if self.api_module is None:
            return False
        try:
            return hasattr(self.api_module, module_name)
        except Exception:
            return False
    def include(self, module_name: str, auto_update: Optional[bool] = None):
        if module_name in self.loaded_modules:
            return self.loaded_modules[module_name]
        if module_name not in self.registry:
            raise CppModuleNotFoundError(
                f"Module '{module_name}' not registered.\n"
                f"Available modules: {list(self.registry.keys())}\n"
                f"Run 'python -m includecpp rebuild' to build modules."
            )
        module_info = self.registry.get(module_name, {})
        pyd_name = f"api_{module_name}"
        pyd_suffix = ".pyd" if platform.system() == "Windows" else ".so"
        pyd_path = self.bindings_dir / f"{pyd_name}{pyd_suffix}"
        try:
            if pyd_path.exists():
                if self.verbose:
                    print(f"[INFO] Loading per-module .pyd: {pyd_name}")
                imported = __import__(pyd_name)
                wrapped = ModuleWrapper(imported, module_info)
                self.loaded_modules[module_name] = wrapped
                return wrapped
        except ImportError as e:
            if self.verbose:
                print(f"[WARNING] Failed to import {pyd_name}: {e}")
                print(f"[INFO] Falling back to monolithic api module")
        if self.api_module is None:
            raise CppModuleNotFoundError(
                f"Module '{module_name}' not built.\n"
                f"Expected: {pyd_path} or api{pyd_suffix}\n"
                f"Run 'python -m includecpp rebuild' to build."
            )
        try:
            module = getattr(self.api_module, module_name)
            wrapped = ModuleWrapper(module, module_info)
            self.loaded_modules[module_name] = wrapped
            return wrapped
        except AttributeError:
            raise CppModuleNotFoundError(
                f"Module '{module_name}' not found.\n"
                f"Neither {pyd_name}{pyd_suffix} nor api.{module_name} exists.\n"
                f"Run 'python -m includecpp rebuild' to build."
            )
    def noFeedback(self):
        self.silent = True
    def list_modules(self):
        return list(self.registry.keys())
    def get_module_info(self, module_name):
        if module_name not in self.registry:
            return None
        return self.registry[module_name]