from __future__ import annotations

import logging
import os
from typing import Any, Optional

import jinja2

from mkdocs import localization, utils
from mkdocs.config.base import ValidationError
from mkdocs.utils import filters

log = logging.getLogger(__name__)


class Theme:
    """
    A Theme object.

    Keywords:

        name: The name of the theme as defined by its entrypoint.

        custom_dir: User defined directory for custom templates.

        static_templates: A list of templates to render as static pages.

    All other keywords are passed as-is and made available as a key/value mapping.

    """

    def __init__(self, name: Optional[str] = None, **user_config) -> None:
        self.name = name
        self._vars = {'name': name, 'locale': 'en'}

        # MkDocs provided static templates are always included
        package_dir = os.path.abspath(os.path.dirname(__file__))
        mkdocs_templates = os.path.join(package_dir, 'templates')
        self.static_templates = set(os.listdir(mkdocs_templates))

        # Build self.dirs from various sources in order of precedence
        self.dirs = []

        if 'custom_dir' in user_config:
            self.dirs.append(user_config.pop('custom_dir'))

        if name:
            self._load_theme_config(name)

        # Include templates provided directly by MkDocs (outside any theme)
        self.dirs.append(mkdocs_templates)

        # Handle remaining user configs. Override theme configs (if set)
        self.static_templates.update(user_config.pop('static_templates', []))
        self._vars.update(user_config)

        # Validate locale and convert to Locale object
        self._vars['locale'] = localization.parse_locale(self._vars['locale'])

    def __repr__(self) -> str:
        return "{}(name='{}', dirs={}, static_templates={}, {})".format(
            self.__class__.__name__,
            self.name,
            self.dirs,
            list(self.static_templates),
            ', '.join(f'{k}={v!r}' for k, v in self._vars.items()),
        )

    def __getitem__(self, key: str) -> Any:
        return self._vars[key]

    def __setitem__(self, key, value):
        self._vars[key] = value

    def __contains__(self, item: str) -> bool:
        return item in self._vars

    def __iter__(self):
        return iter(self._vars)

    def _load_theme_config(self, name: str) -> None:
        """Recursively load theme and any parent themes."""

        theme_dir = utils.get_theme_dir(name)
        self.dirs.append(theme_dir)

        try:
            file_path = os.path.join(theme_dir, 'mkdocs_theme.yml')
            with open(file_path, 'rb') as f:
                theme_config = utils.yaml_load(f)
                if theme_config is None:
                    theme_config = {}
        except OSError as e:
            log.debug(e)
            raise ValidationError(
                f"The theme '{name}' does not appear to have a configuration file. "
                f"Please upgrade to a current version of the theme."
            )

        log.debug(f"Loaded theme configuration for '{name}' from '{file_path}': {theme_config}")

        parent_theme = theme_config.pop('extends', None)
        if parent_theme:
            themes = utils.get_theme_names()
            if parent_theme not in themes:
                raise ValidationError(
                    f"The theme '{name}' inherits from '{parent_theme}', which does not appear to be installed. "
                    f"The available installed themes are: {', '.join(themes)}"
                )
            self._load_theme_config(parent_theme)

        self.static_templates.update(theme_config.pop('static_templates', []))
        self._vars.update(theme_config)

    def get_env(self) -> jinja2.Environment:
        """Return a Jinja environment for the theme."""

        loader = jinja2.FileSystemLoader(self.dirs)
        # No autoreload because editing a template in the middle of a build is not useful.
        env = jinja2.Environment(loader=loader, auto_reload=False)
        env.filters['url'] = filters.url_filter
        localization.install_translations(env, self._vars['locale'], self.dirs)
        return env
