from typing import Any
from collections.abc import Callable
import mistune
import mistune.directives
import mistune.util
import mistune.toc
import pygments
import pygments.lexers
import pygments.formatters
import html
import markupsafe


class LevelAdjustingHTMLRenderer(mistune.HTMLRenderer):
    header_level_delta: int

    def __init__(self, header_level_delta: None | int = None, **kwargs):
        if header_level_delta is None:
            header_level_delta = 0
        self.header_level_delta = header_level_delta
        super().__init__(**kwargs)

    def heading(self, text: str, level: int, **attrs) -> str:
        if 1 > level + self.header_level_delta > 6:
            raise ValueError(
                f"cannot render header with level {level} as it would result in a level outside of 1 <= n <= 6"
            )
        return super().heading(text, level + self.header_level_delta, **attrs)


class SyntaxHighlightingHTMLRenderer(mistune.HTMLRenderer):
    def block_code(self, code: str, info: str | None = None) -> str:
        if info is None:
            return super().block_code(code)

        return pygments.highlight(
            code.replace("\t", "    "),
            pygments.lexers.get_lexer_by_name(info),
            pygments.formatters.HtmlFormatter(
                noclasses=True,  # Use inline styles
                nobackground=True,  # Don't set the background colour of the container
                wrapcode=True,
            ),
        )


class LazyLoadingImageHTMLRenderer(mistune.HTMLRenderer):
    def image(self, text: str, url: str, title: None | str = None) -> str:
        src = self.safe_url(url)
        alt = mistune.util.escape(mistune.util.striptags(text))
        s = '<img loading="lazy" src="' + src + '" alt="' + alt + '"'
        if title:
            s += ' title="' + mistune.util.safe_entity(title) + '"'
        return s + " />"


class CustomHTMLRenderer(
    LevelAdjustingHTMLRenderer,
    SyntaxHighlightingHTMLRenderer,
    LazyLoadingImageHTMLRenderer,
):
    pass


class CustomMarkdown(mistune.Markdown):
    def parse(
        self, s: str, state: None | mistune.BlockState = None
    ) -> tuple[str | list[dict[str, Any]], mistune.BlockState]:
        r, state = super().parse(s, state=state)
        r = '<div class="rendered-markdown">' + r + "</div>"
        return r, state


def create(
    escape: bool = True, header_level_delta: None | int = None
) -> CustomMarkdown:

    r = mistune.create_markdown(
        plugins=[
            "strikethrough",
            "table",
            "footnotes",
            "url",
            mistune.directives.FencedDirective(
                [mistune.directives.Figure(), mistune.directives.TableOfContents()]
            ),
        ],
        renderer=CustomHTMLRenderer(
            header_level_delta=header_level_delta, escape=escape
        ),
    )

    mistune.toc.add_toc_hook(r)
    r.__class__ = CustomMarkdown  # this is as close as you're going to get to a typecast into a subclass

    return r


def new_simple_renderer():
    m = mistune.create_markdown(
        plugins=["strikethrough", "table", "url"],
        renderer=CustomHTMLRenderer(),
    )
    return lambda x: markupsafe.Markup(m(x))  # wrapping this way makes jinja2 treat it as safe content by default


def render_toc_from_state(render_state: dict[str, Any], min_level: int = 1) -> str:
    # hilarious note: if you supply a raw filter object, this dies. hence the cast to list. see https://github.com/lepture/mistune/pull/407
    return mistune.toc.render_toc_ul(
        list(filter(lambda x: x[0] >= min_level, render_state.env["toc_items"]))
    )
