from .string_helpers import camel_to_kebab
from .string_helpers import kebab_to_camel
from .string_helpers import has_mustache
import re
import uuid
import inspect
from typing import Dict, List, Optional
from .component_decorator import Component, load_component_from_path
import os


def convert_mustache_attrs_to_kebab_raw(html):
    def replace_attr(match):
        attr_name = match.group(1)
        equals_quote = match.group(2)
        value = match.group(3)
        end_quote = match.group(4)

        if has_mustache(value):
            new_name = camel_to_kebab(attr_name)
            return f'{new_name}{equals_quote}{value}{end_quote}'
        return match.group(0)

    pattern = r'([a-zA-Z_][a-zA-Z0-9_]*)(=["\'])([^"\']*?)(["\'])'
    return re.sub(pattern, replace_attr, html)


def extract_component_original_attrs(html, pattern):
    component_attrs_map = {}

    for match in re.finditer(pattern, html):
        component_name = match.group(1)
        tag_start = match.start()

        remaining = html[tag_start:]
        tag_end_match = re.search(r'/?>', remaining)
        if tag_end_match:
            full_tag = remaining[:tag_end_match.end()]

            attr_pattern = re.compile(
                r'([a-zA-Z_][\w\-]*)\s*=\s*(?:"[^"]*"|\'[^\']*\'|[^\s>]+)')
            attrs_original = {}
            for attr_match in attr_pattern.finditer(full_tag):
                attr_name = attr_match.group(1)
                attrs_original[attr_name.lower()] = attr_name

            placeholder = f"ppcomponent{component_name.lower()}"
            if placeholder not in component_attrs_map:
                component_attrs_map[placeholder] = []
            component_attrs_map[placeholder].append(attrs_original)

    return component_attrs_map

# ============================================================================
# IMPORT PARSING
# ============================================================================


def extract_imports(html: str) -> List[Dict]:
    """
    Extract all import statements from HTML.

    Supports:
        <!-- @import Test from "./path" -->
        <!-- @import Test from ./path -->  (unquoted)
        <!-- @import Test as MyTest from "./path" -->
        <!-- @import { Button, Input } from "./path" -->
        <!-- @import { Button as Btn, Input as Field } from "./path" -->
    """
    imports = []

    # Grouped imports: @import { A, B as C } from "path" or from path
    grouped_pattern = r'<!--\s*@import\s*\{([^}]+)\}\s*from\s*["\']?([^"\'>\s]+)["\']?\s*-->'
    grouped_re = re.compile(grouped_pattern)
    for match in grouped_re.finditer(html):
        members = match.group(1)
        path = match.group(2)

        for member in members.split(','):
            member = member.strip()
            if not member:
                continue
            if ' as ' in member:
                parts = member.split(' as ')
                original = parts[0].strip()
                alias = parts[1].strip()
            else:
                original = member
                alias = member
            imports.append({
                'original': original,
                'alias': alias,
                'path': path
            })

    # Single imports: @import Name from "path" OR @import Name as Alias from "path" (quotes optional)
    single_pattern = r'<!--\s*@import\s+(?!\{)(\w+)(?:\s+as\s+(\w+))?\s+from\s*["\']?([^"\'>\s]+)["\']?\s*-->'
    single_re = re.compile(single_pattern)
    for match in single_re.finditer(html):
        original = match.group(1)
        alias = match.group(2) or original
        path = match.group(3)
        imports.append({
            'original': original,
            'alias': alias,
            'path': path
        })

    return imports


def strip_imports(html: str) -> str:
    """Remove import comments from HTML"""
    # Remove grouped imports (quoted or unquoted)
    html = re.sub(
        r'<!--\s*@import\s*\{[^}]+\}\s*from\s*["\']?[^"\'>\s]+["\']?\s*-->\n?', '', html)
    # Remove single imports (quoted or unquoted)
    html = re.sub(
        r'<!--\s*@import\s+\w+(?:\s+as\s+\w+)?\s+from\s*["\']?[^"\'>\s]+["\']?\s*-->\n?', '', html)
    return html


def build_alias_map(imports: List[Dict], base_dir: str = "") -> Dict[str, Component]:
    """Build mapping from alias names to Component instances."""
    alias_map = {}

    for imp in imports:
        original = imp['original']
        alias = imp['alias']
        path = imp['path']

        component = load_component_from_path(path, original, base_dir)
        if component:
            alias_map[alias] = component

    return alias_map


def process_imports(html: str, base_dir: str = "") -> tuple[str, Dict[str, Component]]:
    """Process imports and return cleaned HTML with alias map."""
    imports = extract_imports(html)
    alias_map = build_alias_map(imports, base_dir)
    cleaned_html = strip_imports(html)
    return cleaned_html, alias_map


# ============================================================================
# COMPONENT HELPERS
# ============================================================================

def needs_parent_scope(attrs):
    for key, value in attrs.items():
        if key.lower().startswith('on'):
            return True
        if has_mustache(value):
            return True
    return False


