from __future__ import annotations

import abc
import typing as t

from mcstatus.motd.components import (
    AnyFormatting,
    AnyMinecraftColor,
    BedrockFormatting,
    BedrockMinecraftColor,
    InvalidFormatting,
    JavaFormatting,
    JavaMinecraftColor,
    ParsedMotdComponent,
    TranslationTag,
    WebColor,
)

if t.TYPE_CHECKING:
    from collections.abc import Sequence

    from typing_extensions import override
else:
    override = lambda f: f  # noqa: E731

__all__ = [
    "AnsiTransformer",
    "HtmlTransformer",
    "MinecraftTransformer",
    "PlainTransformer",
]

_HOOK_RETURN_TYPE = t.TypeVar("_HOOK_RETURN_TYPE")
_END_RESULT_TYPE = t.TypeVar("_END_RESULT_TYPE")

# MinecraftColor: (foreground, background) # noqa: ERA001 # commented-out code
_SHARED_MINECRAFT_COLOR_TO_RGB = {
    "BLACK": ((0, 0, 0), (0, 0, 0)),
    "DARK_BLUE": ((0, 0, 170), (0, 0, 42)),
    "DARK_GREEN": ((0, 170, 0), (0, 42, 0)),
    "DARK_AQUA": ((0, 170, 170), (0, 42, 42)),
    "DARK_RED": ((170, 0, 0), (42, 0, 0)),
    "DARK_PURPLE": ((170, 0, 170), (42, 0, 42)),
    "GOLD": ((255, 170, 0), (64, 42, 0)),
    "GRAY": ((170, 170, 170), (42, 42, 42)),
    "DARK_GRAY": ((85, 85, 85), (21, 21, 21)),
    "BLUE": ((85, 85, 255), (21, 21, 63)),
    "GREEN": ((85, 255, 85), (21, 63, 21)),
    "AQUA": ((85, 255, 255), (21, 63, 63)),
    "RED": ((255, 85, 85), (63, 21, 21)),
    "LIGHT_PURPLE": ((255, 85, 255), (63, 21, 63)),
    "YELLOW": ((255, 255, 85), (63, 63, 21)),
    "WHITE": ((255, 255, 255), (63, 63, 63)),
}

_MINECRAFT_COLOR_TO_RGB_JAVA = _SHARED_MINECRAFT_COLOR_TO_RGB.copy()
_MINECRAFT_COLOR_TO_RGB_JAVA["GRAY"] = ((170, 170, 170), (42, 42, 42))

_MINECRAFT_COLOR_TO_RGB_BEDROCK = _SHARED_MINECRAFT_COLOR_TO_RGB.copy()
_MINECRAFT_COLOR_TO_RGB_BEDROCK.update(
    {
        "GRAY": ((198, 198, 198), (49, 49, 49)),
        "MINECOIN_GOLD": ((221, 214, 5), (55, 53, 1)),
        "MATERIAL_QUARTZ": ((227, 212, 209), (56, 53, 52)),
        "MATERIAL_IRON": ((206, 202, 202), (51, 50, 50)),
        "MATERIAL_NETHERITE": ((68, 58, 59), (17, 14, 14)),
        "MATERIAL_REDSTONE": ((151, 22, 7), (37, 5, 1)),
        "MATERIAL_COPPER": ((180, 104, 77), (45, 26, 19)),
        "MATERIAL_GOLD": ((222, 177, 45), (55, 44, 11)),
        "MATERIAL_EMERALD": ((17, 159, 54), (4, 40, 13)),
        "MATERIAL_DIAMOND": ((44, 186, 168), (11, 46, 42)),
        "MATERIAL_LAPIS": ((33, 73, 123), (8, 18, 30)),
        "MATERIAL_AMETHYST": ((154, 92, 198), (38, 23, 49)),
        "MATERIAL_RESIN": ((235, 114, 20), (59, 29, 5)),
    }
)


