# ---------------------------------------------------------------------------
#  Copyright (c) DevSecCode. All rights reserved.
# ---------------------------------------------------------------------------
"""Language profile abstraction for multi-language code intelligence.

Each supported language implements a LanguageProfile that provides:
- File extension mapping
- Tree-sitter parser initialization
- Symbol/import/call/heritage extraction
- Language-specific import path resolution
"""

from __future__ import annotations

import logging
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Optional

if TYPE_CHECKING:
    from dsc_api.services.code_intel.store import CodeIntelStore
    from dsc_api.services.code_intel.ts_extractor import ExtractionResult

logger = logging.getLogger(__name__)


class LanguageProfile(ABC):
    """Abstract base for language-specific extraction and resolution."""

    @property
    @abstractmethod
    def language_id(self) -> str:
        """Canonical language identifier (e.g. 'typescript', 'python')."""
        ...

    @property
    @abstractmethod
    def extensions(self) -> frozenset[str]:
        """File extensions handled by this profile (e.g. {'.py', '.pyi'})."""
        ...

    @abstractmethod
    def get_parser(self):
        """Return (or lazily create) the tree-sitter Parser for this language."""
        ...

    @abstractmethod
    def extract_file(self, file_path: str, source: bytes) -> "ExtractionResult":
        """Parse *source* and return extracted symbols, imports, calls, heritage."""
        ...

    @abstractmethod
    def resolve_import_path(
        self,
        from_file: str,
        imported_path: str,
        store: "CodeIntelStore",
        workspace_root: str,
    ) -> Optional[str]:
        """Resolve an import path to a workspace-relative file path.

        Returns None if the import cannot be resolved.
        """
        ...

    @abstractmethod
    def find_file_candidates(self, base_path: str, store: "CodeIntelStore") -> Optional[str]:
        """Try to find a file matching *base_path* with language-specific
        extension hunting and index-file patterns.

        Returns the resolved path or None.
        """
        ...

    def load_config(self, workspace_root: str) -> None:
        """Optional: load language-specific config (tsconfig, pyproject, etc.)."""
        pass

    def language_for_file(self, file_path: str) -> str:
        """Return the sub-language for a file (e.g. 'tsx' for .tsx files).

        Defaults to language_id. Override for languages with sub-variants.
        """
        return self.language_id


class LanguageRegistry:
    """Global registry of language profiles, keyed by file extension."""

    _profiles: dict[str, LanguageProfile] = {}
    _by_id: dict[str, LanguageProfile] = {}

    @classmethod
    def register(cls, profile: LanguageProfile) -> None:
        """Register a language profile for all its extensions."""
        for ext in profile.extensions:
            cls._profiles[ext] = profile
        cls._by_id[profile.language_id] = profile
        logger.debug("Registered language profile: %s (%s)", profile.language_id, ", ".join(profile.extensions))

    @classmethod
    def for_extension(cls, ext: str) -> Optional[LanguageProfile]:
        """Look up the profile for a file extension (e.g. '.py')."""
        return cls._profiles.get(ext)

    @classmethod
    def for_language(cls, language_id: str) -> Optional[LanguageProfile]:
        """Look up the profile by language id."""
        return cls._by_id.get(language_id)

    @classmethod
    def supported_extensions(cls) -> frozenset[str]:
        """Return all registered file extensions."""
        return frozenset(cls._profiles.keys())

    @classmethod
    def all_profiles(cls) -> list[LanguageProfile]:
        """Return all unique registered profiles."""
        seen: set[str] = set()
        result: list[LanguageProfile] = []
        for p in cls._profiles.values():
            if p.language_id not in seen:
                seen.add(p.language_id)
                result.append(p)
        return result

    @classmethod
    def clear(cls) -> None:
        """Remove all registered profiles (for testing)."""
        cls._profiles.clear()
        cls._by_id.clear()
