# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# NukeChineseAnnotation - bootstrap
#
# Author / 作者:YanKaiHu
# Copyright (c) 2026 Nuke学习社 / YanKaiHu
# All Rights Reserved.
#
# LICENSE / 使用许可:
# This source code is source-available for learning and internal use ONLY.
# It is NOT open-source software.
#
# 禁止行为：
# - 禁止任何形式的修改、改写、二次开发、衍生作品
# - 禁止倒卖、转售、收费分发、打包售卖（含付费群/课程/资源包）
# - 禁止转载、二次分发、公开发布全部或部分源码
# - 禁止移除或修改本版权与作者声明
#
# 未经作者书面授权，任何超出以上范围的使用一律禁止。
# 本软件按“现状”提供，作者不对任何损失负责。
# -----------------------------------------------------------------------------
from __future__ import absolute_import, division, print_function, unicode_literals

import importlib
import logging
import os
import sys

_PKG_DIR = os.path.dirname(__file__)
_PKG_NAME = os.path.basename(_PKG_DIR)
_PARENT_DIR = os.path.dirname(_PKG_DIR)

if _PARENT_DIR and _PARENT_DIR not in sys.path:
    sys.path.insert(0, _PARENT_DIR)

_THIS_MODULE = sys.modules.get(__name__)
if _THIS_MODULE is not None:
    sys.modules.setdefault('{0}.bootstrap'.format(_PKG_NAME), _THIS_MODULE)

_BOOTSTRAP_READY = False
_GUI_READY = False
_MANAGEMENT_COMMANDS_READY = False
_INIT_ENTRY_RAN = False
_MENU_ENTRY_RAN = False

_NCA_MENU_ROOT = 'NukeChineseAnnotation'
_MGMT_MENU_PATHS = (
    _NCA_MENU_ROOT + '/偏好设置...',
    _NCA_MENU_ROOT + '/自检面板...',
)
_NODE_HELP_MENU_PATH = _NCA_MENU_ROOT + '/显示中文节点说明 (F1)'

_VIEWER_KNOB_TOOLTIPS_BILINGUAL = {
    'Viewer.gain': (
        'Multiply the image by this amount before display.\n\n'
        '在显示之前按此系数乘以图像 (Gain)。'
    ),
    'Viewer.gamma': (
        'Gamma correction is applied after the gain, viewer process, and any custom input process.\n\n'
        '伽玛 (Gamma) 校正在增益、Viewer 处理及自定义输入过程之后应用。'
    ),
    'Viewer.saturation': (
        'Saturation correction is applied before the gain, viewer process, any custom input process and gamma.\n\n'
        '饱和度校正发生在增益 (Gain)、Viewer 处理、自定义输入过程和伽玛 (Gamma) 之前。'
    ),
    'Viewer.out': (
        'The out point of the clip/sequence.\n\n'
        '剪辑/序列的出点。'
    ),
}

_VIEWER_KNOB_TOOLTIPS_ENGLISH = {
    'Viewer.gain': 'Multiply the image by this amount before display.',
    'Viewer.gamma': 'Gamma correction is applied after the gain, viewer process, and any custom input process.',
    'Viewer.saturation': 'Saturation correction is applied before the gain, viewer process, any custom input process and gamma.',
    'Viewer.out': 'The out point of the clip/sequence.',
}


def _qualify_module_name(module_name):
    if not module_name:
        raise ImportError('Empty module name')

    if module_name.startswith(_PKG_NAME + '.'):
        return module_name

    if '.' in module_name:
        return module_name

    return '{0}.{1}'.format(_PKG_NAME, module_name)


def _import_local(module_name):
    qualified_name = _qualify_module_name(module_name)
    return importlib.import_module(qualified_name)


_BOOTSTRAP_LOGGER = None


_PROFILE_MODULE = None

try:
    from .runtime_logging import log_event as _log_runtime_event, log_exception as _log_runtime_exception
except ImportError:
    try:
        from runtime_logging import log_event as _log_runtime_event, log_exception as _log_runtime_exception  # type: ignore
    except ImportError:
        def _log_runtime_event(logger, level, event, **fields):
            if logger is None:
                return
            logger.log(level, 'event=%s %s' % (event, fields))

        def _log_runtime_exception(logger, level, event, exc, **fields):
            if logger is None:
                return
            fields = dict(fields or {})
            fields.setdefault('exc_type', getattr(getattr(exc, '__class__', None), '__name__', 'Exception'))
            fields.setdefault('exc', str(exc))
            logger.log(level, 'event=%s %s' % (event, fields))



class _NullProfileSpan(object):
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc, tb):
        return False


