"""Type for curses window."""

from __future__ import annotations

import curses
import json
import logging

from typing import TYPE_CHECKING

from .colorize import hex_to_rgb_curses


if TYPE_CHECKING:
    from _curses import window

    from .curses_defs import CursesLine
    from .curses_defs import CursesLinePart
    from .ui_config import UIConfig

    Window = window
else:
    from typing import Any

    Window = Any


COLOR_MAP = {
    "terminal.ansiBlack": 0,
    "terminal.ansiRed": 1,
    "terminal.ansiGreen": 2,
    "terminal.ansiYellow": 3,
    "terminal.ansiBlue": 4,
    "terminal.ansiMagenta": 5,
    "terminal.ansiCyan": 6,
    "terminal.ansiWhite": 7,
    "terminal.ansiBrightBlack": 8,
    "terminal.ansiBrightRed": 9,
    "terminal.ansiBrightGreen": 10,
    "terminal.ansiBrightYellow": 11,
    "terminal.ansiBrightBlue": 12,
    "terminal.ansiBrightMagenta": 13,
    "terminal.ansiBrightCyan": 14,
    "terminal.ansiBrightWhite": 15,
}


class CursesWindow:
    # pylint: disable=too-many-instance-attributes
    """Abstraction for a curses window."""

    def __init__(self, ui_config: UIConfig) -> None:
        """Initialize a curses window.

        Args:
            ui_config: The current user interface configuration
        """
        self._logger = logging.getLogger(__name__)

        self._screen: Window
        self.win: Window
        self._screen_min_height = 3
        self._prefix_color = 8
        self._theme_dir: str
        self._term_osc4_support: bool
        self._ui_config = ui_config
        self._logger.debug("self._ui_config: %s", self._ui_config)
        self._set_colors()

    @property
    def _screen_width(self) -> int:
        """Return the screen width.

        Returns:
            the current screen width
        """
        return self._screen.getmaxyx()[1]

    @property
    def _screen_height(self) -> int:
        """Return the screen height, or notify if too small.

        Returns:
            the current screen height
        """
        while True:
            if self._screen.getmaxyx()[0] >= self._screen_min_height:
                return self._screen.getmaxyx()[0]
            curses.flash()
            curses.beep()
            self._screen.refresh()

    def _color_pair_or_none(self, color: int) -> int | None:
        """Return 0 if colors are disabled, otherwise returns the curses color pair.

        Args:
            color: Int for specific curses color

        Returns:
            Curses color pair
        """
        if not self._ui_config.color or curses.COLORS == 0:
            return None
        color_arg = color % curses.COLORS  # self._number_colors
        return curses.color_pair(color_arg)

    def _curs_set(self, value: int) -> None:
        """Log an error in the case of a TERM with limited capabilities.

        Args:
            value: TERM value
        """
        try:
            curses.curs_set(value)
        except curses.error:
            self._logger.exception("Errors setting up terminal, check TERM value")

    def _render_line_part(
        self,
        window: Window,
        lineno: int,
        line_part: CursesLinePart,
        prefix_len: int,
    ) -> None:
        """Render a single line part to a curses window.

        Args:
            window: A curses window
            lineno: the line number
            line_part: The individual line part to render
            prefix_len: The length of the line prefix
        """
        column = line_part.column + prefix_len
        if column > self._screen_width:
            return

        text = line_part.string[0 : self._screen_width - column + 1]
        try:
            color = self._color_pair_or_none(line_part.color)
            if color is None:
                window.addstr(lineno, column, text)
            else:
                window.addstr(lineno, column, text, color | line_part.decoration)
        except curses.error:
            # curses error at last column & row but I don't care
            # because it still draws it
            # https://stackoverflow.com/questions/10877469/
            # ncurses-setting-last-character-on-screen-without-scrolling-enabled
            if lineno == window.getyx()[0] and column + len(text) == window.getyx()[1] + 1:
                pass
            else:
                self._logger.debug("curses error")
                self._logger.debug(
                    "screen_height: %s, lineno: %s",
                    self._screen_height,
                    lineno,
                )
                self._logger.debug(
                    "screen_w: %s, column: %s text: %s, lentext: %s, end_col: %s",
                    self._screen_width,
                    column,
                    text,
                    len(text),
                    column + len(text),
                )

    def _add_line(
        self,
        window: Window,
        lineno: int,
        line: CursesLine,
        prefix: str | None = None,
    ) -> None:
        """Add a line to a window.

        Args:
            window (Window): A curses window
            lineno (int): the line number
            line (CursesLine): The line to add
            prefix (str or None): The prefix for the line
        """
        if prefix:
            color = self._color_pair_or_none(self._prefix_color)
            if color is None:
                window.addstr(lineno, 0, prefix)
            else:
                window.addstr(lineno, 0, prefix, color)
        if line:
            window.move(lineno, 0)
            for line_part in line:
                self._render_line_part(window, lineno, line_part, len(prefix or ""))

    def _set_colors(self) -> None:
        """Set the colors for curses."""
        # curses colors may have already been initialized
        # with another instance of curses window
        if self._ui_config.colors_initialized is True:
            return

        self._curs_set(0)
        # in the case of a TERM with limited capabilities
        # disable color and get out fast
        try:
            curses.use_default_colors()
        except curses.error:
            self._logger.exception("Errors setting up terminal, no color support")
            self._term_osc4_support = False
            self._ui_config.colors_initialized = True
            return

        self._logger.debug("curses.COLORS: %s", curses.COLORS)
        self._logger.debug("curses.can_change_color: %s", curses.can_change_color())

        self._term_osc4_support = curses.can_change_color()
        if self._ui_config.osc4 is False:
            self._term_osc4_support = False
        self._logger.debug("term_osc4_support: %s", self._term_osc4_support)

        if self._term_osc4_support:
            with self._ui_config.terminal_colors_path.open("r", encoding="utf-8") as fh:
                colors = json.load(fh)

            for color_name, color_hex in colors.items():
                idx = COLOR_MAP[color_name]
                color = hex_to_rgb_curses(color_hex)
                curses.init_color(idx, *color)
            self._logger.debug("Custom colors set")
        else:
            self._logger.debug("Using terminal defaults")

        for i in range(curses.COLORS):
            curses.init_pair(i, i, -1)
        self._ui_config.colors_initialized = True
