"""
React Project Analyzer

Analyzes React codebases to extract component information,
detect patterns, and identify style usage.
"""

import re
from pathlib import Path
from typing import List, Dict, Any, Optional, Set, Tuple
from dataclasses import dataclass, field

from .base import (
    ComponentInfo,
    Location,
    StylePattern,
    StylingApproach,
)


# Patterns for React component detection
FUNCTIONAL_COMPONENT = re.compile(
    r'(?:export\s+)?(?:const|let|var|function)\s+([A-Z][A-Za-z0-9]*)\s*(?::\s*(?:React\.)?FC)?'
    r'\s*(?:=\s*(?:\([^)]*\)|[a-zA-Z_]\w*)\s*=>|\()',
    re.MULTILINE
)

CLASS_COMPONENT = re.compile(
    r'class\s+([A-Z][A-Za-z0-9]*)\s+extends\s+(?:React\.)?(?:Component|PureComponent)',
    re.MULTILINE
)

FORWARD_REF = re.compile(
    r'(?:export\s+)?(?:const|let)\s+([A-Z][A-Za-z0-9]*)\s*=\s*(?:React\.)?forwardRef',
    re.MULTILINE
)

MEMO_COMPONENT = re.compile(
    r'(?:export\s+)?(?:const|let)\s+([A-Z][A-Za-z0-9]*)\s*=\s*(?:React\.)?memo\(',
    re.MULTILINE
)

# Import patterns
IMPORT_PATTERN = re.compile(
    r'import\s+(?:\{[^}]+\}|\*\s+as\s+\w+|\w+)\s+from\s+["\']([^"\']+)["\']',
    re.MULTILINE
)

STYLE_IMPORT = re.compile(
    r'import\s+(?:(\w+)\s+from\s+)?["\']([^"\']+\.(?:css|scss|sass|less|styl))["\']',
    re.MULTILINE
)

# Inline style patterns
INLINE_STYLE_OBJECT = re.compile(
    r'style\s*=\s*\{\s*\{([^}]+)\}\s*\}',
    re.MULTILINE | re.DOTALL
)

INLINE_STYLE_VAR = re.compile(
    r'style\s*=\s*\{(\w+)\}',
    re.MULTILINE
)

# Props extraction
PROPS_DESTRUCTURE = re.compile(
    r'\(\s*\{\s*([^}]+)\s*\}\s*(?::\s*[^)]+)?\)',
    re.MULTILINE
)

PROPS_INTERFACE = re.compile(
    r'interface\s+\w*Props\s*\{([^}]+)\}',
    re.MULTILINE | re.DOTALL
)

PROPS_TYPE = re.compile(
    r'type\s+\w*Props\s*=\s*\{([^}]+)\}',
    re.MULTILINE | re.DOTALL
)