def _profile_module():
    global _PROFILE_MODULE
    if _PROFILE_MODULE is None:
        _PROFILE_MODULE = _safe_import('performance_profile')
    return _PROFILE_MODULE


def _profile_has_session():
    prof = _profile_module()
    if prof is None:
        return False
    try:
        return bool(prof.has_active_session())
    except Exception:
        return False


def _profile_begin(name='startup', reset=False):
    prof = _profile_module()
    if prof is None:
        return None
    try:
        return prof.begin_session(name=name, reset=reset)
    except Exception:
        return None


def _profile_mark(key, value):
    prof = _profile_module()
    if prof is None:
        return value
    try:
        prof.mark(key, value)
    except Exception:
        pass
    return value


def _profile_span(name, extra=None):
    prof = _profile_module()
    if prof is None:
        return _NullProfileSpan()
    try:
        return prof.profile_span(name, extra=extra)
    except Exception:
        return _NullProfileSpan()


def _profile_end(status='ok', save=True):
    prof = _profile_module()
    if prof is None:
        return None
    try:
        return prof.end_session(status=status, save=save)
    except Exception:
        return None


def _get_logger():
    global _BOOTSTRAP_LOGGER
    if _BOOTSTRAP_LOGGER is not None:
        return _BOOTSTRAP_LOGGER

    import logging

    logger = logging.getLogger('NCA.bootstrap')
    logger.propagate = False

    if not logger.handlers:
        handler = logging.StreamHandler()
        handler.setFormatter(logging.Formatter('[%(asctime)s] %(levelname)s %(name)s: %(message)s'))
        logger.addHandler(handler)

    level_name = os.getenv('NCA_LOG_LEVEL', 'WARNING').upper()
    logger.setLevel(getattr(logging, level_name, logging.WARNING))
    _BOOTSTRAP_LOGGER = logger
    return logger


def _license_module():
    return _import_local('license_check')


def _nuke_module():
    try:
        import nuke
        return nuke
    except ImportError:
        return None


def _safe_import(module_name):
    try:
        return _import_local(module_name)
    except ImportError as exc:
        _log_runtime_exception(_get_logger(), logging.DEBUG, 'bootstrap.safe_import.import_error', exc, module=module_name)
        return None


def _safe_call(module_name, fn_name, logger=None, *args, **kwargs):
    try:
        mod = _import_local(module_name)
    except ImportError as exc:
        _log_runtime_exception(logger, logging.DEBUG, 'bootstrap.safe_call.import_error', exc, module=module_name, function=fn_name)
        return None
    except Exception as exc:
        _log_runtime_exception(logger, logging.DEBUG, 'bootstrap.safe_call.import_failed', exc, module=module_name, function=fn_name)
        return None

    if not hasattr(mod, fn_name):
        _log_runtime_event(logger, logging.DEBUG, 'bootstrap.safe_call.missing_attr', module=module_name, function=fn_name)
        return None

    try:
        return getattr(mod, fn_name)(*args, **kwargs)
    except Exception as exc:
        _log_runtime_exception(logger, logging.DEBUG, 'bootstrap.safe_call.invoke_failed', exc, module=module_name, function=fn_name)
        return None


def _remove_menu_item(menu_obj, menu_path):
    if not menu_obj:
        return False

    try:
        menu_obj.removeItem(menu_path)
        return True
    except (AttributeError, RuntimeError, TypeError):
        pass

    try:
        item = menu_obj.findItem(menu_path)
    except (AttributeError, RuntimeError, TypeError):
        item = None

    if item is None:
        return False

    for method_name in ('setEnabled', 'setVisible'):
        try:
            method = getattr(item, method_name, None)
            if method:
                method(False)
        except (AttributeError, RuntimeError, TypeError):
            pass
    return True


def _refresh_top_menu(logger=None):
    try:
        top_menu = _import_local('top_menu')
        if hasattr(top_menu, 'translate_menu_toolbar'):
            top_menu.translate_menu_toolbar()
            return True
    except Exception as exc:
        _log_runtime_exception(logger, logging.DEBUG, 'bootstrap.top_menu.refresh_failed', exc)
    return False


