# Copyright 2025-present DatusAI, Inc.
# Licensed under the Apache License, Version 2.0.
# See http://www.apache.org/licenses/LICENSE-2.0 for details.

"""
Docusaurus sidebar nav resolver.

Parses ``sidebars.json`` to build a file-path → nav_path mapping.
Handles Docusaurus sidebar item formats:
- String items: ``"intro"``
- Doc objects: ``{"type": "doc", "id": "intro", "label": "Introduction"}``
- Category objects: ``{"type": "category", "label": "...", "items": [...]}``
- Autogenerated: ``{"type": "autogenerated", "dirName": "..."}``
"""

import json
from typing import Any, Dict, List, Optional

from datus.storage.document.nav_resolver.base_resolver import BaseNavResolver
from datus.utils.loggings import get_logger

logger = get_logger(__name__)


class DocusaurusResolver(BaseNavResolver):
    """Resolves nav_path from Docusaurus sidebars.json configuration."""

    def resolve(
        self,
        config_content: str,
        file_paths: List[str],
        content_root: str,
        extra_context: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, List[str]]:
        if not config_content:
            return {}

        try:
            sidebars = json.loads(config_content)
        except json.JSONDecodeError as e:
            logger.warning(f"Failed to parse sidebars.json: {e}")
            return {}

        # Build a mapping from doc_id -> nav_path by walking all sidebars
        id_to_nav: Dict[str, List[str]] = {}
        for _sidebar_name, sidebar_items in sidebars.items():
            if isinstance(sidebar_items, list):
                self._walk_items(sidebar_items, [], id_to_nav)

        # Build index: strip content_root and extension from file_paths -> file_path
        path_index: Dict[str, str] = {}
        for fp in file_paths:
            doc_id = self._file_path_to_doc_id(fp, content_root)
            path_index[doc_id] = fp

        # Collect autogenerated prefixes separately
        autogen_prefixes: Dict[str, List[str]] = {}
        explicit_ids: Dict[str, List[str]] = {}
        for doc_id, nav_path in id_to_nav.items():
            if doc_id.startswith("__autogen__"):
                autogen_prefixes[doc_id[len("__autogen__") :]] = nav_path
            else:
                explicit_ids[doc_id] = nav_path

        # Match explicit doc_ids to file paths
        nav_map: Dict[str, List[str]] = {}
        for doc_id, nav_path in explicit_ids.items():
            if doc_id in path_index:
                nav_map[path_index[doc_id]] = nav_path
            else:
                # Try partial matching: doc_id might be relative to content_root
                for pid, fp in path_index.items():
                    if pid.endswith("/" + doc_id) or pid == doc_id:
                        nav_map[fp] = nav_path
                        break

        # Match autogenerated prefixes to remaining unmapped files
        for pid, fp in path_index.items():
            if fp in nav_map:
                continue
            for prefix, nav_path in autogen_prefixes.items():
                if pid.startswith(prefix + "/") or pid == prefix:
                    nav_map[fp] = nav_path
                    break

        logger.info(f"Docusaurus resolver mapped {len(nav_map)}/{len(file_paths)} files")
        return nav_map

    def _walk_items(
        self,
        items: List[Any],
        parent_path: List[str],
        result: Dict[str, List[str]],
    ) -> None:
        """Recursively walk sidebar items to build id -> nav_path mapping."""
        for item in items:
            if isinstance(item, str):
                # Simple string item: the doc ID
                result[item] = list(parent_path)

            elif isinstance(item, dict):
                item_type = item.get("type", "doc")

                if item_type == "category":
                    label = item.get("label", "")
                    child_path = parent_path + [label] if label else parent_path
                    child_items = item.get("items", [])
                    self._walk_items(child_items, child_path, result)

                elif item_type == "doc":
                    doc_id = item.get("id", "")
                    if doc_id:
                        result[doc_id] = list(parent_path)

                elif item_type == "autogenerated":
                    # Autogenerated items are resolved from dirName
                    # We can't resolve individual files without scanning the directory,
                    # so we record the dirName prefix for fallback matching
                    dir_name = item.get("dirName", "")
                    if dir_name:
                        result[f"__autogen__{dir_name}"] = list(parent_path)

                elif item_type == "link":
                    # External links, skip
                    pass

            elif isinstance(item, list):
                # Nested list (less common), recurse
                self._walk_items(item, parent_path, result)

    def _file_path_to_doc_id(self, file_path: str, content_root: str) -> str:
        """Convert a file path to a Docusaurus doc ID.

        Docusaurus doc IDs are relative paths without extension.
        e.g., "docs/en/sql-reference/ddl/CREATE_TABLE.md" -> "sql-reference/ddl/CREATE_TABLE"
        """
        relative = self._strip_content_root(file_path, content_root)
        return self._strip_extension(relative)
