from autohack.core.lib.i18n import *
from typing import Callable
import readchar, inspect, pathlib, time, sys, os


class ANSIHelper:
    CLEAR_LINE = "\x1b[2K\r"
    PREV_LINE = "\x1b[1A"
    NEXT_LINE = "\n"

    TEMPLATE = "\x1b[{}m"

    RESET = "0"
    BOLD = "1"
    DIM = "2"
    ITALIC = "3"
    UNDERLINE = "4"
    REVERSED = "7"
    DELETELINE = "9"

    BLACK = "30"
    RED = "31"
    GREEN = "32"
    YELLOW = "33"
    BLUE = "34"
    MAGENTA = "35"
    CYAN = "36"
    WHITE = "37"

    @staticmethod
    def formatCode(effect: list[str]) -> str:
        return f"{ANSIHelper.TEMPLATE.format(';'.join(effect))}"

    @staticmethod
    def colorText(text: str, effect: str | list[str] | None) -> str:
        effect = [effect] if isinstance(effect, str) else (effect or [])
        return f"{ANSIHelper.formatCode(effect)}{text}{ANSIHelper.formatCode([ANSIHelper.RESET])}"


def ensureDirExists(dirPath: pathlib.Path) -> None:
    dirPath.mkdir(parents=True, exist_ok=True)


def mswindows() -> bool:
    # Trick from subprocess
    try:
        import msvcrt
    except ModuleNotFoundError:
        return False
    else:
        return True


def formatTime(t: time.struct_time = time.localtime()) -> str:
    return time.strftime("%Y%m%d%H%M%S", t)


def writeData(filePath: pathlib.Path, data: bytes) -> None:
    ensureDirExists(filePath.parent)
    open(filePath, "wb").write(data)


def readData(filePath: pathlib.Path) -> bytes:
    return open(filePath, "rb").read()


def inputMessage(prompt: str = "", endl: int = 0, clear: bool = False) -> str:
    showCursor()
    write(prompt, endl=endl, clear=clear)
    try:
        return input()
    finally:
        hideCursor()


def write(*messages: str, endl: int = 0, sep: str = "", clear: bool = False, flush: bool = True) -> None:
    if clear:
        sys.stdout.write(ANSIHelper.CLEAR_LINE)
    message = sep.join(messages)
    sys.stdout.write(message)
    sys.stdout.write("\n" * endl)
    if flush:
        sys.stdout.flush()


def getTranslatedMessage(I18n: I18N, message: str, *args, language: str = "") -> str:
    return I18n.translate(message, language).format(*map(str, args))


def writeMessage(
    I18n: I18N,
    message: str,
    *args,
    language: str = "",
    endl: int = 0,
    clear: bool = False,
    highlight: bool = False,
) -> None:
    message = I18n.translate(message, language).format(*map(str, args))
    if highlight:
        message = ANSIHelper.colorText(message, [ANSIHelper.BOLD, ANSIHelper.RED])
    write(message, endl=endl, clear=clear)


def hideCursor() -> None:
    # https://www.cnblogs.com/chargedcreeper/p/-/ANSI
    write("\x1b[?25l")


def showCursor() -> None:
    write("\x1b[?25h")


def exitProgram(exitCode: int = 0, pure: bool = False) -> None:
    if not pure:
        showCursor()
    sys.exit(exitCode)


def getFunctionInfo(func: Callable) -> tuple[list[type], type]:
    sig = inspect.signature(func)
    params = sig.parameters
    paramTypes = [param.annotation for param in params.values()]
    return (paramTypes, sig.return_annotation)


def getFolderSize(folderPath: pathlib.Path) -> int:
    # return shutil.disk_usage(folderPath).used
    totalSize = 0
    with os.scandir(folderPath) as entries:
        for entry in entries:
            if entry.is_file():
                totalSize += entry.stat().st_size
            elif entry.is_dir():
                totalSize += getFolderSize(pathlib.Path(entry.path))
    return totalSize


def selectionMenu(selectionList: list[str]) -> int:
    currentSelection = 0
    write("Use Up/Down arrows to navigate, Enter to select.", endl=1)

    def updateSelection() -> None:
        for i, selectionItem in enumerate(selectionList):
            write(
                ANSIHelper.colorText(
                    f"{">" if i == currentSelection else " "} {selectionItem}", [ANSIHelper.RED, ANSIHelper.BOLD] if i == currentSelection else []
                ),
                endl=1 if i < len(selectionList) - 1 else 0,
                clear=True,
                # highlight=(i == currentSelection),
            )

    while True:
        updateSelection()
        k = readchar.readkey()
        if k == readchar.key.UP or k == "k":
            currentSelection = currentSelection - 1 if currentSelection > 0 else len(selectionList) - 1
        elif k == readchar.key.DOWN or k == "j":
            currentSelection = currentSelection + 1 if currentSelection < len(selectionList) - 1 else 0
        elif k == readchar.key.ENTER:
            for _ in range(len(selectionList) + 1):
                write(ANSIHelper.CLEAR_LINE)
                write(ANSIHelper.PREV_LINE)
            return currentSelection
        elif k == readchar.key.ESC:
            exitProgram(0)
        for _ in range(len(selectionList) - 1):
            write(ANSIHelper.PREV_LINE)