class ReactAnalyzer:
    """
    Analyzes React projects for component structure and style usage.
    """

    def __init__(self, root_path: str):
        self.root = Path(root_path).resolve()

    async def analyze(
        self,
        component_files: Optional[List[Path]] = None
    ) -> List[ComponentInfo]:
        """
        Analyze React components in the project.

        Args:
            component_files: Optional list of files to analyze.
                            If None, scans the project.

        Returns:
            List of ComponentInfo for each detected component.
        """
        if component_files is None:
            component_files = self._find_component_files()

        components = []

        for file_path in component_files:
            try:
                file_components = await self._analyze_file(file_path)
                components.extend(file_components)
            except Exception as e:
                # Log error but continue
                continue

        return components

    def _find_component_files(self) -> List[Path]:
        """Find all potential React component files."""
        skip_dirs = {'node_modules', '.git', 'dist', 'build', '.next'}
        component_files = []

        for ext in ['*.jsx', '*.tsx']:
            for path in self.root.rglob(ext):
                if not any(skip in path.parts for skip in skip_dirs):
                    component_files.append(path)

        # Also check .js/.ts files that look like components
        for ext in ['*.js', '*.ts']:
            for path in self.root.rglob(ext):
                if any(skip in path.parts for skip in skip_dirs):
                    continue
                # Skip config and utility files
                if any(x in path.name.lower() for x in ['config', 'util', 'helper', 'hook', 'context']):
                    continue
                # Check if PascalCase (likely component)
                if path.stem[0].isupper():
                    component_files.append(path)

        return component_files

    async def _analyze_file(self, file_path: Path) -> List[ComponentInfo]:
        """Analyze a single file for React components."""
        content = file_path.read_text(encoding='utf-8', errors='ignore')
        components = []

        # Find all components in the file
        component_matches = []

        # Functional components
        for match in FUNCTIONAL_COMPONENT.finditer(content):
            name = match.group(1)
            if self._is_valid_component_name(name):
                component_matches.append((name, 'functional', match.start()))

        # Class components
        for match in CLASS_COMPONENT.finditer(content):
            name = match.group(1)
            component_matches.append((name, 'class', match.start()))

        # forwardRef components
        for match in FORWARD_REF.finditer(content):
            name = match.group(1)
            component_matches.append((name, 'forwardRef', match.start()))

        # memo components
        for match in MEMO_COMPONENT.finditer(content):
            name = match.group(1)
            component_matches.append((name, 'memo', match.start()))

        # Dedupe by name (keep first occurrence)
        seen_names = set()
        unique_matches = []
        for name, comp_type, pos in component_matches:
            if name not in seen_names:
                seen_names.add(name)
                unique_matches.append((name, comp_type, pos))

        # Extract imports (shared across all components in file)
        imports = self._extract_imports(content)
        style_files = self._extract_style_imports(content)
        inline_styles = self._find_inline_styles(content)

        # Create ComponentInfo for each
        for name, comp_type, pos in unique_matches:
            # Extract props for this component
            props = self._extract_props(content, name)

            # Find child components used
            children = self._find_child_components(content, seen_names)

            # Check if component has styles
            has_styles = bool(style_files) or bool(inline_styles)

            components.append(ComponentInfo(
                name=name,
                path=str(file_path.relative_to(self.root)),
                type=comp_type,
                props=props,
                has_styles=has_styles,
                style_files=style_files,
                inline_style_count=len(inline_styles),
                imports=imports,
                exports=self._find_exports(content, name),
                children=children,
                line_count=content.count('\n') + 1,
            ))

        return components

    def _is_valid_component_name(self, name: str) -> bool:
        """Check if a name is a valid React component name."""
        # Must be PascalCase
        if not name[0].isupper():
            return False

        # Filter out common non-component patterns
        invalid_names = {
            'React', 'Component', 'PureComponent', 'Fragment',
            'Suspense', 'Provider', 'Consumer', 'Context',
            'Error', 'ErrorBoundary', 'Wrapper', 'Container',
            'Props', 'State', 'Type', 'Interface',
        }

        return name not in invalid_names

    def _extract_imports(self, content: str) -> List[str]:
        """Extract import paths from file."""
        imports = []
        for match in IMPORT_PATTERN.finditer(content):
            import_path = match.group(1)
            # Skip node_modules style imports for brevity
            if not import_path.startswith('.') and '/' not in import_path:
                continue
            imports.append(import_path)
        return imports

    def _extract_style_imports(self, content: str) -> List[str]:
        """Extract style file imports."""
        style_files = []
        for match in STYLE_IMPORT.finditer(content):
            style_path = match.group(2)
            style_files.append(style_path)
        return style_files

    def _find_inline_styles(self, content: str) -> List[Location]:
        """Find inline style usage locations."""
        locations = []

        # style={{ ... }}
        for match in INLINE_STYLE_OBJECT.finditer(content):
            line = content[:match.start()].count('\n') + 1
            locations.append(Location(
                file_path="",  # Will be set by caller
                line=line,
            ))

        return locations

    def _extract_props(self, content: str, component_name: str) -> List[str]:
        """Extract props for a component."""
        props = set()

        # Look for destructured props
        for match in PROPS_DESTRUCTURE.finditer(content):
            props_str = match.group(1)
            # Extract prop names from destructuring
            for prop in re.findall(r'(\w+)(?:\s*[=:])?', props_str):
                if prop and not prop[0].isupper():  # Skip types
                    props.add(prop)

        # Look for Props interface/type
        for pattern in [PROPS_INTERFACE, PROPS_TYPE]:
            for match in pattern.finditer(content):
                props_str = match.group(1)
                # Extract prop names
                for line in props_str.split('\n'):
                    prop_match = re.match(r'\s*(\w+)\s*[?:]', line)
                    if prop_match:
                        props.add(prop_match.group(1))

        return list(props)

    def _find_child_components(
        self,
        content: str,
        current_components: Set[str]
    ) -> List[str]:
        """Find child components used in JSX."""
        children = set()

        # Find JSX elements that look like components (PascalCase)
        jsx_pattern = re.compile(r'<([A-Z][A-Za-z0-9]*)')
        for match in jsx_pattern.finditer(content):
            component_name = match.group(1)
            # Skip current file's components and React built-ins
            if component_name not in current_components:
                if component_name not in {'Fragment', 'Suspense', 'Provider'}:
                    children.add(component_name)

        return list(children)

    def _find_exports(self, content: str, component_name: str) -> List[str]:
        """Find export type for component."""
        exports = []

        # Default export
        if re.search(rf'export\s+default\s+{component_name}\b', content):
            exports.append('default')
        if re.search(rf'export\s+default\s+(?:function|const)\s+{component_name}\b', content):
            exports.append('default')

        # Named export
        if re.search(rf'export\s+(?:const|function|class)\s+{component_name}\b', content):
            exports.append('named')
        if re.search(r'export\s*\{[^}]*\b' + re.escape(component_name) + r'\b[^}]*\}', content):
            exports.append('named')

        return exports

    async def find_inline_styles(self, path: Optional[str] = None) -> List[Dict[str, Any]]:
        """
        Find all inline style usage in the project.

        Returns list of inline style occurrences with:
        - file path
        - line number
        - style content
        - component name (if detectable)
        """
        search_path = Path(path) if path else self.root
        results = []

        for ext in ['*.jsx', '*.tsx', '*.js', '*.ts']:
            for file_path in search_path.rglob(ext):
                if any(skip in file_path.parts for skip in
                       {'node_modules', '.git', 'dist', 'build'}):
                    continue

                try:
                    content = file_path.read_text(encoding='utf-8', errors='ignore')

                    # Find style={{ ... }}
                    for match in INLINE_STYLE_OBJECT.finditer(content):
                        line = content[:match.start()].count('\n') + 1
                        style_content = match.group(1).strip()

                        results.append({
                            'file': str(file_path.relative_to(self.root)),
                            'line': line,
                            'content': style_content[:200],
                            'type': 'object',
                        })

                    # Find style={variable}
                    for match in INLINE_STYLE_VAR.finditer(content):
                        line = content[:match.start()].count('\n') + 1
                        var_name = match.group(1)

                        results.append({
                            'file': str(file_path.relative_to(self.root)),
                            'line': line,
                            'content': f'style={{{var_name}}}',
                            'type': 'variable',
                            'variable': var_name,
                        })

                except Exception:
                    continue

        return results

    async def get_component_tree(self) -> Dict[str, List[str]]:
        """
        Build component dependency tree.

        Returns dict mapping component names to their child components.
        """
        components = await self.analyze()

        tree = {}
        for comp in components:
            tree[comp.name] = comp.children

        return tree

    async def find_style_patterns(self) -> Dict[str, List[Dict]]:
        """
        Find different styling patterns used across the project.

        Returns dict with pattern types and their occurrences.
        """
        patterns = {
            'inline_styles': [],
            'css_modules': [],
            'styled_components': [],
            'emotion': [],
            'tailwind': [],
            'css_classes': [],
        }

        component_files = self._find_component_files()

        for file_path in component_files:
            try:
                content = file_path.read_text(encoding='utf-8', errors='ignore')
                rel_path = str(file_path.relative_to(self.root))

                # CSS Modules
                if re.search(r'import\s+\w+\s+from\s+["\'].*\.module\.', content):
                    patterns['css_modules'].append({'file': rel_path})

                # styled-components
                if re.search(r'styled\.|from\s+["\']styled-components', content):
                    patterns['styled_components'].append({'file': rel_path})

                # Emotion
                if re.search(r'@emotion|css`', content):
                    patterns['emotion'].append({'file': rel_path})

                # Tailwind (className with utility classes)
                if re.search(r'className\s*=\s*["\'][^"\']*(?:flex|grid|p-\d|m-\d|bg-)', content):
                    patterns['tailwind'].append({'file': rel_path})

                # Regular CSS classes
                if re.search(r'className\s*=\s*["\'][a-zA-Z]', content):
                    patterns['css_classes'].append({'file': rel_path})

                # Inline styles
                for match in INLINE_STYLE_OBJECT.finditer(content):
                    line = content[:match.start()].count('\n') + 1
                    patterns['inline_styles'].append({
                        'file': rel_path,
                        'line': line,
                    })

            except Exception:
                continue

        return patterns