def _apply_language_switcher_english(logger=None):
    ls = _safe_import('language_switcher')
    if ls is None:
        return False

    for fn_name in ('uninstall_language_switcher',):
        if hasattr(ls, fn_name):
            try:
                getattr(ls, fn_name)()
                return True
            except Exception as exc:
                _log_runtime_exception(logger, logging.DEBUG, 'bootstrap.language_switcher.uninstall_failed', exc, function=fn_name)

    if hasattr(ls, '_apply_english_mode'):
        try:
            ls._apply_english_mode()
            return True
        except Exception as exc:
            _log_runtime_exception(logger, logging.DEBUG, 'bootstrap.language_switcher.apply_english_failed', exc)

    try:
        bilingual = bool(getattr(ls, 'bilingual_mode', False))
    except Exception:
        bilingual = False

    if bilingual and hasattr(ls, 'toggle_language'):
        try:
            ls.toggle_language()
            return True
        except Exception as exc:
            _log_runtime_exception(logger, logging.DEBUG, 'bootstrap.language_switcher.toggle_failed', exc)

    return False

def is_gui_session():
    nuke = _nuke_module()
    if nuke is None:
        return False
    val = None
    try:
        val = nuke.env.get('gui')
    except Exception:
        pass
    if val is None:
        try:
            val = nuke.env['gui']
        except Exception:
            val = None
    if isinstance(val, str):
        return val.strip().lower() in ('1', 'true', 'yes', 'on')
    return bool(val)


def ensure_bootstrap_ready():
    global _BOOTSTRAP_READY
    if _BOOTSTRAP_READY:
        return True

    with _profile_span('bootstrap.ready'):
        lc = _license_module()
        if not bool(getattr(lc, '_NCA_BOOTSTRAPPED', False)):
            with _profile_span('bootstrap.verify_package_manifest'):
                ok, msg = lc.verify_package_manifest(_PKG_DIR)
            if not ok:
                raise RuntimeError(msg)
            with _profile_span('bootstrap.install_import_guard'):
                manifest_dict = lc.get_last_verified_manifest_dict() or lc.load_manifest_dict(_PKG_DIR)
                lc.install_import_guard(_PKG_DIR, manifest_dict)
            try:
                lc._NCA_BOOTSTRAPPED = True
            except Exception:
                pass

        os.environ.setdefault('NCA_PLUGIN_ROOT', _PKG_DIR)

        try:
            with _profile_span('bootstrap.print_filter'):
                _safe_call('_suppress_output', 'install_print_filter')
        except Exception:
            pass

        _BOOTSTRAP_READY = True
    return True


def ensure_license_for_session(show_ui_if_needed=True):
    lc = _license_module()

    license_ok = bool(getattr(lc, '_NCA_LICENSE_OK', False))
    skipped_in_headless = bool(getattr(lc, '_NCA_LICENSE_SKIPPED_IN_HEADLESS', False))
    
    if license_ok and not skipped_in_headless:
        return True

    enforce_in_headless = os.getenv('NCA_ENFORCE_IN_HEADLESS', '').strip().lower() in ('1', 'true', 'yes', 'on')
    gui = is_gui_session()
    _profile_mark('license.gui_session', bool(gui))
    _profile_mark('license.enforce_in_headless', bool(enforce_in_headless))

    with _profile_span('bootstrap.ensure_license_for_session', {'show_ui_if_needed': bool(show_ui_if_needed)}):
        if gui:
            try:
                with _profile_span('license.enforce.gui_quiet'):
                    lc.enforce_or_prompt(show_ui=False)
            except Exception:
                if show_ui_if_needed:
                    with _profile_span('license.enforce.gui_prompt'):
                        lc.enforce_or_prompt(show_ui=True)
                else:
                    raise
            try:
                lc._NCA_LICENSE_OK = True
                lc._NCA_LICENSE_SKIPPED_IN_HEADLESS = False
            except Exception:
                pass
            return True

        if enforce_in_headless:
            with _profile_span('license.enforce.headless'):
                lc.enforce_or_prompt(show_ui=False)
            try:
                lc._NCA_LICENSE_OK = True
                lc._NCA_LICENSE_SKIPPED_IN_HEADLESS = False
            except Exception:
                pass
            return True

        try:
            lc._NCA_LICENSE_SKIPPED_IN_HEADLESS = True
        except Exception:
            pass
    return True


def _set_viewer_knob_tooltips(mapping):
    nuke = _nuke_module()
    if nuke is None:
        return
    try:
        for knob_name, text in mapping.items():
            nuke.knobTooltip(knob_name, text)
    except Exception:
        pass


def _install_viewer_knob_tooltips():
    _set_viewer_knob_tooltips(_VIEWER_KNOB_TOOLTIPS_BILINGUAL)


def _restore_viewer_knob_tooltips_english():
    _set_viewer_knob_tooltips(_VIEWER_KNOB_TOOLTIPS_ENGLISH)


