from __future__ import annotations

import os

from vibelexity.circular_deps.resolvers.base import PathResolver
from vibelexity.circular_deps.resolvers.shared import check_under_root
from vibelexity.circular_deps.resolvers.tsconfig import TsconfigParser


class TypeScriptPathResolver(PathResolver):
    _INDEX_FILES = ["index.ts", "index.tsx", "index.js", "index.jsx"]

    def __init__(self, module_resolution=None, filepath=None, root_dir=None):
        """Initialize TypeScript path resolver with module resolution mode.

        Priority order for determining mode:
        1. Explicit module_resolution parameter (CLI override)
        2. tsconfig.json closest to filepath
        3. tsconfig.json at root_dir
        4. Default: "node16"

        Args:
            module_resolution: Explicit mode ("node", "node16", "nodenext", "bundler")
            filepath: File path to find closest tsconfig.json
            root_dir: Root directory to search for tsconfig.json
        """
        if module_resolution:
            self.module_resolution = self._normalize_module_resolution(
                module_resolution
            )
        elif filepath:
            self.module_resolution = self._detect_from_tsconfig(filepath)
        elif root_dir:
            self.module_resolution = self._detect_from_tsconfig(root_dir)
        else:
            self.module_resolution = "node16"
        self._validate_module_resolution()

    def _normalize_module_resolution(self, mode: str) -> str:
        if mode in ("nodenext", "node16"):
            return "node16"
        return mode

    def _detect_from_tsconfig(self, path: str) -> str:
        tsconfig_path = TsconfigParser.find_tsconfig_for_file(path)
        if not tsconfig_path:
            return "node16"

        config = TsconfigParser.parse_tsconfig(tsconfig_path)
        if not config:
            return "node16"

        mode = TsconfigParser.get_module_resolution(config)
        return mode or "node16"

    def _validate_module_resolution(self):
        valid_modes = {"node", "node16", "bundler"}
        if self.module_resolution not in valid_modes:
            self.module_resolution = "node16"

    def resolve(self, module, current_file, root_dir):
        if "node_modules" in module:
            return None

        if module.startswith(".") or module.startswith(".."):
            return self._resolve_relative(module, current_file, root_dir)
        else:
            return self._resolve_absolute(module, current_file, root_dir)

    def _resolve_relative(self, module, current_file, root_dir):
        current_dir = os.path.dirname(current_file)
        target_path = os.path.abspath(os.path.join(current_dir, module))

        return self._resolve_module_path(module, target_path, root_dir)

    def _resolve_absolute(self, module, current_file, root_dir):
        module_path = module.replace("/", os.sep)

        current_dir = os.path.dirname(current_file)
        target_path = os.path.join(current_dir, module_path)
        resolved = self._resolve_module_path(module, target_path, root_dir)
        if resolved:
            return resolved

        target_path = os.path.join(root_dir, module_path)
        return self._resolve_module_path(module, target_path, root_dir)

    def _resolve_module_path(
        self, module: str, target_path: str, root_dir: str
    ) -> str | None:
        has_extension = self._has_extension(module)

        if self.module_resolution == "node":
            if has_extension:
                return self._resolve_node_with_extension(target_path, root_dir)
            else:
                return self._resolve_node_without_extension(
                    module, target_path, root_dir
                )
        elif self.module_resolution == "node16":
            if has_extension:
                return self._resolve_node16_with_extension(
                    module, target_path, root_dir
                )
            else:
                return None
        elif self.module_resolution == "bundler":
            if has_extension:
                return self._resolve_node16_with_extension(
                    module, target_path, root_dir
                )
            else:
                return self._resolve_bundler_without_extension(
                    module, target_path, root_dir
                )

        return None

    def _has_extension(self, module_path: str) -> bool:
        return os.path.splitext(module_path)[1] != ""

    def _strip_extension(self, module_path: str) -> str:
        base, ext = os.path.splitext(module_path)
        if ext.lower() in (".ts", ".tsx", ".js", ".jsx"):
            return base
        return module_path

    def _resolve_node_with_extension(
        self, target_path: str, root_dir: str
    ) -> str | None:
        if os.path.isfile(target_path):
            ext = os.path.splitext(target_path)[1]
            if ext in (".ts", ".tsx", ".js", ".jsx"):
                return check_under_root(target_path, root_dir)
            return None

        if os.path.isdir(target_path):
            for index_file in self._INDEX_FILES:
                index_path = os.path.join(target_path, index_file)
                if os.path.isfile(index_path):
                    return check_under_root(index_path, root_dir)

            dirname = os.path.basename(target_path)
            for ext in (".ts", ".tsx", ".js", ".jsx"):
                file_path = os.path.join(target_path, dirname + ext)
                if os.path.isfile(file_path):
                    return check_under_root(file_path, root_dir)

        return None

    def _resolve_node_without_extension(
        self, module: str, target_path: str, root_dir: str
    ) -> str | None:
        if os.path.isdir(target_path) and not module.endswith("/"):
            dirname = os.path.basename(target_path)
            for ext in (".ts", ".tsx"):
                file_path = os.path.join(target_path, dirname + ext)
                if os.path.isfile(file_path):
                    return check_under_root(file_path, root_dir)

            for index_file in self._INDEX_FILES:
                index_path = os.path.join(target_path, index_file)
                if os.path.isfile(index_path):
                    return check_under_root(index_path, root_dir)

        for ext in (".d.ts", ".ts", ".tsx", ".js", ".jsx"):
            file_path = target_path + ext
            if os.path.isfile(file_path):
                return check_under_root(file_path, root_dir)

        return None

    def _resolve_node16_with_extension(
        self, module: str, target_path: str, root_dir: str
    ) -> str | None:
        ext = os.path.splitext(module)[1]

        if os.path.isfile(target_path):
            target_ext = os.path.splitext(target_path)[1]
            if target_ext == ext:
                return check_under_root(target_path, root_dir)

        base = self._strip_extension(target_path)
        for search_ext in (".ts", ".tsx"):
            file_path = base + search_ext
            if os.path.isfile(file_path):
                return check_under_root(file_path, root_dir)

        if os.path.isdir(target_path):
            for index_file in self._INDEX_FILES:
                index_path = os.path.join(target_path, index_file)
                if os.path.isfile(index_path):
                    return check_under_root(index_path, root_dir)

        return None

    def _resolve_bundler_without_extension(
        self, module: str, target_path: str, root_dir: str
    ) -> str | None:
        if os.path.isdir(target_path) and not module.endswith("/"):
            dirname = os.path.basename(target_path)
            for ext in (".ts", ".tsx", ".js", ".jsx"):
                file_path = os.path.join(target_path, dirname + ext)
                if os.path.isfile(file_path):
                    return check_under_root(file_path, root_dir)

        for index_file in self._INDEX_FILES:
            index_path = target_path + "/" + index_file
            if os.path.isfile(index_path):
                return check_under_root(index_path, root_dir)

        for ext in (".ts", ".tsx", ".js", ".jsx"):
            file_path = target_path + ext
            if os.path.isfile(file_path):
                return check_under_root(file_path, root_dir)

        return None
