"""Package for game screens."""

from __future__ import annotations

from typing import Protocol, TypeAlias

import attrs
import tcod.console
import tcod.event

import g


class Screen(Protocol):
    """An abstract game screen."""

    __slots__ = ()

    def on_event(self, event: tcod.event.Event) -> ScreenResult:
        """Called on events."""

    def on_draw(self, console: tcod.console.Console) -> None:
        """Called when the screen is being drawn."""


@attrs.define()
class Push:
    """Push a new screen on top of the stack."""

    screen: Screen


@attrs.define()
class Pop:
    """Remove the current screen from the stack."""


@attrs.define()
class Reset:
    """Replace the entire stack with a new screen."""

    screen: Screen


ScreenResult: TypeAlias = "Push | Pop | Reset | None"
"""Union of screen results."""


def main_draw() -> None:
    """Render and present the active screen."""
    if not g.screens:
        return
    g.console.clear()
    g.screens[-1].on_draw(g.console)
    g.context.present(g.console)


def apply_screen_result(result: ScreenResult) -> None:
    """Apply a ScreenResult to `g.screens`."""
    match result:
        case Push(screen=screen):
            g.screens.append(screen)
        case Pop():
            g.screens.pop()
        case Reset(screen=screen):
            while g.screens:
                apply_screen_result(Pop())
            apply_screen_result(Push(screen))
        case None:
            pass
        case _:
            raise TypeError(result)


def main_loop() -> None:
    """Run the active screen forever."""
    while g.screens:
        main_draw()
        for event in tcod.event.wait():
            tile_event = g.context.convert_event(event)
            if g.screens:
                apply_screen_result(g.screens[-1].on_event(tile_event))


def get_previous_screen(screen: Screen) -> Screen | None:
    """Return the screen before `screen` in the stack if it exists."""
    current_index = next(index for index, value in enumerate(g.screens) if value is screen)
    return g.screens[current_index - 1] if current_index > 0 else None


def draw_previous_screens(screen: Screen, console: tcod.console.Console, dim: bool = True) -> None:
    """Draw previous screens, optionally dimming all but the active screen."""
    prev_screen = get_previous_screen(screen)
    if prev_screen is None:
        return
    prev_screen.on_draw(console)
    if dim and screen is g.screens[-1]:
        console.rgb["fg"] //= 4
        console.rgb["bg"] //= 4