def _open_nca_preferences():
    nuke = _nuke_module()
    if nuke is None:
        return
    try:
        _import_local('plugin_preferences_dialog').open_preferences_dialog()
    except Exception as exc:
        try:
            nuke.message('Failed to open NCA Preferences: {0}'.format(exc))
        except Exception:
            pass


def _open_nca_selfcheck():
    nuke = _nuke_module()
    if nuke is None:
        return
    try:
        _import_local('self_check_dialog').open_self_check_dialog()
    except Exception as exc:
        try:
            nuke.message('Failed to open NCA Self-Check: {0}'.format(exc))
        except Exception:
            pass


def _install_management_commands():
    global _MANAGEMENT_COMMANDS_READY
    if _MANAGEMENT_COMMANDS_READY:
        return
    nuke = _nuke_module()
    if nuke is None:
        return

    try:
        root = nuke.menu('Nuke')
    except Exception:
        root = None

    if root is None:
        return

    for path in _MGMT_MENU_PATHS:
        try:
            _remove_menu_item(root, path)
        except Exception:
            pass

    try:
        root.addCommand(_MGMT_MENU_PATHS[0], _open_nca_preferences)
        root.addCommand(_MGMT_MENU_PATHS[1], _open_nca_selfcheck)
    except Exception:
        pass
    _MANAGEMENT_COMMANDS_READY = True


def _uninstall_management_commands(logger=None):
    global _MANAGEMENT_COMMANDS_READY
    nuke = _nuke_module()
    if nuke is None:
        _MANAGEMENT_COMMANDS_READY = False
        return False

    try:
        root = nuke.menu('Nuke')
    except Exception:
        root = None

    if root is None:
        _MANAGEMENT_COMMANDS_READY = False
        return False

    changed = False
    for path in _MGMT_MENU_PATHS:
        try:
            if _remove_menu_item(root, path):
                changed = True
        except Exception as exc:
            if logger is not None:
                logger.debug('NCA bootstrap: failed to remove management menu item %s: %s', path, exc)

    try:
        _remove_menu_item(root, _NCA_MENU_ROOT)
    except Exception:
        pass

    _MANAGEMENT_COMMANDS_READY = False
    return changed


def _install_node_help(logger=None):
    _safe_call('node_help_popup', 'install_node_help', logger)


def _uninstall_node_help(logger=None):
    removed = False

    if _safe_call('node_help_popup', 'uninstall_node_help', logger) is not None:
        removed = True

    nuke = _nuke_module()
    if nuke is None:
        return removed

    try:
        root = nuke.menu('Nuke')
    except Exception:
        root = None

    if root is None:
        return removed

    try:
        if _remove_menu_item(root, _NODE_HELP_MENU_PATH):
            removed = True
    except Exception as exc:
        if logger is not None:
            logger.debug('NCA bootstrap: failed to remove node help menu item %s: %s', _NODE_HELP_MENU_PATH, exc)

    return removed


def install_gui_modules():
    global _GUI_READY
    if _GUI_READY:
        return True

    logger = _get_logger()
    nuke = _nuke_module()
    if nuke is None or not is_gui_session():
        return False

    with _profile_span('bootstrap.install_gui_modules'):
        try:
            with _profile_span('bootstrap.nuke_plugin_path'):
                nuke.pluginAddPath(_PKG_DIR)
        except Exception:
            pass

        try:
            with _profile_span('bootstrap.top_menu_translate'):
                _import_local('top_menu').translate_menu_toolbar()
        except Exception as exc:
            _log_runtime_exception(logger, logging.DEBUG, 'bootstrap.top_menu.install_failed', exc)

        try:
            with _profile_span('bootstrap.language_switcher_install'):
                ls = _import_local('language_switcher')
                if hasattr(ls, 'install_language_switcher'):
                    ls.install_language_switcher()
                if hasattr(ls, 'apply_startup_mode'):
                    ls.apply_startup_mode()
                elif hasattr(ls, 'toggle_language'):
                    ls.toggle_language()
        except Exception as exc:
            _log_runtime_exception(logger, logging.DEBUG, 'bootstrap.language_switcher.install_failed', exc)

        try:
            with _profile_span('bootstrap.windows_menu_install'):
                _import_local('windows_menu_annotation').install_windows_menu_translator()
        except Exception as exc:
            _log_runtime_exception(logger, logging.DEBUG, 'bootstrap.windows_menu.install_failed', exc)

        try:
            with _profile_span('bootstrap.node_help_install'):
                _install_node_help(logger)
        except Exception as exc:
            _log_runtime_exception(logger, logging.DEBUG, 'bootstrap.node_help.install_failed', exc)

        with _profile_span('bootstrap.management_commands_install'):
            _install_management_commands()
        _GUI_READY = True
    return True


