"""JavaScript language adapter using tree-sitter.

Handles .js and .jsx files using the tree-sitter-javascript grammar.
Supports both ES modules and CommonJS patterns.
Uses shared parsing utilities from _treesitter_utils.py.
"""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING

import tree_sitter_javascript as tsjs
from tree_sitter import Language, Parser

from unfamiliar.analysis.adapters._treesitter_utils import (
    calculate_complexity,
    get_node_text,
    resolve_js_ts_import,
    traverse,
)
from unfamiliar.analysis.adapters._ts_definitions import extract_definitions
from unfamiliar.analysis.adapters._ts_entry_points import extract_entry_points
from unfamiliar.analysis.adapters._ts_imports import (
    extract_es_imports,
    extract_require_imports,
)
from unfamiliar.analysis.models import EntryPoint, ParsedFile
from unfamiliar.constants import EntryType

if TYPE_CHECKING:
    from collections.abc import Sequence
    from pathlib import Path

    from tree_sitter import Node

logger = logging.getLogger(__name__)

# Pre-create language for performance
_JS_LANGUAGE = Language(tsjs.language())


class JavaScriptAdapter:
    """Language adapter for JavaScript source files using tree-sitter.

    Supports both ES modules and CommonJS patterns.
    """

    def __init__(self) -> None:
        self._parser = Parser(_JS_LANGUAGE)

    def language_name(self) -> str:
        """Return the lowercase name of the language."""
        return "javascript"

    def supported_extensions(self) -> frozenset[str]:
        """Return supported file extensions."""
        return frozenset({".js", ".jsx"})

    def resolution_extensions(self) -> Sequence[str]:
        """Extensions to try when resolving imports, in preference order.

        JS adapter prefers .js/.jsx over .ts/.tsx
        """
        return [
            ".js",
            ".jsx",
            ".ts",
            ".tsx",
        ]

    def can_handle(self, path: str) -> bool:
        """Check if this adapter can parse the given file."""
        return path.endswith((".js", ".jsx"))

    def parse_file(self, path: Path, content: bytes) -> ParsedFile:
        """Parse a JavaScript file and extract structure.

        Args:
            path: Relative path to the file.
            content: Raw file contents as bytes.

        Returns:
            ParsedFile with imports, definitions, entry points, and complexity.
            Returns empty ParsedFile on parse errors (logs warning).
        """
        path_str = str(path)
        line_count = content.count(b"\n") + 1 if content else 0

        try:
            tree = self._parser.parse(content)
        except (ValueError, OSError) as e:
            logger.warning("Parse error in %s: %s", path_str, e)
            return ParsedFile(
                path=path_str,
                language="javascript",
                line_count=line_count,
            )

        root = tree.root_node

        # Check for parse errors
        if root.has_error:
            logger.debug(
                "Syntax errors in %s, partial results may be incomplete",
                path_str,
            )

        # Extract all information using shared utilities
        # JavaScript supports both ES modules and CommonJS
        imports = extract_es_imports(root, path_str)
        imports.extend(extract_require_imports(root, path_str))

        definitions = extract_definitions(root)
        entry_points = extract_entry_points(root, path_str)
        entry_points.extend(self._detect_commonjs_entry_points(root, path_str))
        complexity = calculate_complexity(root)

        return ParsedFile(
            path=path_str,
            language="javascript",
            imports=imports,
            definitions=definitions,
            entry_points=entry_points,
            complexity=complexity,
            line_count=line_count,
        )

    def _detect_commonjs_entry_points(
        self, root_node: Node, file_path: str
    ) -> list[EntryPoint]:
        """Detect CommonJS-specific entry point patterns.

        Looks for:
        - module.exports = ...
        - exports.foo = ...

        Args:
            root_node: The root node of the parse tree.
            file_path: Path of the file being analyzed.

        Returns:
            List of EntryPoint objects for CommonJS entry patterns.
        """
        entry_points: list[EntryPoint] = []

        for node in traverse(root_node):
            if node.type == "assignment_expression":
                left = node.child_by_field_name("left")
                if left:
                    left_text = get_node_text(left)

                    # Check for module.exports = ...
                    if left_text == "module.exports":
                        entry_points.append(
                            EntryPoint(
                                file_path=file_path,
                                entry_type=EntryType.MAIN,
                                description="module.exports assignment",
                                line=node.start_point.row + 1,
                            )
                        )
                    # Check for exports.foo = ...
                    elif left_text.startswith("exports."):
                        entry_points.append(
                            EntryPoint(
                                file_path=file_path,
                                entry_type=EntryType.API_ROUTE,
                                description=f"CommonJS export: {left_text}",
                                line=node.start_point.row + 1,
                            )
                        )

        return entry_points

    def resolve_import(
        self,
        importing_file: str,
        import_target: str,
        repo_files: set[str],
    ) -> str | None:
        """Resolve a JavaScript import to a file path in the repo.

        Handles:
        - Relative imports (.foo, ../bar)
        - Extension-less imports (tries .js, .jsx, .ts, .tsx)
        - Index files (tries index.js, index.jsx, etc.)

        Bare imports (no . prefix) are treated as external packages.

        Args:
            importing_file: Path of the file containing the import.
            import_target: The raw import string (e.g., "./utils", "../helpers").
            repo_files: Set of all file paths in the repository.

        Returns:
            Resolved file path relative to repo root, or None for external imports.
        """

        return resolve_js_ts_import(
            importing_file, import_target, repo_files, self.resolution_extensions()
        )
