"""A directive to generate a gallery of images from structured data.

Generating a gallery of images that are all the same size is a common pattern in
documentation, and this can be cumbersome if the gallery is generated
programmatically. This directive wraps this particular use-case in a helper-
directive to generate it with a single YAML configuration file.

It currently exists for maintainers of the pydata-sphinx-theme, but might be
abstracted into a standalone package if it proves useful.
"""
from pathlib import Path
from typing import Any, Dict, List

from docutils import nodes
from docutils.parsers.rst import directives
from sphinx.application import Sphinx
from sphinx.util import logging
from sphinx.util.docutils import SphinxDirective
from yaml import safe_load

logger = logging.getLogger(__name__)


TEMPLATE_GRID = """
`````{{grid}} {grid_columns}
{container_options}

{content}

`````
"""

GRID_CARD = """
````{{grid-item-card}} {title}
{card_options}

{content}
````
"""


class GalleryDirective(SphinxDirective):
    """A directive to show a gallery of images and links in a grid."""

    name = "gallery-grid"
    has_content = True
    required_arguments = 0
    optional_arguments = 1
    final_argument_whitespace = True
    option_spec = {
        # A class to be added to the resulting container
        "grid-columns": directives.unchanged,
        "class-container": directives.unchanged,
        "class-card": directives.unchanged,
    }

    def run(self) -> List[nodes.Node]:
        if self.arguments:
            # If an argument is given, assume it's a path to a YAML file
            # Parse it and load it into the directive content
            path_data_rel = Path(self.arguments[0])
            path_doc, _ = self.get_source_info()
            path_doc = Path(path_doc).parent
            path_data = (path_doc / path_data_rel).resolve()
            if not path_data.exists():
                logger.warn(f"Could not find grid data at {path_data}.")
                nodes.text("No grid data found at {path_data}.")
                return
            yaml_string = path_data.read_text()
        else:
            yaml_string = "\n".join(self.content)

        # Read in YAML so we can generate the gallery
        grid_data = safe_load(yaml_string)

        grid_items = []
        for item in grid_data:
            # Grid card parameters
            options = {}
            if "website" in item:
                options["link"] = item["website"]

            if "class-card" in self.options:
                options["class-card"] = self.options["class-card"]

            if "img-background" in item:
                options["img-background"] = item["img-background"]

            if "img-top" in item:
                options["img-top"] = item["img-top"]

            if "img-bottom" in item:
                options["img-bottom"] = item["img-bottom"]

            options_str = "\n".join(f":{k}: {v}" for k, v in options.items()) + "\n\n"

            # Grid card content
            content_str = ""
            if "header" in item:
                content_str += f"{item['header']}\n\n^^^\n\n"

            if "image" in item:
                content_str += f"![Gallery image]({item['image']})\n\n"

            if "content" in item:
                content_str += f"{item['content']}\n\n"

            if "footer" in item:
                content_str += f"+++\n\n{item['footer']}\n\n"

            title = item.get("title", "")
            content_str += "\n"
            grid_items.append(
                GRID_CARD.format(
                    card_options=options_str, content=content_str, title=title
                )
            )

        # Parse the template with Sphinx Design to create an output
        container = nodes.container()
        # Prep the options for the template grid
        container_options = {"gutter": 2, "class-container": "gallery-directive"}
        if "class-container" in self.options:
            container_options[
                "class-container"
            ] += f' {self.options["class-container"]}'
        container_options_str = "\n".join(
            f":{k}: {v}" for k, v in container_options.items()
        )

        # Create the directive string for the grid
        grid_directive = TEMPLATE_GRID.format(
            grid_columns=self.options.get("grid-columns", "1 2 3 4"),
            container_options=container_options_str,
            content="\n".join(grid_items),
        )
        # Parse content as a directive so Sphinx Design processes it
        self.state.nested_parse([grid_directive], 0, container)
        # Sphinx Design outputs a container too, so just use that
        container = container.children[0]

        # Add extra classes
        if self.options.get("container-class", []):
            container.attributes["classes"] += self.options.get("class", [])
        return [container]


def setup(app: Sphinx) -> Dict[str, Any]:
    """Add custom configuration to sphinx app.

    Args:
        app: the Sphinx application
    Returns:
        the 2 parallel parameters set to ``True``.
    """
    app.add_directive("gallery-grid", GalleryDirective)

    return {
        "parallel_read_safe": True,
        "parallel_write_safe": True,
    }