from .string_helpers import camel_to_kebab
from .caspian_config import get_config
from typing import Callable, TypeVar, Dict, Optional
import inspect
import os
import importlib
import importlib.util
import re

R = TypeVar('R')

# Global component registry (used internally for decorator registration)
_component_registry: Dict[str, 'Component'] = {}


class Component:
    """Wrapper class for PulsePoint components"""
    _is_pp_component: bool = True
    # Store source path to help recursion
    source_path: Optional[str] = None

    def __init__(self, fn: Callable[..., str], source_path: Optional[str] = None):
        self.fn = fn
        self.source_path = source_path
        self.__name__ = fn.__name__
        self.__doc__ = fn.__doc__
        self.__signature__ = inspect.signature(fn)
        # Register with source path as key to avoid name collisions
        _component_registry[fn.__name__] = self

    def __call__(self, *args, **kwargs) -> str:
        # --- MIDDLEWARE: Prop Normalization ---
        # We need to map React-style props (className) to Python-style args (class_name).
        # We MUST NOT convert to 'class' or 'for' here, as those are reserved keywords
        # and cannot be passed as named arguments to the decorated function.

        # 1. Normalize 'className' to 'class_name'
        if "className" in kwargs:
            kwargs["class_name"] = kwargs.pop("className")

        # 2. Normalize 'htmlFor' to 'html_for'
        if "htmlFor" in kwargs:
            kwargs["html_for"] = kwargs.pop("htmlFor")

        return self.fn(*args, **kwargs)


def component(fn: Callable[..., str]) -> Component:
    """Decorator to mark and register a PulsePoint component"""
    return Component(fn)


def load_component_from_path(path: str, component_name: str, base_dir: str = "") -> Component | None:
    # 1. Resolve absolute file path
    if path == '.' or path == './':
        if base_dir:
            full_path = os.path.join(base_dir, f"{component_name}.py")
        else:
            full_path = f"{component_name}.py"
    elif path.startswith('./') or path.startswith('../'):
        if base_dir:
            full_path = os.path.normpath(os.path.join(base_dir, path))
        else:
            full_path = os.path.normpath(path)
    else:
        full_path = path

    # Handle directory vs file resolution
    if full_path.endswith('.py'):
        file_path = full_path
    elif os.path.isdir(full_path):
        file_path = os.path.join(full_path, f"{component_name}.py")
    else:
        if os.path.isdir(full_path):
            file_path = os.path.join(full_path, f"{component_name}.py")
        else:
            file_path = f"{full_path}.py"

    if not os.path.exists(file_path):
        return None

    # 2. Try loading as a standard module first to support relative imports
    try:
        # Calculate module dotted path (e.g., src.app.components.Dialog)
        cwd = os.getcwd()
        abs_file_path = os.path.abspath(file_path)

        if abs_file_path.startswith(cwd):
            rel_path = os.path.relpath(abs_file_path, cwd)
            # Check if it's a valid python path structure
            if not rel_path.startswith('..'):
                # Convert path to dotted notation
                module_name = os.path.splitext(
                    rel_path)[0].replace(os.sep, '.')

                # Import using standard machinery
                module = importlib.import_module(module_name)

                # Retrieve component
                comp = getattr(module, component_name, None)
                if isinstance(comp, Component):
                    comp.source_path = abs_file_path
                    return comp
    except (ImportError, ValueError, AttributeError):
        pass

    # 3. Fallback: Manual Spec Loading
    try:
        unique_module_name = f"pp_component_{os.path.abspath(file_path).replace(os.sep, '_').replace('.', '_')}"
        spec = importlib.util.spec_from_file_location(
            unique_module_name, file_path)
        if spec is None or spec.loader is None:
            return None

        module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(module)

        comp = getattr(module, component_name, None)
        if isinstance(comp, Component):
            comp.source_path = file_path
            return comp
    except Exception as e:
        print(
            f"Error loading component {component_name} from {file_path}: {e}")
        return None

    return None


def html_file(file_path: str, **kwargs) -> str:
    """
    Load HTML template, inject props, and handle [[...props]] with 
    conditional camel-to-kebab conversion.
    """
    # --- 1. Resolve File Path ---
    frame = inspect.currentframe()
    if frame and frame.f_back:
        caller_file = frame.f_back.f_globals.get('__file__', '')
        if caller_file:
            base_dir = os.path.dirname(caller_file)
            full_path = os.path.join(base_dir, file_path)
        else:
            full_path = file_path
    else:
        full_path = file_path

    with open(full_path, 'r', encoding='utf-8') as f:
        content = f.read()

    # --- 2. Build Attributes String for [[...props]] ---
    attributes_parts: list[str] = []

    for key, value in kwargs.items():
        # Middleware Logic for html_file as well (optional but recommended consistency)
        if key in ["class_name", "className"]:
            final_key = "class"
        elif key in ["html_for", "htmlFor"]:
            final_key = "for"
        else:
            final_key = key

        str_value = str(value)

        # LOGIC: If value contains mustache syntax like "{variable}",
        # convert the attribute name (key) to kebab-case.
        if "{" in str_value and "}" in str_value:
            final_key = camel_to_kebab(final_key)

        attributes_parts.append(f'{final_key}="{str_value}"')

    attributes_string = " ".join(attributes_parts)

    # --- 3. Replacements ---
    content = content.replace('[[...props]]', attributes_string)

    for key, value in kwargs.items():
        content = re.sub(rf'\[\[{key}\]\]', str(value), content)

    return content
