# Copyright (c) 2026 동일비전(Dongil Vision Korea). All Rights Reserved.
"""
╔══════════════════════════════════════════════════════════════════════════════╗
║  ALASKA v2.0 - Qt Decorators                                                 ║
║  Qt 스레딩 안전 데코레이터                                                   ║
╠══════════════════════════════════════════════════════════════════════════════╣
║  Version : 2.0.3                                                             ║
║  Date    : 2026-03-04                                                        ║
╠══════════════════════════════════════════════════════════════════════════════╣
║  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')


class _QtImports:
    """Qt 런타임 lazy import 캡슐화 (PySide6 → PyQt6 → PyQt5 순서 시도)"""
    __slots__ = ('done', 'QObject', 'Signal', 'QApplication', 'QThread',
                 '_invoker', '_invoker_cls', '_lock')

    def __init__(self):
        self.done = False
        self.QObject = None
        self.Signal = None
        self.QApplication = None
        self.QThread = None
        self._invoker = None
        self._invoker_cls = None
        self._lock = threading.Lock()

    def ensure(self):
        """Qt import 수행 (최초 1회만)"""
        if self.done:
            return
        try:
            from PySide6.QtCore import QObject, Signal, QThread
            from PySide6.QtWidgets import QApplication
            self.QObject, self.Signal = QObject, Signal
            self.QApplication, self.QThread = QApplication, QThread
        except ImportError:
            try:
                from PyQt6.QtCore import QObject, pyqtSignal as Signal, QThread
                from PyQt6.QtWidgets import QApplication
                self.QObject, self.Signal = QObject, Signal
                self.QApplication, self.QThread = QApplication, QThread
            except ImportError:
                from PyQt5.QtCore import QObject, pyqtSignal as Signal, QThread
                from PyQt5.QtWidgets import QApplication
                self.QObject, self.Signal = QObject, Signal
                self.QApplication, self.QThread = QApplication, QThread
        self.done = True

    def get_invoker(self):
        """UI 스레드 호출을 위한 헬퍼 객체 반환 (lazy 생성)"""
        if self._invoker is not None:
            return self._invoker
        self._ensure_invoker()
        return self._invoker

    def _ensure_invoker(self):
        """UI invoker를 UI 스레드에서 초기화"""
        if self._invoker is not None:
            return
        with self._lock:
            if self._invoker is not None:
                return
            if self._invoker_cls is None:
                self.ensure()
                _QObject = self.QObject
                _Signal = self.Signal

                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)

                self._invoker_cls = _UiInvoker
            self._invoker = self._invoker_cls()


# 모듈 싱글톤
_qt = _QtImports()


def _ensure_ui_invoker_initialized():
    """UI invoker를 UI 스레드에서 초기화 (AlaskaApp에서 호출)"""
    _qt._ensure_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):
        _qt.ensure()

        app = _qt.QApplication.instance()
        if app is None:
            return func(self, *args, **kwargs)

        if app.thread() == _qt.QThread.currentThread():
            return func(self, *args, **kwargs)

        invoker = _qt.get_invoker()
        if invoker is None:
            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
