import sys
from typing import Sequence, Optional, Callable


if sys.platform == "win32":
    import colorama  # pylint:disable=import-error


ansi_color_enabled: bool = False


def setup_terminal():
    """
    Check if we are running in a TTY. If so, make sure the terminal supports ANSI escape sequences. If not, disable
    colorized output. Sets global `ansi_color_enabled` to True if colorized output should be enabled by default.
    """
    isatty = (
        hasattr(sys.stdout, "isatty") and sys.stdout.isatty() and hasattr(sys.stderr, "isatty") and sys.stderr.isatty()
    )
    if sys.platform == "win32" and isatty:
        if not isinstance(sys.stdout, colorama.ansitowin32.StreamWrapper):
            colorama.init()

    global ansi_color_enabled  # pylint:disable=global-statement
    ansi_color_enabled = isatty


def ansi_color(s: str, color: Optional[str]) -> str:
    """
    Colorize string `s` by wrapping in ANSI escape sequence for given `color`.

    This function does not consider whether escape sequences are functional or not; it is up to the caller to determine
    if its appropriate. Check global `ansi_color_enabled` value in this module.
    """
    if color is None:
        return s

    codes = {
        "black": "30m",
        "bright_black": "90m",
        "gray": "90m",  # alias 'bright black'
        "blue": "34m",
        "bright_blue": "94m",
        "cyan": "36m",
        "bright_cyan": "96m",
        "green": "32m",
        "bright_green": "92m",
        "magenta": "35m",
        "bright_magenta": "95m",
        "red": "31m",
        "bright_red": "91m",
        "white": "37m",
        "bright_white": "97m",
        "yellow": "33m",
        "bright_yellow": "93m",
    }
    return "\u001b[" + codes[color] + s + "\u001b[0m"


def add_edge_to_buffer(
    buf: Sequence[str],
    ref: Sequence[str],
    start: int,
    end: int,
    formatter: Optional[Callable[[str], str]] = None,
    dashed: bool = False,
    ascii_only: Optional[bool] = None,
):
    """
    Draw an edge by adding Unicode box and arrow glyphs to beginning of each line in a list of lines.

    :param buf: Output buffer, used to render formatted edges.
    :param ref: Reference buffer, used to calculate edge depth.
    :param start: Start line.
    :param end: End line, where arrow points.
    :param formatter: Optional callback function used to format the edge before writing it to output buffer.
    :param dashed: Render edge line dashed instead of solid.
    :param ascii_only: Render edge using ASCII characters only. If unspecified, guess by stdout encoding.
    :return:
    """
    abs_start = min(start, end)
    abs_end = max(start, end)
    max_depth = max(map(len, ref[abs_start : abs_end + 1]))
    descending = start < end

    if ascii_only is None:
        # Guess whether we should only use ASCII characters based on stdout encoding
        ascii_only = getattr(sys.stdout, "encoding", None) != "utf-8"

    if ascii_only:
        chars = {
            "start_cap": "-",
            "start_corner": "+",
            "end_cap": ">",
            "end_corner": "+",
            "horizontal": "+" if dashed else "-",
            "vertical": "+" if dashed else "|",
            "spin": "@ ",
        }
    else:
        chars = {
            "start_cap": "╴",
            "start_corner": "╭" if descending else "╰",
            "end_cap": "▸",
            "end_corner": "╰" if descending else "╭",
            "horizontal": "╌" if dashed else "─",
            "vertical": "╎" if dashed else "│",
            "spin": "⟳ ",
        }

    def handle_line(i, edge_str):
        if formatter is not None:
            edge_str = formatter(edge_str)
        buf[i] = edge_str + buf[i]

    if start == end:
        handle_line(start, chars["spin"])
    else:
        handle_line(
            start, (chars["start_corner"] + chars["horizontal"] * (max_depth - len(ref[start])) + chars["start_cap"])
        )
        handle_line(end, (chars["end_corner"] + chars["horizontal"] * (max_depth - len(ref[end])) + chars["end_cap"]))
        for i in range(abs_start + 1, abs_end):
            handle_line(i, chars["vertical"] + " " * (1 + max_depth - len(ref[i])))