def install_all():
    _profile_begin('startup', reset=not _profile_has_session())
    _profile_mark('entry', 'install_all')
    try:
        with _profile_span('bootstrap.install_all'):
            ensure_bootstrap_ready()
            ensure_license_for_session(show_ui_if_needed=True)
            install_gui_modules()
        _profile_end(status='ok', save=True)
        return True
    except Exception:
        _profile_end(status='error', save=True)
        raise


def uninstall_all():
    global _GUI_READY, _MANAGEMENT_COMMANDS_READY, _MENU_ENTRY_RAN
    logger = _get_logger()

    try:
        _apply_language_switcher_english(logger)
    except Exception as exc:
        _log_runtime_exception(logger, logging.DEBUG, 'bootstrap.language_switcher.rollback_failed', exc)

    try:
        _refresh_top_menu(logger)
    except Exception:
        pass

    for mod_name, fn_name in [
        ('viewer_toolbar_annotation', 'uninstall_viewer_toolbar_tooltips'),
        ('windows_menu_annotation', 'uninstall_windows_menu_translator'),
        ('_suppress_output', 'uninstall_print_filter'),
    ]:
        try:
            mod = _import_local(mod_name)
            if hasattr(mod, fn_name):
                getattr(mod, fn_name)()
        except Exception as exc:
            _log_runtime_exception(logger, logging.DEBUG, 'bootstrap.uninstall.step_failed', exc, module=mod_name, function=fn_name)

    try:
        _uninstall_node_help(logger)
    except Exception as exc:
        _log_runtime_exception(logger, logging.DEBUG, 'bootstrap.node_help.uninstall_failed', exc)

    try:
        _uninstall_management_commands(logger)
    except Exception as exc:
        _log_runtime_exception(logger, logging.DEBUG, 'bootstrap.management.uninstall_failed', exc)

    try:
        _restore_viewer_knob_tooltips_english()
    except Exception as exc:
        _log_runtime_exception(logger, logging.DEBUG, 'bootstrap.viewer_knob_tooltips.restore_failed', exc)

    try:
        runtime_state = _safe_import('runtime_state')
        if runtime_state is not None and hasattr(runtime_state, 'reset_runtime_state'):
            runtime_state.reset_runtime_state()
    except Exception as exc:
        _log_runtime_exception(logger, logging.DEBUG, 'bootstrap.runtime_state.reset_failed', exc)

    _GUI_READY = False
    _MANAGEMENT_COMMANDS_READY = False
    _MENU_ENTRY_RAN = False
    return True


def _schedule_late_menu_refresh():
    if not is_gui_session():
        return
    try:
        qt_compat = _import_local('qt_compat')
        QtCore = getattr(qt_compat, 'QtCore', None)
    except Exception:
        QtCore = None
    if QtCore is None:
        return

    def _refresh_once():
        try:
            _import_local('top_menu').translate_menu_toolbar()
        except Exception:
            pass
        try:
            ls = _import_local('language_switcher')
            if getattr(ls, 'bilingual_mode', False):
                _import_local('top_menu').translate_menu_toolbar()
        except Exception:
            pass

    for ms in (0, 250, 1000):
        try:
            QtCore.QTimer.singleShot(ms, _refresh_once)
        except Exception:
            pass


def run_init_entry():
    global _INIT_ENTRY_RAN
    if _INIT_ENTRY_RAN:
        return True
    _profile_begin('startup', reset=True)
    _profile_mark('entry', 'init')
    _profile_mark('gui_session', bool(is_gui_session()))
    try:
        with _profile_span('bootstrap.init_entry'):
            ensure_bootstrap_ready()
            ensure_license_for_session(show_ui_if_needed=is_gui_session())
        _INIT_ENTRY_RAN = True
        _profile_end(status='ok', save=True)
        return True
    except Exception:
        _profile_end(status='error', save=True)
        raise



def run_menu_entry():
    global _MENU_ENTRY_RAN
    if _MENU_ENTRY_RAN:
        return True
    _profile_begin('startup', reset=not _profile_has_session())
    _profile_mark('entry', 'menu')
    _profile_mark('gui_session', bool(is_gui_session()))
    try:
        with _profile_span('bootstrap.menu_entry'):
            ensure_bootstrap_ready()
            ensure_license_for_session(show_ui_if_needed=False)
            if is_gui_session():
                install_gui_modules()
                _schedule_late_menu_refresh()
        _MENU_ENTRY_RAN = True
        _profile_end(status='ok', save=True)
        return True
    except Exception:
        _profile_end(status='error', save=True)
        raise
