# Copyright (c) 2026 동일비전(Dongil Vision Korea). All Rights Reserved.
"""
╔══════════════════════════════════════════════════════════════════════════════╗
║  ALASKA v2.0 - Qt Decorators                                                 ║
║  Qt 스레딩 안전 데코레이터                                                   ║
╠══════════════════════════════════════════════════════════════════════════════╣
║  Version : 2.0.2                                                             ║
║  Date    : 2026-02-10                                                        ║
╠══════════════════════════════════════════════════════════════════════════════╣
║  Summary:                                                                    ║
║    Qt UI 스레드 안전 데코레이터                                              ║
║    - @ui_thread: 다른 스레드에서 호출해도 UI 스레드에서 실행                 ║
║    - Qt 스레딩 버그 원천 차단                                                ║
╚══════════════════════════════════════════════════════════════════════════════╝

Usage:
    from py_alaska.qt import ui_thread

    class ViewerWidget(QWidget):
        @ui_thread
        def on_frame_ready(self, signal):
            self.label.setText(str(signal.data))  # 안전하게 UI 업데이트
"""

from __future__ import annotations
from functools import wraps
from typing import Callable, TypeVar, ParamSpec
import threading

P = ParamSpec('P')
T = TypeVar('T')

# Qt imports (lazy)
_qt_imports_done = False
_QObject = None
_Signal = None
_QApplication = None
_QThread = None


def _init_qt_imports():
    """Qt imports (lazy initialization)"""
    global _qt_imports_done, _QObject, _Signal, _QApplication, _QThread
    if _qt_imports_done:
        return

    try:
        from PySide6.QtCore import QObject, Signal, QThread
        from PySide6.QtWidgets import QApplication
        _QObject = QObject
        _Signal = Signal
        _QApplication = QApplication
        _QThread = QThread
    except ImportError:
        try:
            from PyQt6.QtCore import QObject, pyqtSignal as Signal, QThread
            from PyQt6.QtWidgets import QApplication
            _QObject = QObject
            _Signal = Signal
            _QApplication = QApplication
            _QThread = QThread
        except ImportError:
            from PyQt5.QtCore import QObject, pyqtSignal as Signal, QThread
            from PyQt5.QtWidgets import QApplication
            _QObject = QObject
            _Signal = Signal
            _QApplication = QApplication
            _QThread = QThread

    _qt_imports_done = True


# UI Invoker (created after Qt imports)
_ui_invoker = None
_ui_invoker_lock = threading.Lock()
_UiInvokerClass = None


def _create_ui_invoker_class():
    """UiInvoker 클래스 생성 (Qt import 후)"""
    global _UiInvokerClass
    if _UiInvokerClass is not None:
        return _UiInvokerClass

    _init_qt_imports()

    class _UiInvoker(_QObject):
        """UI 스레드에서 함수 실행을 위한 헬퍼"""
        invoke_signal = _Signal(object)

        def __init__(self):
            super().__init__()
            self.invoke_signal.connect(self._execute)

        def _execute(self, func):
            try:
                func()
            except Exception as e:
                print(f"[ui_thread] Error: {e}")

        def invoke(self, func):
            self.invoke_signal.emit(func)

    _UiInvokerClass = _UiInvoker
    return _UiInvokerClass


def _ensure_ui_invoker_initialized():
    """UI invoker를 UI 스레드에서 초기화 (AlaskaApp에서 호출)"""
    global _ui_invoker
    if _ui_invoker is not None:
        return

    with _ui_invoker_lock:
        if _ui_invoker is not None:
            return

        UiInvokerClass = _create_ui_invoker_class()
        _ui_invoker = UiInvokerClass()


def _get_ui_invoker():
    """UI 스레드 호출을 위한 헬퍼 객체 반환"""
    global _ui_invoker
    if _ui_invoker is None:
        _ensure_ui_invoker_initialized()
    return _ui_invoker


def ui_thread(func: Callable[P, T]) -> Callable[P, T]:
    """UI 스레드에서 실행되도록 보장하는 데코레이터

    다른 스레드(예: Task의 run 스레드, Signal 핸들러)에서 호출해도
    자동으로 Qt UI 스레드로 전환하여 실행합니다.

    Qt 규칙: UI 위젯은 메인(UI) 스레드에서만 조작 가능

    Example:
        # Before (복잡하고 실수하기 쉬움)
        class ViewerTask(QWidget):
            update_signal = Signal(dict)

            def __init__(self):
                super().__init__()
                self.update_signal.connect(self._do_update)

            def on_frame_ready(self, signal):
                # 다른 스레드에서 호출됨 → 직접 UI 수정 불가
                self.update_signal.emit(signal.data)

            def _do_update(self, data):
                self.label.setText(str(data))  # UI 스레드에서 실행

        # After (간단)
        class ViewerTask(QWidget):
            @ui_thread
            def on_frame_ready(self, signal):
                self.label.setText(str(signal.data))  # 그냥 쓰면 됨

    Args:
        func: UI 작업을 수행하는 메서드

    Returns:
        UI 스레드 안전 래퍼 함수
    """
    @wraps(func)
    def wrapper(self, *args, **kwargs):
        _init_qt_imports()

        app = _QApplication.instance()
        if app is None:
            # Qt 앱이 없으면 그냥 실행
            return func(self, *args, **kwargs)

        # 이미 UI 스레드면 바로 실행
        if app.thread() == _QThread.currentThread():
            return func(self, *args, **kwargs)

        # 다른 스레드면 Qt Signal을 통해 UI 스레드로 전달
        invoker = _get_ui_invoker()
        if invoker is None:
            # invoker가 없으면 직접 실행 (fallback)
            print(f"[ui_thread] Warning: invoker not initialized, direct call")
            return func(self, *args, **kwargs)

        # 클로저로 함수 호출을 캡처
        def execute():
            func(self, *args, **kwargs)

        invoker.invoke(execute)
        return None  # 비동기 실행이므로 반환값 없음

    return wrapper