class _BaseTransformer(abc.ABC, t.Generic[_HOOK_RETURN_TYPE, _END_RESULT_TYPE]):
    """Base MOTD transformer class.

    Transformers are responsible for providing a way to generate an alternative
    representation of MOTD, for example, as HTML.

    The methods ``_handle_*`` handle each
    :type:`~mcstatus.motd.components.ParsedMotdComponent` individually.
    """

    def __init__(self, *, bedrock: bool) -> None:
        self.bedrock: bool = bedrock

    def transform(self, motd_components: Sequence[ParsedMotdComponent]) -> _END_RESULT_TYPE:
        return self._format_output([handled for component in motd_components for handled in self._handle_component(component)])

    @abc.abstractmethod
    def _format_output(self, results: list[_HOOK_RETURN_TYPE]) -> _END_RESULT_TYPE: ...

    def _handle_component(
        self, component: ParsedMotdComponent
    ) -> tuple[_HOOK_RETURN_TYPE, _HOOK_RETURN_TYPE] | tuple[_HOOK_RETURN_TYPE]:
        color_enum = BedrockMinecraftColor if self.bedrock else JavaMinecraftColor
        formatting_enum = BedrockFormatting if self.bedrock else JavaFormatting

        def handler(component: ParsedMotdComponent) -> _HOOK_RETURN_TYPE:
            if type(component) is color_enum:
                return self._handle_minecraft_color(component)
            if type(component) is WebColor:
                return self._handle_web_color(component)
            if type(component) is formatting_enum:
                return self._handle_formatting(component)
            if type(component) is InvalidFormatting:
                return self._handle_invalid_formatting(component)
            if type(component) is TranslationTag:
                return self._handle_translation_tag(component)
            if type(component) is str:
                return self._handle_str(component)

            raise RuntimeError(f"Invalid component type: {type(component)}: {component!r}")

        additional = None
        if isinstance(component, color_enum):
            additional = self._handle_formatting(formatting_enum.RESET)

        return (additional, handler(component)) if additional is not None else (handler(component),)

    @abc.abstractmethod
    def _handle_str(self, element: str, /) -> _HOOK_RETURN_TYPE: ...

    @abc.abstractmethod
    def _handle_translation_tag(self, _: TranslationTag, /) -> _HOOK_RETURN_TYPE: ...

    @abc.abstractmethod
    def _handle_web_color(self, element: WebColor, /) -> _HOOK_RETURN_TYPE: ...

    @abc.abstractmethod
    def _handle_formatting(self, element: AnyFormatting, /) -> _HOOK_RETURN_TYPE: ...

    @abc.abstractmethod
    def _handle_invalid_formatting(self, element: InvalidFormatting, /) -> _HOOK_RETURN_TYPE: ...

    @abc.abstractmethod
    def _handle_minecraft_color(self, element: AnyMinecraftColor, /) -> _HOOK_RETURN_TYPE: ...


class _NothingTransformer(_BaseTransformer[str, str]):
    """Transformer that transforms all elements into empty strings.

    This transformer acts as a base for other transformers with string result type.
    """

    @override
    def _format_output(self, results: list[str]) -> str:
        return "".join(results)

    @override
    def _handle_str(self, _element: str, /) -> str:
        return ""

    @override
    def _handle_minecraft_color(self, _element: AnyMinecraftColor, /) -> str:
        return ""

    @override
    def _handle_web_color(self, _element: WebColor, /) -> str:
        return ""

    @override
    def _handle_formatting(self, _element: AnyFormatting, /) -> str:
        return ""

    @override
    def _handle_invalid_formatting(self, _element: InvalidFormatting, /) -> str:
        return ""

    @override
    def _handle_translation_tag(self, _element: TranslationTag, /) -> str:
        return ""


class PlainTransformer(_NothingTransformer):
    @override
    def _handle_str(self, element: str, /) -> str:
        return element


@t.final
class MinecraftTransformer(PlainTransformer):
    @override
    def _handle_component(self, component: ParsedMotdComponent) -> tuple[str, str] | tuple[str]:
        result = super()._handle_component(component)
        if len(result) == 2:
            return (result[1],)
        return result

    @override
    def _handle_minecraft_color(self, element: AnyMinecraftColor, /) -> str:
        return "§" + element.value

    @override
    def _handle_formatting(self, element: AnyFormatting, /) -> str:
        return "§" + element.value

    # future compatibility
    @override
    def _handle_invalid_formatting(self, element: InvalidFormatting, /) -> str:
        return "§" + element.value