def wrap_scope(html, scope):
    return f'<!-- pp-scope:{scope} -->{html}<!-- /pp-scope -->'


def accepts_children(fn):
    sig = fn.__signature__ if isinstance(
        fn, Component) else inspect.signature(fn)
    params = sig.parameters
    return 'children' in params or any(p.kind == inspect.Parameter.VAR_KEYWORD for p in params.values())


# ============================================================================
# MAIN TRANSFORM
# ============================================================================

def transform_components(
    html: str,
    parent_scope: str = "app",
    base_dir: str = "",
    alias_map: Optional[Dict[str, Component]] = None,
    _depth: int = 0
) -> str:
    """
    Transform custom component tags. Components MUST be imported to be resolved.
    Recursively processes the output of components to handle nested imports/components.
    """
    if _depth > 50:
        return html

    # 1. Process imports for the current level
    html, local_alias_map = process_imports(html, base_dir)

    if alias_map:
        merged_map = {**alias_map, **local_alias_map}
    else:
        merged_map = local_alias_map

    # If no components are imported, we might still have standard HTML to return,
    # but we can't transform custom tags without a map.
    if not merged_map:
        return html

    # 2. Convert mustache attributes (e.g. someProp="{val}") to kebab-case (some-prop="{val}")
    html = convert_mustache_attrs_to_kebab_raw(html)

    # 3. Process components iteratively
    max_iterations = 100
    iteration = 0

    while iteration < max_iterations:
        iteration += 1

        # Find a component tag (prefer innermost by finding one without nested components)
        # This regex looks for <CapitalizedTag ... /> or <CapitalizedTag ...>...</CapitalizedTag>
        component_pattern = r'<([A-Z][a-zA-Z0-9]*)([^>]*?)(?:/>|>([\s\S]*?)</\1>)'

        match = None
        for m in re.finditer(component_pattern, html):
            tag_name = m.group(1)
            # Only process if we have an import for this tag
            if tag_name in merged_map:
                inner_content = m.group(3) or ""
                # Check if this has nested components - if so, skip for now (inner-first strategy)
                if not re.search(r'<[A-Z][a-zA-Z0-9]*[^>]*(?:/>|>)', inner_content):
                    match = m
                    break
                elif match is None:
                    match = m  # Fallback to first match if all have nested components

        if not match:
            break

        tag_name = match.group(1)
        attrs_str = match.group(2) or ""
        children = match.group(3) or ""

        component_fn = merged_map.get(tag_name)
        if not component_fn:
            # Should not happen due to the check in the loop, but safety first
            break

        # 4. Parse attributes
        props = {}
        attr_pattern = r'([a-zA-Z_][\w\-]*)\s*=\s*(?:"([^"]*)"|\'([^\']*)\'|(\S+))'
        for attr_match in re.finditer(attr_pattern, attrs_str):
            attr_name = attr_match.group(1)
            attr_value = attr_match.group(2) or attr_match.group(
                3) or attr_match.group(4) or ""
            camel_name = kebab_to_camel(attr_name)
            props[camel_name] = attr_value

        # Handle boolean attributes (no value)
        bool_attr_pattern = r'\s([a-zA-Z_][\w\-]*)(?=\s|/?>|$)(?!=)'
        for bool_match in re.finditer(bool_attr_pattern, attrs_str):
            attr_name = bool_match.group(1)
            if attr_name not in props:
                props[kebab_to_camel(attr_name)] = ""

        unique_id = f"{tag_name.lower()}_{uuid.uuid4().hex[:8]}"

        # 5. Add children if component accepts them
        if children.strip() and accepts_children(component_fn):
            props['children'] = wrap_scope(children, parent_scope)

        # 6. Render the component (get the raw string output)
        component_html = component_fn(**props)

        # 7. RECURSIVE TRANSFORM [CRITICAL FIX]
        # We must process the returned HTML to resolve imports and components
        # inside the child component (e.g., EditTodo -> Button).
        if hasattr(component_fn, 'source_path') and component_fn.source_path:
            component_dir = os.path.dirname(component_fn.source_path)
            component_html = transform_components(
                component_html,
                parent_scope=parent_scope,  # Or a new scope if you want isolation
                base_dir=component_dir,     # Use the component's directory for relative imports
                _depth=_depth + 1
            )

        # 8. Add pp-component attribute to root element of the result
        # (This marks it for PulsePoint reactivity in the DOM)
        component_html = re.sub(
            r'^(\s*<[a-zA-Z][a-zA-Z0-9]*)',
            rf'\1 pp-component="{unique_id}"',
            component_html.strip(),
            count=1
        )

        # 9. Wrap in scope if needed (for events/state binding)
        if needs_parent_scope(props):
            component_html = wrap_scope(component_html, parent_scope)

        # 10. Replace the original tag in the HTML with the transformed result
        html = html[:match.start()] + component_html + html[match.end():]

    return html
