# -*- coding: utf-8 -*-

# Copyright © 2012-2025 Roberto Alsina and others.

# Permission is hereby granted, free of charge, to any
# person obtaining a copy of this software and associated
# documentation files (the "Software"), to deal in the
# Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the
# Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice
# shall be included in all copies or substantial portions of
# the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""Render the category pages and feeds."""

import os

from nikola.plugin_categories import Taxonomy
from nikola import utils, hierarchy_utils


class ClassifyCategories(Taxonomy):
    """Classify the posts by categories."""

    name = "classify_categories"

    classification_name = "category"
    overview_page_variable_name = "categories"
    overview_page_items_variable_name = "cat_items"
    overview_page_hierarchy_variable_name = "cat_hierarchy"
    more_than_one_classifications_per_post = False
    has_hierarchy = True
    include_posts_from_subhierarchies = True
    include_posts_into_hierarchy_root = False
    show_list_as_subcategories_list = False
    template_for_classification_overview = "tags.tmpl"
    always_disable_rss = False
    always_disable_atom = False
    apply_to_posts = True
    apply_to_pages = False
    minimum_post_count_per_classification_in_overview = 1
    omit_empty_classifications = True
    add_other_languages_variable = True
    path_handler_docstrings = {
        'category_index': """A link to the category index.

Example:

link://category_index => /categories/index.html""",
        'category': """A link to a category. Takes page number as optional keyword argument.

Example:

link://category/dogs => /categories/dogs.html""",
        'category_atom': """A link to a category's Atom feed.

Example:

link://category_atom/dogs => /categories/dogs.atom""",
        'category_rss': """A link to a category's RSS feed.

Example:

link://category_rss/dogs => /categories/dogs.xml""",
    }

    def set_site(self, site):
        """Set site, which is a Nikola instance."""
        super().set_site(site)
        self.show_list_as_index = self.site.config['CATEGORY_PAGES_ARE_INDEXES']
        self.template_for_single_list = "tagindex.tmpl" if self.show_list_as_index else "tag.tmpl"
        self.translation_manager = utils.ClassificationTranslationManager()

        # Needed to undo names for CATEGORY_PAGES_FOLLOW_DESTPATH
        self.destpath_names_reverse = {}
        for lang in self.site.config['TRANSLATIONS']:
            self.destpath_names_reverse[lang] = {}
            for k, v in self.site.config['CATEGORY_DESTPATH_NAMES'](lang).items():
                self.destpath_names_reverse[lang][v] = k
        self.destpath_names_reverse = utils.TranslatableSetting(
            '_CATEGORY_DESTPATH_NAMES_REVERSE', self.destpath_names_reverse,
            self.site.config['TRANSLATIONS'])

    def is_enabled(self, lang=None):
        """Return True if this taxonomy is enabled, or False otherwise."""
        return True

    def classify(self, post, lang):
        """Classify the given post for the given language."""
        cat = post.meta('category', lang=lang).strip()
        return [cat] if cat else []

    def get_classification_friendly_name(self, classification, lang, only_last_component=False):
        """Extract a friendly name from the classification."""
        classification = self.extract_hierarchy(classification)
        return classification[-1] if classification else ''

    def get_overview_path(self, lang, dest_type='page'):
        """Return a path for the list of all classifications."""
        if self.site.config['CATEGORIES_INDEX_PATH'](lang):
            path = self.site.config['CATEGORIES_INDEX_PATH'](lang)
            append_index = 'never'
        else:
            path = self.site.config['CATEGORY_PATH'](lang)
            append_index = 'always'
        return [component for component in path.split('/') if component], append_index

    def slugify_tag_name(self, name, lang):
        """Slugify a tag name."""
        if self.site.config['SLUG_TAG_PATH']:
            name = utils.slugify(name, lang)
        return name

    def slugify_category_name(self, path, lang):
        """Slugify a category name."""
        if self.site.config['CATEGORY_OUTPUT_FLAT_HIERARCHY']:
            path = path[-1:]  # only the leaf
        result = [self.slugify_tag_name(part, lang) for part in path]
        result[0] = self.site.config['CATEGORY_PREFIX'] + result[0]
        if not self.site.config['PRETTY_URLS']:
            result = ['-'.join(result)]
        return result

    def get_path(self, classification, lang, dest_type='page'):
        """Return a path for the given classification."""
        cat_string = '/'.join(classification)
        classification_raw = classification  # needed to undo CATEGORY_DESTPATH_NAMES
        destpath_names_reverse = self.destpath_names_reverse(lang)
        if self.site.config['CATEGORY_PAGES_FOLLOW_DESTPATH']:
            base_dir = None
            for post in self.site.posts_per_category[cat_string]:
                if post.category_from_destpath:
                    base_dir = post.folder_base(lang)
                    # Handle CATEGORY_DESTPATH_NAMES
                    if cat_string in destpath_names_reverse:
                        cat_string = destpath_names_reverse[cat_string]
                        classification_raw = cat_string.split('/')
                    break

            if not self.site.config['CATEGORY_DESTPATH_TRIM_PREFIX']:
                # If prefixes are not trimmed, we'll already have the base_dir in classification_raw
                base_dir = ''

            if base_dir is None:
                # fallback: first POSTS entry + classification
                base_dir = self.site.config['POSTS'][0][1]
            base_dir_list = base_dir.split(os.sep)
            sub_dir = [self.slugify_tag_name(part, lang) for part in classification_raw]
            return [_f for _f in (base_dir_list + sub_dir) if _f], 'auto'
        else:
            return [_f for _f in [self.site.config['CATEGORY_PATH'](lang)] if _f] + self.slugify_category_name(
                classification, lang), 'auto'

    def extract_hierarchy(self, classification):
        """Given a classification, return a list of parts in the hierarchy."""
        return hierarchy_utils.parse_escaped_hierarchical_category_name(classification)

    def recombine_classification_from_hierarchy(self, hierarchy):
        """Given a list of parts in the hierarchy, return the classification string."""
        return hierarchy_utils.join_hierarchical_category_path(hierarchy)

    def provide_overview_context_and_uptodate(self, lang):
        """Provide data for the context and the uptodate list for the list of all classifiations."""
        kw = {
            'category_path': self.site.config['CATEGORY_PATH'],
            'category_prefix': self.site.config['CATEGORY_PREFIX'],
            "category_pages_are_indexes": self.site.config['CATEGORY_PAGES_ARE_INDEXES'],
            "tzinfo": self.site.tzinfo,
            "category_descriptions": self.site.config['CATEGORY_DESCRIPTIONS'],
            "category_titles": self.site.config['CATEGORY_TITLES'],
        }
        context = {
            "title": self.site.MESSAGES[lang]["Categories"],
            "description": self.site.MESSAGES[lang]["Categories"],
            "pagekind": ["list", "tags_page"],
            "category_descriptions": self.site.config['CATEGORY_DESCRIPTIONS'](lang),
            "category_titles": self.site.config['CATEGORY_TITLES'](lang),
        }
        kw.update(context)
        return context, kw

    def provide_context_and_uptodate(self, classification, lang, node=None):
        """Provide data for the context and the uptodate list for the list of the given classifiation."""
        cat_path = self.extract_hierarchy(classification)
        kw = {
            'category_path': self.site.config['CATEGORY_PATH'],
            'category_prefix': self.site.config['CATEGORY_PREFIX'],
            "category_pages_are_indexes": self.site.config['CATEGORY_PAGES_ARE_INDEXES'],
            "tzinfo": self.site.tzinfo,
            "category_descriptions": self.site.config['CATEGORY_DESCRIPTIONS'],
            "category_titles": self.site.config['CATEGORY_TITLES'],
        }
        posts = self.site.posts_per_classification[self.classification_name][lang]
        if node is None:
            children = []
        else:
            children = [child for child in node.children if len([post for post in posts.get(child.classification_name, []) if self.site.config['SHOW_UNTRANSLATED_POSTS'] or post.is_translation_available(lang)]) > 0]
        subcats = [(child.name, self.site.link(self.classification_name, child.classification_name, lang)) for child in children]
        friendly_name = self.get_classification_friendly_name(classification, lang)
        context = {
            "title": self.site.config['CATEGORY_TITLES'](lang).get(classification, self.site.MESSAGES[lang]["Posts about %s"] % friendly_name),
            "description": self.site.config['CATEGORY_DESCRIPTIONS'](lang).get(classification),
            "pagekind": ["tag_page", "index" if self.show_list_as_index else "list"],
            "tag": friendly_name,
            "category": classification,
            "category_path": cat_path,
            "subcategories": subcats,
        }
        kw.update(context)
        return context, kw

    def get_other_language_variants(self, classification, lang, classifications_per_language):
        """Return a list of variants of the same category in other languages."""
        return self.translation_manager.get_translations_as_list(classification, lang, classifications_per_language)

    def postprocess_posts_per_classification(self, posts_per_classification_per_language, flat_hierarchy_per_lang=None, hierarchy_lookup_per_lang=None):
        """Rearrange, modify or otherwise use the list of posts per classification and per language."""
        self.translation_manager.read_from_config(self.site, 'CATEGORY', posts_per_classification_per_language, False)

    def should_generate_classification_page(self, classification, post_list, lang):
        """Only generates list of posts for classification if this function returns True."""
        if self.site.config["CATEGORY_PAGES_FOLLOW_DESTPATH"]:
            # In destpath mode, allow users to replace the default category index with a custom page.
            classification_hierarchy = self.extract_hierarchy(classification)
            dest_list, _ = self.get_path(classification_hierarchy, lang)
            short_destination = os.sep.join(dest_list + [self.site.config["INDEX_FILE"]])
            if short_destination in self.site.post_per_file:
                return False
        return True

    def should_generate_atom_for_classification_page(self, classification, post_list, lang):
        """Only generates Atom feed for list of posts for classification if this function returns True."""
        return True

    def should_generate_rss_for_classification_page(self, classification, post_list, lang):
        """Only generates RSS feed for list of posts for classification if this function returns True."""
        return True
