"""Utility functions for Sphinx documentation generation which can be used in 'conf.py'."""

import io
import json
import os
import traceback
from pathlib import Path
from typing import Any

from py_app_dev.core.exceptions import UserNotificationException

from yanga_core.domain.reports import ComponentReportData, ReportData, VariantReportData


def _relativize_path(file: Path, project_dir: Path) -> str:
    return file.absolute().relative_to(project_dir).as_posix()


class SphinxReportConfig(ReportData):
    @classmethod
    def from_json_file(cls, file_path: Path) -> "SphinxReportConfig":
        try:
            result = cls.from_dict(json.loads(file_path.read_text()))
        except Exception as e:
            output = io.StringIO()
            traceback.print_exc(file=output)
            raise UserNotificationException(output.getvalue()) from e
        return result

    def _relativize_path(self, file: Path) -> str:
        return _relativize_path(file, self.project_dir)

    def create_component_myst_toc(self, component_name: str, maxdepth: int = 1, caption: str | None = None) -> str:
        """Generate the myst table of contents for the component documentation."""
        content = [
            "\n```{toctree}",
            f":maxdepth: {maxdepth}\n",
        ]
        for file in self.get_component_files_list(component_name):
            content.append(f"{file}")
        content.append("```\n")
        return "\n".join(content)

    def _collect_files_from_config(self, config: ComponentReportData | VariantReportData) -> list[str]:
        """Helper method to collect and relativize file paths from a configuration object."""
        content = []
        file_attributes = ["docs_files", "test_results", "coverage_results", "lint_results", "sources", "other_files"]

        for attr in file_attributes:
            if hasattr(config, attr):
                files = getattr(config, attr)
                for file in files:
                    content.append(f"/{self._relativize_path(file)}")
        for html_content in config.html_content or []:
            # (!) I found this approach of referencing html content here:
            # https://stackoverflow.com/questions/27979803/external-relative-link-in-sphinx-toctree-directive
            content.append(f"{html_content.name} <./{html_content.index_html.as_posix()}#http://>")
        return content

    def get_component_files_list(self, component_name: str) -> list[str]:
        component = next((comp for comp in self.components if comp.name == component_name), None)
        if not component:
            return []
        return self._collect_files_from_config(component)

    def get_variant_files_list(self) -> list[str]:
        if not self.variant_data:
            return []
        return self._collect_files_from_config(self.variant_data)

    @property
    def say_hello(self) -> str:
        return "Hello Sphinx!"


class SphinxConfig:
    """Configuration options for Sphinx documentation generation."""

    REPORT_CONFIGURATION_FILE_ENV_NAME = "REPORT_CONFIGURATION_FILE"

    def __init__(self) -> None:
        self.report_data = self._load_report_config_data()

    def _load_report_config_data(self) -> SphinxReportConfig | None:
        report_config_file_path_str = os.environ.get(self.REPORT_CONFIGURATION_FILE_ENV_NAME, None)

        if report_config_file_path_str:
            report_config_file = Path(report_config_file_path_str)
            if report_config_file.is_file():
                return SphinxReportConfig.from_json_file(report_config_file)
        return None

    @property
    def project(self) -> str:
        return self.report_data.variant_name if self.report_data else "Unknown"

    @property
    def html_context(self) -> dict[str, Any]:
        return {"report_data": self.report_data}

    @property
    def include_patterns(self) -> list[str]:
        """Collect all files from the report config."""
        if self.report_data:
            return [_relativize_path(file, self.report_data.project_dir) for file in self.report_data.collect_all_files()]
        return []

    @staticmethod
    def render_with_jinja(app, docname, source) -> None:  # type: ignore
        """Render pages using jinja templating."""
        # Make sure we're outputting HTML
        if app.builder.format != "html":
            return
        src = source[0]
        rendered = app.builder.templates.render_string(src, app.config.html_context)
        source[0] = rendered
        return