@t.final
class HtmlTransformer(PlainTransformer):
    _FORMATTING_TO_HTML_TAGS: t.ClassVar = {
        "BOLD": "b",
        "STRIKETHROUGH": "s",
        "ITALIC": "i",
        "UNDERLINED": "u",
    }

    def __init__(self, *, bedrock: bool) -> None:
        super().__init__(bedrock=bedrock)
        self.on_reset: list[str] = []

    @override
    def transform(self, motd_components: Sequence[ParsedMotdComponent]) -> str:
        self.on_reset = []
        return super().transform(motd_components)

    @override
    def _format_output(self, results: list[str]) -> str:
        return "<p>" + super()._format_output(results) + "".join(self.on_reset) + "</p>"

    @override
    def _handle_str(self, element: str, /) -> str:
        return element.replace("\n", "<br>")

    @override
    def _handle_minecraft_color(self, element: AnyMinecraftColor, /) -> str:
        color_map = _MINECRAFT_COLOR_TO_RGB_BEDROCK if self.bedrock else _MINECRAFT_COLOR_TO_RGB_JAVA
        fg_color, bg_color = color_map[element.name]

        self.on_reset.append("</span>")
        return f"<span style='color:rgb{fg_color};text-shadow:0 0 1px rgb{bg_color}'>"

    @override
    def _handle_web_color(self, element: WebColor, /) -> str:
        self.on_reset.append("</span>")
        return f"<span style='color:rgb{element.rgb}'>"

    @override
    def _handle_formatting(self, element: AnyFormatting, /) -> str:
        if element.name == "RESET":
            to_return = "".join(self.on_reset)
            self.on_reset = []
            return to_return

        if element.name == "OBFUSCATED":
            self.on_reset.append("</span>")
            return "<span class=obfuscated>"

        tag_name = self._FORMATTING_TO_HTML_TAGS[element.name]
        self.on_reset.append(f"</{tag_name}>")
        return f"<{tag_name}>"


@t.final
class AnsiTransformer(PlainTransformer):
    _FORMATTING_TO_ANSI_TAGS: t.ClassVar = {
        "BOLD": "1",
        "STRIKETHROUGH": "9",
        "ITALIC": "3",
        "UNDERLINED": "4",
        "OBFUSCATED": "5",
    }
    _MINECRAFT_COLOR_TO_RGB_JAVA: t.ClassVar = {
        key: foreground for key, (foreground, _background) in _MINECRAFT_COLOR_TO_RGB_JAVA.items()
    }
    _MINECRAFT_COLOR_TO_RGB_BEDROCK: t.ClassVar = {
        key: foreground for key, (foreground, _background) in _MINECRAFT_COLOR_TO_RGB_BEDROCK.items()
    }

    def __init__(self, *, bedrock: bool) -> None:
        super().__init__(bedrock=bedrock)

    def ansi_color(self, color: tuple[int, int, int] | AnyMinecraftColor) -> str:
        """Transform RGB color to ANSI color code."""
        if isinstance(color, AnyMinecraftColor):
            color_to_rgb = self._MINECRAFT_COLOR_TO_RGB_BEDROCK if self.bedrock else self._MINECRAFT_COLOR_TO_RGB_JAVA
            color = color_to_rgb[color.name]

        return "\033[38;2;{};{};{}m".format(*color)

    @override
    def _format_output(self, results: list[str]) -> str:
        return "\033[0m" + super()._format_output(results) + "\033[0m"

    @override
    def _handle_minecraft_color(self, element: AnyMinecraftColor, /) -> str:
        return self.ansi_color(element)

    @override
    def _handle_web_color(self, element: WebColor, /) -> str:
        return self.ansi_color(element.rgb)

    @override
    def _handle_formatting(self, element: AnyFormatting, /) -> str:
        if element.name == "RESET":
            return "\033[0m"
        return "\033[" + self._FORMATTING_TO_ANSI_TAGS[element.name] + "m"
