import atexit
import sys
import threading
import time
from typing import Optional

from tqdm import tqdm

_LOCK = threading.Lock()
_TIMER_PBAR = None
_TIMER_STOP_EVENT: Optional[threading.Event] = None
_TIMER_THREAD: Optional[threading.Thread] = None
_TIMER_POSITION = 1
_TIMER_START_TS: Optional[float] = None
_CURSOR_HIDDEN = False
_ATEXIT_REGISTERED = False
_WIN_VT_READY: Optional[bool] = None

_WIN_STD_OUTPUT_HANDLE = -11
_WIN_ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004


def _enable_windows_vt_locked() -> bool:
    global _WIN_VT_READY
    if not sys.platform.startswith("win"):
        return True
    if _WIN_VT_READY is not None:
        return _WIN_VT_READY
    try:
        import ctypes

        kernel32 = ctypes.windll.kernel32
        handle = kernel32.GetStdHandle(_WIN_STD_OUTPUT_HANDLE)
        if handle in (0, -1):
            _WIN_VT_READY = False
            return False

        mode = ctypes.c_uint32()
        if kernel32.GetConsoleMode(handle, ctypes.byref(mode)) == 0:
            _WIN_VT_READY = False
            return False

        new_mode = int(mode.value) | _WIN_ENABLE_VIRTUAL_TERMINAL_PROCESSING
        if kernel32.SetConsoleMode(handle, new_mode) == 0:
            _WIN_VT_READY = False
            return False

        _WIN_VT_READY = True
        return True
    except Exception:
        _WIN_VT_READY = False
        return False


def _supports_ansi_cursor() -> bool:
    try:
        if not hasattr(sys.stdout, "isatty") or (not sys.stdout.isatty()):
            return False
        if sys.platform.startswith("win"):
            return _enable_windows_vt_locked()
        return True
    except Exception:
        return False


def _hide_cursor_locked() -> None:
    global _CURSOR_HIDDEN
    if _CURSOR_HIDDEN or (not _supports_ansi_cursor()):
        return
    try:
        sys.stdout.write("\033[?25l")
        sys.stdout.flush()
        _CURSOR_HIDDEN = True
    except Exception:
        pass


def _show_cursor_locked() -> None:
    global _CURSOR_HIDDEN
    if (not _CURSOR_HIDDEN) or (not _supports_ansi_cursor()):
        return
    try:
        sys.stdout.write("\033[?25h")
        sys.stdout.flush()
        _CURSOR_HIDDEN = False
    except Exception:
        pass


def _restore_cursor_at_exit() -> None:
    with _LOCK:
        _show_cursor_locked()


def _create_timer_pbar_locked():
    pbar = tqdm(
        total=0,
        desc="Total elapsed time",
        dynamic_ncols=True,
        ascii=True,
        bar_format="{desc}: {elapsed}",
        position=_TIMER_POSITION,
        leave=True,
        file=sys.stdout,
    )
    if _TIMER_START_TS is not None:
        try:
            pbar.start_t = float(_TIMER_START_TS)
            pbar.last_print_t = float(_TIMER_START_TS)
            pbar.refresh()
        except Exception:
            pass
    return pbar


def start_elapsed_timer(enable: bool = True) -> None:
    global _TIMER_PBAR, _TIMER_STOP_EVENT, _TIMER_THREAD, _TIMER_START_TS, _ATEXIT_REGISTERED
    if not enable:
        return
    with _LOCK:
        if not _ATEXIT_REGISTERED:
            try:
                atexit.register(_restore_cursor_at_exit)
            except Exception:
                pass
            _ATEXIT_REGISTERED = True
        if _TIMER_PBAR is not None:
            return
        if _TIMER_START_TS is None:
            _TIMER_START_TS = time.time()
        _TIMER_STOP_EVENT = threading.Event()
        _TIMER_PBAR = _create_timer_pbar_locked()
        _hide_cursor_locked()
        stop_event = _TIMER_STOP_EVENT

    def _refresh_elapsed_timer():
        while stop_event is not None and (not stop_event.wait(1.0)):
            with _LOCK:
                pbar = _TIMER_PBAR
                if pbar is None:
                    break
                try:
                    pbar.refresh()
                except Exception:
                    break

    thread = threading.Thread(target=_refresh_elapsed_timer, daemon=True)
    with _LOCK:
        _TIMER_THREAD = thread
    thread.start()


def stop_elapsed_timer(clear_output_line: bool = False) -> None:
    global _TIMER_PBAR, _TIMER_STOP_EVENT, _TIMER_THREAD, _TIMER_START_TS, _TIMER_POSITION
    with _LOCK:
        pbar = _TIMER_PBAR
        stop_event = _TIMER_STOP_EVENT
        thread = _TIMER_THREAD
        _TIMER_PBAR = None
        _TIMER_STOP_EVENT = None
        _TIMER_THREAD = None
        _TIMER_START_TS = None
        _TIMER_POSITION = 1

    if stop_event is not None:
        stop_event.set()
    if thread is not None and thread.is_alive():
        thread.join(timeout=1.5)
    if pbar is not None:
        try:
            if clear_output_line:
                pbar.leave = False
                pbar.clear()
            pbar.refresh()
            pbar.close()
        except Exception:
            pass
    with _LOCK:
        _show_cursor_locked()


def set_elapsed_timer_position(position: int) -> None:
    global _TIMER_POSITION, _TIMER_PBAR, _TIMER_STOP_EVENT, _TIMER_THREAD
    pos = max(0, int(position))
    with _LOCK:
        if _TIMER_POSITION == pos:
            return
        _TIMER_POSITION = pos
        pbar = _TIMER_PBAR
        stop_event = _TIMER_STOP_EVENT
        thread = _TIMER_THREAD
        start_ts = _TIMER_START_TS
        _TIMER_PBAR = None
        _TIMER_STOP_EVENT = None
        _TIMER_THREAD = None

    if stop_event is not None:
        stop_event.set()
    if thread is not None and thread.is_alive():
        thread.join(timeout=1.5)
    if pbar is not None:
        try:
            pbar.leave = False
            pbar.clear()
            pbar.close()
        except Exception:
            pass

    if start_ts is None:
        return

    with _LOCK:
        _TIMER_STOP_EVENT = threading.Event()
        _TIMER_PBAR = _create_timer_pbar_locked()
        new_stop_event = _TIMER_STOP_EVENT

    def _refresh_elapsed_timer():
        while new_stop_event is not None and (not new_stop_event.wait(1.0)):
            with _LOCK:
                pbar_local = _TIMER_PBAR
                if pbar_local is None:
                    break
                try:
                    pbar_local.refresh()
                except Exception:
                    break

    new_thread = threading.Thread(target=_refresh_elapsed_timer, daemon=True)
    with _LOCK:
        _TIMER_THREAD = new_thread
    new_thread.start()


def log_progress_line(msg: str) -> None:
    with _LOCK:
        try:
            text = str(msg)
            # Force output from column 0 and clear any residual progress-bar chars.
            sys.stdout.write("\r")
            if _supports_ansi_cursor():
                sys.stdout.write("\033[2K")
            sys.stdout.flush()
            tqdm.write(text, file=sys.stdout)
        except Exception:
            print(str(msg), flush=True)
