from __future__     import annotations

from typing         import Optional, Sequence
from dataclasses    import dataclass, field
from threading      import Event, Thread
from datetime       import datetime
from rich.console   import Console
from time           import sleep



def _now_hms() -> str:
    return datetime.now().strftime("%H:%M:%S")


@dataclass(slots=True)
class Logger:
    name: str
    console: Console = field(default_factory=Console)
    name_width: int = 14
    level_width: int = 8
    msg_color: str = "[bright_blue]"
    name_color: str = "[magenta2]"
    bracket_color: str = "[grey70]"
    sep: str = " "

    def _prefix(self, level: str, level_color: str) -> str:
        ts = _now_hms()
        nm = self.name[: self.name_width].ljust(self.name_width)
        lv = level[: self.level_width].ljust(self.level_width)
        return (
            f"{self.bracket_color}[ {self.name_color}{nm}[/]{self.bracket_color} - {ts} - "
            f"{level_color}{lv}[/]{self.bracket_color} ] > [/]"
        )

    def _log(
        self,
        *message: object,
        level: str,
        level_color: str,
        color: Optional[str] = None,
        end: str = "\n",
        sep: Optional[str] = None,
    ) -> None:
        text = (sep if sep is not None else self.sep).join(map(str, message))
        self.console.print(self._prefix(level, level_color) + (color or self.msg_color) + text, end=end)

    def info(self, *message: object) -> None:
        self._log(*message, level="INFO", level_color="[dodger_blue1]")

    def success(self, *message: object) -> None:
        self._log(*message, level="SUCCESS", level_color="[green3]")

    def error(self, *message: object) -> None:
        self._log(*message, level="ERROR", level_color="[red1]")

    def warning(self, *message: object) -> None:
        self._log(*message, level="WARNING", level_color="[yellow1]")

    def prompt(self, *message: object) -> str:
        self._log(*message, level="INPUT", level_color="[yellow1]", end="")
        return input()

    def custom(
        self,
        *message: object,
        label: str = "CUSTOM",
        label_color: str = "[deep_pink1]",
        message_color: Optional[str] = None,
        end: str = "\n",
    ) -> None:
        self._log(*message, level=label, level_color=label_color, color=message_color, end=end)

    class Spinner:
        def __init__(
            self,
            logger: "Logger",
            *,
            label: str = "CHECKING",
            label_color: str = "[deep_pink1]",
            frames: Sequence[str] = ("|", "/", "-", "\\"),
            interval: float = 0.1,
        ) -> None:
            self.logger = logger
            self.label = label
            self.label_color = label_color
            self.frames = tuple(frames)
            self.interval = interval
            self._stop = Event()
            self._thread: Optional[Thread] = None

        def start(self) -> None:
            if self._thread and self._thread.is_alive():
                return
            self._stop.clear()
            self._thread = Thread(target=self._spin, daemon=True)
            self._thread.start()

        def stop(self) -> None:
            self._stop.set()
            if self._thread:
                self._thread.join(timeout=1)
                self._thread = None
            self.logger._log(" " * 40, level=self.label, level_color=self.label_color, end="\r")

        def _spin(self) -> None:
            i = 0
            while not self._stop.is_set():
                frame = self.frames[i % len(self.frames)]
                self.logger._log(
                    f"< {frame} >",
                    level=self.label,
                    level_color=self.label_color,
                    end="\r",
                    color="[bright_blue]",
                )
                i += 1
                sleep(self.interval)
