Quick-Text-Buttons

Adds customizable text buttons to paste frequently used prompts into ChatGPT/Gemini inputs.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Quick-Text-Buttons
// @namespace    https://github.com/p65536
// @version      3.1.5
// @license      MIT
// @description  Adds customizable text buttons to paste frequently used prompts into ChatGPT/Gemini inputs.
// @icon         https://raw.githubusercontent.com/p65536/p65536/main/images/qtb.svg
// @author       p65536
// @match        https://chatgpt.com/*
// @match        https://gemini.google.com/*
// @grant        GM.getValue
// @grant        GM.setValue
// @grant        GM_addValueChangeListener
// @grant        GM_removeValueChangeListener
// @run-at       document-idle
// @noframes
// ==/UserScript==

(() => {
  'use strict';

  // =================================================================================
  // SECTION: Script-Specific Definitions (DO NOT COPY TO OTHER PLATFORM)
  // =================================================================================

  const OWNERID = 'p65536';
  const APPID = 'qtbux';
  const APPNAME = 'Quick Text Buttons';
  const LOG_PREFIX = `[${APPID.toUpperCase()}]`;

  // =================================================================================
  // SECTION: Logging Utility
  // Description: Centralized logging interface for consistent log output across modules.
  //              Handles log level control, message formatting, and console API wrapping.
  // =================================================================================

  class Logger {
    /** @property {object} levels - Defines the numerical hierarchy of log levels. */
    static levels = {
      error: 0,
      warn: 1,
      info: 2,
      log: 3,
      debug: 4,
    };
    /** @property {string} level - The current active log level. */
    static level = 'log'; // Default level

    /**
     * Defines the available badge styles.
     * @property {object} styles
     */
    static styles = {
      BASE: 'color: white; padding: 2px 6px; border-radius: 4px; font-weight: bold;',
      RED: 'background: #dc3545;',
      YELLOW: 'background: #ffc107; color: black;',
      GREEN: 'background: #28a745;',
      BLUE: 'background: #007bff;',
      GRAY: 'background: #6c757d;',
      ORANGE: 'background: #fd7e14;',
      PINK: 'background: #e83e8c;',
      PURPLE: 'background: #6f42c1;',
      CYAN: 'background: #17a2b8; color: black;',
      TEAL: 'background: #20c997; color: black;',
    };

    /**
     * Maps log levels to default badge styles.
     * @private
     */
    static _defaultStyles = {
      error: this.styles.RED,
      warn: this.styles.YELLOW,
      info: this.styles.BLUE,
      log: this.styles.GREEN,
      debug: this.styles.GRAY,
    };

    /**
     * Sets the current log level.
     * @param {string} level The new log level. Must be one of 'error', 'warn', 'info', 'log', 'debug'.
     */
    static setLevel(level) {
      if (Object.hasOwn(this.levels, level)) {
        this.level = level;
      } else {
        // Use default style (empty string) for the badge
        this._out('warn', 'INVALID LEVEL', '', `Invalid log level "${level}". Valid levels are: ${Object.keys(this.levels).join(', ')}. Level not changed.`);
      }
    }

    /**
     * Internal method to output logs if the level permits.
     * @private
     * @param {string} level - The log level ('error', 'warn', 'info', 'log', 'debug').
     * @param {string} badgeText - The text inside the badge. If empty, no badge is shown.
     * @param {string} badgeStyle - The background-color style (from Logger.styles). If empty, uses default.
     * @param {...unknown} args - The messages to log.
     */
    static _out(level, badgeText, badgeStyle, ...args) {
      if (this.levels[this.level] >= this.levels[level]) {
        const consoleMethod = console[level] || console.log;

        if (badgeText !== '') {
          // Badge mode: Use %c formatting
          let style = badgeStyle;
          if (style === '') {
            style = this._defaultStyles[level] || this.styles.GRAY;
          }
          const combinedStyle = `${this.styles.BASE} ${style}`;

          consoleMethod(
            `%c${LOG_PREFIX}%c %c${badgeText}%c`,
            'font-weight: bold;', // Style for the prefix
            'color: inherit;', // Reset for space
            combinedStyle, // Style for the badge
            'color: inherit;', // Reset for the rest of the message
            ...args
          );
        } else {
          // No badge mode: Direct output for better object inspection
          consoleMethod(LOG_PREFIX, ...args);
        }
      }
    }

    /**
     * Internal method to start a log group if the level permits (debug or higher).
     * @private
     * @param {'group'|'groupCollapsed'} method - The console method to use.
     * @param {string} badgeText
     * @param {string} badgeStyle
     * @param {...unknown} args
     */
    static _groupOut(method, badgeText, badgeStyle, ...args) {
      if (this.levels[this.level] >= this.levels.debug) {
        const consoleMethod = console[method];

        if (badgeText !== '') {
          let style = badgeStyle;
          if (style === '') {
            style = this.styles.GRAY;
          }
          const combinedStyle = `${this.styles.BASE} ${style}`;

          consoleMethod(`%c${LOG_PREFIX}%c %c${badgeText}%c`, 'font-weight: bold;', 'color: inherit;', combinedStyle, 'color: inherit;', ...args);
        } else {
          consoleMethod(LOG_PREFIX, ...args);
        }
      }
    }

    /**
     * @param {string} badgeText
     * @param {string} badgeStyle
     * @param {...unknown} args
     */
    static error(badgeText, badgeStyle, ...args) {
      this._out('error', badgeText, badgeStyle, ...args);
    }

    /**
     * @param {string} badgeText
     * @param {string} badgeStyle
     * @param {...unknown} args
     */
    static warn(badgeText, badgeStyle, ...args) {
      this._out('warn', badgeText, badgeStyle, ...args);
    }

    /**
     * @param {string} badgeText
     * @param {string} badgeStyle
     * @param {...unknown} args
     */
    static info(badgeText, badgeStyle, ...args) {
      this._out('info', badgeText, badgeStyle, ...args);
    }

    /**
     * @param {string} badgeText
     * @param {string} badgeStyle
     * @param {...unknown} args
     */
    static log(badgeText, badgeStyle, ...args) {
      this._out('log', badgeText, badgeStyle, ...args);
    }

    /**
     * Logs messages for debugging. Only active in 'debug' level.
     * @param {string} badgeText
     * @param {string} badgeStyle
     * @param {...unknown} args
     */
    static debug(badgeText, badgeStyle, ...args) {
      this._out('debug', badgeText, badgeStyle, ...args);
    }

    /**
     * Starts a timer for performance measurement. Only active in 'debug' level.
     * @param {string} label The label for the timer.
     */
    static time(label) {
      if (this.levels[this.level] >= this.levels.debug) {
        console.time(`${LOG_PREFIX} ${label}`);
      }
    }

    /**
     * Ends a timer and logs the elapsed time. Only active in 'debug' level.
     * @param {string} label The label for the timer, must match the one used in time().
     */
    static timeEnd(label) {
      if (this.levels[this.level] >= this.levels.debug) {
        console.timeEnd(`${LOG_PREFIX} ${label}`);
      }
    }

    /**
     * Starts a log group. Only active in 'debug' level.
     * @param {string} badgeText
     * @param {string} badgeStyle
     * @param {...unknown} args The title for the log group.
     */
    static group(badgeText, badgeStyle, ...args) {
      this._groupOut('group', badgeText, badgeStyle, ...args);
    }

    /**
     * Starts a collapsed log group. Only active in 'debug' level.
     * @param {string} badgeText
     * @param {string} badgeStyle
     * @param {...unknown} args The title for the log group.
     */
    static groupCollapsed(badgeText, badgeStyle, ...args) {
      this._groupOut('groupCollapsed', badgeText, badgeStyle, ...args);
    }

    /**
     * Closes the current log group. Only active in 'debug' level.
     * @returns {void}
     */
    static groupEnd() {
      if (this.levels[this.level] >= this.levels.debug) {
        console.groupEnd();
      }
    }
  }

  // Alias for ease of use
  const LOG_STYLES = Logger.styles;

  // =================================================================================
  // SECTION: Execution Guard
  // Description: Prevents the script from being executed multiple times per page.
  // =================================================================================

  class ExecutionGuard {
    // A shared key for all scripts from the same author to avoid polluting the window object.
    static #GUARD_KEY = `__${OWNERID}_guard__`;
    // A specific key for this particular script.
    static #APP_KEY = `${APPID}_executed`;

    /**
     * Checks if the script has already been executed on the page.
     * @returns {boolean} True if the script has run, otherwise false.
     */
    static hasExecuted() {
      return window[this.#GUARD_KEY]?.[this.#APP_KEY] || false;
    }

    /**
     * Sets the flag indicating the script has now been executed.
     */
    static setExecuted() {
      window[this.#GUARD_KEY] = window[this.#GUARD_KEY] || {};
      window[this.#GUARD_KEY][this.#APP_KEY] = true;
    }
  }

  // =================================================================================
  // SECTION: Configuration and Constants
  // Description: Defines default settings, global constants, and CSS selectors.
  // =================================================================================

  const CONSTANTS = {
    CONFIG_KEY: `${APPID}_config`,
    CONFIG_SIZE_RECOMMENDED_LIMIT_BYTES: 5 * 1024 * 1024, // 5MB
    CONFIG_SIZE_LIMIT_BYTES: 10 * 1024 * 1024, // 10MB
    ID_PREFIX: `${APPID}-id-`,
    Z_INDICES: {
      SETTINGS_PANEL: 11000,
      TEXT_LIST: 20001,
    },
    TIMING: {
      TIMEOUTS: {
        POST_NAVIGATION_DOM_SETTLE: 200,
        HIDE_DELAY_MS: 250,
      },
    },
    // Platform-specific path exclusions
    URL_EXCLUSIONS: {
      chatgpt: [/^\/codex/, /^\/gpts/, /^\/apps/],
      gemini: [/^\/gems/],
    },
    PLATFORM: {
      CHATGPT: {
        ID: 'chatgpt',
        HOST: 'chatgpt.com',
      },
      GEMINI: {
        ID: 'gemini',
        HOST: 'gemini.google.com',
      },
    },
    SELECTORS: {
      chatgpt: {
        // Reference element for button positioning (Parent container)
        INSERTION_ANCHOR: 'form[data-type="unified-composer"] div[class*="[grid-area:leading]"]',
        // Actual input element for text insertion
        INPUT_TARGET: 'div.ProseMirror#prompt-textarea',
        // Explicit settings for layout strategy
        ANCHOR_PADDING_LEFT: null, // No padding adjustment needed
        INSERT_METHOD: 'prepend',
      },
      gemini: {
        // Reference element for button positioning - Main text input wrapper (Stable parent)
        INSERTION_ANCHOR: 'input-area-v2 .text-input-field',
        // Actual input element for text insertion
        INPUT_TARGET: 'rich-textarea .ql-editor',
        // Settings for absolute positioning strategy
        // Button occupies 48px (left:8px + width:40px). 52px provides a 4px gap.
        ANCHOR_PADDING_LEFT: '52px',
        INSERT_METHOD: 'append',
      },
    },
    MODAL_TYPES: {
      JSON: 'json',
      TEXT_EDITOR: 'textEditor',
    },
  };

  const EVENTS = {
    CONFIG_SIZE_EXCEEDED: `${APPID}:configSizeExceeded`,
    CONFIG_SAVE_SUCCESS: `${APPID}:configSaveSuccess`,
    REOPEN_MODAL: `${APPID}:reOpenModal`,
    CONFIG_UPDATED: `${APPID}:configUpdated`,
    UI_REPOSITION: `${APPID}:uiReposition`,
    NAVIGATION_START: `${APPID}:navigationStart`,
    NAVIGATION: `${APPID}:navigation`,
  };

  // =================================================================================
  // SECTION: Style System & Definitions
  // Description: Centralizes all CSS generation logic, class name definitions, and DOM injection mechanics.
  // =================================================================================

  /**
   * @class StyleDefinitions
   * @description Manages pure style definitions, class names, and CSS generation logic.
   */
  class StyleDefinitions {
    static ICONS = (() => {
      const COMMON_PROPS = {
        xmlns: 'http://www.w3.org/2000/svg',
        height: '24px',
        viewBox: '0 -960 960 960',
        width: '24px',
        fill: 'currentColor',
      };

      const def = (d, options = {}) => ({
        tag: 'svg',
        props: { ...COMMON_PROPS, ...options.props },
        children: [{ tag: 'path', props: { d, ...options.pathProps } }],
      });

      return {
        up: def('M480-528 296-344l-56-56 240-240 240 240-56 56-184-184Z'),
        down: def('M480-344 240-584l56-56 184 184 184-184 56 56-240 240Z'),
        delete: def('m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z'),
        insert: def(
          'm499-287 335-335-52-52-335 335 52 52Zm-261 87q-100-5-149-42T40-349q0-65 53.5-105.5T242-503q39-3 58.5-12.5T320-542q0-26-29.5-39T193-600l7-80q103 8 151.5 41.5T400-542q0 53-38.5 83T248-423q-64 5-96 23.5T120-349q0 35 28 50.5t94 18.5l-4 80Zm280 7L353-358l382-382q20-20 47.5-20t47.5 20l70 70q20 20 20 47.5T900-575L518-193Zm-159 33q-17 4-30-9t-9-30l33-159 165 165-159 33Z'
        ),
        dragHandle: def(
          'M349.85-524.85q-14.52 0-24.68-10.16-10.17-10.17-10.17-24.69t10.17-24.68q10.16-10.17 24.68-10.17t24.69 10.17q10.16 10.16 10.16 24.68t-10.16 24.69q-10.17 10.16-24.69 10.16Zm260.3,0q-14.52 0-24.68-10.16-10.17-10.17-10.17-24.69t10.17-24.68q10.16-10.17 24.68-10.17t24.69 10.17q10.16 10.16 10.16 24.68t-10.16 24.69q-10.17 10.16-24.69 10.16Zm-260.3-170q-14.52 0-24.68-10.17-10.17-10.16-10.17-24.68t10.17-24.69q10.16-10.16 24.68-10.16t24.69 10.16q10.16 10.17 10.16 24.69t-10.16 24.68q-10.17 10.17-24.69 10.17Zm260.3,0q-14.52 0-24.68-10.17-10.17-10.16-10.17-24.68t10.17-24.69q10.16-10.16 24.68-10.16t24.69 10.16q10.16 10.17 10.16 24.69t-10.16 24.68q-10.17 10.17-24.69 10.17Zm-260.3,340q-14.52 0-24.68-10.17-10.17-10.16-10.17-24.68t10.17-24.69q10.16-10.16 24.68-10.16t24.69 10.16q10.16 10.17 10.16 24.69t-10.16 24.68q-10.17 10.17-24.69 10.17Zm260.3,0q-14.52 0-24.68-10.17-10.17-10.16-10.17-24.68t10.17-24.69q10.16-10.16 24.68-10.16t24.69 10.16q10.16 10.17 10.16 24.69t-10.16 24.68q-10.17 10.17-24.69 10.17Z'
        ),
      };
    })();

    static COMMON_CLASSES = (() => {
      const prefix = `${APPID}-common`;
      return {
        modalButton: `${prefix}-btn`,
        primaryBtn: `${prefix}-btn-primary`,
        pushRightBtn: `${prefix}-btn-push-right`,

        // Form Elements
        formField: `${prefix}-form-field`,
        inputWrapper: `${prefix}-input-wrapper`,
        toggleSwitch: `${prefix}-toggle`,
        toggleSlider: `${prefix}-toggle-slider`,
        selectInput: `${prefix}-select`,

        // Layouts
        submenuRow: `${prefix}-row`,
        submenuFieldset: `${prefix}-fieldset`,
        submenuSeparator: `${prefix}-separator`,
        settingsNote: `${prefix}-note`,

        // Notification
        conflictText: `${prefix}-conflict-text`,
        conflictReloadBtnId: `${prefix}-conflict-reload-btn`,
        warningBanner: `${prefix}-warning-banner`,
      };
    })();

    static MODAL_CLASSES = (() => {
      const prefix = `${APPID}-modal`;
      return {
        dialog: `${prefix}-dialog`,
        box: `${prefix}-box`,
        header: `${prefix}-header`,
        content: `${prefix}-content`,
        footer: `${prefix}-footer`,
        footerMessage: `${prefix}-footer-message`,
        buttonGroup: `${prefix}-button-group`,
      };
    })();

    static SETTINGS_PANEL_CLASSES = (() => {
      const prefix = `${APPID}-settings-panel`;
      return {
        panel: `${prefix}-container`,
        topRow: `${prefix}-top-row`,
      };
    })();

    static VARS = {
      // Backgrounds
      MODAL_BG: `--${APPID}-modal-bg`,
      PANEL_BG: `--${APPID}-panel-bg`,
      INPUT_BG: `--${APPID}-input-bg`,

      // Text Colors
      TEXT_PRIMARY: `--${APPID}-text-primary`,
      TEXT_SECONDARY: `--${APPID}-text-secondary`,
      TEXT_DANGER: `--${APPID}-text-danger`,
      TEXT_WARNING: `--${APPID}-text-warning`,
      TEXT_ACCENT: `--${APPID}-text-accent`,

      // Borders
      BORDER_DEFAULT: `--${APPID}-border-default`,
      BORDER_MEDIUM: `--${APPID}-border-medium`,
      BORDER_LIGHT: `--${APPID}-border-light`,

      // Buttons (Standard)
      BTN_BG: `--${APPID}-btn-bg`,
      BTN_HOVER_BG: `--${APPID}-btn-hover-bg`,
      BTN_TEXT: `--${APPID}-btn-text`,
      BTN_BORDER: `--${APPID}-btn-border`,

      // Toggle Switch
      TOGGLE_BG_OFF: `--${APPID}-toggle-bg-off`,
      TOGGLE_BG_ON: `--${APPID}-toggle-bg-on`,
      TOGGLE_KNOB: `--${APPID}-toggle-knob`,

      // Components specific (Text List & Tabs)
      LIST_BG: `--${APPID}-list-bg`,
      LIST_SHADOW: `--${APPID}-list-shadow`,
      TAB_BG: `--${APPID}-tab-bg`,
      TAB_TEXT: `--${APPID}-tab-text`,
      TAB_BORDER: `--${APPID}-tab-border`,
      TAB_HOVER_BG: `--${APPID}-tab-hover-bg`,
      TAB_ACTIVE_BG: `--${APPID}-tab-active-bg`,
      TAB_ACTIVE_BORDER: `--${APPID}-tab-active-border`,
      TAB_ACTIVE_OUTLINE: `--${APPID}-tab-active-outline`,

      OPTION_BG: `--${APPID}-option-bg`,
      OPTION_TEXT: `--${APPID}-option-text`,
      OPTION_BORDER: `--${APPID}-option-border`,
      OPTION_HOVER_BG: `--${APPID}-option-hover-bg`,
      OPTION_HOVER_BORDER: `--${APPID}-option-hover-border`,
      OPTION_HOVER_OUTLINE: `--${APPID}-option-hover-outline`,

      // Insert Button
      INSERT_BTN_COLOR: `--${APPID}-insert-btn-color`,
      INSERT_BTN_HOVER_BG: `--${APPID}-insert-btn-hover-bg`,
      INSERT_BTN_POSITION: `--${APPID}-insert-btn-position`,
      INSERT_BTN_LEFT: `--${APPID}-insert-btn-left`,
      INSERT_BTN_BOTTOM: `--${APPID}-insert-btn-bottom`,
      INSERT_BTN_SIZE: `--${APPID}-insert-btn-size`,

      // Anchor Layout
      ANCHOR_PADDING_LEFT: `--${APPID}-anchor-padding-left`,
      ANCHOR_GAP: `--${APPID}-anchor-gap`,

      // Theme Modal Specific
      DELETE_BTN_TEXT: `--${APPID}-delete-btn-text`,
      DELETE_BTN_BG: `--${APPID}-delete-btn-bg`,
      DELETE_BTN_HOVER_TEXT: `--${APPID}-delete-btn-hover-text`,
      DELETE_BTN_HOVER_BG: `--${APPID}-delete-btn-hover-bg`,
      DND_INDICATOR: `--${APPID}-dnd-indicator`,
    };

    static PLATFORM_THEMES = {
      chatgpt: {
        [this.VARS.MODAL_BG]: 'var(--main-surface-primary)',
        [this.VARS.PANEL_BG]: 'var(--sidebar-surface-primary)',
        [this.VARS.INPUT_BG]: 'var(--bg-primary)',

        [this.VARS.TEXT_PRIMARY]: 'var(--text-primary)',
        [this.VARS.TEXT_SECONDARY]: 'var(--text-secondary)',
        [this.VARS.TEXT_DANGER]: 'var(--text-danger)',
        [this.VARS.TEXT_WARNING]: '#FFD54F',
        [this.VARS.TEXT_ACCENT]: 'var(--text-accent)',

        [this.VARS.BORDER_DEFAULT]: 'var(--border-default)',
        [this.VARS.BORDER_MEDIUM]: 'var(--border-medium)',
        [this.VARS.BORDER_LIGHT]: 'var(--border-light)',

        [this.VARS.BTN_BG]: 'var(--interactive-bg-tertiary-default)',
        [this.VARS.BTN_HOVER_BG]: 'var(--interactive-bg-secondary-hover)',
        [this.VARS.BTN_TEXT]: 'var(--text-primary)',
        [this.VARS.BTN_BORDER]: 'var(--border-default)',

        [this.VARS.TOGGLE_BG_OFF]: 'var(--bg-primary)',
        [this.VARS.TOGGLE_BG_ON]: 'var(--text-accent)',
        [this.VARS.TOGGLE_KNOB]: 'var(--text-primary)',

        [this.VARS.LIST_BG]: 'var(--main-surface-primary)',
        [this.VARS.LIST_SHADOW]: 'var(--drop-shadow-md, 0 3px 3px #0000001f)',
        [this.VARS.TAB_BG]: 'var(--interactive-bg-tertiary-default)',
        [this.VARS.TAB_TEXT]: 'var(--text-primary)',
        [this.VARS.TAB_BORDER]: 'var(--border-light)',
        [this.VARS.TAB_HOVER_BG]: 'var(--interactive-bg-secondary-hover)',
        [this.VARS.TAB_ACTIVE_BG]: 'var(--interactive-bg-secondary-hover)',
        [this.VARS.TAB_ACTIVE_BORDER]: 'var(--border-default)',
        [this.VARS.TAB_ACTIVE_OUTLINE]: 'var(--border-default)',

        [this.VARS.OPTION_BG]: 'var(--interactive-bg-tertiary-default)',
        [this.VARS.OPTION_TEXT]: 'var(--text-primary)',
        [this.VARS.OPTION_BORDER]: 'var(--border-default)',
        [this.VARS.OPTION_HOVER_BG]: 'var(--interactive-bg-secondary-hover)',
        [this.VARS.OPTION_HOVER_BORDER]: 'var(--border-default)',
        [this.VARS.OPTION_HOVER_OUTLINE]: 'var(--border-default)',

        [this.VARS.INSERT_BTN_COLOR]: 'var(--text-primary)',
        [this.VARS.INSERT_BTN_HOVER_BG]: 'var(--interactive-bg-secondary-hover)',
        [this.VARS.INSERT_BTN_POSITION]: 'static',
        [this.VARS.INSERT_BTN_LEFT]: 'auto',
        [this.VARS.INSERT_BTN_BOTTOM]: 'auto',
        [this.VARS.INSERT_BTN_SIZE]: 'calc(var(--spacing)*9)',

        [this.VARS.ANCHOR_PADDING_LEFT]: '0',
        [this.VARS.ANCHOR_GAP]: '2px',

        [this.VARS.DELETE_BTN_TEXT]: 'var(--interactive-label-danger-secondary-default)',
        [this.VARS.DELETE_BTN_BG]: 'var(--interactive-bg-danger-secondary-default)',
        [this.VARS.DELETE_BTN_HOVER_TEXT]: 'var(--interactive-label-danger-secondary-hover)',
        [this.VARS.DELETE_BTN_HOVER_BG]: 'var(--interactive-bg-secondary-hover)',
        [this.VARS.DND_INDICATOR]: 'var(--text-accent)',
      },
      gemini: {
        [this.VARS.MODAL_BG]: 'var(--gem-sys-color--surface-container-highest)',
        [this.VARS.PANEL_BG]: 'var(--gem-sys-color--surface-container-highest)',
        [this.VARS.INPUT_BG]: 'var(--gem-sys-color--surface-container-low)',

        [this.VARS.TEXT_PRIMARY]: 'var(--gem-sys-color--on-surface)',
        [this.VARS.TEXT_SECONDARY]: 'var(--gem-sys-color--on-surface-variant)',
        [this.VARS.TEXT_DANGER]: 'var(--gem-sys-color--error)',
        [this.VARS.TEXT_WARNING]: '#FFD54F',
        [this.VARS.TEXT_ACCENT]: 'var(--gem-sys-color--primary)',

        [this.VARS.BORDER_DEFAULT]: 'var(--gem-sys-color--outline)',
        [this.VARS.BORDER_MEDIUM]: 'var(--gem-sys-color--outline)',
        [this.VARS.BORDER_LIGHT]: 'var(--gem-sys-color--outline-low)',

        [this.VARS.BTN_BG]: 'var(--gem-sys-color--surface-container-high)',
        [this.VARS.BTN_HOVER_BG]: 'var(--gem-sys-color--surface-container-higher)',
        [this.VARS.BTN_TEXT]: 'var(--gem-sys-color--on-surface-variant)',
        [this.VARS.BTN_BORDER]: 'var(--gem-sys-color--outline)',

        [this.VARS.TOGGLE_BG_OFF]: 'var(--gem-sys-color--surface-container)',
        [this.VARS.TOGGLE_BG_ON]: 'var(--gem-sys-color--primary)',
        [this.VARS.TOGGLE_KNOB]: 'var(--gem-sys-color--on-primary-container)',

        [this.VARS.LIST_BG]: 'var(--gem-sys-color--surface-container-high)',
        [this.VARS.LIST_SHADOW]: '0 4px 12px rgb(0 0 0 / 0.25)',
        [this.VARS.TAB_BG]: 'var(--gem-sys-color--surface-container)',
        [this.VARS.TAB_TEXT]: 'var(--gem-sys-color--on-surface-variant)',
        [this.VARS.TAB_BORDER]: 'var(--gem-sys-color--outline)',
        [this.VARS.TAB_HOVER_BG]: 'var(--gem-sys-color--secondary-container)',
        [this.VARS.TAB_ACTIVE_BG]: 'var(--gem-sys-color--surface-container-higher)',
        [this.VARS.TAB_ACTIVE_BORDER]: 'var(--gem-sys-color--primary)',
        [this.VARS.TAB_ACTIVE_OUTLINE]: 'var(--gem-sys-color--primary)',

        [this.VARS.OPTION_BG]: 'var(--gem-sys-color--surface-container)',
        [this.VARS.OPTION_TEXT]: 'var(--gem-sys-color--on-surface-variant)',
        [this.VARS.OPTION_BORDER]: 'var(--gem-sys-color--outline)',
        [this.VARS.OPTION_HOVER_BG]: 'var(--gem-sys-color--secondary-container)',
        [this.VARS.OPTION_HOVER_BORDER]: 'var(--gem-sys-color--outline)',
        [this.VARS.OPTION_HOVER_OUTLINE]: 'var(--gem-sys-color--primary)',

        [this.VARS.INSERT_BTN_COLOR]: 'var(--mat-icon-button-icon-color, var(--mat-sys-on-surface-variant))',
        [this.VARS.INSERT_BTN_HOVER_BG]: 'color-mix(in srgb, var(--mat-icon-button-state-layer-color) 8%, transparent)',
        [this.VARS.INSERT_BTN_POSITION]: 'absolute',
        [this.VARS.INSERT_BTN_LEFT]: '8px',
        [this.VARS.INSERT_BTN_BOTTOM]: '12px',
        [this.VARS.INSERT_BTN_SIZE]: '40px',

        [this.VARS.ANCHOR_PADDING_LEFT]: '52px',
        [this.VARS.ANCHOR_GAP]: '0',

        [this.VARS.DELETE_BTN_TEXT]: 'var(--gem-sys-color--on-error-container)',
        [this.VARS.DELETE_BTN_BG]: 'var(--gem-sys-color--error-container)',
        [this.VARS.DELETE_BTN_HOVER_TEXT]: 'var(--gem-sys-color--on-error-container)',
        [this.VARS.DELETE_BTN_HOVER_BG]: 'color-mix(in srgb, var(--gem-sys-color--on-error-container) 15%, var(--gem-sys-color--error-container))',
        [this.VARS.DND_INDICATOR]: 'var(--gem-sys-color--primary)',
      },
    };

    static getPlatformVariables(platformId) {
      const theme = this.PLATFORM_THEMES[platformId];
      if (!theme) return '';

      return Object.entries(theme)
        .map(([key, value]) => `${key}: ${value};`)
        .join('\n');
    }

    static getCommon() {
      const key = 'common';
      const cls = StyleDefinitions.COMMON_CLASSES;
      const v = StyleDefinitions.VARS;

      const cssGenerator = (classes) => `
/* Buttons */
.${classes.modalButton} {
background: var(${v.BTN_BG});
border: 1px solid var(${v.BTN_BORDER});
border-radius: var(--radius-md, 5px);
color: var(${v.BTN_TEXT});
cursor: pointer;
font-size: 13px;
padding: 5px 16px;
transition: background 0.12s, color 0.12s, opacity 0.12s;
display: flex;
align-items: center;
justify-content: center;
white-space: nowrap;
min-width: 80px;
}
.${classes.modalButton}:hover {
background: var(${v.BTN_HOVER_BG}) !important;
border-color: var(${v.BTN_BORDER});
}
.${classes.modalButton}:disabled {
background: var(${v.BTN_BG}) !important;
cursor: not-allowed;
opacity: 0.5;
}
.${classes.primaryBtn} {
background-color: #1a73e8 !important;
color: #ffffff !important;
border: 1px solid transparent !important;
}
.${classes.primaryBtn}:hover {
background-color: #1557b0 !important;
}
.${classes.pushRightBtn} {
margin-left: auto !important;
}

/* Fieldset & Layout */
.${classes.submenuFieldset} {
border: 1px solid var(${v.BORDER_DEFAULT});
border-radius: 4px;
padding: 8px 12px 12px;
margin: 0 0 12px 0;
min-width: 0;
}
.${classes.submenuFieldset} legend {
padding: 0 4px;
font-weight: 500;
color: var(${v.TEXT_SECONDARY});
}
.${classes.submenuRow} {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-top: 8px;
}
.${classes.submenuRow} label {
white-space: nowrap;
flex-shrink: 0;
}
.${classes.submenuRow} select {
width: 50%;
}
.${classes.submenuSeparator} {
border-top: 1px solid var(${v.BORDER_LIGHT});
margin: 12px 0;
}
.${classes.settingsNote} {
font-size: 0.85em;
color: var(${v.TEXT_SECONDARY});
text-align: left;
margin-top: 8px;
padding: 0 4px;
}

/* Notification Banner */
.${classes.warningBanner} {
background-color: var(--bg-danger, #ffe6e6);
color: var(${v.TEXT_DANGER});
padding: 8px 12px;
margin-bottom: 12px;
border: 1px solid var(--border-danger, #ffcdd2);
border-radius: 4px;
font-size: 0.9em;
line-height: 1.4;
white-space: pre-wrap;
}

/* Form Inputs */
.${classes.selectInput} {
width: 100%;
box-sizing: border-box;
background: var(${v.INPUT_BG});
border: 1px solid var(${v.BORDER_DEFAULT});
color: var(${v.TEXT_PRIMARY});
border-radius: 4px;
padding: 4px 6px;
}

/* Toggle Switch */
.${classes.toggleSwitch} {
position: relative;
display: inline-block;
width: 40px;
height: 22px;
flex-shrink: 0;
}
.${classes.toggleSwitch} input {
opacity: 0;
width: 0;
height: 0;
}
.${classes.toggleSlider} {
position: absolute;
cursor: pointer;
top: 0; left: 0; right: 0; bottom: 0;
background-color: var(${v.TOGGLE_BG_OFF});
transition: .3s;
border-radius: 22px;
}
.${classes.toggleSlider}:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 3px;
bottom: 3px;
background-color: var(${v.TOGGLE_KNOB});
transition: .3s;
border-radius: 50%;
}
.${classes.toggleSwitch} input:checked + .${classes.toggleSlider} {
background-color: var(${v.TOGGLE_BG_ON});
}
.${classes.toggleSwitch} input:checked + .${classes.toggleSlider}:before {
transform: translateX(18px);
}
`;
      return { key, classes: cls, vars: {}, generator: cssGenerator };
    }

    static getModal() {
      const key = 'modal';
      const cls = StyleDefinitions.MODAL_CLASSES;
      const common = StyleDefinitions.COMMON_CLASSES;
      const v = StyleDefinitions.VARS;

      const cssGenerator = (classes) => `
dialog.${classes.dialog} {
padding: 0;
border: none;
background: transparent;
max-width: 100vw;
max-height: 100vh;
overflow: visible;
}
dialog.${classes.dialog}::backdrop {
background: rgb(0 0 0 / 0.5);
pointer-events: auto;
}
.${classes.box} {
display: flex;
flex-direction: column;
background: var(${v.MODAL_BG});
color: var(${v.TEXT_PRIMARY});
border: 1px solid var(${v.BORDER_DEFAULT});
border-radius: 8px;
box-shadow: 0 4px 16px rgb(0 0 0 / 0.2);
}
.${classes.header}, .${classes.footer} {
flex-shrink: 0;
padding: 12px 16px;
}
.${classes.header} {
font-size: 1.1em;
font-weight: 600;
border-bottom: 1px solid var(${v.BORDER_DEFAULT});
}
.${classes.content} {
flex-grow: 1;
padding: 16px;
overflow-y: auto;
}
.${classes.footer} {
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
border-top: 1px solid var(${v.BORDER_DEFAULT});
}
.${classes.footerMessage} {
flex-grow: 1;
font-size: 0.9em;
}
.${classes.buttonGroup} {
display: flex;
gap: 8px;
}

/* Conflict Notification */
.${classes.footerMessage}.${common.conflictText} {
color: var(${v.TEXT_DANGER});
display: flex;
align-items: center;
}
.${classes.footerMessage} #${common.conflictReloadBtnId} {
border-color: var(${v.TEXT_DANGER});
}
`;
      return { key, classes: cls, vars: {}, generator: cssGenerator };
    }

    static getSettingsPanel() {
      const key = 'settings-panel';
      const cls = StyleDefinitions.SETTINGS_PANEL_CLASSES;
      const common = StyleDefinitions.COMMON_CLASSES;
      const v = StyleDefinitions.VARS;

      const cssGenerator = (classes) => `
#${classes.panel} {
position: fixed;
width: min(340px, 95vw);
max-height: 85vh;
overflow-y: auto;
overscroll-behavior: contain;
background: var(${v.PANEL_BG});
color: var(${v.TEXT_PRIMARY});
border-radius: 0.5rem;
box-shadow: 0 4px 20px 0 rgb(0 0 0 / 15%);
padding: 12px;
z-index: ${CONSTANTS.Z_INDICES.SETTINGS_PANEL};
border: 1px solid var(${v.BORDER_MEDIUM});
font-size: 0.9em;
}
.${classes.topRow} {
display: flex; gap: 12px;
}
.${classes.topRow} .${common.submenuFieldset} {
flex: 1 1 0px;
}
`;
      return { key, classes: cls, vars: {}, generator: cssGenerator };
    }

    static getTextList() {
      const key = 'text-list';
      const prefix = `${APPID}-text-list`;
      const classes = {
        list: `${prefix}-container`,
        profileBar: `${prefix}-profile-bar`,
        profileName: `${prefix}-profile-name`,
        navBtn: `${prefix}-nav-btn`,
        rotateLeft: `${prefix}-rotate-left`,
        rotateRight: `${prefix}-rotate-right`,
        tabs: `${prefix}-tabs`,
        separator: `${prefix}-separator`,
        tab: `${prefix}-tab`,
        options: `${prefix}-options`,
        option: `${prefix}-option`,
      };
      const v = StyleDefinitions.VARS;

      const cssGenerator = (cls) => `
#${cls.list} {
position: fixed;
z-index: ${CONSTANTS.Z_INDICES.TEXT_LIST};
display: none;
width: min(500px, 95vw);
padding: 4px 8px;
border-radius: var(--radius-md, 4px);
background: var(${v.LIST_BG});
color: var(${v.TEXT_PRIMARY});
border: 1px solid var(${v.BORDER_MEDIUM});
box-shadow: var(${v.LIST_SHADOW});
display: flex;
flex-direction: column;
box-sizing: border-box;
}
.${cls.profileBar} {
display: flex;
align-items: center;
justify-content: space-between;
margin: 4px 0;
padding: 4px 0;
border-top: 1px solid var(${v.BORDER_DEFAULT});
border-bottom: 1px solid var(${v.BORDER_DEFAULT});
flex: 0 0 auto;
gap: 8px;
}
.${cls.profileName} {
flex-grow: 1;
text-align: center;
font-weight: bold;
font-size: 0.95em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0 8px;
color: var(${v.TEXT_PRIMARY});
cursor: pointer;
border-radius: 4px;
transition: background 0.2s;
}
.${cls.profileName}:hover {
background: var(${v.OPTION_HOVER_BG});
}
.${cls.navBtn} {
background: transparent;
border: none;
color: var(${v.TEXT_PRIMARY});
cursor: pointer;
padding: 2px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
min-width: 24px;
height: 24px;
opacity: 0.7;
transition: opacity 0.2s, background 0.2s;
}
.${cls.navBtn}:hover {
opacity: 1;
background: var(${v.OPTION_HOVER_BG});
}
.${cls.rotateLeft} { transform: rotate(-90deg); }
.${cls.rotateRight} { transform: rotate(90deg); }

.${cls.tabs} {
display: flex;
margin: 4px 0;
flex: 0 0 auto;
}
.${cls.separator} {
height: 1px;
margin: 4px 0;
background: var(${v.BORDER_DEFAULT});
flex: 0 0 auto;
}
.${cls.tab} {
flex: 1 1 0;
min-width: 0;
max-width: 90px;
margin-right: 4px;
padding: 4px 0;
border-radius: var(--radius-md, 4px);
font-size: 12px;
text-align: center;
background: var(${v.TAB_BG});
color: var(${v.TAB_TEXT});
border: 1px solid var(${v.TAB_BORDER});
cursor: pointer;
transition: background 0.15s;
}
.${cls.tab}.active {
background: var(${v.TAB_ACTIVE_BG});
border-color: var(${v.TAB_ACTIVE_BORDER});
outline: 2px solid var(${v.TAB_ACTIVE_OUTLINE});
}
.${cls.tab}:hover {
background: var(${v.TAB_HOVER_BG});
}

.${cls.options} {
flex: 1 1 auto;
overflow-y: auto;
min-height: 0;
overscroll-behavior: contain;
}
.${cls.option} {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
width: 100%;
margin: 4px 0;
padding: 4px;
font-size: 13px;
text-align: left;
border-radius: var(--radius-md, 5px);
background: var(${v.OPTION_BG});
color: var(${v.OPTION_TEXT});
border: 1px solid var(${v.OPTION_BORDER});
cursor: pointer;
}
.${cls.option}:hover, .${cls.option}:focus {
background: var(${v.OPTION_HOVER_BG}) !important;
border-color: var(${v.OPTION_HOVER_BORDER}) !important;
outline: 2px solid var(${v.OPTION_HOVER_OUTLINE});
}
.${cls.option}.active {
background: var(${v.TAB_ACTIVE_BG});
border-color: var(${v.TAB_ACTIVE_BORDER});
font-weight: bold;
}
`;
      return { key, classes, vars: {}, generator: cssGenerator };
    }

    static getInsertButton() {
      const key = 'insert-btn';
      const prefix = `${APPID}-insert`;
      const classes = {
        buttonId: `${CONSTANTS.ID_PREFIX}insert-btn`,
        anchorStyled: `${prefix}-anchor-styled`,
      };
      const v = StyleDefinitions.VARS;

      const cssGenerator = (cls) => `
#${cls.buttonId} {
/* Dynamic Layout via Vars */
position: var(${v.INSERT_BTN_POSITION}) !important;
left: var(${v.INSERT_BTN_LEFT});
bottom: var(${v.INSERT_BTN_BOTTOM});
width: var(${v.INSERT_BTN_SIZE});
height: var(${v.INSERT_BTN_SIZE});
margin: 0 !important;

/* Visuals */
display: flex;
background: transparent;
border: none;
border-radius: 50%;
color: var(${v.INSERT_BTN_COLOR});

/* Base */
font-size: 16px;
cursor: pointer;
box-shadow: var(--drop-shadow-xs, 0 1px 1px #0000000d);
transition: background 0.12s, border-color 0.12s, box-shadow 0.12s;
align-items: center;
justify-content: center;
padding: 0;
pointer-events: auto !important;
}
#${cls.buttonId}:hover {
background: var(${v.INSERT_BTN_HOVER_BG});
}

/* Anchor Styling */
.${cls.anchorStyled} {
position: relative;
display: flex;
align-items: center;
gap: var(${v.ANCHOR_GAP});
padding-left: var(${v.ANCHOR_PADDING_LEFT}) !important;
}
`;
      return { key, classes, vars: {}, generator: cssGenerator };
    }

    static getTextEditorModal() {
      const key = 'text-editor';
      const prefix = `${APPID}-text-editor`;
      const classes = {
        // Header Controls
        headerControls: `${prefix}-header-controls`,
        headerRow: `${prefix}-header-row`,
        renameArea: `${prefix}-rename-area`,
        actionArea: `${prefix}-action-area`,
        mainActions: `${prefix}-main-actions`,
        renameActions: `${prefix}-rename-actions`,
        deleteConfirmGroup: `${prefix}-delete-confirm-group`,
        deleteConfirmLabel: `${prefix}-delete-confirm-label`,
        deleteConfirmBtnYes: `${prefix}-delete-confirm-btn-yes`,

        // Content Layout
        modalContent: `${prefix}-modal-content`,
        scrollableArea: `${prefix}-scrollable-area`,

        // Text Item
        textItem: `${prefix}-text-item`,
        dragHandle: `${prefix}-drag-handle`,
        itemControls: `${prefix}-text-item-controls`,

        // Buttons
        moveBtn: `${prefix}-move-btn`,
        deleteBtn: `${prefix}-delete-btn`,
      };
      const v = StyleDefinitions.VARS;
      const common = StyleDefinitions.COMMON_CLASSES;

      const cssGenerator = (cls) => `
/* Header Layout */
.${cls.headerControls} {
display: flex;
flex-direction: column;
gap: 12px;
}
.${cls.headerRow} {
display: grid;
/* Label | Flexible Input | Action Buttons */
grid-template-columns: 5.5rem 1fr auto;
gap: 8px;
align-items: center;
}
@media (max-width: 800px) {
.${cls.headerRow} {
grid-template-columns: 1fr;
gap: 8px;
}
.${cls.headerRow} > label {
text-align: left;
}
.${cls.headerRow} > .${cls.renameArea} {
grid-column: 1;
}
.${cls.headerRow} > .${cls.actionArea} {
grid-column: 1;
}
}
.${cls.headerRow}.is-disabled {
opacity: 0.5;
pointer-events: none;
}
.${cls.headerRow} > label {
grid-column: 1;
text-align: right;
color: var(${v.TEXT_SECONDARY});
font-size: 0.9em;
}
.${cls.headerRow} > .${cls.renameArea} {
grid-column: 2;
min-width: 180px;
}
.${cls.headerRow} > .${cls.actionArea} {
grid-column: 3;
display: grid;
grid-template-columns: 1fr;
align-items: center;
}
.${cls.actionArea} > * {
grid-area: 1 / 1;
width: 100%;
display: flex;
align-items: center;
}
.${cls.mainActions},
.${cls.renameActions} {
justify-content: flex-start;
gap: 8px;
flex-wrap: nowrap;
}
.${cls.deleteConfirmGroup} {
justify-content: space-between;
gap: 8px;
}

/* Delete Confirmation Styles */
.${cls.deleteConfirmLabel} {
color: var(${v.TEXT_DANGER});
font-style: italic;
white-space: nowrap;
}
.${cls.deleteConfirmBtnYes} {
background-color: var(${v.DELETE_BTN_BG}) !important;
color: var(${v.DELETE_BTN_TEXT}) !important;
}
.${cls.deleteConfirmBtnYes}:hover {
background-color: var(${v.DELETE_BTN_HOVER_BG}) !important;
color: var(${v.DELETE_BTN_HOVER_TEXT}) !important;
filter: brightness(0.85);
}

/* Content Layout */
.${cls.modalContent} {
display: flex;
flex-direction: column;
gap: 12px;
height: 60vh;
min-height: 200px;
overflow: hidden;
}
.${cls.scrollableArea} {
flex-grow: 1;
overflow-y: auto;
padding: 4px 8px 4px 4px;
display: flex;
flex-direction: column;
gap: 12px;
border: 1px solid var(${v.BORDER_DEFAULT});
border-radius: 4px;
background: var(${v.INPUT_BG});
transition: opacity 0.2s;
}
.${cls.scrollableArea}.is-disabled {
pointer-events: none;
opacity: 0.5;
}

/* Text Item (Drag & Drop) */
.${cls.textItem} {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 4px;
border-radius: 4px;
border-top: 2px solid transparent;
border-bottom: 2px solid transparent;
transition: background-color 0.2s, border-color 0.1s;
}
.${cls.textItem}.dragging {
opacity: 0.4;
background-color: rgb(255 255 255 / 0.1);
}
.${cls.textItem}.drag-over-top {
border-top: 2px solid var(${v.DND_INDICATOR});
}
.${cls.textItem}.drag-over-bottom {
border-bottom: 2px solid var(${v.DND_INDICATOR});
}
.${cls.dragHandle} {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
flex-shrink: 0;
align-self: center;
cursor: grab;
color: var(${v.TEXT_SECONDARY});
opacity: 0.6;
}
.${cls.dragHandle}:hover {
opacity: 1;
}
.${cls.dragHandle}:active {
cursor: grabbing;
}
.${cls.textItem} textarea {
flex-grow: 1;
resize: none;
min-height: 80px;
max-height: 250px;
overflow-y: auto;
font-family: monospace;
}
.${cls.textItem}.dragging textarea {
pointer-events: none;
}

/* Item Controls */
.${cls.itemControls} {
display: flex;
flex-direction: column;
gap: 4px;
}
.${common.modalButton}.${cls.moveBtn} {
line-height: 1;
min-width: 24px;
padding: 4px;
height: 24px;
width: 24px;
}
.${common.modalButton}.${cls.deleteBtn} {
line-height: 1;
min-width: 24px;
padding: 4px;
height: 24px;
width: 24px;
font-size: 16px;
color: var(${v.TEXT_DANGER});
}

/* Validation & Common Overrides within this modal */
.is-invalid {
border-color: var(${v.TEXT_DANGER}) !important;
}

/* Generic Input Styles for this Modal */
.${StyleDefinitions.MODAL_CLASSES.box} input,
.${StyleDefinitions.MODAL_CLASSES.box} select,
.${StyleDefinitions.MODAL_CLASSES.box} textarea {
width: 100%;
box-sizing: border-box;
background: var(${v.INPUT_BG});
color: var(${v.TEXT_PRIMARY});
border: 1px solid var(${v.BORDER_DEFAULT});
border-radius: 4px;
padding: 4px 6px;
}
`;
      return { key, classes, vars: {}, generator: cssGenerator };
    }

    static getJsonModal() {
      const key = 'json-modal';
      const prefix = `${APPID}-json`;
      const classes = {
        modalRoot: `${prefix}-root`,
        statusContainer: `${prefix}-status-container`,
        content: `${prefix}-content`,
        editor: `${prefix}-editor`,
        statusRow: `${prefix}-status-row`,
        msg: `${prefix}-modal-msg`,
        sizeInfo: `${prefix}-modal-size-info`,
      };
      const v = StyleDefinitions.VARS;
      const common = StyleDefinitions.COMMON_CLASSES;
      const modalClasses = StyleDefinitions.MODAL_CLASSES;

      const cssGenerator = (cls) => `
/* Footer Layout Adjustments - Scoped to JSON Modal */
/* Hide footer message only when it does NOT have the conflict warning class */
.${cls.modalRoot} .${modalClasses.footerMessage}:not(.${common.conflictText}) {
display: none !important;
}

/* Allow wrapping in footer to prevent overflow when warning message is displayed */
.${cls.modalRoot} .${modalClasses.footer} {
flex-wrap: wrap;
}

.${cls.modalRoot} .${modalClasses.buttonGroup} {
width: 100%;
}

/* Utility classes override for this modal context */
.${common.modalButton}.${common.pushRightBtn} {
margin-left: auto !important;
}
.${common.primaryBtn} {
background-color: #1a73e8 !important;
color: #ffffff !important;
border: 1px solid transparent !important;
}
.${common.primaryBtn}:hover {
background-color: #1557b0 !important;
}

.${cls.content} {
display: flex;
flex-direction: column;
gap: 6px;
min-width: 0;
width: 100%;
}
.${cls.modalRoot} .${cls.editor} {
width: 100% !important;
height: 200px;
min-width: 0 !important;
max-width: 100%;
resize: none;
box-sizing: border-box !important;
margin: 0 !important;
font-family: monospace;
font-size: 13px;
border: 1px solid var(${v.BORDER_DEFAULT});
background: var(${v.INPUT_BG});
color: var(${v.TEXT_PRIMARY});
padding: 6px;
border-radius: 4px;
}
.${cls.statusRow} {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 8px;
}
.${cls.msg} {
color: var(${v.TEXT_DANGER});
font-size: 0.9em;
flex: 1;
min-height: 1.2em;
word-break: break-word;
}
.${cls.sizeInfo} {
color: var(${v.TEXT_SECONDARY});
font-size: 0.85em;
white-space: normal;
word-break: break-all;
text-align: right;
margin-top: 2px;
flex-shrink: 0;
}
`;
      return { key, classes, vars: {}, generator: cssGenerator };
    }

    static getShortcutModal() {
      const key = 'shortcut-modal';
      const prefix = `${APPID}-shortcut`;
      const classes = {
        grid: `${prefix}-grid`,
        keyGroup: `${prefix}-key-group`,
        key: `${prefix}-key`,
        desc: `${prefix}-desc`,
        sectionHeader: `${prefix}-section-header`,
      };
      const v = StyleDefinitions.VARS;

      const cssGenerator = (cls) => `
.${cls.grid} {
display: grid;
grid-template-columns: max-content 1fr;
gap: 8px 16px;
align-items: baseline;
padding: 4px;
}
.${cls.sectionHeader} {
grid-column: 1 / -1;
font-weight: bold;
color: var(${v.TEXT_SECONDARY});
border-bottom: 1px solid var(${v.BORDER_LIGHT});
margin-top: 12px;
margin-bottom: 4px;
padding-bottom: 2px;
font-size: 0.9em;
}
.${cls.sectionHeader}:first-child {
margin-top: 0;
}
.${cls.keyGroup} {
text-align: right;
white-space: nowrap;
}
.${cls.key} {
display: inline-block;
padding: 2px 6px;
font-family: monospace;
font-size: 0.9em;
line-height: 1.2;
color: var(${v.TEXT_PRIMARY});
background-color: var(${v.BTN_BG});
border: 1px solid var(${v.BORDER_DEFAULT});
border-radius: 4px;
box-shadow: 0 1px 1px rgb(0 0 0 / 0.1);
min-width: 1.2em;
text-align: center;
}
.${cls.desc} {
color: var(${v.TEXT_PRIMARY});
font-size: 0.95em;
}
`;
      return { key, classes, vars: {}, generator: cssGenerator };
    }
  }

  /**
   * @class StyleManager
   * @description Centralizes the creation, injection, and management of CSS style elements.
   */
  class StyleManager {
    static _handles = new Map();

    /**
     * @param {string} id The ID of the style element.
     * @param {string} cssContent The CSS content to inject.
     */
    static _inject(id, cssContent) {
      let style = document.getElementById(id);
      if (!style) {
        const newStyle = document.createElement('style');
        newStyle.id = id;
        style = newStyle;
        const target = document.head || document.documentElement;
        if (target) {
          target.appendChild(style);
        }
      }
      if (style) {
        style.textContent = cssContent;
      }
    }

    /**
     * Requests a style handle for the given definition provider.
     * @param {() => object} defProvider Function that returns the style definition.
     * @returns {{id: string, prefix: string, classes: object, vars: object}} The style handle.
     */
    static request(defProvider) {
      if (!this._handles.has(defProvider)) {
        const def = defProvider();
        const id = `${APPID}-style-${def.key}`;
        const prefix = `${APPID}-${def.key}`;
        const cssContent = def.generator(def.classes);

        this._inject(id, cssContent);
        this._handles.set(defProvider, { id, prefix, classes: def.classes, vars: def.vars });
      }
      return this._handles.get(defProvider);
    }

    /**
     * Injects platform-specific CSS variables into the root element.
     * @param {string} platformId
     */
    static injectPlatformVariables(platformId) {
      const css = StyleDefinitions.getPlatformVariables(platformId);
      if (css) {
        this._inject(`${APPID}-platform-vars`, `body { ${css} }`);
      }
    }
  }

  // =================================================================================
  // SECTION: Configuration Schema
  // Description: Defines the structure, validation rules, and UI metadata for settings.
  //              Used for data validation and procedural UI generation.
  // =================================================================================

  const CONFIG_SCHEMA = {
    options: {
      trigger_mode: {
        type: 'select',
        default: 'click',
        options: [
          { value: 'click', label: 'Click' },
          { value: 'hover', label: 'Hover' },
        ],
        ui: { label: 'Trigger Mode', title: 'Choose how to open the text list.' },
      },
      enable_shortcut: {
        type: 'toggle',
        default: true,
        ui: { label: 'Enable Shortcut (Alt+Q)', title: 'Enables the keyboard shortcut (Alt+Q) to toggle the Text List.' },
      },
      insertion_position: {
        type: 'select',
        default: 'cursor',
        options: [
          { value: 'start', label: 'Start' },
          { value: 'cursor', label: 'Cursor' },
          { value: 'end', label: 'End' },
        ],
        ui: { label: 'Insertion position', title: 'Determines where the text is inserted in the input field.' },
      },
      insert_before_newline: {
        type: 'toggle',
        default: false,
        ui: { label: 'Insert newline before text', title: 'Automatically add a newline before the pasted text.' },
      },
      insert_after_newline: {
        type: 'toggle',
        default: false,
        ui: { label: 'Insert newline after text', title: 'Automatically add a newline after the pasted text.' },
      },
    },
    developer: {
      logger_level: {
        type: 'select',
        default: 'log',
        options: ['error', 'warn', 'info', 'log', 'debug'],
        ui: { label: 'Log Level', title: 'Developer console log level.' },
      },
    },
  };

  const DEFAULT_CONFIG = {
    options: {
      enable_shortcut: true,
      insert_before_newline: false,
      insert_after_newline: false,
      insertion_position: 'cursor', // 'start', 'cursor', 'end'
      trigger_mode: 'click', // 'click', 'hover'
      activeProfileName: 'Default',
    },
    developer: {
      logger_level: 'log', // 'error', 'warn', 'info', 'log', 'debug'
    },
    texts: [
      {
        name: 'Default',
        categories: [
          {
            name: 'Structured',
            items: ['Explain this step by step.', 'Summarize this using bullet points.', 'Provide the answer in a table format.'],
          },
          {
            name: 'Refine',
            items: ['Can you clarify this point with a concrete example?', 'Rephrase this in a more concise and technical manner.', 'List the assumptions you are making in this explanation.'],
          },
          {
            name: 'Coding',
            items: ['Show a minimal reproducible example.', 'Explain this from a performance and maintainability perspective.', 'Point out potential edge cases or pitfalls.'],
          },
          {
            name: 'Twist',
            items: ['Explain this as if you were teaching a beginner.', 'Explain this to an expert in one paragraph.', 'Give an alternative perspective or unconventional approach.'],
          },
          {
            name: 'Image-gen',
            items: [
              'Based on all of our previous conversations, generate an image of me as you imagine. Make it super-realistic. Please feel free to fill in any missing information with your own imagination. Do not ask follow-up questions; generate the image immediately.',
              'Based on all of our previous conversations, generate an image of my ideal partner (opposite sex) as you imagine. Make it super-realistic. Please feel free to fill in any missing information with your own imagination. Do not ask follow-up questions; generate the image immediately.',
              'Based on all of our previous conversations, generate an image of a person who is the exact opposite of my ideal partner. Make it super-realistic. Please feel free to fill in any missing information with your own imagination. Do not ask follow-up questions; generate the image immediately.',
            ],
          },
        ],
      },
    ],
  };

  // =================================================================================
  // SECTION: Platform-Specific Adapter
  // Description: Centralizes all platform-specific logic, such as selectors and
  //              DOM manipulation strategies.
  // =================================================================================

  const PlatformAdapters = {
    General: {
      getPlatformDetails() {
        const { host } = location;
        // ChatGPT
        if (host.includes(CONSTANTS.PLATFORM.CHATGPT.HOST)) {
          return {
            platformId: CONSTANTS.PLATFORM.CHATGPT.ID,
            selectors: CONSTANTS.SELECTORS.chatgpt,
          };
        }
        // Gemini
        if (host.includes(CONSTANTS.PLATFORM.GEMINI.HOST)) {
          return {
            platformId: CONSTANTS.PLATFORM.GEMINI.ID,
            selectors: CONSTANTS.SELECTORS.gemini,
          };
        }
        // invalid
        return null;
      },

      /**
       * Checks if the current page URL is on the exclusion list for this platform.
       * @returns {boolean} True if the page should be excluded, otherwise false.
       */
      isExcludedPage() {
        const platform = this.getPlatformDetails();
        if (!platform) return false;

        const exclusions = CONSTANTS.URL_EXCLUSIONS[platform.platformId] || [];
        const pathname = window.location.pathname;

        return exclusions.some((pattern) => pattern.test(pathname));
      },

      /**
       * Finds the editor element and delegates the text insertion task to the EditorController.
       * @param {string} text The text to insert.
       * @param {object} options The insertion options.
       */
      insertText(text, options = {}) {
        const platform = this.getPlatformDetails();
        if (!platform) {
          Logger.error('PLATFORM', LOG_STYLES.RED, 'Platform details not found.');
          return;
        }

        // Use INPUT_TARGET for text insertion logic
        const editor = document.querySelector(platform.selectors.INPUT_TARGET);
        if (!editor || !(editor instanceof HTMLElement)) {
          Logger.error('DOM ERROR', LOG_STYLES.RED, 'Input element not found via selector:', platform.selectors.INPUT_TARGET);
          return;
        }

        // Delegate the complex insertion logic to the specialized controller.
        EditorController.insertText(text, editor, options);
      },
    },

    Editor: {
      /**
       * Builds the DOM structure for text insertion based on platform specifics.
       * @param {string} text The text to insert.
       * @returns {{ fragment: Node, lastNode: Node }}
       */
      buildInsertionDOM(text) {
        const platform = PlatformAdapters.General.getPlatformDetails();

        // ChatGPT requires <br> for multiline text
        if (platform?.platformId === CONSTANTS.PLATFORM.CHATGPT.ID) {
          const fragment = document.createDocumentFragment();
          const lines = text.split('\n');
          let lastNode = null;

          lines.forEach((line, index) => {
            if (line.length > 0) {
              const textNode = document.createTextNode(line);
              fragment.appendChild(textNode);
              lastNode = textNode;
            }

            // Add <br> for every newline, except after the last line
            if (index < lines.length - 1) {
              const br = document.createElement('br');
              fragment.appendChild(br);
              lastNode = br;
            }
          });

          // Fallback for completely empty string
          if (!lastNode) {
            lastNode = document.createTextNode('');
            fragment.appendChild(lastNode);
          }

          return { fragment, lastNode };
        }

        // Default / Gemini (Quill) relies on single TextNode with \n
        const textNode = document.createTextNode(text);
        return { fragment: textNode, lastNode: textNode };
      },

      /**
       * Creates a closure to restore the caret position after DOM normalization.
       * Returns null if the current platform doesn't need restoration.
       * @param {HTMLElement} editor
       * @param {Range} range The range before insertion
       * @returns {(() => void) | null}
       */
      createCaretRestorer(editor, range) {
        const platform = PlatformAdapters.General.getPlatformDetails();
        // Only Gemini requires this workaround due to its aggressive DOM normalization
        if (platform?.platformId === CONSTANTS.PLATFORM.GEMINI.ID) {
          const offset = this._getCaretOffset(editor, range);
          if (typeof offset !== 'number') return null;

          return () => {
            // Double rAF to ensure we run after Gemini's internal rendering cycle
            requestAnimationFrame(() => {
              requestAnimationFrame(() => {
                this._setCaretOffset(editor, offset);
              });
            });
          };
        }
        return null;
      },

      /**
       * @private
       * Calculates the logical character offset of the range end.
       */
      _getCaretOffset(editor, range) {
        try {
          const preRange = document.createRange();
          preRange.selectNodeContents(editor);
          preRange.setEnd(range.endContainer, range.endOffset);
          return preRange.toString().length;
        } catch {
          return null;
        }
      },

      /**
       * @private
       * Restores the caret to the specific character offset using a TreeWalker.
       */
      _setCaretOffset(editor, offset) {
        try {
          const selection = window.getSelection();
          if (!selection) return;

          const walker = document.createTreeWalker(editor, NodeFilter.SHOW_TEXT);
          let node = walker.nextNode();
          let remaining = offset;

          while (node) {
            const textLen = node.textContent ? node.textContent.length : 0;
            if (remaining <= textLen) {
              const r = document.createRange();
              r.setStart(node, remaining);
              r.collapse(true);
              selection.removeAllRanges();
              selection.addRange(r);
              return;
            }
            remaining -= textLen;
            node = walker.nextNode();
          }

          // Fallback: place caret at the end
          const r = document.createRange();
          r.selectNodeContents(editor);
          r.collapse(false);
          selection.removeAllRanges();
          selection.addRange(r);
        } catch {
          // no-op
        }
      },
    },

    UI: {
      repositionInsertButton(insertButton) {
        if (!insertButton?.element) return;

        withLayoutCycle({
          measure: () => {
            // Read phase
            const platform = PlatformAdapters.General.getPlatformDetails();
            if (!platform) return { anchor: null };

            const anchor = document.querySelector(platform.selectors.INSERTION_ANCHOR);
            if (!(anchor instanceof HTMLElement)) return { anchor: null };

            // Retrieve configuration for positioning
            // Configuration now handled via CSS classes, but we verify method logic here
            const insertMethod = platform.selectors.INSERT_METHOD;

            if (!insertMethod) {
              Logger.warn('LAYOUT', LOG_STYLES.YELLOW, 'INSERT_METHOD is not defined for this platform.');
            }

            // Ghost Detection Logic
            const existingBtn = document.getElementById(insertButton.element.id);
            const isGhost = existingBtn && existingBtn !== insertButton.element;

            // Check if button is already inside
            const isInside = !isGhost && anchor.contains(insertButton.element);

            // Check specific position validity
            let isAtCorrectPosition = isInside;
            if (isInside) {
              if (insertMethod === 'append') {
                isAtCorrectPosition = anchor.lastElementChild === insertButton.element;
              } else if (insertMethod === 'prepend') {
                isAtCorrectPosition = anchor.firstElementChild === insertButton.element;
              }
            }

            return {
              anchor,
              isGhost,
              existingBtn,
              shouldInject: !isAtCorrectPosition,
              insertMethod,
            };
          },
          mutate: (measured) => {
            // Write phase

            // Guard: Component might be destroyed during async wait
            if (!insertButton || !insertButton.element) {
              return;
            }

            if (!measured || !measured.anchor) {
              insertButton.element.style.display = 'none';
              return;
            }

            const { anchor, isGhost, existingBtn, shouldInject, insertMethod } = measured;

            if (!anchor.isConnected) {
              Logger.debug('UI RETRY', LOG_STYLES.CYAN, 'Anchor detached. Retrying reposition.');
              EventBus.publish(EVENTS.UI_REPOSITION);
              return;
            }

            // 1. Ghost Buster
            if (isGhost && existingBtn) {
              Logger.warn('GHOST BUSTER', '', 'Detected non-functional ghost button. Removing...');
              existingBtn.remove();
            }

            // 2. Injection
            if (shouldInject || isGhost) {
              // Add marker class to apply flex/relative styles defined in CSS
              // Retrieve class name dynamically from StyleDefinitions
              const styledClass = StyleDefinitions.getInsertButton().classes.anchorStyled;
              if (!anchor.classList.contains(styledClass)) {
                anchor.classList.add(styledClass);
              }

              // Insert based on explicit method
              if (insertMethod === 'append') {
                anchor.appendChild(insertButton.element);
              } else if (insertMethod === 'prepend') {
                anchor.prepend(insertButton.element);
              }
              Logger.debug('UI INJECTION', LOG_STYLES.GREEN, `Button injected into Anchor (${insertMethod}).`);
            }

            insertButton.element.style.display = '';
          },
        });
      },
    },

    Observer: {
      /**
       * Returns an array of platform-specific observer initialization functions.
       * @returns {Function[]} An array of functions to be called by ObserverManager.
       */
      getInitializers() {
        return [this.triggerInitialPlacement];
      },

      /**
       * @private
       * @description triggers the initial button placement when the anchor element is detected.
       * @returns {() => void} Cleanup function to remove the listener and hide the UI.
       */
      triggerInitialPlacement() {
        const platform = PlatformAdapters.General.getPlatformDetails();
        if (!platform) return () => {};

        const selector = platform.selectors.INSERTION_ANCHOR;

        const handleAnchorAppearance = () => {
          EventBus.publish(EVENTS.UI_REPOSITION);
        };

        sentinel.on(selector, handleAnchorAppearance);

        // Initial check in case the element is already present
        const initialInputArea = document.querySelector(selector);
        if (initialInputArea instanceof HTMLElement) {
          handleAnchorAppearance();
        }

        return () => {
          sentinel.off(selector, handleAnchorAppearance);

          // Ensure the button is hidden when the observer is cleaned up (e.g., on excluded pages)
          const btnId = `${CONSTANTS.ID_PREFIX}insert-btn`;
          const btn = document.getElementById(btnId);
          if (btn) {
            btn.style.display = 'none';
          }
        };
      },
    },
  };

  // =================================================================================
  // SECTION: Editor Controller
  // Description: Handles all direct DOM manipulation and logic for rich text editors.
  // =================================================================================

  class EditorController {
    /**
     * Inserts text for rich text editors (ChatGPT/Gemini) using Range API and InputEvent.
     * @param {string} text The text to insert.
     * @param {HTMLElement} editor The target editor element.
     * @param {object} options The insertion options.
     */
    static insertText(text, editor, options) {
      // 1. Snapshot current state
      const isFocused = document.activeElement === editor;
      const selection = window.getSelection();
      let range;

      // 2. Determine target Range
      if (options.insertion_position === 'start') {
        range = document.createRange();
        range.selectNodeContents(editor);
        range.collapse(true);
      } else if (options.insertion_position === 'end') {
        range = document.createRange();
        range.selectNodeContents(editor);
        range.collapse(false);
      } else {
        // 'cursor' (default)
        if (isFocused && selection.rangeCount > 0 && editor.contains(selection.anchorNode)) {
          range = selection.getRangeAt(0).cloneRange();
        } else if (options._savedRange) {
          range = options._savedRange.cloneRange();
        } else {
          range = document.createRange();
          range.selectNodeContents(editor);

          // Check if editor has content (text OR non-text elements like images/widgets)
          const hasText = editor.textContent.trim().length > 0;
          // Detect images, media, or non-editable widgets (common in rich editors)
          const hasMedia = editor.querySelector('img, picture, video, audio, [contenteditable="false"]');

          // If content exists, append to end. Otherwise, select all to replace ghost tags.
          if (hasText || hasMedia) {
            range.collapse(false);
          }
        }
      }

      // 3. Prepare Editor State
      if (!isFocused) {
        editor.focus();
      }

      selection.removeAllRanges();
      selection.addRange(range);

      // 4. Process text
      let textToInsert = text;
      if (options.insert_before_newline) textToInsert = '\n' + textToInsert;
      if (options.insert_after_newline) textToInsert += '\n';

      // 5. Delete selected text if any
      if (!range.collapsed) {
        range.deleteContents();
      }

      // 6. Direct DOM Insertion
      const { fragment, lastNode } = PlatformAdapters.Editor.buildInsertionDOM(textToInsert);
      range.insertNode(fragment);

      // 7. Update Cursor (After inserted text)
      range.setStartAfter(lastNode);
      range.setEndAfter(lastNode);
      selection.removeAllRanges();
      selection.addRange(range);

      // [Platform Hook] Prepare caret restoration if needed
      // Delegates platform-specific logic to the adapter
      const caretRestorer = PlatformAdapters.Editor.createCaretRestorer(editor, range);

      // 8. Notify Framework
      try {
        const inputEvent = new InputEvent('input', {
          bubbles: true,
          cancelable: true,
          inputType: 'insertText',
          data: textToInsert,
          composed: true,
        });
        editor.dispatchEvent(inputEvent);
      } catch {
        editor.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
      }

      editor.dispatchEvent(new Event('change', { bubbles: true, composed: true }));

      // [Platform Hook] Execute caret restoration
      if (caretRestorer) {
        caretRestorer();
      }
    }
  }

  // =================================================================================
  // SECTION: Event-Driven Architecture (Pub/Sub)
  // Description: A event bus for decoupled communication between classes.
  // =================================================================================

  const EventBus = {
    events: {},
    uiWorkQueue: [],
    isUiWorkScheduled: false,
    _logAggregation: {},
    // prettier-ignore
    _aggregatedEvents: new Set([
      EVENTS.UI_REPOSITION,
      EVENTS.NAVIGATION,
    ]),
    _aggregationDelay: 500, // ms

    /**
     * Subscribes a listener to an event using a unique key.
     * If a subscription with the same event and key already exists, it will be overwritten.
     * @param {string} event The event name.
     * @param {Function} listener The callback function.
     * @param {string} key A unique key for this subscription (e.g., 'ClassName.methodName').
     */
    subscribe(event, listener, key) {
      if (!key) {
        Logger.error('', '', 'EventBus.subscribe requires a unique key.');
        return;
      }
      if (!this.events[event]) {
        this.events[event] = new Map();
      }
      this.events[event].set(key, listener);
    },
    /**
     * Subscribes a listener that will be automatically unsubscribed after one execution.
     * @param {string} event The event name.
     * @param {Function} listener The callback function.
     * @param {string} key A unique key for this subscription.
     */
    once(event, listener, key) {
      if (!key) {
        Logger.error('', '', 'EventBus.once requires a unique key.');
        return;
      }
      const onceListener = (...args) => {
        this.unsubscribe(event, key);
        listener(...args);
      };
      this.subscribe(event, onceListener, key);
    },
    /**
     * Unsubscribes a listener from an event using its unique key.
     * @param {string} event The event name.
     * @param {string} key The unique key used during subscription.
     */
    unsubscribe(event, key) {
      if (!this.events[event] || !key) {
        return;
      }
      this.events[event].delete(key);
      if (this.events[event].size === 0) {
        delete this.events[event];
      }
    },
    /**
     * Publishes an event, calling all subscribed listeners with the provided data.
     * @param {string} event The event name.
     * @param {...any} args The data to pass to the listeners.
     */
    publish(event, ...args) {
      if (!this.events[event]) {
        return;
      }

      if (Logger.levels[Logger.level] >= Logger.levels.debug) {
        // --- Aggregation logic START ---
        if (this._aggregatedEvents.has(event)) {
          if (!this._logAggregation[event]) {
            this._logAggregation[event] = { timer: null, count: 0 };
          }
          const aggregation = this._logAggregation[event];
          aggregation.count++;

          clearTimeout(aggregation.timer);
          aggregation.timer = setTimeout(() => {
            const finalCount = this._logAggregation[event]?.count || 0;
            if (finalCount > 0) {
              Logger.debug('EventBus', LOG_STYLES.PURPLE, `Event Published: ${event} (x${finalCount})`);
            }
            delete this._logAggregation[event];
          }, this._aggregationDelay);

          // Execute subscribers for the aggregated event, but without the verbose individual logs.
          [...this.events[event].values()].forEach((listener) => {
            try {
              listener(...args);
            } catch (e) {
              Logger.error('', '', `EventBus error in listener for event "${event}":`, e);
            }
          });
          return; // End execution here for aggregated events in debug mode.
        }
        // --- Aggregation logic END ---

        // In debug mode, provide detailed logging for NON-aggregated events.
        const subscriberKeys = [...this.events[event].keys()];

        Logger.groupCollapsed('EventBus', LOG_STYLES.PURPLE, `Event Published: ${event}`);

        if (args.length > 0) {
          console.log('  - Payload:', ...args);
        } else {
          console.log('  - Payload: (No data)');
        }

        // Displaying subscribers helps in understanding the event's impact.
        if (subscriberKeys.length > 0) {
          console.log('  - Subscribers:\n' + subscriberKeys.map((key) => `    > ${key}`).join('\n'));
        } else {
          console.log('  - Subscribers: (None)');
        }

        // Iterate with keys for better logging
        this.events[event].forEach((listener, key) => {
          try {
            // Log which specific subscriber is being executed
            Logger.debug('', LOG_STYLES.PURPLE, `-> Executing: ${key}`);
            listener(...args);
          } catch (e) {
            // Enhance error logging with the specific subscriber key
            Logger.error('LISTENER ERROR', LOG_STYLES.RED, `Listener "${key}" failed for event "${event}":`, e);
          }
        });

        Logger.groupEnd();
      } else {
        // Iterate over a copy of the values in case a listener unsubscribes itself.
        [...this.events[event].values()].forEach((listener) => {
          try {
            listener(...args);
          } catch (e) {
            Logger.error('LISTENER ERROR', LOG_STYLES.RED, `Listener failed for event "${event}":`, e);
          }
        });
      }
    },

    /**
     * Queues a function to be executed on the next animation frame.
     * Batches multiple UI updates into a single repaint cycle.
     * @param {Function} workFunction The function to execute.
     */
    queueUIWork(workFunction) {
      this.uiWorkQueue.push(workFunction);
      if (!this.isUiWorkScheduled) {
        this.isUiWorkScheduled = true;
        requestAnimationFrame(this._processUIWorkQueue.bind(this));
      }
    },

    /**
     * @private
     * Processes all functions in the UI work queue.
     */
    _processUIWorkQueue() {
      // Prevent modifications to the queue while processing.
      const queueToProcess = [...this.uiWorkQueue];
      this.uiWorkQueue.length = 0;

      for (const work of queueToProcess) {
        try {
          work();
        } catch (e) {
          Logger.error('UI QUEUE ERROR', LOG_STYLES.RED, 'Error in queued UI work:', e);
        }
      }
      this.isUiWorkScheduled = false;
    },
  };

  /**
   * Creates a unique, consistent event subscription key for EventBus.
   * @param {object} context The `this` context of the subscribing class instance.
   * @param {string} eventName The full event name from the EVENTS constant.
   * @returns {string} A key in the format 'ClassName.purpose'.
   */
  function createEventKey(context, eventName) {
    // Extract a meaningful 'purpose' from the event name
    const parts = eventName.split(':');
    const purpose = parts.length > 1 ? parts.slice(1).join('_') : parts[0];

    let contextName = 'UnknownContext';
    if (context && context.constructor && context.constructor.name) {
      contextName = context.constructor.name;
    }
    return `${contextName}.${purpose}`;
  }

  // =================================================================================
  // SECTION: Utility Functions
  // =================================================================================

  /**
   * Schedules a function to run when the browser is idle.
   * Returns a cancel function to abort the scheduled task.
   * In environments without `requestIdleCallback`, this runs asynchronously immediately (1ms delay) to prevent blocking,
   * effectively ignoring the `timeout` constraint by satisfying it instantly.
   * @param {(deadline: IdleDeadline) => void} callback The function to execute.
   * @param {number} timeout The maximum time to wait for idle before forcing execution.
   * @returns {() => void} A function to cancel the scheduled task.
   */
  function runWhenIdle(callback, timeout) {
    if ('requestIdleCallback' in window) {
      const id = window.requestIdleCallback(callback, { timeout });
      return () => window.cancelIdleCallback(id);
    } else {
      // Fallback: Execute almost immediately (1ms) to avoid blocking.
      // This satisfies the "run by timeout" contract trivially since 1ms < timeout.
      const id = setTimeout(() => {
        // Provide a minimal IdleDeadline-like object.
        // timeRemaining() returns 50ms to simulate a fresh frame.
        callback({
          didTimeout: false,
          timeRemaining: () => 50,
        });
      }, 1);

      return () => clearTimeout(id);
    }
  }

  /**
   * @param {Function} func
   * @param {number} delay
   * @param {boolean} useIdle
   * @returns {((...args: any[]) => void) & { cancel: () => void }}
   */
  function debounce(func, delay, useIdle) {
    let timerId = null;
    let cancelIdle = null;

    const cancel = () => {
      if (timerId !== null) {
        clearTimeout(timerId);
        timerId = null;
      }
      if (cancelIdle) {
        cancelIdle();
        cancelIdle = null;
      }
    };

    const debounced = function (...args) {
      cancel();
      timerId = setTimeout(() => {
        timerId = null; // Timer finished
        if (useIdle) {
          // Calculate idle timeout based on delay: clamp(delay * 4, 200, 2000)
          // This ensures short delays don't wait too long, while long delays are capped.
          const idleTimeout = Math.min(Math.max(delay * 4, 200), 2000);

          // Schedule idle callback and store the cancel function
          // Explicitly receive 'deadline' to match runWhenIdle signature
          cancelIdle = runWhenIdle((deadline) => {
            cancelIdle = null; // Idle callback finished
            func.apply(this, args);
          }, idleTimeout);
        } else {
          func.apply(this, args);
        }
      }, delay);
    };

    debounced.cancel = cancel;
    return debounced;
  }

  /**
   * Helper function to check if an item is a non-array object.
   * @param {unknown} item The item to check.
   * @returns {item is Record<string, any>}
   */
  function isObject(item) {
    return !!(item && typeof item === 'object' && !Array.isArray(item));
  }

  /**
   * Creates a deep copy of a JSON-serializable object.
   * @template T
   * @param {T} obj The object to clone.
   * @returns {T} The deep copy of the object.
   */
  function deepClone(obj) {
    try {
      return structuredClone(obj);
    } catch (e) {
      Logger.error('CLONE FAILED', '', 'deepClone failed. Data contains non-clonable items.', e);
      throw e;
    }
  }

  /**
   * Recursively resolves the configuration by overlaying source properties onto the target object.
   * The target object is mutated. This handles recursive updates for nested objects but overwrites arrays/primitives.
   *
   * [MERGE BEHAVIOR]
   * Keys present in 'source' but missing in 'target' are ignored.
   * The 'target' object acts as a schema; it must contain all valid keys.
   *
   * @param {object} target The target object (e.g., a deep copy of default config).
   * @param {object} source The source object (e.g., user config).
   * @returns {object} The mutated target object.
   */
  function resolveConfig(target, source) {
    for (const [key, sourceVal] of Object.entries(source)) {
      // Security: Prevent prototype pollution
      if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
        continue;
      }

      // Strict check: Ignore keys that do not exist in the target (default config).
      if (!Object.hasOwn(target, key)) {
        continue;
      }

      const targetVal = target[key];

      if (isObject(sourceVal) && isObject(targetVal)) {
        // If both are objects, recurse
        resolveConfig(targetVal, sourceVal);
      } else if (typeof sourceVal !== 'undefined') {
        // Otherwise, overwrite or set the value from the source
        target[key] = sourceVal;
      }
    }
    return target;
  }

  /**
   * Proposes a unique name by appending a suffix if the base name already exists in a given set.
   * Supports both an array of strings and an array of objects with a 'name' property.
   * @param {string} baseName The initial name to check.
   * @param {Set<string>|Array<string>|Array<object>} existingItems Collection of existing names or objects.
   * @returns {string} A unique name.
   */
  function proposeUniqueName(baseName, existingItems) {
    const lowerNames = new Set();
    const items = Array.isArray(existingItems) ? existingItems : Array.from(existingItems);

    // Normalize input to a set of lowercase strings
    items.forEach((item) => {
      if (typeof item === 'string') {
        lowerNames.add(item.toLowerCase());
      } else if (item && typeof item === 'object' && typeof item.name === 'string') {
        lowerNames.add(item.name.toLowerCase());
      }
    });

    if (!lowerNames.has(baseName.trim().toLowerCase())) {
      return baseName;
    }

    let proposedName = `${baseName} Copy`;
    if (!lowerNames.has(proposedName.trim().toLowerCase())) {
      return proposedName;
    }

    let counter = 2;
    while (true) {
      proposedName = `${baseName} Copy ${counter}`;
      if (!lowerNames.has(proposedName.trim().toLowerCase())) {
        return proposedName;
      }
      counter++;
    }
  }

  /**
   * @description A utility to prevent layout thrashing by separating DOM reads (measure)
   * from DOM writes (mutate). The mutate function is executed in the next animation frame.
   * @param {{
   * measure: () => T,
   * mutate: (data: T) => void
   * }} param0 - An object containing the measure and mutate functions.
   * @returns {Promise<void>} A promise that resolves after the mutate function has completed.
   * @template T
   */
  function withLayoutCycle({ measure, mutate }) {
    return new Promise((resolve, reject) => {
      let measuredData;

      // Phase 1: Synchronously read all required layout properties from the DOM.
      try {
        measuredData = measure();
      } catch (e) {
        Logger.error('LAYOUT ERROR', LOG_STYLES.RED, 'Error during measure phase:', e);
        reject(e);
        return;
      }

      // Phase 2: Schedule the DOM mutations to run in the next animation frame.
      requestAnimationFrame(() => {
        try {
          mutate(measuredData);
          resolve();
        } catch (e) {
          Logger.error('LAYOUT ERROR', LOG_STYLES.RED, 'Error during mutate phase:', e);
          reject(e);
        }
      });
    });
  }

  /**
   * @typedef {Node|string|number|boolean|null|undefined} HChild
   */
  /**
   * Creates a DOM element using a hyperscript-style syntax.
   * @param {string} tag - Tag name with optional ID/class (e.g., "div#app.container", "my-element").
   * @param {object | HChild | HChild[]} [propsOrChildren] - Attributes object or children.
   * @param {HChild | HChild[]} [children] - Children (if props are specified).
   * @returns {HTMLElement | SVGElement} The created DOM element.
   */
  function h(tag, propsOrChildren, children) {
    const SVG_NS = 'http://www.w3.org/2000/svg';
    const match = tag.match(/^([a-z0-9-]+)(#[\w-]+)?((\.[\w-]+)*)$/i);
    if (!match) throw new Error(`Invalid tag syntax: ${tag}`);

    const [, tagName, id, classList] = match;
    const isSVG = ['svg', 'circle', 'rect', 'path', 'g', 'line', 'text', 'use', 'defs', 'clipPath'].includes(tagName);
    const el = isSVG ? document.createElementNS(SVG_NS, tagName) : document.createElement(tagName);

    if (id) el.id = id.slice(1);
    if (classList) {
      const classes = classList.replaceAll('.', ' ').trim();
      if (classes) {
        el.classList.add(...classes.split(/\s+/));
      }
    }

    let props = {};
    let childrenArray;
    if (propsOrChildren && Object.prototype.toString.call(propsOrChildren) === '[object Object]') {
      props = propsOrChildren;
      childrenArray = children;
    } else {
      childrenArray = propsOrChildren;
    }

    // --- Start of Attribute/Property Handling ---
    const directProperties = new Set(['value', 'checked', 'selected', 'readOnly', 'disabled', 'multiple', 'textContent']);
    const urlAttributes = new Set(['href', 'src', 'action', 'formaction']);
    const safeProtocols = new Set(['https:', 'http:', 'mailto:', 'tel:', 'blob:', 'data:']);

    for (const [key, value] of Object.entries(props)) {
      // 0. Handle `ref` callback (highest priority after props parsing).
      if (key === 'ref' && typeof value === 'function') {
        value(el);
      }
      // 1. Security check for URL attributes.
      else if (urlAttributes.has(key)) {
        const url = String(value);
        try {
          const parsedUrl = new URL(url); // Throws if not an absolute URL.
          if (safeProtocols.has(parsedUrl.protocol)) {
            el.setAttribute(key, url);
          } else {
            el.setAttribute(key, '#');
            Logger.warn('UNSAFE URL', LOG_STYLES.YELLOW, `Blocked potentially unsafe protocol "${parsedUrl.protocol}" in attribute "${key}":`, url);
          }
        } catch {
          el.setAttribute(key, '#');
          Logger.warn('INVALID URL', LOG_STYLES.YELLOW, `Blocked invalid or relative URL in attribute "${key}":`, url);
        }
      }
      // 2. Direct property assignments.
      else if (directProperties.has(key)) {
        el[key] = value;
      }
      // 3. Other specialized handlers.
      else if (key === 'style' && typeof value === 'object') {
        Object.assign(el.style, value);
      } else if (key === 'dataset' && typeof value === 'object') {
        for (const [dataKey, dataVal] of Object.entries(value)) {
          el.dataset[dataKey] = dataVal;
        }
      } else if (key.startsWith('on')) {
        if (typeof value === 'function') {
          el.addEventListener(key.slice(2).toLowerCase(), value);
        }
      } else if (key === 'className') {
        const classes = String(value).trim();
        if (classes) {
          el.classList.add(...classes.split(/\s+/));
        }
      } else if (key.startsWith('aria-')) {
        el.setAttribute(key, String(value));
      }
      // 4. Default attribute handling.
      else if (value !== false && value !== null && typeof value !== 'undefined') {
        el.setAttribute(key, value === true ? '' : String(value));
      }
    }
    // --- End of Attribute/Property Handling ---

    const fragment = document.createDocumentFragment();
    /**
     * Appends a child node or text to the document fragment.
     * @param {HChild} child - The child to append.
     */
    function append(child) {
      if (child === null || child === false || typeof child === 'undefined') return;
      if (typeof child === 'string' || typeof child === 'number') {
        fragment.appendChild(document.createTextNode(String(child)));
      } else if (Array.isArray(child)) {
        child.forEach(append);
      } else if (child instanceof Node) {
        fragment.appendChild(child);
      } else {
        throw new Error('Unsupported child type');
      }
    }
    append(childrenArray);

    el.appendChild(fragment);

    if (el instanceof HTMLElement || el instanceof SVGElement) {
      return el;
    }
    throw new Error('Created element is not a valid HTMLElement or SVGElement');
  }

  /**
   * Recursively builds a DOM element from a definition object using the h() function.
   * @param {object} def The definition object for the element.
   * @returns {HTMLElement | SVGElement | null} The created DOM element.
   */
  function createIconFromDef(def) {
    if (!def) return null;
    const children = def.children ? def.children.map((child) => createIconFromDef(child)) : [];
    return h(def.tag, def.props, children);
  }

  /**
   * A helper function to safely retrieve a nested property from an object using a dot-notation string.
   * @param {object} obj The object to query.
   * @param {string} path The dot-separated path to the property.
   * @returns {any} The value of the property, or undefined if not found.
   */
  function getPropertyByPath(obj, path) {
    if (!obj || typeof path !== 'string') {
      return undefined;
    }
    return path.split('.').reduce((o, k) => (o === undefined || o === null ? undefined : o[k]), obj);
  }

  /**
   * Sets a nested property on an object using a dot-notation path.
   * @param {object} obj The object to modify.
   * @param {string} path The dot-separated path to the property.
   * @param {unknown} value The value to set.
   * @returns {boolean} True if successful, false if the path was invalid or blocked.
   */
  function setPropertyByPath(obj, path, value) {
    if (!obj || typeof obj !== 'object') {
      Logger.warn('', '', `setPropertyByPath: Target object is invalid (type: ${typeof obj}).`);
      return false;
    }
    if (!path) return false;
    const keys = path.split('.');
    let current = obj;

    for (let i = 0; i < keys.length - 1; i++) {
      const key = keys[i];

      if (!key) {
        Logger.warn('', '', `setPropertyByPath: Invalid empty key in path "${path}".`);
        return false;
      }

      // Security: Prevent prototype pollution
      if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
        Logger.warn('', '', `setPropertyByPath: Blocked forbidden key "${key}" in path "${path}".`);
        return false;
      }

      // If the property exists, validate that it is an object (and not null) to allow nesting.
      if (current[key] !== undefined && current[key] !== null) {
        if (typeof current[key] !== 'object') {
          Logger.warn('', '', `setPropertyByPath: Cannot set property "${keys[i + 1]}" on non-object value at "${keys.slice(0, i + 1).join('.')}" in path "${path}".`);
          return false;
        }
      } else {
        // Only create a new object if the property is null or undefined.
        // This prevents overwriting existing values (like arrays) with empty objects.
        current[key] = {};
      }

      current = current[key];
    }

    const lastKey = keys.at(-1);

    if (!lastKey) {
      Logger.warn('', '', `setPropertyByPath: Invalid empty key at end of path "${path}".`);
      return false;
    }

    // Security: Prevent prototype pollution on the last key
    if (lastKey === '__proto__' || lastKey === 'constructor' || lastKey === 'prototype') {
      Logger.warn('', '', `setPropertyByPath: Blocked forbidden key "${lastKey}" in path "${path}".`);
      return false;
    }

    current[lastKey] = value;
    return true;
  }

  // =================================================================================
  // SECTION: Base Manager
  // Description: Provides common lifecycle and event subscription management.
  // =================================================================================

  /**
   * @class BaseManager
   * @description Provides common lifecycle and event subscription management capabilities.
   * Implements the Template Method pattern for init/destroy cycles.
   */
  class BaseManager {
    constructor() {
      /** @type {Array<{event: string, key: string}>} */
      this.subscriptions = [];
      this.isInitialized = false;
      this.isDestroyed = false;
      /** @type {Promise<void>|null} */
      this._initPromise = null;
    }

    /**
     * Initializes the manager.
     * Prevents double initialization and supports async hooks.
     * @param {...any} args Arguments to pass to the hook method.
     * @returns {Promise<void>}
     */
    async init(...args) {
      if (this.isInitialized) return;

      // Guard against concurrent initialization
      if (this._initPromise) {
        await this._initPromise;
        return;
      }

      this.isDestroyed = false;

      this._initPromise = (async () => {
        await this._onInit(...args);
        // Check if destroyed during async init
        if (!this.isDestroyed) {
          this.isInitialized = true;
        }
      })();

      try {
        await this._initPromise;
      } finally {
        this._initPromise = null;
      }
    }

    /**
     * Destroys the manager and cleans up resources.
     * Idempotent: safe to call multiple times.
     */
    destroy() {
      if (this.isDestroyed) return;
      this.isDestroyed = true;
      this.isInitialized = false;

      // 1. Hook for subclass specific cleanup
      this._onDestroy();

      // 2. Unsubscribe from all events
      // Create a snapshot to safely iterate even if unsubscribing modifies the original array
      const subsToUnsubscribe = [...this.subscriptions];
      // Immediately clear the array to prevent re-entry or access during cleanup
      this.subscriptions = [];

      // Iterate in reverse order (LIFO) to respect dependencies
      for (let i = subsToUnsubscribe.length - 1; i >= 0; i--) {
        const { event, key } = subsToUnsubscribe[i];
        EventBus.unsubscribe(event, key);
      }
    }

    /**
     * Registers a platform-specific listener.
     * Exposes subscription capability to adapters safely.
     * @param {string} event
     * @param {Function} callback
     */
    registerPlatformListener(event, callback) {
      this._subscribe(event, callback);
    }

    /**
     * Registers a one-time platform-specific listener.
     * Exposes single subscription capability to adapters safely.
     * @param {string} event
     * @param {Function} callback
     */
    registerPlatformListenerOnce(event, callback) {
      this._subscribeOnce(event, callback);
    }

    /**
     * Hook method for initialization logic.
     * @protected
     * @param {...any} args
     */
    _onInit(...args) {
      // To be implemented by subclasses
    }

    /**
     * Hook method for cleanup logic.
     * @protected
     */
    _onDestroy() {
      // To be implemented by subclasses
    }

    /**
     * Helper to subscribe to EventBus and track the subscription for cleanup.
     * Appends the listener name and a unique suffix to the key to avoid conflicts.
     * @protected
     * @param {string} event
     * @param {Function} listener
     */
    _subscribe(event, listener) {
      const baseKey = createEventKey(this, event);
      // Use function name for debugging aid, fallback to 'anonymous'
      const listenerName = listener.name || 'anonymous';
      // Generate a short random suffix to guarantee uniqueness even for anonymous functions
      const uniqueSuffix = Math.random().toString(36).substring(2, 7);
      const key = `${baseKey}_${listenerName}_${uniqueSuffix}`;

      EventBus.subscribe(event, listener, key);
      this.subscriptions.push({ event, key });
    }

    /**
     * Helper to subscribe to EventBus once and track the subscription for cleanup.
     * Appends the listener name and a unique suffix to the key to avoid conflicts.
     * @protected
     * @param {string} event
     * @param {Function} listener
     */
    _subscribeOnce(event, listener) {
      const baseKey = createEventKey(this, event);
      // Use function name for debugging aid, fallback to 'anonymous'
      const listenerName = listener.name || 'anonymous';
      // Generate a short random suffix to guarantee uniqueness even for anonymous functions
      const uniqueSuffix = Math.random().toString(36).substring(2, 7);
      const key = `${baseKey}_${listenerName}_${uniqueSuffix}`;

      // Wrapper to cleanup the subscription record after firing
      const wrappedListener = (...args) => {
        this._removeSubscriptionRecord(key);
        listener(...args);
      };

      EventBus.once(event, wrappedListener, key);
      this.subscriptions.push({ event, key });
    }

    /**
     * Helper to unsubscribe from specific events dynamically.
     * @protected
     * @param {string} event The event name.
     * @param {string} [keyPrefix] Optional prefix to filter specific listeners (e.g. 'ClassName.purpose').
     */
    _unsubscribe(event, keyPrefix) {
      const fullKeyPrefix = keyPrefix || createEventKey(this, event);

      // Find matching subscriptions
      const toRemove = this.subscriptions.filter((sub) => sub.event === event && sub.key.startsWith(fullKeyPrefix));

      // Unsubscribe and remove from list
      toRemove.forEach((sub) => {
        EventBus.unsubscribe(sub.event, sub.key);
        this._removeSubscriptionRecord(sub.key);
      });
    }

    /**
     * Internal helper to remove a subscription record from the array by key.
     * @private
     * @param {string} key
     */
    _removeSubscriptionRecord(key) {
      this.subscriptions = this.subscriptions.filter((sub) => sub.key !== key);
    }
  }

  // =================================================================================
  // SECTION: UI Construction System
  // =================================================================================

  /**
   * @abstract
   * @class UIComponentBase
   * @extends BaseManager
   * @description Base class for a UI component with lifecycle management.
   */
  class UIComponentBase extends BaseManager {
    constructor(callbacks = {}) {
      super();
      this.callbacks = callbacks;
      this.element = null;
      this.storeSubscriptions = [];
    }

    /**
     * @abstract
     * Renders the component's DOM structure. Must be implemented by subclasses.
     * @returns {HTMLElement|void}
     */
    render() {
      throw new Error('Component must implement render method.');
    }

    /**
     * Adds a store subscription handle (unsubscribe function) to be managed by the component.
     * @param {() => void} unsub - The unsubscribe function returned by store.subscribe.
     */
    addStoreSubscription(unsub) {
      if (typeof unsub === 'function') {
        this.storeSubscriptions.push(unsub);
      }
    }

    /**
     * Executes all registered store subscription handles and clears the list.
     * Can be called manually (e.g. on modal close) or automatically on destroy.
     */
    clearStoreSubscriptions() {
      this.storeSubscriptions.forEach((unsub) => unsub());
      this.storeSubscriptions = [];
    }

    /**
     * Lifecycle hook for cleanup.
     * Removes the component's element from the DOM.
     * @protected
     * @override
     */
    _onDestroy() {
      this.clearStoreSubscriptions();
      this.element?.remove();
      this.element = null;
    }
  }

  /**
   * @class ReactiveStore
   * @description A simple reactive store implementing the Pub/Sub pattern.
   * It allows setting and getting values using dot-notation paths and notifies listeners on changes.
   *
   * [NOTIFICATION BEHAVIOR]
   * - Notifications are triggered strictly for the exact path being modified.
   * - Changes do NOT bubble up to parent paths.
   * - Subscribers are responsible for determining if a change affects them (e.g., using startsWith checks).
   */
  class ReactiveStore {
    constructor(initialState) {
      this.state = deepClone(initialState);
      this.listeners = new Set();
    }

    /**
     * Retrieves a value from the store by path.
     * @param {string} path - The dot-notation path to the property.
     * @returns {any} The value at the specified path.
     */
    get(path) {
      return getPropertyByPath(this.state, path);
    }

    /**
     * Sets a value in the store at the specified path.
     * Notifies listeners if the value has effectively changed.
     * @param {string} path - The dot-notation path to the property.
     * @param {any} value - The new value to set.
     */
    set(path, value) {
      // Validate path to prevent errors in split logic or setPropertyByPath
      if (!path || typeof path !== 'string') {
        Logger.warn('Store', '', `ReactiveStore.set: Invalid path "${path}"`);
        return;
      }

      const currentValue = this.get(path);
      // Use Object.is for strict equality check (handles NaN, -0/+0 correctly)
      if (Object.is(currentValue, value)) return;

      // Only notify if the update was successful
      if (setPropertyByPath(this.state, path, value)) {
        // Notify only the specific path that changed.
        this.notify(path);
      }
    }

    /**
     * Returns a deep copy of the current full state object.
     * @returns {object}
     */
    getData() {
      return deepClone(this.state);
    }

    /**
     * Returns a direct reference to the current state object.
     * WARNING: The returned object MUST be treated as Read-Only. Do not mutate directly.
     * Use this for performance-critical reads where deep cloning is too expensive.
     * @returns {Readonly<object>}
     */
    getStateRef() {
      return this.state;
    }

    /**
     * Replaces the entire state with a new object and notifies listeners.
     * Triggers notifications for all top-level keys in both old and new states.
     * @param {object} newState - The new full state object. Must be a valid object (null is ignored).
     */
    replaceState(newState) {
      if (!newState || typeof newState !== 'object') {
        Logger.warn('Store', '', 'ReactiveStore.replaceState: Invalid state object.');
        return;
      }

      const oldState = this.state;
      this.state = deepClone(newState);

      // Notify for the union of keys in old and new states to ensure deleted keys are handled
      const keys = new Set([...Object.keys(oldState || {}), ...Object.keys(this.state || {})]);

      keys.forEach((key) => {
        this.notify(key);
      });
    }

    /**
     * Subscribes to store updates.
     * WARNING: The state object passed to the callback is a direct reference to the store's internal state.
     * Do not mutate the state object directly within the callback. Use store.set() for updates.
     * @param {function(object, string): void} callback - The function to call on update. Receives (state, changedPath).
     * @returns {function(): void} A function to unsubscribe.
     */
    subscribe(callback) {
      if (typeof callback !== 'function') {
        Logger.warn('Store', '', 'ReactiveStore.subscribe: Callback must be a function.');
        return () => {};
      }
      this.listeners.add(callback);
      return () => this.listeners.delete(callback);
    }

    /**
     * @private
     * Notifies all subscribers of a change.
     * @param {string} changedPath - The path that was updated.
     */
    notify(changedPath) {
      for (const listener of this.listeners) {
        try {
          listener(this.state, changedPath);
        } catch (e) {
          Logger.error('Store', '', 'ReactiveStore listener failed:', e);
        }
      }
    }
  }

  /**
   * @class UIBuilder
   * @description A lightweight, procedural UI builder that handles DOM generation,
   * two-way data binding with ReactiveStore, and lifecycle management.
   */
  class UIBuilder {
    /**
     * @param {ReactiveStore} store
     * @param {object} context
     * @param {(fn: () => void) => void} disposer
     */
    constructor(store, context, disposer) {
      this.store = store;
      this.context = context;
      this.styles = context.styles || {};
      this.disposer = disposer;
    }

    /**
     * Creates a DOM element wrapper using the global h() function.
     * @param {string} tag
     * @param {object} [props]
     * @param {Array<Node|string>} [children]
     * @returns {HTMLElement | SVGElement}
     */
    create(tag, props = {}, children = []) {
      return h(tag, props, children);
    }

    /**
     * Observes store paths and triggers callback on change.
     * Automatically registers cleanup.
     * @param {string|string[]} paths - Config key(s) to observe.
     * @param {function(any): void} callback - Called with full store state on change.
     */
    observe(paths, callback) {
      const pathList = Array.isArray(paths) ? paths : [paths];
      // Initial call
      callback(this.store.getStateRef());

      const unsub = this.store.subscribe((state, changedPath) => {
        // Check if changedPath matches or is parent/child of any observed path
        const isMatch = pathList.some((p) => p === changedPath || changedPath.startsWith(p + '.') || p.startsWith(changedPath + '.'));
        if (isMatch) {
          callback(state);
        }
      });
      this.disposer(unsub);
    }

    /**
     * @private
     * Sets up dynamic visibility and disabled state.
     * @param {HTMLElement} element
     * @param {object} options
     */
    _setupDynamicState(element, options) {
      if (options.visibleIf || options.disabledIf) {
        const deps = options.dependencies || [];
        // If specific dependencies aren't listed but we have a key, watch the key too (unlikely for visibility but safe)
        if (options.key && !deps.includes(options.key)) deps.push(options.key);

        if (deps.length > 0) {
          this.observe(deps, (state) => {
            if (options.visibleIf) {
              element.style.display = options.visibleIf(state) ? '' : 'none';
            }
            if (options.disabledIf) {
              const isDisabled = options.disabledIf(state);
              const targets = element.matches('input, select, textarea, button') ? [element] : element.querySelectorAll('input, select, textarea, button');
              targets.forEach((t) => {
                if (t instanceof HTMLInputElement || t instanceof HTMLSelectElement || t instanceof HTMLTextAreaElement || t instanceof HTMLButtonElement) {
                  t.disabled = isDisabled;
                }
              });
              element.classList.toggle('is-disabled', isDisabled);
              element.style.opacity = isDisabled ? '0.5' : '';
              element.style.pointerEvents = isDisabled ? 'none' : '';
            }
          });
        }
      }
    }

    /**
     * @private
     * Binds an input element to the store key.
     * @param {HTMLElement} element - The container element.
     * @param {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement} input - The input element.
     * @param {string} key - Store key.
     * @param {object} options - Transform options.
     */
    _bindInput(element, input, key, options) {
      // 1. Store -> UI Update
      this.observe(key, (state) => {
        const rawValue = getPropertyByPath(state, key);

        // Validation & Self-healing hook
        if (options.validate) {
          const correctedValue = options.validate(rawValue);
          if (correctedValue !== rawValue) {
            // If validation fails, update store with corrected value and stop UI update
            this.store.set(key, correctedValue);
            return;
          }
        }

        // Allow manual override for complex components
        if (options.onStoreUpdate) {
          options.onStoreUpdate(input, rawValue);
          return;
        }

        const uiValue = options.toInputValue ? options.toInputValue(rawValue) : rawValue;

        if (input instanceof HTMLInputElement && input.type === 'checkbox') {
          input.checked = !!uiValue;
        } else if (input.value !== String(uiValue ?? '')) {
          // Avoid resetting cursor position if value is effectively same
          input.value = String(uiValue ?? '');
        }

        // Hook for label update
        if (options.onUIUpdate) options.onUIUpdate(rawValue, uiValue);
      });

      // 2. UI -> Store Update
      let eventType = 'input';
      if (input instanceof HTMLInputElement && input.type === 'checkbox') {
        eventType = 'change';
      } else if (input instanceof HTMLSelectElement) {
        eventType = 'change';
      }

      const handler = (e) => {
        let value;
        if (input instanceof HTMLInputElement && input.type === 'checkbox') {
          value = input.checked;
        } else {
          value = input.value;
        }

        if (input instanceof HTMLInputElement && (input.type === 'number' || input.type === 'range')) {
          value = value === '' ? null : parseFloat(String(value));
        } else if (typeof value === 'string' && value === '') {
          value = null; // Normalize empty string to null
        }

        if (options.transformValue) {
          value = options.transformValue(value);
        }

        this.store.set(key, value);
      };

      input.addEventListener(eventType, handler);
      this.disposer(() => input.removeEventListener(eventType, handler));
    }

    // --- Components ---

    text(key, label, options = {}) {
      const cls = this.styles;
      const input = this.create('input', { type: 'text' });

      if (cls.selectInput) input.classList.add(cls.selectInput);

      const children = [input];

      const container = this.create('div', { className: cls.formField }, [
        this.create('div', { className: cls.labelRow }, [this.create('label', { title: options.tooltip }, label)]),
        this.create('div', { className: cls.inputWrapper }, children),
      ]);

      if (container instanceof HTMLElement && input instanceof HTMLInputElement) {
        this._bindInput(container, input, key, options);
        this._setupDynamicState(container, options);
      }

      return container;
    }

    textarea(key, label, options = {}) {
      const cls = this.styles;
      const input = this.create('textarea', { rows: options.rows || 3 });
      if (cls.selectInput) input.classList.add(cls.selectInput);

      const container = this.create('div', { className: cls.formField }, [this.create('label', { title: options.tooltip }, label), input]);

      if (container instanceof HTMLElement && input instanceof HTMLTextAreaElement) {
        this._bindInput(container, input, key, options);
        this._setupDynamicState(container, options);
      }

      return container;
    }

    toggle(key, label, options = {}) {
      const cls = this.styles;
      const input = this.create('input', { type: 'checkbox' });

      const container = this.create('div', { className: cls.submenuRow }, [
        this.create('label', { title: options.title }, label), // Label on left
        this.create('label', { className: cls.toggleSwitch, title: options.title }, [
          // Switch on right
          input,
          this.create('span', { className: cls.toggleSlider }),
        ]),
      ]);

      if (container instanceof HTMLElement && input instanceof HTMLInputElement) {
        this._bindInput(container, input, key, options);
        this._setupDynamicState(container, options);
      }

      return container;
    }

    select(key, label, options = {}) {
      const cls = this.styles;
      const validValues = new Set();

      const selectOptions = (options.options || []).map((opt) => {
        // Handle simple strings or object {value, label}
        const val = typeof opt === 'object' ? opt.value : opt;
        const txt = typeof opt === 'object' ? opt.label : opt;
        const text = txt === '' ? '(not set)' : txt;

        validValues.add(val);
        return this.create('option', { value: val }, text);
      });

      // Inject validation logic to self-heal invalid values
      const enhancedOptions = {
        ...options,
        validate: (value) => {
          if (validValues.size > 0 && !validValues.has(value)) {
            // Fallback to first option if value is invalid
            const fallback = options.options[0];
            return typeof fallback === 'object' ? fallback.value : fallback;
          }
          return value;
        },
      };

      const input = this.create('select', {}, selectOptions);
      if (cls.selectInput) input.classList.add(cls.selectInput);

      let container;
      if (options.showLabel) {
        container = this.create('div', { className: cls.formField }, [this.create('label', { title: options.tooltip }, label), input]);
      } else {
        // Bare select (often used in rows)
        container = input;
      }

      if (container instanceof HTMLElement && input instanceof HTMLSelectElement) {
        this._bindInput(container, input, key, enhancedOptions);
        this._setupDynamicState(container, options);
      }

      return container;
    }

    button(id, text, onClick, options = {}) {
      const cls = this.styles;
      const className = options.className ? `${cls.modalButton} ${options.className}` : cls.modalButton;
      const btn = this.create(
        'button',
        {
          id,
          className,
          type: 'button',
          title: options.title || '',
          onclick: (e) => {
            e.preventDefault();
            if (typeof onClick === 'function') {
              onClick(e);
            }
          },
          style: { width: options.fullWidth ? '100%' : 'auto' },
        },
        text
      );
      return btn;
    }

    // --- Layouts ---

    group(label, children, options = {}) {
      const cls = this.styles;
      const container = this.create('fieldset', { className: cls.submenuFieldset }, [this.create('legend', {}, label), ...children]);
      if (container instanceof HTMLElement) {
        this._setupDynamicState(container, options);
      }
      return container;
    }

    row(children, options = {}) {
      const cls = this.styles;
      let className = cls.submenuRow;
      if (options.className && cls[options.className]) {
        className += ` ${cls[options.className]}`;
      } else if (options.className) {
        className += ` ${options.className}`;
      }
      const container = this.create('div', { className }, children);
      if (container instanceof HTMLElement) {
        this._setupDynamicState(container, options);
      }
      return container;
    }

    separator(options = {}) {
      const container = this.create('div', { className: this.styles.submenuSeparator });
      if (container instanceof HTMLElement) {
        this._setupDynamicState(container, options);
      }
      return container;
    }

    label(text, options = {}) {
      const container = this.create('label', { title: options.title }, text);
      if (container instanceof HTMLElement) {
        this._setupDynamicState(container, options);
      }
      return container;
    }

    container(children, options = {}) {
      let className = '';
      if (options.className && this.styles[options.className]) {
        className = this.styles[options.className];
      }
      const container = this.create('div', { className }, children);
      if (container instanceof HTMLElement) {
        this._setupDynamicState(container, options);
      }
      return container;
    }
  }

  // =================================================================================
  // SECTION: Configuration Processor
  // Description: Handles validation, sanitization, and merging of configuration data.
  // =================================================================================

  const ConfigProcessor = {
    /**
     * Processes and sanitizes an entire configuration object.
     * @param {object|null} userConfig The user configuration object (partial or full).
     * @returns {object} The complete, sanitized configuration object.
     */
    process(userConfig) {
      // 1. Start with a deep copy of the defaults.
      const completeConfig = deepClone(DEFAULT_CONFIG);

      if (isObject(userConfig)) {
        // 2. Merge user config into default config.
        // resolveConfig enforces the structure of completeConfig (DEFAULT_CONFIG),
        // automatically ignoring keys like 'system' that don't exist in the default.
        resolveConfig(completeConfig, userConfig);
      }

      // 3. Sanitize specific structures (Deep validation & Migration for texts)
      // Even if 'texts' was overwritten with an old object format by resolveConfig,
      // _sanitizeTexts handles the migration to array format.
      completeConfig.texts = this._sanitizeTexts(completeConfig.texts);

      // 4. Validate Options (Consistency check)
      this._validateOptions(completeConfig);

      return completeConfig;
    },

    /**
     * Migrates old object-based texts structure to new array-based structure.
     * @param {object} oldTexts The old texts object.
     * @returns {Array} The new texts array.
     */
    _migrateTexts(oldTexts) {
      if (!isObject(oldTexts)) return [];
      return Object.keys(oldTexts).map((profileName) => {
        const profileObj = oldTexts[profileName] || {};
        const categories = Object.keys(profileObj).map((catName) => ({
          name: catName,
          items: Array.isArray(profileObj[catName]) ? profileObj[catName] : [],
        }));
        return {
          name: profileName,
          categories: categories,
        };
      });
    },

    /**
     * Sanitizes the nested texts structure.
     * Handles migration from Object to Array and enforces unique names.
     * @param {object|Array} texts
     * @returns {Array} Sanitized texts array
     */
    _sanitizeTexts(texts) {
      // 1. Migration: Convert object to array if necessary
      const profiles = Array.isArray(texts) ? texts : this._migrateTexts(texts);

      // 2. Sanitize contents
      const sanitizedProfiles = [];
      const usedProfileNames = new Set();

      for (const profile of profiles) {
        if (!isObject(profile)) continue;

        let pName = String(profile.name || 'Untitled Profile').trim();
        if (!pName) pName = 'Untitled Profile';

        // Unique Profile Name Enforcement
        if (usedProfileNames.has(pName)) {
          let counter = 2;
          while (usedProfileNames.has(`${pName} ${counter}`)) counter++;
          pName = `${pName} ${counter}`;
        }
        usedProfileNames.add(pName);

        // Sanitize Categories
        const rawCategories = Array.isArray(profile.categories) ? profile.categories : [];
        const sanitizedCategories = [];
        const usedCatNames = new Set();

        for (const cat of rawCategories) {
          if (!isObject(cat)) continue;

          let cName = String(cat.name || 'Untitled Category').trim();
          if (!cName) cName = 'Untitled Category';

          // Unique Category Name Enforcement
          if (usedCatNames.has(cName)) {
            let counter = 2;
            while (usedCatNames.has(`${cName} ${counter}`)) counter++;
            cName = `${cName} ${counter}`;
          }
          usedCatNames.add(cName);

          // Sanitize Items
          const rawItems = Array.isArray(cat.items) ? cat.items : [];
          const items = rawItems.map((item) => {
            if (typeof item !== 'string') {
              Logger.warn('SANITIZER', LOG_STYLES.YELLOW, `Sanitizing invalid text entry: Item in category "${cName}" was not a string.`);
              return String(item);
            }
            return item;
          });

          sanitizedCategories.push({
            name: cName,
            items: items,
          });
        }

        // Ensure there's at least one category
        if (sanitizedCategories.length === 0) {
          Logger.warn('SANITIZER', LOG_STYLES.YELLOW, `Profile "${pName}" has no categories. Adding a default category.`);
          sanitizedCategories.push({ name: 'New Category', items: [] });
        }

        sanitizedProfiles.push({
          name: pName,
          categories: sanitizedCategories,
        });
      }

      // Ensure there's at least one profile.
      if (sanitizedProfiles.length === 0) {
        Logger.warn('', '', 'Configuration resulted in no profiles after sanitization. Restoring default texts structure.');
        // Restore from default config (already array structure)
        return deepClone(DEFAULT_CONFIG.texts);
      }

      return sanitizedProfiles;
    },

    /**
     * Validates and corrects option values based on the sanitized config.
     * @param {object} config
     */
    _validateOptions(config) {
      // Ensure options object exists
      if (!config.options) {
        config.options = deepClone(DEFAULT_CONFIG.options);
      }
      if (!config.developer) {
        config.developer = deepClone(DEFAULT_CONFIG.developer);
      }

      // 1. Validate activeProfileName (Dynamic dependency, handled manually)
      const activeProfileName = config.options.activeProfileName;
      const profileExists = config.texts.some((p) => p.name === activeProfileName);

      if (!profileExists) {
        const fallback = config.texts.length > 0 ? config.texts[0].name : null;
        Logger.info('CONFIG', LOG_STYLES.YELLOW, `Active profile "${activeProfileName || 'undefined'}" not found. Falling back to "${fallback}".`);
        config.options.activeProfileName = fallback;
      }

      // 2. Validate Schema-driven options
      this._validateSchema(config.options, CONFIG_SCHEMA.options, 'options');
      this._validateSchema(config.developer, CONFIG_SCHEMA.developer, 'developer');
    },

    /**
     * Validates a configuration section against a schema definition.
     * @param {object} targetObj The configuration object to validate (e.g. config.options).
     * @param {object} schemaSection The schema definition for this section.
     * @param {string} sectionName The name of the section for logging.
     */
    _validateSchema(targetObj, schemaSection, sectionName) {
      for (const [key, schema] of Object.entries(schemaSection)) {
        let value = targetObj[key];
        let isValid = true;

        if (schema.type === 'select') {
          // Extract valid values from options array (supports both string and object{value, label})
          const validValues = schema.options.map((opt) => (typeof opt === 'object' ? opt.value : opt));
          if (!validValues.includes(value)) {
            isValid = false;
          }
        } else if (schema.type === 'toggle') {
          if (typeof value !== 'boolean') {
            // Attempt type coercion for strings "true"/"false"
            if (value === 'true') value = true;
            else if (value === 'false') value = false;
            else isValid = false;
          }
        }

        if (!isValid) {
          Logger.warn('CONFIG', LOG_STYLES.YELLOW, `Invalid value "${targetObj[key]}" for "${sectionName}.${key}". Resetting to default "${schema.default}".`);
          targetObj[key] = schema.default;
        } else if (value !== targetObj[key]) {
          // Apply coerced value
          targetObj[key] = value;
        }
      }
    },
  };

  // =================================================================================
  // SECTION: Configuration Management (GM Storage)
  // =================================================================================

  class ConfigManager extends BaseManager {
    constructor() {
      super();
      this.CONFIG_KEY = CONSTANTS.CONFIG_KEY;
      this.DEFAULT_CONFIG = DEFAULT_CONFIG;
      /** @type {object|null} */
      this.config = null;
    }

    /**
     * @returns {object|null} The current configuration object.
     */
    get() {
      return this.config;
    }

    /**
     * Loads the configuration from storage, delegates processing to ConfigProcessor.
     */
    async load() {
      const raw = await GM.getValue(this.CONFIG_KEY);
      let userConfig = null;
      if (raw) {
        try {
          userConfig = JSON.parse(raw);
        } catch (e) {
          Logger.error('LOAD FAILED', LOG_STYLES.RED, 'Failed to parse configuration. Using default settings.', e);
          userConfig = null;
        }
      }
      this.config = ConfigProcessor.process(userConfig);
    }

    /**
     * Processes via ConfigProcessor, checks size, and saves to storage.
     * @param {object} obj The configuration object to save.
     */
    async save(obj) {
      const completeConfig = ConfigProcessor.process(obj);

      // Size Check
      const validation = this.validateConfigSize(completeConfig);
      if (!validation.isValid) {
        EventBus.publish(EVENTS.CONFIG_SIZE_EXCEEDED, { message: validation.message });
        throw new Error(validation.message);
      }

      const jsonString = JSON.stringify(completeConfig);
      this.config = completeConfig;
      await GM.setValue(this.CONFIG_KEY, jsonString);
      EventBus.publish(EVENTS.CONFIG_SAVE_SUCCESS);
    }

    /**
     * Decodes a raw string from storage into a user configuration object.
     * @param {string | null} rawValue The raw string from GM.getValue.
     * @returns {Promise<object | null>} The parsed user configuration object, or null if parsing fails.
     */
    async decode(rawValue) {
      if (!rawValue) return null;
      try {
        const parsed = JSON.parse(rawValue);
        if (isObject(parsed)) {
          return parsed;
        }
        return null;
      } catch (e) {
        Logger.error('LOAD FAILED', LOG_STYLES.RED, `Failed to parse raw value. Error: ${e.message}`);
        return null;
      }
    }

    /**
     * Validates if the given configuration object is within the storage size limit.
     * @param {object} configObj - The configuration object to check.
     * @returns {{ isValid: boolean, message: string | null }}
     */
    validateConfigSize(configObj) {
      const jsonString = JSON.stringify(configObj);
      const configSize = new TextEncoder().encode(jsonString).length;

      if (configSize > CONSTANTS.CONFIG_SIZE_LIMIT_BYTES) {
        const sizeInMB = (configSize / 1024 / 1024).toFixed(2);
        const limitInMB = (CONSTANTS.CONFIG_SIZE_LIMIT_BYTES / 1024 / 1024).toFixed(1);
        const message = `Configuration size (${sizeInMB} MB) exceeds the ${limitInMB} MB limit.\nChanges are not saved.`;
        return { isValid: false, message };
      }

      return { isValid: true, message: null };
    }
  }

  // =================================================================================
  // SECTION: UI Elements - Components and Manager
  // =================================================================================

  /**
   * @class CustomModal
   * @description A reusable, promise-based modal component. It provides a flexible
   * structure with header, content, and footer sections, and manages its own
   * lifecycle and styles using StyleManager.
   */
  class CustomModal {
    /**
     * @param {object} [options]
     * @param {string} [options.title=''] - The title displayed in the modal header.
     * @param {string} [options.width='500px'] - The width of the modal.
     * @param {boolean} [options.closeOnBackdropClick=true] - Whether to close the modal when clicking the backdrop.
     * @param {Array<object>} [options.buttons=[]] - An array of button definitions for the footer.
     * @param {function(Event): void} [options.onCancel] - A callback function for the cancel event (ESC key).
     * @param {function(): void} [options.onDestroy] - A callback function executed when the modal is destroyed.
     * @param {{text: string, id: string, className: string, onClick: (modalInstance: CustomModal, event: Event) => void}} options.buttons[]
     */
    constructor(options = {}) {
      this.options = {
        title: '',
        width: 'min(500px, 95vw)', // Responsive default
        closeOnBackdropClick: true,
        buttons: [],
        onCancel: null,
        onDestroy: null,
        ...options,
      };
      this.style = StyleManager.request(StyleDefinitions.getModal);
      this.commonStyle = StyleManager.request(StyleDefinitions.getCommon);
      this.element = null;
      this.dom = {}; // To hold references to internal elements like header, content, footer
      this._createModalElement();
    }

    _createModalElement() {
      const cls = this.style.classes;
      const commonCls = this.commonStyle.classes;

      // Define variables to hold references to key elements.
      let header, headerTitle, headerContent, content, footer, modalBox, footerMessage;
      // Create footer buttons declaratively using map and h().
      const buttons = this.options.buttons.map((btnDef) => {
        // Merge passed className with the common modal button class
        const fullClassName = [commonCls.modalButton, btnDef.className].filter(Boolean).join(' ');
        return h(
          'button',
          {
            id: btnDef.id,
            className: fullClassName,
            onclick: (e) => btnDef.onClick(this, e),
          },
          btnDef.text
        );
      });

      const buttonGroup = h(`div.${cls.buttonGroup}`, buttons);

      // Create the entire modal structure using h().
      this.element = h(
        `dialog.${cls.dialog}`,
        (modalBox = h(`div.${cls.box}`, { style: { width: this.options.width } }, [
          (header = h(`div.${cls.header}`, [(headerTitle = h('div', this.options.title)), (headerContent = h('div'))])),
          (content = h(`div.${cls.content}`)),
          (footer = h(`div.${cls.footer}`, [(footerMessage = h(`div.${cls.footerMessage}`)), buttonGroup])),
        ]))
      );

      // The 'close' event is the single source of truth for when the dialog has been dismissed.
      this.element.addEventListener('close', () => this.destroy());

      // Listen for the 'cancel' event (fired on ESC) to allow intercepting the close action.
      this.element.addEventListener('cancel', (e) => {
        if (typeof this.options.onCancel === 'function') {
          this.options.onCancel(e);
        }
      });

      if (this.options.closeOnBackdropClick) {
        this.element.addEventListener('click', (e) => {
          if (e.target === this.element) {
            this.close();
          }
        });
      }

      // Store references and append the final element to the body.
      this.dom = { header, headerTitle, headerContent, content, footer, modalBox, footerMessage };
      document.body.appendChild(this.element);
    }

    show(anchorElement = null) {
      if (this.element instanceof HTMLDialogElement && typeof this.element.showModal === 'function') {
        if (this.element.open) return; // Prevent InvalidStateError

        this.element.showModal();
        // Positioning logic
        if (anchorElement && typeof anchorElement.getBoundingClientRect === 'function') {
          // ANCHORED POSITIONING
          const modalBox = this.dom.modalBox;
          const btnRect = anchorElement.getBoundingClientRect();
          const margin = 8;
          // Use actual offsetWidth if available, otherwise fallback to a safe fixed value (500)
          const modalWidth = modalBox.offsetWidth || 500;
          const modalHeight = modalBox.offsetHeight;

          let left = btnRect.left;
          let top = btnRect.bottom + 4;

          if (left + modalWidth > window.innerWidth - margin) {
            left = window.innerWidth - modalWidth - margin;
          }

          // Vertical collision detection & adjustment
          if (top + modalHeight > window.innerHeight - margin) {
            // Try positioning above the anchor
            const topAbove = btnRect.top - modalHeight - 4;
            if (topAbove > margin) {
              top = topAbove;
            } else {
              // If it doesn't fit above, pin to the bottom edge of the window
              top = window.innerHeight - modalHeight - margin;
            }
          }

          Object.assign(this.element.style, {
            position: 'absolute',
            left: `${Math.max(left, margin)}px`,
            top: `${Math.max(top, margin)}px`,
            margin: '0',
            transform: 'none',
          });
        } else {
          // DEFAULT CENTERING
          Object.assign(this.element.style, {
            position: 'fixed',
            left: '50%',
            top: '50%',
            transform: 'translate(-50%, -50%)',
            margin: '0',
          });
        }
      }
    }

    close() {
      if (this.element instanceof HTMLDialogElement && this.element.open) {
        this.element.close();
      }
    }

    destroy() {
      if (!this.element) return;
      this.element.remove();
      this.element = null;

      if (this.options.onDestroy) {
        this.options.onDestroy();
      }
    }

    setContent(element) {
      this.dom.content.replaceChildren(element);
    }

    setHeader(element) {
      this.dom.headerContent.replaceChildren(element);
    }

    getContentContainer() {
      return this.dom.content;
    }
  }

  /**
   * @abstract
   * @description Base class for a settings panel/submenu UI component.
   */
  class SettingsPanelBase extends UIComponentBase {
    constructor(callbacks) {
      super(callbacks);
      this.debouncedSave = debounce(this._handleDebouncedSave.bind(this), 300, true);
      this._handleDocumentClick = this._handleDocumentClick.bind(this);
      this._handleDocumentKeydown = this._handleDocumentKeydown.bind(this);
      this._handleConfigUpdate = this._handleConfigUpdate.bind(this);
    }

    _onInit() {
      this._subscribe(EVENTS.CONFIG_UPDATED, this._handleConfigUpdate);
    }

    /**
     * @override
     * @protected
     */
    _onDestroy() {
      this.hide();
      this.debouncedSave.cancel();
      super._onDestroy();
    }

    /**
     * @private
     * Collects data and calls the onSave callback. Designed to be debounced.
     */
    async _handleDebouncedSave() {
      const newConfig = await this._collectDataFromForm();
      try {
        await this.callbacks.onSave?.(newConfig);
      } catch (e) {
        // Log the error to console. User notification is handled via EventBus (CONFIG_SIZE_EXCEEDED).
        Logger.error('SAVE FAILED', LOG_STYLES.RED, 'SettingsPanel save failed:', e);
      }
    }

    /**
     * @private
     * Handles the CONFIG_UPDATED event to refresh the form if the panel is open.
     * @param {object} newConfig The updated configuration object from the event payload.
     */
    async _handleConfigUpdate(newConfig) {
      if (this.isOpen()) {
        Logger.debug('UI REFRESH', LOG_STYLES.CYAN, 'Settings panel is open, refreshing form due to config update.');
        // Pass the received config directly to populateForm
        await this.populateForm(newConfig);
      }
    }

    render() {
      this._injectStyles();
      this.element = this._createPanelContainer();
      const content = this._createPanelContent();
      this.element.appendChild(content);

      document.body.appendChild(this.element);
      this._setupEventListeners();
      return this.element;
    }

    toggle() {
      const shouldShow = this.element.style.display === 'none';
      if (shouldShow) {
        this.show();
      } else {
        this.hide();
      }
    }

    isOpen() {
      return this.element && this.element.style.display !== 'none';
    }

    async show() {
      if (this.isOpen()) return; // Prevent re-entry

      await this.populateForm();
      const anchorRect = this.callbacks.getAnchorElement().getBoundingClientRect();

      let top = anchorRect.bottom + 4;
      let left = anchorRect.left;

      this.element.style.display = 'block';
      const panelWidth = this.element.offsetWidth;
      const panelHeight = this.element.offsetHeight;

      if (left + panelWidth > window.innerWidth - 8) {
        left = window.innerWidth - panelWidth - 8;
      }
      if (top + panelHeight > window.innerHeight - 8) {
        top = window.innerHeight - panelHeight - 8;
      }

      this.element.style.left = `${Math.max(8, left)}px`;
      this.element.style.top = `${Math.max(8, top)}px`;
      document.addEventListener('click', this._handleDocumentClick, true);
      document.addEventListener('keydown', this._handleDocumentKeydown, true);
    }

    hide() {
      this.element.style.display = 'none';
      document.removeEventListener('click', this._handleDocumentClick, true);
      document.removeEventListener('keydown', this._handleDocumentKeydown, true);
    }

    _createPanelContainer() {
      return h(`div#${APPID}-settings-panel`, { style: { display: 'none' }, role: 'menu' });
    }

    _handleDocumentClick(e) {
      const anchor = this.callbacks.getAnchorElement();
      if (this.element && !this.element.contains(e.target) && anchor && !anchor.contains(e.target)) {
        this.hide();
      }
    }

    _handleDocumentKeydown(e) {
      if (e.key === 'Escape') {
        this.hide();
      }
    }

    // --- Abstract methods to be implemented by subclasses ---
    _createPanelContent() {
      throw new Error('Subclass must implement _createPanelContent()');
    }
    _injectStyles() {
      throw new Error('Subclass must implement _injectStyles()');
    }
    populateForm(config) {
      throw new Error('Subclass must implement populateForm()');
    }
    _collectDataFromForm() {
      throw new Error('Subclass must implement _collectDataFromForm()');
    }
    _setupEventListeners() {
      throw new Error('Subclass must implement _setupEventListeners()');
    }
  }

  class SettingsPanelComponent extends SettingsPanelBase {
    constructor(callbacks) {
      super(callbacks);
      this.formContainer = null;
      this.store = new ReactiveStore(DEFAULT_CONFIG);
      this.isPopulating = false;
      this.styleHandle = null;
      this.commonStyleHandle = null;
      this.warningState = null;
      /** @type {Array<() => void>} */
      this._uiSubscriptions = []; // Transient subscriptions for the current render cycle

      // Delegate subscription management to the base class
      this.addStoreSubscription(
        this.store.subscribe((state, changedPath) => {
          // Prevent infinite loop: do not trigger save if the change is internal (system state)
          if (changedPath && changedPath.startsWith('system')) return;

          if (!this.isPopulating) {
            this.debouncedSave();
          }
        })
      );
    }

    _onInit() {
      super._onInit();
      this._subscribe(EVENTS.CONFIG_SIZE_EXCEEDED, ({ message }) => {
        this.warningState = { text: message };
        if (this.store) {
          this.store.set('system.warning', this.warningState);
        }
      });
      this._subscribe(EVENTS.CONFIG_SAVE_SUCCESS, () => {
        this.warningState = null;
        if (this.store) {
          this.store.set('system.warning', null);
        }
      });
    }

    /**
     * @override
     * @protected
     */
    _onDestroy() {
      this._uiSubscriptions.forEach((unsub) => unsub());
      this._uiSubscriptions = [];
      super._onDestroy();
    }

    _injectStyles() {
      this.styleHandle = StyleManager.request(StyleDefinitions.getSettingsPanel);
      this.commonStyleHandle = StyleManager.request(StyleDefinitions.getCommon);
    }

    _createPanelContainer() {
      const cls = this.styleHandle.classes;
      return h(`div#${cls.panel}`, { style: { display: 'none' }, role: 'menu' });
    }

    _createPanelContent() {
      this.formContainer = h('div');
      return this.formContainer;
    }

    /**
     * Populates the settings form with configuration data.
     * Uses the provided config object if available, otherwise fetches the current config.
     * @param {object} [config] - Optional. The configuration object to use for population.
     */
    async populateForm(config) {
      const currentConfig = config || (await this.callbacks.getCurrentConfig());
      if (!currentConfig || !currentConfig.options) return;

      this.isPopulating = true;

      // Merge current config with warning state if present
      const storeData = deepClone(currentConfig);
      if (this.warningState) {
        storeData.system = { warning: this.warningState };
      } else {
        storeData.system = { warning: null };
      }

      // Update the store state. This triggers UI updates via UIBuilder bindings.
      this.store.replaceState(storeData);

      // If the form is already rendered, we are done. (Reactivity handles the updates)
      if (this.formContainer && this.formContainer.hasChildNodes()) {
        this.isPopulating = false;
        return;
      }

      // Clean up previous render's resources
      this._uiSubscriptions.forEach((unsub) => unsub());
      this._uiSubscriptions = [];

      if (this.formContainer) {
        this.formContainer.replaceChildren();
        this._renderContent();
      }

      this.isPopulating = false;
    }

    /**
     * @private
     * Renders the form content using UIBuilder.
     */
    _renderContent() {
      const cls = { ...this.commonStyleHandle.classes, ...this.styleHandle.classes };
      const context = { styles: cls, store: this.store };

      const uiDisposer = (unsub) => {
        if (typeof unsub === 'function') {
          this._uiSubscriptions.push(unsub);
        }
      };
      const ui = new UIBuilder(this.store, context, uiDisposer);

      const fragment = document.createDocumentFragment();

      // 1. System Warning Banner
      const warningKey = 'system.warning';
      const warningBanner = ui.create('div', { className: cls.warningBanner, style: { display: 'none' } });
      ui.observe(warningKey, (state) => {
        const warning = getPropertyByPath(state, warningKey);
        warningBanner.textContent = warning ? warning.text : '';
        warningBanner.style.display = warning ? '' : 'none';
      });
      fragment.appendChild(warningBanner);

      // 2. Default Profile (Dynamic Select Implementation)
      const profileSelectId = `${APPID}-profile-select`;
      const profileSelect = ui.create('select', {
        id: profileSelectId,
        className: cls.selectInput,
        title: 'Select the default profile loaded on startup.',
      });

      // Bind UI -> Store (Change Event)
      const changeHandler = (e) => {
        const target = e.target;
        if (target instanceof HTMLSelectElement) {
          this.store.set('options.activeProfileName', target.value);
        }
      };
      profileSelect.addEventListener('change', changeHandler);
      uiDisposer(() => profileSelect.removeEventListener('change', changeHandler));

      // Bind Store -> UI (Update Options & Value)
      ui.observe(['texts', 'options.activeProfileName'], (state) => {
        const profiles = state.texts || [];
        const currentVal = state.options?.activeProfileName;

        // --- 1. Data Integrity Check & Self-healing ---
        // If the current profile is invalid (e.g. deleted), fallback to the first available one.
        const isValid = profiles.some((p) => p.name === currentVal);
        if (!isValid && profiles.length > 0) {
          const fallback = profiles[0].name;

          // Guard: Only update if strictly different to avoid infinite loops
          if (currentVal !== fallback) {
            this.store.set('options.activeProfileName', fallback);
            return; // Stop here. The store update will trigger this observer again with valid data.
          }
        }

        // --- 2. DOM Update ---
        // Rebuild options only if signature changed
        // Use JSON.stringify to prevent collision issues with special characters (e.g. pipe '|') in names
        const newSignature = JSON.stringify(profiles.map((p) => p.name));
        if (profileSelect.dataset.signature !== newSignature) {
          const options = profiles.map((p) => ui.create('option', { value: p.name }, p.name));
          profileSelect.replaceChildren(...options);
          profileSelect.dataset.signature = newSignature;
        }

        // Sync value
        if (profileSelect instanceof HTMLSelectElement) {
          if (profiles.length === 0) {
            profileSelect.value = '';
          } else {
            profileSelect.value = currentVal;
          }
        }
      });

      fragment.appendChild(
        ui.group(
          'Default Profile',
          [
            ui.create('div', { className: cls.formField }, [
              // Wrap in standard form structure if needed, or just push the select
              // ui.select usually wraps in a label if provided, here we assume direct placement inside group
              profileSelect,
            ]),
          ],
          { title: 'Select the default profile loaded on startup.' }
        )
      );

      // 3. Submenu Buttons (Texts & JSON)
      const submenuRow = ui.row(
        [
          ui.group(
            'Texts',
            [
              ui.button(
                `${APPID}-submenu-edit-texts-btn`,
                'Edit Texts...',
                () => {
                  this.callbacks.onShowTextEditorModal?.();
                  this.hide();
                },
                { fullWidth: true, title: 'Open the text editor.' }
              ),
            ],
            { title: 'Open the text editor.' }
          ),
          ui.group(
            'JSON',
            [
              ui.button(
                `${APPID}-submenu-json-btn`,
                'JSON...',
                () => {
                  this.callbacks.onShowJsonModal?.();
                  this.hide();
                },
                {
                  fullWidth: true,
                  title: 'Import, export, or edit settings via JSON.',
                }
              ),
            ],
            { title: 'Import, export, or edit settings via JSON.' }
          ),
        ],
        { className: 'topRow' }
      );
      fragment.appendChild(submenuRow);

      // 4. Options Group (Procedurally generated using Schema)
      const s = CONFIG_SCHEMA.options;
      const optionsGroup = ui.group(
        'Options',
        [
          ui.row([
            ui.label(s.trigger_mode.ui.label, { for: `${APPID}-opt-trigger-mode`, title: s.trigger_mode.ui.title }),
            ui.select('options.trigger_mode', null, {
              id: `${APPID}-opt-trigger-mode`,
              title: s.trigger_mode.ui.title,
              options: s.trigger_mode.options,
            }),
          ]),
          ui.row([
            ui.label(s.enable_shortcut.ui.label, {
              for: `${APPID}-opt-enable-shortcut`,
              title: s.enable_shortcut.ui.title,
            }),
            ui.toggle('options.enable_shortcut', null, {
              id: `${APPID}-opt-enable-shortcut`,
              title: s.enable_shortcut.ui.title,
            }),
          ]),
          ui.container(
            [
              ui.button(
                `${APPID}-submenu-shortcut-btn`,
                'Shortcuts & Controls',
                () => {
                  this.callbacks.onShowShortcutModal?.();
                  this.hide();
                },
                { title: 'Show the keyboard shortcuts cheat sheet.' }
              ),
            ],
            { className: 'submenuRow' }
          ),
          ui.separator(),
          ui.row([
            ui.label(s.insertion_position.ui.label, {
              for: `${APPID}-opt-insertion-position`,
              title: s.insertion_position.ui.title,
            }),
            ui.select('options.insertion_position', null, {
              id: `${APPID}-opt-insertion-position`,
              title: s.insertion_position.ui.title,
              options: s.insertion_position.options,
            }),
          ]),
          ui.row([
            ui.label(s.insert_before_newline.ui.label, {
              for: `${APPID}-opt-insert-before-newline`,
              title: s.insert_before_newline.ui.title,
            }),
            ui.toggle('options.insert_before_newline', null, {
              id: `${APPID}-opt-insert-before-newline`,
              title: s.insert_before_newline.ui.title,
            }),
          ]),
          ui.row([
            ui.label(s.insert_after_newline.ui.label, {
              for: `${APPID}-opt-insert-after-newline`,
              title: s.insert_after_newline.ui.title,
            }),
            ui.toggle('options.insert_after_newline', null, {
              id: `${APPID}-opt-insert-after-newline`,
              title: s.insert_after_newline.ui.title,
            }),
          ]),
          ui.container(
            [ui.create('div', { className: cls.settingsNote }, ["Note: Option behavior may depend on the input field's state (focus and existing content). For consistent results, click an insert button while the input field is focused."])],
            {}
          ),
        ],
        { title: 'Configure general options and behavior.' }
      );
      fragment.appendChild(optionsGroup);

      this.formContainer.appendChild(fragment);
    }

    async _collectDataFromForm() {
      return this.store.getData();
    }

    _setupEventListeners() {
      // Handled by UIBuilder
    }
  }

  /**
   * Manages the Text Settings modal by leveraging the CustomModal component.
   */
  class TextEditorModalComponent extends UIComponentBase {
    constructor(callbacks) {
      super(callbacks);
      this.modal = null;
      this.store = null;
      this.draggedIndex = null;
      this.cachedConfig = null; // Config cache for Store synchronization
      this.styleHandle = null;
      this.commonStyleHandle = null;
      /** @type {Array<() => void>} */
      this._uiSubscriptions = []; // Transient subscriptions for the current render cycle
    }

    _onInit() {
      // Subscribe to size exceeded event to show error in footer
      this._subscribe(EVENTS.CONFIG_SIZE_EXCEEDED, ({ message }) => {
        const footerMessage = this.modal?.dom?.footerMessage;
        if (footerMessage) {
          footerMessage.textContent = message;
          footerMessage.style.color = `var(${StyleDefinitions.VARS.TEXT_DANGER})`;
        }
      });

      // Subscribe to save success event to clear footer
      this._subscribe(EVENTS.CONFIG_SAVE_SUCCESS, () => {
        const footerMessage = this.modal?.dom?.footerMessage;
        if (footerMessage) {
          footerMessage.textContent = '';
          footerMessage.style.color = '';
        }
      });
    }

    _checkConfigSize() {
      if (!this.cachedConfig || !this.modal?.dom?.footerMessage) return;

      const validation = this.callbacks.checkConfigSize(this.cachedConfig);

      if (!validation.isValid) {
        const footerMessage = this.modal.dom.footerMessage;
        footerMessage.textContent = validation.message;
        footerMessage.style.color = `var(${StyleDefinitions.VARS.TEXT_DANGER})`;
      }
    }

    /**
     * @override
     * @protected
     */
    _onDestroy() {
      this._uiSubscriptions.forEach((unsub) => unsub());
      this._uiSubscriptions = [];
      this.modal?.destroy();
      super._onDestroy();
    }

    render() {
      this.styleHandle = StyleManager.request(StyleDefinitions.getTextEditorModal);
      this.commonStyleHandle = StyleManager.request(StyleDefinitions.getCommon);
    }

    async open(targetProfile, targetCategoryKey) {
      if (this.modal || this.isOpening) return; // Prevent re-entry

      // Ensure styles are ready
      if (!this.styleHandle) this.render();

      this.isOpening = true;

      try {
        const commonCls = this.commonStyleHandle.classes;

        // Load and cache initial config
        const globalConfig = await this.callbacks.getCurrentConfig();
        this.cachedConfig = deepClone(globalConfig);

        if (!this.cachedConfig || !this.cachedConfig.texts) return;

        // Determine initial display position
        const profiles = this.cachedConfig.texts;
        const initialProfileName = targetProfile || this.cachedConfig.options.activeProfileName || profiles[0]?.name;
        const validProfileObj = profiles.find((p) => p.name === initialProfileName) || profiles[0];
        const validProfile = validProfileObj?.name;

        const categories = validProfileObj?.categories || [];
        let initialCategory = categories[0]?.name || null;

        if (targetCategoryKey && categories.some((c) => c.name === targetCategoryKey)) {
          initialCategory = targetCategoryKey;
        }

        this.callbacks.onModalOpenStateChange?.(true);

        // Initialize Store
        this.store = new ReactiveStore({
          editor: {
            activeProfile: validProfile,
            activeCategory: initialCategory,
            mode: 'normal', // 'normal' | 'rename' | 'delete_confirm'
            targetType: null, // 'profile' | 'category'
            targetKey: null,
            currentTexts: [], // Initial value will be loaded later
            listFingerprint: 0,
            focusedIndex: -1,
            lastUpdated: Date.now(),
          },
        });

        this.modal = new CustomModal({
          title: `${APPNAME} - Settings`,
          width: 'min(880px, 95vw)',
          closeOnBackdropClick: false,
          buttons: [
            { text: 'Cancel', id: `${APPID}-editor-modal-cancel-btn`, className: '', title: 'Discard changes and close the modal.', onClick: () => this.close() },
            { text: 'Apply', id: `${APPID}-editor-modal-apply-btn`, className: '', title: 'Save changes and keep the modal open.', onClick: () => this._handleSaveAction(false) },
            { text: 'Save', id: `${APPID}-editor-modal-save-btn`, className: commonCls.primaryBtn, title: 'Save changes and close the modal.', onClick: () => this._handleSaveAction(true) },
          ],
          onCancel: (e) => {
            const mode = this.store.get('editor.mode');
            if (mode === 'rename') {
              // In rename mode, ESC should only cancel the rename, not close the modal.
              e.preventDefault();
              this._exitRenameMode(true);
            } else if (mode === 'delete_confirm') {
              // In delete confirm mode, ESC should only cancel the confirmation.
              e.preventDefault();
              this._exitDeleteConfirmationMode();
            }
          },
          onDestroy: () => {
            this.callbacks.onModalOpenStateChange?.(false);
            this._uiSubscriptions.forEach((unsub) => unsub());
            this._uiSubscriptions = [];
            this.store = null;
            this.modal = null;
            this.cachedConfig = null;
          },
        });

        // Render UI content using UIBuilder
        const content = this._renderEditorUI();

        // Override base modal styles for specific layout needs
        Object.assign(this.modal.dom.header.style, {
          paddingBottom: '12px',
          display: 'flex',
          flexDirection: 'column',
          alignItems: 'stretch',
          gap: '12px',
        });
        Object.assign(this.modal.dom.footer.style, {
          paddingTop: '16px',
        });

        // Split content into header and body
        const headerContent = content.querySelector(`.${this.styleHandle.classes.headerControls}`);
        const bodyContent = content.querySelector(`.${this.styleHandle.classes.modalContent}`);

        if (headerContent) this.modal.setHeader(headerContent);
        if (bodyContent) this.modal.setContent(bodyContent);

        this._setupEventListeners();

        // Load initial state
        this._loadStateFromConfig(validProfile, initialCategory);

        // Actively check config size and display warning if already exceeded
        this._checkConfigSize();

        this.modal.show();
      } finally {
        this.isOpening = false;
      }
    }

    close() {
      this.modal?.close();
    }

    _renderEditorUI() {
      const cls = { ...this.commonStyleHandle.classes, ...this.styleHandle.classes };
      const context = { styles: cls, store: this.store };

      const uiDisposer = (unsub) => {
        if (typeof unsub === 'function') {
          this._uiSubscriptions.push(unsub);
        }
      };
      const ui = new UIBuilder(this.store, context, uiDisposer);

      const container = document.createDocumentFragment();

      // 1. Header Controls
      container.appendChild(this._renderHeaderControls(ui));

      // 2. Content Area
      const contentArea = ui.container(
        [
          // Text List Area
          this._renderTextListArea(ui),
          // Add New Button
          ui.button(`${APPID}-text-new-btn`, 'Add New Text', null, { className: cls.modalButton }),
        ],
        { className: 'modalContent' } // Mapped to cls.modalContent
      );
      container.appendChild(contentArea);

      return container;
    }

    _renderHeaderControls(ui) {
      const cls = ui.context.styles;
      const icons = StyleDefinitions.ICONS;
      const container = ui.create('div', { className: cls.headerControls });

      const createControlRow = (type, label) => {
        const row = ui.create('div', { className: cls.headerRow, 'data-type': type });

        // Container 1: Label
        const labelEl = ui.create('label', { htmlFor: `${APPID}-${type}-select` }, [label]);
        row.appendChild(labelEl);

        // Container 2: Select / Input (Dynamic)
        const inputArea = ui.create('div', { className: cls.renameArea });
        const select = ui.create('select', { id: `${APPID}-${type}-select`, className: cls.selectInput });
        const renameInput = ui.create('input', { type: 'text', id: `${APPID}-${type}-rename-input`, className: cls.selectInput, style: { display: 'none' } });
        inputArea.append(select, renameInput);
        row.appendChild(inputArea);

        // Container 3: Action Buttons
        const actionArea = ui.create('div', { className: cls.actionArea });

        // 3.1 Main Actions
        const mainActions = ui.create('div', { className: cls.mainActions });
        mainActions.append(
          ui.button(`${APPID}-${type}-rename-btn`, 'Rename', null),
          ui.button(`${APPID}-${type}-up-btn`, [createIconFromDef(icons.up)], null, { className: cls.moveBtn }),
          ui.button(`${APPID}-${type}-down-btn`, [createIconFromDef(icons.down)], null, { className: cls.moveBtn }),
          ui.button(`${APPID}-${type}-new-btn`, 'New', null),
          ui.button(`${APPID}-${type}-copy-btn`, 'Copy', null),
          ui.button(`${APPID}-${type}-delete-btn`, 'Delete', null)
        );

        // 3.2 Rename Actions
        const renameActions = ui.create('div', { className: cls.renameActions, style: { display: 'none' } });
        renameActions.append(ui.button(`${APPID}-${type}-rename-ok-btn`, 'OK', null), ui.button(`${APPID}-${type}-rename-cancel-btn`, 'Cancel', null));

        // 3.3 Delete Confirm
        const deleteConfirmGroup = ui.create('div', { className: cls.deleteConfirmGroup, style: { display: 'none' } });
        const confirmBtnGroup = ui.create('div', { style: { display: 'flex', gap: '8px' } });
        confirmBtnGroup.append(ui.button(`${APPID}-${type}-delete-confirm-btn`, 'Confirm Delete', null, { className: cls.deleteConfirmBtnYes }), ui.button(`${APPID}-${type}-delete-cancel-btn`, 'Cancel', null));
        deleteConfirmGroup.append(ui.create('span', { className: cls.deleteConfirmLabel }, ['Are you sure?']), confirmBtnGroup);

        actionArea.append(mainActions, renameActions, deleteConfirmGroup);
        row.appendChild(actionArea);

        // --- Logic: Observe Store to update UI state ---
        ui.observe(['editor.activeProfile', 'editor.activeCategory', 'editor.mode', 'editor.targetType', 'editor.lastUpdated'], (state) => {
          const { activeProfile, activeCategory, mode, targetType } = state.editor;
          const isRenaming = mode === 'rename';
          const isDeleting = mode === 'delete_confirm';
          const isAnyActionInProgress = isRenaming || isDeleting;

          const isRenamingThis = isRenaming && targetType === type;
          const isDeletingThis = isDeleting && targetType === type;
          const isTargetDisabled = isAnyActionInProgress && targetType !== type;

          row.classList.toggle('is-disabled', isTargetDisabled);

          // Disable select box during any action (rename/delete) to prevent inconsistencies
          select.disabled = isAnyActionInProgress;
          // Visually gray out the select box and label even if the row itself isn't disabled (e.g. target row)
          select.style.opacity = isAnyActionInProgress ? '0.5' : '';
          if (targetType === type) {
            labelEl.style.opacity = isAnyActionInProgress ? '0.5' : '';
          } else {
            labelEl.style.opacity = '';
          }

          // Toggle Select / Input display
          select.style.display = isRenamingThis ? 'none' : 'block';
          renameInput.style.display = isRenamingThis ? 'block' : 'none';

          // Toggle button area display
          mainActions.style.visibility = !isRenamingThis && !isDeletingThis ? 'visible' : 'hidden';
          renameActions.style.display = isRenamingThis ? 'flex' : 'none';
          deleteConfirmGroup.style.display = isDeletingThis ? 'flex' : 'none';

          // Update Options (if not renaming)
          if (!isRenamingThis) {
            const profiles = this.cachedConfig?.texts || [];
            let keys = [];
            let activeKey = '';

            if (type === 'profile') {
              keys = profiles.map((p) => p.name);
              activeKey = activeProfile;
            } else {
              const profile = profiles.find((p) => p.name === activeProfile);
              keys = profile ? profile.categories.map((c) => c.name) : [];
              activeKey = activeCategory;
            }

            // Check signature to avoid unnecessary DOM thrashing
            const signature = keys.join('|');
            if (select.dataset.signature !== signature) {
              const currentScroll = select.scrollTop; // Save scroll position
              const options = keys.map((key, index) => ui.create('option', { value: key }, [`${index + 1}. ${key}`]));
              select.replaceChildren(...options);
              select.dataset.signature = signature;
              select.scrollTop = currentScroll; // Restore scroll position
            }
            if (select.value !== activeKey) {
              select.value = activeKey || '';
            }

            // Update Button States
            const index = keys.indexOf(activeKey);
            const setDisabled = (id, disabled) => {
              const btn = row.querySelector(`#${id}`);
              if (btn) btn.disabled = disabled;
            };

            setDisabled(`${APPID}-${type}-up-btn`, isAnyActionInProgress || index <= 0);
            setDisabled(`${APPID}-${type}-down-btn`, isAnyActionInProgress || index >= keys.length - 1);
            setDisabled(`${APPID}-${type}-delete-btn`, isAnyActionInProgress || keys.length <= 1);
            setDisabled(`${APPID}-${type}-new-btn`, isAnyActionInProgress);
            setDisabled(`${APPID}-${type}-copy-btn`, isAnyActionInProgress);
            setDisabled(`${APPID}-${type}-rename-btn`, isAnyActionInProgress);
          } else {
            // In rename mode, populate input if empty
            if (renameInput.value === '') {
              renameInput.value = type === 'profile' ? activeProfile : activeCategory;
            }
          }
        });

        return row;
      };

      container.appendChild(createControlRow('profile', 'Profile:'));
      container.appendChild(createControlRow('category', 'Category:'));

      // Global modal controls (Footer buttons disabled state)
      ui.observe('editor.mode', (state) => {
        const isAnyActionInProgress = state.editor.mode !== 'normal';
        const modalElement = this.modal?.element;
        if (!modalElement) return;

        const scrollArea = modalElement.querySelector(`.${cls.scrollableArea}`);
        if (scrollArea) scrollArea.classList.toggle('is-disabled', isAnyActionInProgress);

        const setDisabled = (selector) => {
          const el = modalElement.querySelector(selector);
          if (el) el.disabled = isAnyActionInProgress;
        };
        setDisabled(`#${APPID}-text-new-btn`);
        setDisabled(`#${APPID}-editor-modal-apply-btn`);
        setDisabled(`#${APPID}-editor-modal-save-btn`);
        setDisabled(`#${APPID}-editor-modal-cancel-btn`);
      });

      return container;
    }

    _renderTextListArea(ui) {
      const cls = ui.context.styles;
      const container = ui.create('div', { className: cls.scrollableArea });

      // Observe ONLY listFingerprint to avoid re-rendering on text input
      ui.observe('editor.listFingerprint', (state) => {
        const currentTexts = state.editor.currentTexts || [];
        const focusedIndex = state.editor.focusedIndex;

        container.replaceChildren(); // Clear list

        currentTexts.forEach((text, index) => {
          const textItem = ui.create('div', { className: cls.textItem, 'data-index': index });

          // Drag Handle
          textItem.appendChild(ui.create('div', { className: cls.dragHandle, title: 'Drag to reorder', draggable: 'true' }, [createIconFromDef(StyleDefinitions.ICONS.dragHandle)]));

          // Textarea
          const textarea = ui.create('textarea', {
            'data-index': index,
            rows: 3,
            value: text, // Initial value
          });

          // Input Handler: Update Store WITHOUT triggering Fingerprint update
          textarea.addEventListener('input', (e) => {
            this._handleTextPixelInput(index, e.target.value);
            this._autoResizeTextarea(e.target);
          });

          // Focus Handler: Track insertion point
          textarea.addEventListener('focus', () => {
            this.store.set('editor.focusedIndex', index);
          });

          textItem.appendChild(textarea);

          // Controls
          const controls = ui.create('div', { className: cls.itemControls });
          controls.append(
            ui.button(null, [createIconFromDef(StyleDefinitions.ICONS.up)], null, { className: `${cls.moveBtn} move-up-btn`, title: 'Move up', disabled: index === 0 }),
            ui.button(null, [createIconFromDef(StyleDefinitions.ICONS.down)], null, { className: `${cls.moveBtn} move-down-btn`, title: 'Move down', disabled: index === currentTexts.length - 1 }),
            ui.button(null, [createIconFromDef(StyleDefinitions.ICONS.delete)], null, { className: `${cls.deleteBtn} delete-btn`, title: 'Delete' })
          );
          textItem.appendChild(controls);

          container.appendChild(textItem);
        });

        // Post-render Layout & Focus restoration
        requestAnimationFrame(() => {
          container.querySelectorAll('textarea').forEach((ta) => this._autoResizeTextarea(ta));

          if (focusedIndex >= 0 && focusedIndex < currentTexts.length) {
            const target = container.querySelector(`textarea[data-index="${focusedIndex}"]`);
            if (target) {
              target.focus();
              const len = target.value.length;
              target.setSelectionRange(len, len);
            }
          }
        });
      });

      return container;
    }

    _setupEventListeners() {
      if (!this.modal) return;
      const modalElement = this.modal.element;
      const cls = this.styleHandle.classes;

      modalElement.addEventListener('click', (e) => {
        const target = e.target.closest('button');
        if (!target) return;

        const textItemControls = target.closest(`.${cls.itemControls}`);
        if (textItemControls) {
          const textItem = textItemControls.closest(`.${cls.textItem}`);
          const textarea = textItem.querySelector('textarea');
          const index = parseInt(textarea.dataset.index, 10);

          if (target.classList.contains('move-up-btn')) this._handleTextMove(index, -1, 'up');
          if (target.classList.contains('move-down-btn')) this._handleTextMove(index, 1, 'down');
          if (target.classList.contains('delete-btn')) this._handleTextDelete(index);
          return;
        }

        const actionMap = {
          // Profile Actions
          [`${APPID}-profile-new-btn`]: () => this._handleItemNew('profile'),
          [`${APPID}-profile-copy-btn`]: () => this._handleItemCopy('profile'),
          [`${APPID}-profile-delete-btn`]: () => this._enterDeleteConfirmationMode('profile'),
          [`${APPID}-profile-delete-confirm-btn`]: () => this._handleItemDelete(),
          [`${APPID}-profile-delete-cancel-btn`]: () => this._exitDeleteConfirmationMode(),
          [`${APPID}-profile-up-btn`]: () => this._handleItemMove('profile', -1),
          [`${APPID}-profile-down-btn`]: () => this._handleItemMove('profile', 1),
          [`${APPID}-profile-rename-btn`]: () => this._enterRenameMode('profile'),
          [`${APPID}-profile-rename-ok-btn`]: () => this._handleRenameConfirm('profile'),
          [`${APPID}-profile-rename-cancel-btn`]: () => this._exitRenameMode(true),
          // Category Actions
          [`${APPID}-category-new-btn`]: () => this._handleItemNew('category'),
          [`${APPID}-category-copy-btn`]: () => this._handleItemCopy('category'),
          [`${APPID}-category-delete-btn`]: () => this._enterDeleteConfirmationMode('category'),
          [`${APPID}-category-delete-confirm-btn`]: () => this._handleItemDelete(),
          [`${APPID}-category-delete-cancel-btn`]: () => this._exitDeleteConfirmationMode(),
          [`${APPID}-category-up-btn`]: () => this._handleItemMove('category', -1),
          [`${APPID}-category-down-btn`]: () => this._handleItemMove('category', 1),
          [`${APPID}-category-rename-btn`]: () => this._enterRenameMode('category'),
          [`${APPID}-category-rename-ok-btn`]: () => this._handleRenameConfirm('category'),
          [`${APPID}-category-rename-cancel-btn`]: () => this._exitRenameMode(true),
          // Text Actions
          [`${APPID}-text-new-btn`]: () => this._handleTextNew(),
        };
        const action = actionMap[target.id];
        if (action) action();
      });

      // Reflect values directly to Store (With Two-Step Commit)
      modalElement.addEventListener('change', async (e) => {
        if (e.target.matches(`#${APPID}-profile-select`)) {
          // 1. Prepare new state
          const newProfileName = e.target.value;
          const profiles = this.cachedConfig.texts || [];
          const activeProfileObj = profiles.find((p) => p.name === newProfileName);
          const newCategory = activeProfileObj?.categories[0]?.name || null;

          // 2. Update Store & Load new data
          this.store.set('editor.activeProfile', newProfileName);
          this.store.set('editor.activeCategory', newCategory);
          this._loadStateFromConfig(newProfileName, newCategory);
        } else if (e.target.matches(`#${APPID}-category-select`)) {
          // 1. Update Store & Load new data
          const newCategory = e.target.value;
          this.store.set('editor.activeCategory', newCategory);
          this._loadStateFromConfig(this.store.get('editor.activeProfile'), newCategory);
        }
      });

      // Input handling for validation visual cues
      modalElement.addEventListener('input', (e) => {
        const target = e.target;
        if (target.matches('textarea')) {
          target.classList.toggle('is-invalid', target.value.trim() === '');
          const footerMessage = this.modal?.dom?.footerMessage;
          if (footerMessage && footerMessage.textContent) {
            footerMessage.textContent = '';
          }
        }
        if (target.matches('input[type="text"]')) {
          target.classList.remove('is-invalid');
          const footerMessage = this.modal?.dom?.footerMessage;
          if (footerMessage && footerMessage.textContent) {
            footerMessage.textContent = '';
          }
        }
      });

      modalElement.addEventListener('keydown', (e) => {
        if (e.target.matches('input[type="text"]')) {
          if (e.key === 'Enter') {
            e.preventDefault();
            const type = e.target.id.includes('profile') ? 'profile' : 'category';
            this._handleRenameConfirm(type);
          }
        }
      });

      // Drag and Drop
      // Use the dynamic class for scrollable area
      const getScrollArea = () => modalElement.querySelector(`.${cls.scrollableArea}`);

      modalElement.addEventListener('dragstart', (e) => {
        const scrollArea = getScrollArea();
        if (!scrollArea) return;

        const handle = e.target.closest(`.${cls.dragHandle}`);
        if (handle) {
          const target = handle.closest(`.${cls.textItem}`);
          if (target && scrollArea.contains(target)) {
            this.draggedIndex = parseInt(target.dataset.index, 10);
            setTimeout(() => target.classList.add('dragging'), 0);
          }
        }
      });

      modalElement.addEventListener('dragend', () => {
        const scrollArea = getScrollArea();
        if (!scrollArea || this.draggedIndex === null) return;

        const draggingElement = scrollArea.querySelector('.dragging');
        if (draggingElement) draggingElement.classList.remove('dragging');
        this.draggedIndex = null;
      });

      modalElement.addEventListener('dragover', (e) => {
        const scrollArea = getScrollArea();
        if (!scrollArea || !scrollArea.contains(e.target)) return;

        e.preventDefault();
        const draggingElement = scrollArea.querySelector('.dragging');
        if (!draggingElement) return;

        scrollArea.querySelectorAll(`.${cls.textItem}`).forEach((el) => {
          el.classList.remove('drag-over-top', 'drag-over-bottom');
        });

        const target = e.target.closest(`.${cls.textItem}`);
        if (target && target !== draggingElement) {
          const box = target.getBoundingClientRect();
          const isAfter = e.clientY > box.top + box.height / 2;
          target.classList.toggle('drag-over-bottom', isAfter);
          target.classList.toggle('drag-over-top', !isAfter);
        } else if (!target) {
          const lastElement = scrollArea.querySelector(`.${cls.textItem}:last-child:not(.dragging)`);
          if (lastElement) {
            lastElement.classList.add('drag-over-bottom');
          }
        }
      });

      modalElement.addEventListener('drop', (e) => {
        const scrollArea = getScrollArea();
        if (!scrollArea || !scrollArea.contains(e.target)) return;

        e.preventDefault();
        if (this.draggedIndex === null) return;

        const dropTarget = scrollArea.querySelector('.drag-over-top, .drag-over-bottom');
        // Calculate drop index
        let dropIndex = -1;

        if (dropTarget) {
          const targetIndex = parseInt(dropTarget.dataset.index, 10);
          if (dropTarget.classList.contains('drag-over-bottom')) {
            dropIndex = targetIndex + 1;
          } else {
            dropIndex = targetIndex;
          }
          // Adjust dropIndex if moving downwards
          if (this.draggedIndex < dropIndex) {
            dropIndex--;
          }
        } else {
          // Check if dropped at the end
          const lastElement = scrollArea.querySelector(`.${cls.textItem}:last-child:not(.dragging)`);
          if (lastElement && lastElement.classList.contains('drag-over-bottom')) {
            dropIndex = this.store.get('editor.currentTexts').length - 1;
          }
        }

        // Cleanup visual cues
        scrollArea.querySelectorAll(`.${cls.textItem}`).forEach((el) => {
          el.classList.remove('drag-over-top', 'drag-over-bottom');
        });

        // Perform move if valid
        if (dropIndex !== -1 && dropIndex !== this.draggedIndex) {
          this._handleTextMove(this.draggedIndex, dropIndex - this.draggedIndex, 'drag');
        }
      });
    }

    /**
     * Commit Store's currentTexts to cachedConfig (Array-based).
     * Must be called before Profile/Category switch or Save.
     */
    _saveCurrentStateToConfig() {
      const activeProfile = this.store.get('editor.activeProfile');
      const activeCategory = this.store.get('editor.activeCategory');
      const currentTexts = this.store.get('editor.currentTexts');

      const profileObj = this.cachedConfig.texts.find((p) => p.name === activeProfile);
      if (profileObj) {
        const categoryObj = profileObj.categories.find((c) => c.name === activeCategory);
        if (categoryObj) {
          categoryObj.items = [...currentTexts];
        }
      }
    }

    /**
     * Load data from Config cache to Store (Array-based).
     * Call on initialization or Profile/Category switch.
     */
    _loadStateFromConfig(profileName, categoryName) {
      const profileObj = this.cachedConfig.texts.find((p) => p.name === profileName);
      const categoryObj = profileObj ? profileObj.categories.find((c) => c.name === categoryName) : null;
      const texts = categoryObj ? categoryObj.items : [];

      this.store.set('editor.currentTexts', [...texts]); // Deep copy
      this.store.set('editor.listFingerprint', Date.now()); // Force render
      this.store.set('editor.focusedIndex', -1);
    }

    /**
     * Handler for text pixel input (No re-render)
     */
    _handleTextPixelInput(index, value) {
      const currentTexts = this.store.get('editor.currentTexts');
      if (currentTexts && currentTexts[index] !== undefined) {
        // Create a shallow copy to respect Read-Only contract
        const newTexts = [...currentTexts];
        newTexts[index] = value;
        // Update Store, but Fingerprint check in onUpdate prevents DOM rebuild
        this.store.set('editor.currentTexts', newTexts);
      }
    }

    async _exitRenameMode(refresh = false) {
      const currentMode = this.store.get('editor.mode');
      if (currentMode !== 'rename') return;

      const type = this.store.get('editor.targetType');

      this.store.set('editor.mode', 'normal');
      this.store.set('editor.targetType', null);

      if (this.modal) {
        const input = this.modal.element.querySelector(`#${APPID}-${type}-rename-input`);
        if (input) {
          input.value = ''; // Clear value to prevent flashing on next open
          input.classList.remove('is-invalid');
        }
        const footerMessage = this.modal.dom.footerMessage;
        if (footerMessage) footerMessage.textContent = '';
      }

      // When forced update is required (e.g., on cancel, to restore original value)
      if (refresh) {
        this.store.set('editor.lastUpdated', Date.now());
      }
    }

    _getDragAfterElement(container, y) {
      const cls = this.styleHandle.classes;
      const draggableElements = [...container.querySelectorAll(`.${cls.textItem}:not(.dragging)`)];
      return draggableElements.reduce(
        (closest, child) => {
          const box = child.getBoundingClientRect();
          const offset = y - box.top - box.height / 2;
          if (offset < 0 && offset > closest.offset) {
            return { offset: offset, element: child };
          } else {
            return closest;
          }
        },
        { offset: Number.NEGATIVE_INFINITY }
      ).element;
    }

    _autoResizeTextarea(textarea) {
      if (!textarea) return;
      // Temporarily set height to auto to allow the textarea to shrink if text is deleted.
      // Use requestAnimationFrame to prevent layout thrashing during rapid typing.
      requestAnimationFrame(() => {
        textarea.style.height = 'auto';
        // Set the height to its scrollHeight to fit the content.
        textarea.style.height = `${textarea.scrollHeight}px`;
      });
    }

    _handleTextNew() {
      // 1. Modify data
      const currentTexts = [...this.store.get('editor.currentTexts')];
      const focusedIndex = this.store.get('editor.focusedIndex');
      let insertIndex;

      // Determine insertion position: after the focused item, or at the end
      if (focusedIndex >= 0 && focusedIndex < currentTexts.length) {
        insertIndex = focusedIndex + 1;
      } else {
        insertIndex = currentTexts.length;
      }

      currentTexts.splice(insertIndex, 0, ''); // Insert empty text

      // 2. Update Store to trigger render
      // Set focusedIndex BEFORE triggering render via listFingerprint
      this.store.set('editor.currentTexts', currentTexts);
      this.store.set('editor.focusedIndex', insertIndex); // Focus new item
      this.store.set('editor.listFingerprint', Date.now());
    }

    _handleTextDelete(index) {
      // 1. Modify data
      const currentTexts = this.store.get('editor.currentTexts');
      const newTexts = currentTexts.filter((_, i) => i !== index);

      // 2. Update Store to trigger render
      this.store.set('editor.currentTexts', newTexts);
      this.store.set('editor.focusedIndex', -1); // Do not focus textarea
      this.store.set('editor.listFingerprint', Date.now());
    }

    _handleTextMove(index, direction, btnType) {
      // 1. Modify data
      const currentTexts = [...this.store.get('editor.currentTexts')];
      const newIndex = index + direction;

      if (newIndex < 0 || newIndex >= currentTexts.length) return;

      // Remove from old position
      const [movedItem] = currentTexts.splice(index, 1);
      // Insert at new position
      currentTexts.splice(newIndex, 0, movedItem);

      // 2. Update Store to trigger render
      this.store.set('editor.currentTexts', currentTexts);
      this.store.set('editor.focusedIndex', -1); // Do not focus textarea
      this.store.set('editor.listFingerprint', Date.now());

      // 3. Restore Button Focus
      requestAnimationFrame(() => {
        if (!this.modal) return;
        const cls = this.styleHandle.classes;
        const scrollArea = this.modal.element.querySelector(`.${cls.scrollableArea}`);
        if (!scrollArea) return;

        // Find the row at the new index
        const targetRow = scrollArea.querySelector(`.${cls.textItem}[data-index="${newIndex}"]`);
        if (targetRow) {
          const selector = btnType === 'up' ? '.move-up-btn' : '.move-down-btn';
          const btn = targetRow.querySelector(selector);
          if (btn && !btn.disabled) {
            btn.focus();
          }
        }
      });
    }

    /**
     * @private
     * @param {'profile' | 'category'} itemType The type of item to create.
     */
    async _handleItemNew(itemType) {
      const newConfig = deepClone(this.cachedConfig);
      const activeProfileName = this.store.get('editor.activeProfile');
      const activeCategoryName = this.store.get('editor.activeCategory');

      if (itemType === 'profile') {
        const profiles = newConfig.texts;
        const activeIndex = profiles.findIndex((p) => p.name === activeProfileName);
        // Insert after current or at end
        const insertIndex = activeIndex >= 0 ? activeIndex + 1 : profiles.length;

        const newName = proposeUniqueName('New Profile', profiles);
        const newProfile = {
          name: newName,
          categories: [{ name: 'New Category', items: [] }],
        };

        profiles.splice(insertIndex, 0, newProfile);

        await this.callbacks.onSave(newConfig);
        this.cachedConfig = newConfig;

        this.store.set('editor.activeProfile', newName);
        this.store.set('editor.activeCategory', 'New Category');
      } else {
        const profile = newConfig.texts.find((p) => p.name === activeProfileName);
        if (!profile) return;

        const categories = profile.categories;
        const activeIndex = categories.findIndex((c) => c.name === activeCategoryName);
        const insertIndex = activeIndex >= 0 ? activeIndex + 1 : categories.length;

        const newName = proposeUniqueName('New Category', categories);
        const newCategory = {
          name: newName,
          items: [],
        };

        categories.splice(insertIndex, 0, newCategory);

        // Update config (since profile is a reference to object inside newConfig, it's updated)
        await this.callbacks.onSave(newConfig);
        this.cachedConfig = newConfig;

        this.store.set('editor.activeCategory', newName);
      }

      // Reload texts
      this._loadStateFromConfig(this.store.get('editor.activeProfile'), this.store.get('editor.activeCategory'));

      // Enter rename mode
      this._enterRenameMode(itemType);
    }

    /**
     * @private
     * @param {'profile' | 'category'} itemType The type of item to copy.
     */
    async _handleItemCopy(itemType) {
      const newConfig = deepClone(this.cachedConfig);
      const activeProfileName = this.store.get('editor.activeProfile');
      const activeCategoryName = this.store.get('editor.activeCategory');

      if (itemType === 'profile') {
        const profiles = newConfig.texts;
        const activeIndex = profiles.findIndex((p) => p.name === activeProfileName);
        if (activeIndex === -1) return;

        const sourceProfile = profiles[activeIndex];
        const newName = proposeUniqueName(`${sourceProfile.name} Copy`, profiles);

        const newProfile = deepClone(sourceProfile);
        newProfile.name = newName;

        profiles.splice(activeIndex + 1, 0, newProfile);

        await this.callbacks.onSave(newConfig);
        this.cachedConfig = newConfig;

        this.store.set('editor.activeProfile', newName);
        this.store.set('editor.activeCategory', newProfile.categories[0]?.name || null);
      } else {
        const profile = newConfig.texts.find((p) => p.name === activeProfileName);
        if (!profile) return;

        const categories = profile.categories;
        const activeIndex = categories.findIndex((c) => c.name === activeCategoryName);
        if (activeIndex === -1) return;

        const sourceCategory = categories[activeIndex];
        const newName = proposeUniqueName(`${sourceCategory.name} Copy`, categories);

        const newCategory = deepClone(sourceCategory);
        newCategory.name = newName;

        categories.splice(activeIndex + 1, 0, newCategory);

        await this.callbacks.onSave(newConfig);
        this.cachedConfig = newConfig;

        this.store.set('editor.activeCategory', newName);
      }

      // Reload texts
      this._loadStateFromConfig(this.store.get('editor.activeProfile'), this.store.get('editor.activeCategory'));
    }

    /**
     * @private
     */
    async _handleItemDelete() {
      const itemType = this.store.get('editor.targetType');
      const keyToDelete = this.store.get('editor.targetKey'); // name

      if (!keyToDelete) {
        this._exitDeleteConfirmationMode();
        return;
      }

      const newConfig = deepClone(this.cachedConfig);

      if (itemType === 'profile') {
        const profiles = newConfig.texts;
        const indexToDelete = profiles.findIndex((p) => p.name === keyToDelete);

        if (indexToDelete !== -1) {
          profiles.splice(indexToDelete, 1);

          // Determine next profile to show
          const nextIndex = Math.min(indexToDelete, profiles.length - 1);
          const nextProfile = profiles[nextIndex] || profiles[0];
          const nextViewKey = nextProfile?.name || null;

          if (newConfig.options.activeProfileName === keyToDelete) {
            newConfig.options.activeProfileName = nextViewKey;
          }

          await this.callbacks.onSave(newConfig);
          this.cachedConfig = newConfig;

          this.store.set('editor.activeProfile', nextViewKey);
          this.store.set('editor.activeCategory', nextProfile?.categories[0]?.name || null);
        }
      } else {
        const activeProfileName = this.store.get('editor.activeProfile');
        const profile = newConfig.texts.find((p) => p.name === activeProfileName);

        if (profile) {
          const categories = profile.categories;
          const indexToDelete = categories.findIndex((c) => c.name === keyToDelete);

          if (indexToDelete !== -1) {
            categories.splice(indexToDelete, 1);

            // Determine next category
            const nextIndex = Math.min(indexToDelete, categories.length - 1);
            const nextCategory = categories[nextIndex] || categories[0];
            const nextViewKey = nextCategory?.name || null;

            await this.callbacks.onSave(newConfig);
            this.cachedConfig = newConfig;

            this.store.set('editor.activeCategory', nextViewKey);
          }
        }
      }

      // Reload texts
      this._loadStateFromConfig(this.store.get('editor.activeProfile'), this.store.get('editor.activeCategory'));

      this._exitDeleteConfirmationMode();
    }

    /**
     * @private
     * @param {'profile' | 'category'} itemType The type of item to move.
     * @param {number} direction The direction to move (-1 for up, 1 for down).
     */
    async _handleItemMove(itemType, direction) {
      const newConfig = deepClone(this.cachedConfig);
      const activeProfileName = this.store.get('editor.activeProfile');
      const activeCategoryName = this.store.get('editor.activeCategory');
      let targetArray;
      let activeKey;

      if (itemType === 'profile') {
        targetArray = newConfig.texts;
        activeKey = activeProfileName;
      } else {
        const profile = newConfig.texts.find((p) => p.name === activeProfileName);
        if (!profile) return;
        targetArray = profile.categories;
        activeKey = activeCategoryName;
      }

      const currentIndex = targetArray.findIndex((item) => item.name === activeKey);

      if (currentIndex === -1) return;
      const newIndex = currentIndex + direction;
      if (newIndex < 0 || newIndex >= targetArray.length) return;

      // Move using splice (Arrays maintain order correctly)
      const [movedItem] = targetArray.splice(currentIndex, 1);
      targetArray.splice(newIndex, 0, movedItem);

      await this.callbacks.onSave(newConfig);
      this.cachedConfig = newConfig;

      // Value hasn't changed, but trigger forced update for reordering UI
      this.store.set('editor.lastUpdated', Date.now());
    }

    async _handleSaveAction(shouldClose) {
      const footerMessage = this.modal?.dom?.footerMessage;
      if (footerMessage) {
        // Clear previous messages (unless it's a conflict warning)
        if (!footerMessage.classList.contains(this.commonStyleHandle.classes.conflictText)) {
          footerMessage.textContent = '';
          footerMessage.style.color = '';
        }
      }
      this.modal.element.querySelectorAll('.is-invalid').forEach((el) => el.classList.remove('is-invalid'));

      // 1. Validate
      const currentTexts = this.store.get('editor.currentTexts');
      const cls = this.styleHandle.classes;

      if (currentTexts.some((t) => t.trim() === '')) {
        // Mark invalid elements manually since we synced to store
        const scrollArea = this.modal.element.querySelector(`.${cls.scrollableArea}`);
        if (scrollArea) {
          scrollArea.querySelectorAll('textarea').forEach((ta, i) => {
            if (currentTexts[i].trim() === '') ta.classList.add('is-invalid');
          });
        }
        if (footerMessage) {
          footerMessage.textContent = 'Text fields cannot be empty.';
          footerMessage.style.color = `var(${StyleDefinitions.VARS.TEXT_DANGER})`;
        }
        return;
      }

      // 2. Commit to Config
      this._saveCurrentStateToConfig();

      // 3. Save
      try {
        await this.callbacks.onSave(this.cachedConfig);
        this.cachedConfig = deepClone(this.cachedConfig); // Re-clone to be safe

        if (shouldClose) {
          this.close();
        }
      } catch (e) {
        // Primary error handling is done via events (e.g. CONFIG_SIZE_EXCEEDED).
        // This catch block serves as a fallback for other unexpected errors.
        if (footerMessage) {
          footerMessage.textContent = e.message;
          footerMessage.style.color = `var(${StyleDefinitions.VARS.TEXT_DANGER})`;
        }
      }
    }

    _enterRenameMode(type) {
      const currentMode = this.store.get('editor.mode');
      if (currentMode === 'rename') return;

      const currentValue = type === 'profile' ? this.store.get('editor.activeProfile') : this.store.get('editor.activeCategory');

      this.store.set('editor.mode', 'rename');
      this.store.set('editor.targetType', type);

      // Set focus and initial value after UI update
      requestAnimationFrame(() => {
        if (this.modal) {
          const input = this.modal.element.querySelector(`#${APPID}-${type}-rename-input`);
          if (input) {
            input.value = currentValue || ''; // Force update to current selection
            input.focus();
            input.select();
          }
        }
      });
    }

    _enterDeleteConfirmationMode(itemType) {
      if (this.store.get('editor.mode') !== 'normal') return;

      const keyToDelete = itemType === 'profile' ? this.store.get('editor.activeProfile') : this.store.get('editor.activeCategory');

      if (!keyToDelete) return;

      this.store.set('editor.mode', 'delete_confirm');
      this.store.set('editor.targetType', itemType);
      this.store.set('editor.targetKey', keyToDelete);
    }

    _exitDeleteConfirmationMode() {
      this.store.set('editor.mode', 'normal');
      this.store.set('editor.targetType', null);
      this.store.set('editor.targetKey', null);
    }

    async _handleRenameConfirm(type) {
      const footerMessage = this.modal?.dom?.footerMessage;
      const input = this.modal.element.querySelector(`#${APPID}-${type}-rename-input`);
      const newName = input.value.trim();
      const oldName = type === 'profile' ? this.store.get('editor.activeProfile') : this.store.get('editor.activeCategory');

      if (!newName) {
        if (footerMessage) {
          footerMessage.textContent = `${type.charAt(0).toUpperCase() + type.slice(1)} name cannot be empty.`;
          footerMessage.style.color = `var(${StyleDefinitions.VARS.TEXT_DANGER})`;
        }
        input.classList.add('is-invalid');
        return;
      }

      // Check for duplicates
      const config = this.cachedConfig;
      let itemsToCheck;
      if (type === 'profile') {
        itemsToCheck = config.texts;
      } else {
        const profile = config.texts.find((p) => p.name === this.store.get('editor.activeProfile'));
        itemsToCheck = profile ? profile.categories : [];
      }

      if (newName.toLowerCase() !== oldName.toLowerCase() && itemsToCheck.some((item) => item.name.toLowerCase() === newName.toLowerCase())) {
        if (footerMessage) {
          footerMessage.textContent = `Name "${newName}" is already in use.`;
          footerMessage.style.color = `var(${StyleDefinitions.VARS.TEXT_DANGER})`;
        }
        input.classList.add('is-invalid');
        return;
      }

      // Notify UIManager about the rename BEFORE saving to maintain session consistency
      if (this.callbacks.onRename) {
        const activeProfile = this.store.get('editor.activeProfile');
        this.callbacks.onRename(type, oldName, newName, activeProfile);
      }

      const newConfig = deepClone(config);

      if (type === 'profile') {
        const profile = newConfig.texts.find((p) => p.name === oldName);
        if (profile) {
          profile.name = newName; // Simply update the property
        }

        // Update activeProfileName option if needed
        if (config.options.activeProfileName === oldName) {
          newConfig.options.activeProfileName = newName;
        }

        await this.callbacks.onSave(newConfig);
        this.cachedConfig = newConfig;

        this.store.set('editor.activeProfile', newName);
      } else {
        const activeProfileName = this.store.get('editor.activeProfile');
        const profile = newConfig.texts.find((p) => p.name === activeProfileName);
        if (profile) {
          const category = profile.categories.find((c) => c.name === oldName);
          if (category) {
            category.name = newName; // Simply update the property
          }
        }

        await this.callbacks.onSave(newConfig);
        this.cachedConfig = newConfig;

        this.store.set('editor.activeCategory', newName);
      }

      this._exitRenameMode(true);
    }

    getContextForReopen() {
      const activeCategory = this.store ? this.store.get('editor.activeCategory') : null;
      return { type: CONSTANTS.MODAL_TYPES.TEXT_EDITOR, key: activeCategory };
    }
  }

  /**
   * Manages the JSON editing modal by using the CustomModal component.
   */
  class JsonModalComponent extends UIComponentBase {
    constructor(callbacks) {
      super(callbacks);
      this.modal = null; // To hold the CustomModal instance
      this.store = null;
      this.styleHandle = null;
      this.commonStyleHandle = null;
      this.debouncedUpdateSize = debounce((text) => this._updateSizeDisplay(text), 300, true);
      /** @type {Array<() => void>} */
      this._uiSubscriptions = []; // Transient subscriptions for the current render cycle
    }

    render() {
      this.styleHandle = StyleManager.request(StyleDefinitions.getJsonModal);
      this.commonStyleHandle = StyleManager.request(StyleDefinitions.getCommon);
    }

    async open(anchorElement) {
      if (this.modal || this.isOpening) return; // Prevent re-entry
      this.callbacks.onModalOpenStateChange?.(true);

      // Ensure styles are ready
      if (!this.styleHandle) this.render();

      this.isOpening = true;

      try {
        const cls = this.styleHandle.classes;
        const commonCls = this.commonStyleHandle.classes;

        this.modal = new CustomModal({
          title: `${APPNAME} Settings`,
          width: 'min(440px, 95vw)',
          closeOnBackdropClick: true,
          buttons: [
            { text: 'Export', id: `${APPID}-json-modal-export-btn`, className: '', onClick: () => this._handleExport() },
            { text: 'Import', id: `${APPID}-json-modal-import-btn`, className: '', onClick: () => this._handleImport() },
            { text: 'Cancel', id: `${APPID}-json-modal-cancel-btn`, className: commonCls.pushRightBtn, onClick: () => this.close() },
            { text: 'Save', id: `${APPID}-json-modal-save-btn`, className: commonCls.primaryBtn, onClick: () => this._handleSave() },
          ],
          onDestroy: () => {
            this.debouncedUpdateSize.cancel();
            // Clean up UI subscriptions
            this._uiSubscriptions.forEach((unsub) => unsub());
            this._uiSubscriptions = [];

            this.callbacks.onModalOpenStateChange?.(false);
            this.store = null;
            this.modal = null;
          },
        });

        // Apply scoped root class to prevent style leak
        this.modal.element.classList.add(cls.modalRoot);

        // Set content specific styles using mapped class names
        const contentContainer = this.modal.getContentContainer();
        contentContainer.classList.add(cls.statusContainer); // Use container class for padding/layout if needed

        this.store = new ReactiveStore({
          json: {
            editor: '',
            status: { text: '' },
            sizeInfo: { text: 'Checking size...' },
          },
        });

        // Render content using UIBuilder
        const content = this._renderJsonContent();
        this.modal.setContent(content);

        this.callbacks.onModalOpen?.(); // Notify UIManager

        const config = await this.callbacks.getCurrentConfig();
        const initialText = JSON.stringify(config, null, 2);
        this.store.set('json.editor', initialText);
        this._updateSizeDisplay(initialText);

        this.modal.show(anchorElement);

        // Defer focus and scroll adjustment until the modal is visible
        requestAnimationFrame(() => {
          const textarea = this.modal.getContentContainer().querySelector(`textarea`);
          if (!textarea) return;
          textarea.focus();
          textarea.scrollTop = 0;
          textarea.selectionStart = 0;
          textarea.selectionEnd = 0;
        });
      } finally {
        this.isOpening = false;
      }
    }

    close() {
      if (this.modal) {
        this.modal.close();
      }
    }

    /**
     * @override
     * @protected
     */
    _onDestroy() {
      this.debouncedUpdateSize.cancel();
      this._uiSubscriptions.forEach((unsub) => unsub());
      this._uiSubscriptions = [];
      this.store = null;
      this.modal?.destroy();
      super._onDestroy();
    }

    _renderJsonContent() {
      const cls = { ...this.commonStyleHandle.classes, ...this.styleHandle.classes };

      const context = { styles: cls, store: this.store };
      const uiDisposer = (unsub) => {
        if (typeof unsub === 'function') {
          this._uiSubscriptions.push(unsub);
        }
      };
      const ui = new UIBuilder(this.store, context, uiDisposer);

      const container = document.createDocumentFragment();

      // 1. JSON Editor (Manual binding for textarea)
      const textarea = ui.create('textarea', {
        className: cls.editor || `${APPID}-json-editor`,
        spellcheck: false,
      });

      // Store -> UI
      ui.observe('json.editor', (state) => {
        if (document.activeElement === textarea) return;
        if (textarea instanceof HTMLTextAreaElement) {
          textarea.value = state.json?.editor || '';
        }
      });

      // UI -> Store
      textarea.addEventListener('input', (e) => {
        const target = e.target;
        if (target instanceof HTMLTextAreaElement) {
          const value = target.value;
          this.store.set('json.editor', value);
          this.debouncedUpdateSize(value || '');
        }
      });

      const contentWrapper = ui.container([textarea], { className: 'content' }); // Mapped to cls.content
      container.appendChild(contentWrapper);

      // 2. Status Row
      const statusMsg = ui.create('div', { className: cls.msg });
      const sizeInfo = ui.create('div', { className: cls.sizeInfo });

      ui.observe(['json.status', 'json.sizeInfo'], (state) => {
        const status = state.json?.status || {};
        const info = state.json?.sizeInfo || {};

        // Update Status Message
        if (statusMsg.textContent !== (status.text || '')) {
          statusMsg.textContent = status.text || '';
          statusMsg.style.color = status.color || '';
        }

        // Update Size Info
        if (sizeInfo.textContent !== (info.text || '')) {
          sizeInfo.textContent = info.text || '';
          sizeInfo.style.color = info.color || '';
          sizeInfo.style.fontWeight = info.bold ? 'bold' : 'normal';
          if (sizeInfo instanceof HTMLElement) {
            sizeInfo.title = info.title || '';
          }
        }
      });

      const statusRow = ui.container([statusMsg, sizeInfo], { className: 'statusRow' }); // Mapped to cls.statusRow
      container.appendChild(statusRow);

      return container;
    }

    async _handleSave() {
      try {
        this._setStatus('');
        const jsonText = this.store?.get('json.editor') || '';
        const obj = JSON.parse(jsonText);
        await this.callbacks.onSave(obj);
        this.close();
      } catch (e) {
        this._setStatus(e.message, `var(${StyleDefinitions.VARS.TEXT_DANGER})`);
      }
    }

    async _handleExport() {
      try {
        this._setStatus('');

        const config = await this.callbacks.getCurrentConfig();
        const jsonString = JSON.stringify(config, null, 2);
        const blob = new Blob([jsonString], { type: 'application/json' });
        const url = URL.createObjectURL(blob);
        const a = h('a', {
          href: url,
          download: `${APPID}_config.json`,
        });

        if (a instanceof HTMLElement) {
          a.click();
        }

        // Revoke the URL after a delay to ensure the download has time to start.
        setTimeout(() => URL.revokeObjectURL(url), 10000);
        this._setStatus('Export successful.', `var(${StyleDefinitions.VARS.TEXT_ACCENT})`);
      } catch (e) {
        this._setStatus(`Export failed: ${e.message}`, `var(${StyleDefinitions.VARS.TEXT_DANGER})`);
      }
    }

    _handleImport() {
      const textarea = this.modal.getContentContainer().querySelector(`textarea`);
      if (!(textarea instanceof HTMLTextAreaElement)) return;

      const fileInput = h('input', {
        type: 'file',
        accept: 'application/json',
        onchange: (event) => {
          const target = event.target;
          if (!(target instanceof HTMLInputElement)) return;

          const file = target.files?.[0];
          // Reset input value to allow re-importing the same file if needed
          target.value = '';

          if (!file) return;

          const reader = new FileReader();

          // Step 1: Update UI immediately upon load completion
          reader.onload = (e) => {
            this._setStatus('Processing...');
            document.body.style.cursor = 'wait';

            // Step 2: Defer heavy processing to allow UI to render
            requestAnimationFrame(() => {
              // Guard: Check if modal is still open before proceeding
              if (!this.modal || !textarea.isConnected) {
                document.body.style.cursor = '';
                return;
              }

              try {
                const readerTarget = e.target;
                if (readerTarget && typeof readerTarget.result === 'string') {
                  // Heavy operations
                  const importedConfig = JSON.parse(readerTarget.result);
                  const jsonString = JSON.stringify(importedConfig, null, 2);

                  this.store?.set('json.editor', jsonString);
                  this._updateSizeDisplay(jsonString);

                  this._setStatus('Import successful. Click "Save" to apply.', `var(${StyleDefinitions.VARS.TEXT_ACCENT})`);
                }
              } catch (err) {
                this._setStatus(`Import failed: ${err.message}`, `var(${StyleDefinitions.VARS.TEXT_DANGER})`);
              } finally {
                document.body.style.cursor = '';
              }
            });
          };

          reader.onerror = () => {
            this._setStatus('Failed to read file.', `var(${StyleDefinitions.VARS.TEXT_DANGER})`);
          };

          // Initial status
          this._setStatus('Reading file...');
          reader.readAsText(file);
        },
      });

      if (fileInput instanceof HTMLElement) {
        fileInput.click();
      }
    }

    getContextForReopen() {
      return { type: 'json' };
    }

    _updateSizeDisplay(text) {
      const container = this.modal?.getContentContainer();
      if (!container) return;

      let sizeInBytes = 0;
      let isRaw = false;

      try {
        // Try to parse and minify to get the actual storage size
        const obj = JSON.parse(text);
        const minified = JSON.stringify(obj);
        sizeInBytes = new TextEncoder().encode(minified).length;
      } catch {
        // Fallback to raw text size if parsing fails
        sizeInBytes = new TextEncoder().encode(text).length;
        isRaw = true;
      }

      const sizeStr = this._formatBytes(sizeInBytes);
      const recommendedStr = this._formatBytes(CONSTANTS.CONFIG_SIZE_RECOMMENDED_LIMIT_BYTES);
      const limitStr = this._formatBytes(CONSTANTS.CONFIG_SIZE_LIMIT_BYTES);

      // Display format: 1.23 MB / 5.00 MB (Max: 10.00 MB)
      let color = '';
      let bold = false;
      const v = StyleDefinitions.VARS;

      // Use colors directly or vars
      if (sizeInBytes >= CONSTANTS.CONFIG_SIZE_LIMIT_BYTES) {
        color = `var(${v.TEXT_DANGER})`;
        bold = true;
      } else if (sizeInBytes >= CONSTANTS.CONFIG_SIZE_RECOMMENDED_LIMIT_BYTES) {
        color = `var(${v.TEXT_WARNING})`;
        bold = true;
      }
      const sizeText = `${isRaw ? '(Raw) ' : ''}${sizeStr} / ${recommendedStr} (Max: ${limitStr})`;
      this.store?.set('json.sizeInfo', {
        text: sizeText,
        color,
        bold,
        title: `Recommended Limit: ${recommendedStr} (Warns if exceeded)\nHard Limit: ${limitStr} (Cannot save if exceeded)`,
      });
    }

    _setStatus(text, color = '') {
      this.store?.set('json.status', { text, color, bold: false });
    }

    _formatBytes(bytes) {
      if (bytes === 0) return '0 B';
      const k = 1024;
      const sizes = ['B', 'KB', 'MB'];
      const i = Math.floor(Math.log(bytes) / Math.log(k));
      return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    }
  }

  /**
   * Manages the Shortcut Cheat Sheet modal.
   */
  class ShortcutModalComponent extends UIComponentBase {
    constructor(callbacks) {
      super(callbacks);
      this.modal = null;
      this.styleHandle = null;
    }

    render() {
      this.styleHandle = StyleManager.request(StyleDefinitions.getShortcutModal);
    }

    open(anchorElement) {
      if (this.modal) return; // Prevent re-entry

      // Ensure styles are ready
      if (!this.styleHandle) this.render();

      const cls = this.styleHandle.classes;
      const commonCls = StyleManager.request(StyleDefinitions.getCommon).classes;

      this.modal = new CustomModal({
        title: `${APPNAME} Keyboard Shortcuts`,
        width: 'min(400px, 95vw)',
        closeOnBackdropClick: true,
        buttons: [{ text: 'Close', id: `${APPID}-shortcut-close-btn`, className: commonCls.primaryBtn, onClick: () => this.close() }],
        onDestroy: () => {
          this.modal = null;
        },
      });

      // Helper to create grid rows
      const createRow = (keys, desc, separator = ' + ') => {
        const keyElements = keys
          .map((k, i) => {
            const els = [h(`span.${cls.key}`, k)];
            if (i < keys.length - 1) els.push(h('span', separator));
            return els;
          })
          .flat();

        return [h(`div.${cls.keyGroup}`, keyElements), h(`div.${cls.desc}`, desc)];
      };

      const createHeader = (text) => h(`div.${cls.sectionHeader}`, text);

      const content = h(`div.${cls.grid}`, [
        createHeader('Global'),
        ...createRow(['Alt', 'Q'], 'Toggle Text List (If enabled)'),

        createHeader('Navigation'),
        ...createRow(['↑ / ↓'], 'Select Item'),
        ...createRow(['← / →'], 'Switch Category'),
        ...createRow(['Ctrl', '← / →'], 'Switch Profile'),
        ...createRow(['Ctrl', '↑ / ↓'], 'Toggle Profile List'),

        createHeader('Action'),
        ...createRow(['Enter', 'Space', 'Tab'], 'Select item', ' / '),
        ...createRow(['Esc'], 'Close Text List'),
      ]);

      this.modal.setContent(content);
      this.modal.show();
    }

    close() {
      this.modal?.close();
    }

    /**
     * @override
     * @protected
     */
    _onDestroy() {
      this.close();
      super._onDestroy();
    }
  }

  class InsertButtonComponent extends UIComponentBase {
    constructor(callbacks, options) {
      super(callbacks);
      this.options = options;
      this.styleHandle = null;
    }

    /**
     * Updates the button's title (tooltip) dynamically.
     * @param {string} title The new title text.
     */
    setTitle(title) {
      this.options.title = title;
      if (this.element) {
        this.element.title = title;
      }
    }

    /**
     * Renders the settings button element and appends it to the document body.
     * @returns {HTMLElement} The created button element.
     */
    render() {
      // Use StyleManager
      this.styleHandle = StyleManager.request(StyleDefinitions.getInsertButton);
      const cls = this.styleHandle.classes;

      // Remove existing if any (safety check)
      const oldElement = document.getElementById(cls.buttonId);
      if (oldElement) oldElement.remove();

      this.element = h('button', {
        type: 'button',
        id: cls.buttonId,
        title: this.options.title,
        // Prevent focus loss from the input area
        onmousedown: (e) => e.preventDefault(),
        // Restore event handlers directly on the element
        onclick: (e) => {
          e.stopPropagation();
          this.callbacks.onClick?.(e);
        },
        oncontextmenu: (e) => {
          e.preventDefault();
          e.stopPropagation();
          this.callbacks.onContextMenu?.(e);
        },
        onmouseenter: (e) => this.callbacks.onMouseEnter?.(e),
        onmouseleave: (e) => this.callbacks.onMouseLeave?.(e),
      });

      // Retrieve icon definition from unified definition
      const iconDef = StyleDefinitions.ICONS.insert;
      if (iconDef) {
        const svgElement = createIconFromDef(iconDef);
        if (svgElement) {
          this.element.appendChild(svgElement);
        }
      }

      return this.element;
    }
  }

  class TextListComponent extends UIComponentBase {
    constructor(callbacks, options) {
      super(callbacks);
      this.options = options;
      this.styleHandle = null;
      this.elements = {
        tabsContainer: null,
        optionsContainer: null,
      };
    }

    /**
     * @override
     * @protected
     */
    _onDestroy() {
      super._onDestroy();
    }

    /**
     * @private
     * Retrieves the current list of option buttons.
     * @returns {HTMLElement[]}
     */
    _getOptions() {
      if (!this.elements.optionsContainer) return [];
      // Use the class name from the style handle
      const cls = this.styleHandle.classes;
      // Convert to Array to satisfy return type and allow array methods
      return Array.from(this.elements.optionsContainer.querySelectorAll(`.${cls.option}`));
    }

    /**
     * Focuses the option at the specified index.
     * @param {number} index
     */
    focusOption(index) {
      const options = this._getOptions();
      if (options.length === 0) return;

      // Clamp index
      let targetIndex = index;
      if (targetIndex < 0) targetIndex = 0;
      if (targetIndex >= options.length) targetIndex = options.length - 1;

      options[targetIndex].focus();
    }

    /**
     * Focuses the next option in the list. Loops to start if at end.
     */
    focusNext() {
      const options = this._getOptions();
      if (options.length === 0) return;

      const current = document.activeElement;
      const currentIndex = current instanceof HTMLElement ? options.indexOf(current) : -1;

      // If focus is not on an option, start from -1 so next is 0
      let nextIndex = currentIndex + 1;
      if (nextIndex >= options.length) nextIndex = 0;

      options[nextIndex].focus();
    }

    /**
     * Focuses the previous option in the list. Loops to end if at start.
     */
    focusPrev() {
      const options = this._getOptions();
      if (options.length === 0) return;

      const current = document.activeElement;
      // If focus is not on an option, start from length (so prev is length-1)
      let currentIndex = current instanceof HTMLElement ? options.indexOf(current) : -1;
      if (currentIndex === -1) currentIndex = 0;

      let prevIndex = currentIndex - 1;
      if (prevIndex < 0) prevIndex = options.length - 1;

      options[prevIndex].focus();
    }

    render() {
      this.styleHandle = StyleManager.request(StyleDefinitions.getTextList);
      const cls = this.styleHandle.classes;

      // Retrieve icon definitions from StyleDefinitions
      // Reuse "up" icon for navigation arrows by rotating via CSS classes
      const upIcon = StyleDefinitions.ICONS.up;

      // Create profile bar
      const profileBar = h(`div.${cls.profileBar}`, [
        // Previous button (Left arrow: upIcon rotated -90 degrees)
        (this.elements.prevBtn = h(
          `button.${cls.navBtn}.${cls.rotateLeft}`,
          {
            title: 'Previous Profile',
            onclick: (e) => {
              e.stopPropagation(); // Stop propagation to prevent list from closing
              this.callbacks.onPrevProfile?.();
            },
          },
          [createIconFromDef(upIcon)]
        )),
        // Next button (Right arrow: upIcon rotated 90 degrees)
        (this.elements.nextBtn = h(
          `button.${cls.navBtn}.${cls.rotateRight}`,
          {
            title: 'Next Profile',
            onclick: (e) => {
              e.stopPropagation(); // Stop propagation to prevent list from closing
              this.callbacks.onNextProfile?.();
            },
          },
          [createIconFromDef(upIcon)]
        )),
        // Profile name display area
        (this.elements.profileName = h(`span.${cls.profileName}`, {
          title: 'Current Profile (Click to switch / Right-Click to edit)',
          onclick: (e) => {
            e.stopPropagation();
            this.callbacks.onProfileNameClick?.();
          },
          oncontextmenu: (e) => {
            e.preventDefault();
            e.stopPropagation();
            this.callbacks.onProfileNameContextMenu?.();
          },
        })),
      ]);

      this.element = h(
        `div#${cls.list}`,
        {
          style: { display: 'none' },
          onmouseenter: (e) => this.callbacks.onMouseEnter?.(e),
          onmouseleave: (e) => this.callbacks.onMouseLeave?.(e),
          onmousemove: (e) => this.callbacks.onMouseMove?.(e),
        },
        [
          profileBar,
          (this.elements.tabsContainer = h(`div.${cls.tabs}`)),
          (this.elements.separator = h(`div.${cls.separator}`)), // Store reference
          (this.elements.optionsContainer = h(`div.${cls.options}`)),
        ]
      );

      document.body.appendChild(this.element);
      return this.element;
    }

    /**
     * Updates the profile name displayed in the header.
     * @param {string} name
     */
    setProfileName(name) {
      if (this.elements.profileName) {
        this.elements.profileName.textContent = name;
      }
    }

    /**
     * Toggles the visibility of category tabs and separator.
     * @param {boolean} visible
     */
    toggleTabs(visible) {
      const display = visible ? '' : 'none';
      if (this.elements.tabsContainer) this.elements.tabsContainer.style.display = display;
      if (this.elements.separator) this.elements.separator.style.display = display;
    }
  }

  class UIManager extends BaseManager {
    /** * @param {object} config
     * @param {(config: object) => Promise<void>} onSave
     * @param {object} platformDetails
     * @param {() => void} onModalClose
     * @param {(config: object) => {isValid: boolean, message: string|null}} checkConfigSizeCallback
     */
    constructor(config, onSave, platformDetails, onModalClose, checkConfigSizeCallback) {
      super();
      this.config = config;
      this.onSave = onSave;
      this.platformDetails = platformDetails;
      this.onModalClose = onModalClose;

      // Flag for requestAnimationFrame throttling
      this.isRepositioningScheduled = false;

      // State flag for input mode
      this.isKeyboardNavigating = false;

      // Initialize Session State (Array-based access)
      const profiles = this.config.texts || [];
      this.sessionActiveProfile = this.config.options.activeProfileName || profiles[0]?.name;

      const activeProfile = profiles.find((p) => p.name === this.sessionActiveProfile) || profiles[0];
      // Update sessionActiveProfile in case the configured one was missing
      if (activeProfile) {
        this.sessionActiveProfile = activeProfile.name;
      }

      const categories = activeProfile ? activeProfile.categories : [];
      this.activeCategory = categories[0]?.name || null;

      this.hideTimeoutId = null;
      this.isModalOpen = false;
      this.isProfileListMode = false;
      this.lastFocusedIndex = 0; // Remember last position for UX

      this._handlers = {
        globalClick: this._handleGlobalClick.bind(this),
        globalKeydown: this._handleGlobalKeydown.bind(this),
      };

      this.components = {
        settingsPanel: null,
        insertBtn: null,
        textList: null,
        textEditorModal: null,
        jsonModal: null,
        shortcutModal: null,
      };

      const modalCallbacks = {
        onSave: (newConfig) => this.onSave(newConfig),
        getCurrentConfig: () => this.config,
        onModalOpenStateChange: (isOpen) => this.setModalState(isOpen),
        checkConfigSize: checkConfigSizeCallback, // Pass validator to modals
        // Handle renaming to maintain session consistency
        onRename: (type, oldName, newName, parentContext) => {
          if (type === 'profile') {
            if (this.sessionActiveProfile === oldName) {
              this.sessionActiveProfile = newName;
            }
          } else if (type === 'category') {
            if (this.sessionActiveProfile === parentContext && this.activeCategory === oldName) {
              this.activeCategory = newName;
            }
          }
        },
      };

      // Initialize Settings Panel first to reference it in InsertButton callbacks
      this.components.settingsPanel = new SettingsPanelComponent({
        onSave: (newConfig) => this.onSave(newConfig),
        getCurrentConfig: () => this.config,
        // Anchor to insertBtn (will be assigned later, but reference is dynamic)
        getAnchorElement: () => this.components.insertBtn.element,
        // Pass only session profile
        onShowTextEditorModal: () => this.components.textEditorModal.open(this.sessionActiveProfile),
        onShowJsonModal: () => {
          this.components.jsonModal.open(this.components.insertBtn.element);
        },
        onShowShortcutModal: () => {
          this.components.shortcutModal.open(this.components.insertBtn.element);
        },
      });

      this.components.insertBtn = new InsertButtonComponent(
        {
          onClick: (e) => {
            const mode = this.config.options.trigger_mode || 'hover';

            if (this.components.settingsPanel.isOpen()) {
              this.components.settingsPanel.hide();
              this._showTextList(false);
              return;
            }

            if (mode === 'click') {
              if (this.components.textList.element.style.display === 'none') {
                this._showTextList(false);
              } else {
                this._hideTextList();
              }
            } else {
              this._hideTextList();
              this.components.settingsPanel.show();
            }
          },
          onContextMenu: (e) => {
            this._hideTextList();
            this.components.settingsPanel.toggle();
          },
          onMouseEnter: () => {
            const mode = this.config.options.trigger_mode || 'hover';
            if (mode === 'hover' && !this.components.settingsPanel.isOpen()) {
              this._showTextList(false);
            }
          },
          onMouseLeave: () => {
            const mode = this.config.options.trigger_mode || 'hover';
            if (mode !== 'click') {
              this._startHideTimer();
            }
          },
        },
        {
          id: `${CONSTANTS.ID_PREFIX}insert-btn`,
          title: 'Add quick text',
        }
      );

      this.components.textList = new TextListComponent(
        {
          onMouseEnter: () => clearTimeout(this.hideTimeoutId),
          onMouseLeave: () => {
            const mode = this.config.options.trigger_mode || 'hover';
            if (mode !== 'click') {
              this._startHideTimer();
            }
          },
          onMouseMove: (e) => {
            if (this.isKeyboardNavigating) {
              // Check if mouse actually moved to prevent unexpected blur on layout changes
              if (e.movementX === 0 && e.movementY === 0) return;

              this.isKeyboardNavigating = false;

              // Remove focus from the list item to clear selection visual
              if (document.activeElement instanceof HTMLElement && this.components.textList.element.contains(document.activeElement)) {
                document.activeElement.blur();
              }
            }
          },
          onPrevProfile: () => this._switchProfile(-1),
          onNextProfile: () => this._switchProfile(1),
          onProfileNameClick: () => this.toggleProfileList(),
          onProfileNameContextMenu: () => {
            this._hideTextList();
            this.components.textEditorModal.open(this.sessionActiveProfile);
          },
        },
        {
          id: `${CONSTANTS.ID_PREFIX}text-list`,
        }
      );

      this.components.jsonModal = new JsonModalComponent(modalCallbacks);
      this.components.textEditorModal = new TextEditorModalComponent({
        ...modalCallbacks,
        onShowJsonModal: () => {
          this.components.textEditorModal.close();
          this.components.jsonModal.open(this.components.insertBtn.element);
        },
      });
      this.components.shortcutModal = new ShortcutModalComponent(modalCallbacks);
    }

    async _onInit() {
      // Explicitly render components that require it
      this.components.insertBtn?.render();
      this.components.textList?.render();
      this.components.settingsPanel?.render();
      this.components.textEditorModal?.render();
      this.components.jsonModal?.render();
      this.components.shortcutModal?.render();

      this.renderContent();
      this._updateButtonTitle();

      this._subscribe(EVENTS.REOPEN_MODAL, ({ type, key }) => {
        if (type === CONSTANTS.MODAL_TYPES.JSON) {
          this.components.jsonModal.open(this.components.insertBtn.element);
        } else if (type === CONSTANTS.MODAL_TYPES.TEXT_EDITOR) {
          this.components.textEditorModal.open(this.sessionActiveProfile, key);
        }
      });

      this._subscribe(EVENTS.UI_REPOSITION, () => this.repositionInsertButton());

      this._subscribe(EVENTS.NAVIGATION_START, () => {
        this.components.settingsPanel.hide();
        this._hideTextList();
      });

      this.repositionInsertButton();

      // Register global key listener for shortcuts (Alt+Q)
      document.addEventListener('keydown', this._handlers.globalKeydown);

      if (this.components.settingsPanel) {
        await this.components.settingsPanel.init();
      }
      if (this.components.textEditorModal) {
        await this.components.textEditorModal.init();
      }
    }

    /**
     * @override
     * @protected
     */
    _onDestroy() {
      this._hideTextList();
      clearTimeout(this.hideTimeoutId);

      // Remove global listeners
      document.removeEventListener('keydown', this._handlers.globalKeydown);
      document.removeEventListener('click', this._handlers.globalClick); // Ensure click listener is also removed

      for (const key in this.components) {
        this.components[key]?.destroy();
      }
      super._onDestroy();
    }

    repositionInsertButton() {
      if (this.isRepositioningScheduled) return;

      this.isRepositioningScheduled = true;
      requestAnimationFrame(() => {
        PlatformAdapters.UI.repositionInsertButton(this.components.insertBtn);
        this.isRepositioningScheduled = false;
      });
    }

    getActiveModal() {
      if (this.components.jsonModal?.modal?.element?.open) {
        return this.components.jsonModal;
      }
      if (this.components.textEditorModal?.modal?.element?.open) {
        return this.components.textEditorModal;
      }
      return null;
    }

    setModalState(isOpen) {
      this.isModalOpen = isOpen;
      if (!isOpen) {
        this.onModalClose?.();
      }
    }

    toggleProfileList() {
      this.isProfileListMode = !this.isProfileListMode;
      if (this.isProfileListMode) {
        this.renderProfileList();
      } else {
        this.renderContent();
      }
    }

    renderProfileList() {
      if (!this.components.textList) return;

      this.components.textList.toggleTabs(false);

      const { optionsContainer } = this.components.textList.elements;

      // Array-based access
      const profiles = this.config.texts || [];
      const activeProfileName = this.sessionActiveProfile;

      // Retrieve class names from StyleDefinitions
      const cls = StyleDefinitions.getTextList().classes;

      const buttons = profiles.map((profile) => {
        const profileName = profile.name;
        return h('button', {
          className: `${cls.option}` + (profileName === activeProfileName ? ' active' : ''),
          textContent: profileName,
          title: 'Switch to this profile',
          onclick: (e) => {
            e.preventDefault();
            e.stopPropagation();
            this._setProfile(profileName);

            // Only focus if navigating via keyboard
            if (this.isKeyboardNavigating) {
              requestAnimationFrame(() => this.components.textList.focusOption(0));
            }
          },
        });
      });
      optionsContainer.replaceChildren(...buttons);

      optionsContainer.scrollTop = 0;
    }

    updateConfig(newConfig) {
      this.config = newConfig;

      // Array-based validation
      const profiles = this.config.texts || [];
      const activeProfileObj = profiles.find((p) => p.name === this.sessionActiveProfile);

      if (!activeProfileObj) {
        Logger.warn('STATE RECOVERY', LOG_STYLES.ORANGE, `Session profile "${this.sessionActiveProfile}" no longer exists. Falling back to default.`);
        const fallbackProfile = profiles[0];
        this.sessionActiveProfile = this.config.options.activeProfileName || fallbackProfile?.name;

        const activeProfile = profiles.find((p) => p.name === this.sessionActiveProfile) || fallbackProfile;
        this.activeCategory = activeProfile?.categories[0]?.name || null;
      } else {
        const categories = activeProfileObj.categories;
        const categoryExists = categories.some((c) => c.name === this.activeCategory);
        if (this.activeCategory && !categoryExists) {
          this.activeCategory = categories[0]?.name || null;
        }
      }

      this.renderContent();
      this._updateButtonTitle();
    }

    renderContent() {
      if (!this.components.textList) return;

      this.isProfileListMode = false;
      this.components.textList.toggleTabs(true);

      const { tabsContainer, optionsContainer } = this.components.textList.elements;

      const activeProfileName = this.sessionActiveProfile;
      const profiles = this.config.texts || [];
      const activeProfile = profiles.find((p) => p.name === activeProfileName);

      if (!activeProfile) {
        Logger.warn('', '', `Active profile "${activeProfileName}" not found.`);
        return;
      }

      this.components.textList.setProfileName(activeProfileName);

      // Retrieve class names from StyleDefinitions
      const cls = StyleDefinitions.getTextList().classes;
      const categories = activeProfile.categories || [];

      const tabs = categories.map((cat) => {
        const catName = cat.name;
        return h('button', {
          className: `${cls.tab}` + (catName === this.activeCategory ? ' active' : ''),
          textContent: catName,
          // Prevent focus loss when clicking tabs
          onmousedown: (e) => e.preventDefault(),
          onclick: (e) => {
            e.preventDefault();
            e.stopPropagation();
            this.activeCategory = catName;
            this.renderContent();

            // Only focus if navigating via keyboard
            if (this.isKeyboardNavigating) {
              requestAnimationFrame(() => this.components.textList.focusOption(0));
            }
          },
        });
      });
      tabsContainer.replaceChildren(...tabs);

      // Find active category object
      const activeCategoryObj = categories.find((c) => c.name === this.activeCategory);
      const items = activeCategoryObj ? activeCategoryObj.items : [];

      const buttons = items.map((txt) => {
        return h('button', {
          className: `${cls.option}`,
          textContent: txt,
          title: txt,
          // Prevent focus loss from editor when clicking text options
          onmousedown: (e) => e.preventDefault(),
          onclick: (e) => {
            e.preventDefault();
            e.stopPropagation();
            this._insertText(txt);
            this._hideTextList();
          },
        });
      });
      optionsContainer.replaceChildren(...buttons);
    }

    async _switchCategory(direction) {
      const profiles = this.config.texts || [];
      const activeProfile = profiles.find((p) => p.name === this.sessionActiveProfile);
      if (!activeProfile) return;

      const categories = activeProfile.categories || [];
      if (categories.length <= 1) return;

      const currentIndex = categories.findIndex((c) => c.name === this.activeCategory);
      // Fallback if not found
      const baseIndex = currentIndex === -1 ? 0 : currentIndex;

      let newIndex = (baseIndex + direction) % categories.length;
      if (newIndex < 0) newIndex += categories.length;

      const newCategory = categories[newIndex];
      this.activeCategory = newCategory.name;

      this.renderContent();

      // Reset focus to the first item in the new category
      requestAnimationFrame(() => this.components.textList.focusOption(0));
    }

    async _switchProfile(direction) {
      const profiles = this.config.texts || [];
      if (profiles.length <= 1) return;

      const currentProfileIndex = profiles.findIndex((p) => p.name === this.sessionActiveProfile);
      // Fallback if not found
      const baseIndex = currentProfileIndex === -1 ? 0 : currentProfileIndex;

      let newIndex = (baseIndex + direction) % profiles.length;
      if (newIndex < 0) newIndex += profiles.length;

      const newProfile = profiles[newIndex];
      this.sessionActiveProfile = newProfile.name;
      this.activeCategory = newProfile.categories[0]?.name || null;

      this.renderContent();

      if (this.components.textList.elements.optionsContainer) {
        this.components.textList.elements.optionsContainer.scrollTop = 0;
      }
    }

    async _setProfile(profileName) {
      if (profileName === this.sessionActiveProfile) {
        this.renderContent();
        return;
      }

      this.sessionActiveProfile = profileName;

      const profiles = this.config.texts || [];
      const activeProfile = profiles.find((p) => p.name === profileName);

      this.activeCategory = activeProfile?.categories[0]?.name || null;

      this.renderContent();

      if (this.components.textList.elements.optionsContainer) {
        this.components.textList.elements.optionsContainer.scrollTop = 0;
      }
    }

    _updateButtonTitle() {
      if (!this.components.insertBtn) return;
      const mode = this.config.options.trigger_mode || 'hover';
      const shortcutSuffix = this.config.options.enable_shortcut ? ' (Alt+Q)' : '';

      let title;
      if (mode === 'click') {
        title = `Click to open Text List${shortcutSuffix} / Right-click to open Settings`;
      } else {
        title = `Hover to open Text List${shortcutSuffix} / Click to open Settings`;
      }
      this.components.insertBtn.setTitle(title);
    }

    _insertText(text) {
      // Pass the captured range to the controller
      const options = { ...this.config.options, _savedRange: this.lastEditorRange };
      PlatformAdapters.General.insertText(text, options);
    }

    _positionList() {
      requestAnimationFrame(() => {
        const btnRect = this.components.insertBtn.element.getBoundingClientRect();
        const listElem = this.components.textList.element;
        const margin = 8;
        const gap = 4;

        const spaceAbove = btnRect.top - margin - gap;
        const spaceBelow = window.innerHeight - btnRect.bottom - margin - gap;

        const placeOnTop = spaceAbove >= 200 || spaceAbove >= spaceBelow;

        if (placeOnTop) {
          listElem.style.flexDirection = 'column-reverse';
          listElem.style.top = 'auto';
          listElem.style.bottom = `${window.innerHeight - btnRect.top + gap}px`;
          listElem.style.maxHeight = `${spaceAbove}px`;
        } else {
          listElem.style.flexDirection = 'column';
          listElem.style.bottom = 'auto';
          listElem.style.top = `${btnRect.bottom + gap}px`;
          listElem.style.maxHeight = `${spaceBelow}px`;
        }

        const listWidth = listElem.offsetWidth || 500;
        let left = btnRect.left;
        if (left + listWidth > window.innerWidth - margin) {
          left = window.innerWidth - listWidth - margin;
        }
        left = Math.max(left, margin);
        listElem.style.left = `${left}px`;

        listElem.style.opacity = '1';
      });
    }

    _showTextList(isKeyboard) {
      clearTimeout(this.hideTimeoutId);
      const listElem = this.components.textList.element;

      if (listElem.style.display !== 'none') return;

      // 1. Capture the current cursor position before focus might shift to the list
      const editor = document.querySelector(this.platformDetails.selectors.INPUT_TARGET);
      if (editor && editor.contains(document.activeElement)) {
        const sel = window.getSelection();
        if (sel.rangeCount > 0) {
          this.lastEditorRange = sel.getRangeAt(0).cloneRange();
        }
      } else {
        this.lastEditorRange = null;
      }

      // Set navigation mode flag based on trigger source
      this.isKeyboardNavigating = isKeyboard;

      listElem.style.display = 'flex';
      listElem.style.opacity = '0';
      listElem.style.left = '-9999px';
      listElem.style.top = '';
      listElem.style.bottom = '';

      this._positionList();

      // Setup global click listener for closing (only when open)
      document.addEventListener('click', this._handlers.globalClick);

      // Restore focus (Native Focus Management)
      if (isKeyboard) {
        requestAnimationFrame(() => {
          if (this.isProfileListMode) {
            this.components.textList.focusOption(0);
          } else {
            this.components.textList.focusOption(this.lastFocusedIndex);
          }
        });
      }
    }

    _startHideTimer() {
      this.hideTimeoutId = setTimeout(() => {
        this._hideTextList();
      }, CONSTANTS.TIMING.TIMEOUTS.HIDE_DELAY_MS);
    }

    _hideTextList() {
      clearTimeout(this.hideTimeoutId);

      const listElem = this.components.textList.element;
      if (listElem.style.display === 'none') return;

      // Remember focus index if not in profile list mode
      if (!this.isProfileListMode) {
        const options = this.components.textList._getOptions();
        const current = document.activeElement;
        const index = current instanceof HTMLElement ? options.indexOf(current) : -1;
        if (index >= 0) {
          this.lastFocusedIndex = index;
        }
      }

      listElem.style.display = 'none';

      // Remove global click listener (Keydown listener remains active)
      document.removeEventListener('click', this._handlers.globalClick);

      if (this.isProfileListMode) {
        this.renderContent(); // Reset to text content view
      }

      // UX: Restore focus to the input editor
      const editorSelector = this.platformDetails.selectors.INPUT_TARGET;
      const editor = document.querySelector(editorSelector);
      if (editor instanceof HTMLElement) {
        editor.focus();
      }
    }

    _handleGlobalClick(e) {
      const listEl = this.components.textList.element;
      const btnEl = this.components.insertBtn.element;

      // If closed, do nothing (though listener should be removed)
      if (listEl.style.display === 'none') return;

      // Ignore clicks inside the list or on the toggle button
      if (listEl.contains(e.target) || btnEl.contains(e.target)) {
        return;
      }

      this._hideTextList();
    }

    /**
     * Checks if any modal or settings panel is currently open.
     * @returns {boolean}
     * @private
     */
    _isAnyModalOpen() {
      // Check Settings Panel
      if (this.components.settingsPanel && this.components.settingsPanel.isOpen()) {
        return true;
      }

      // Check Dialog Modals
      const modals = [this.components.textEditorModal, this.components.jsonModal, this.components.shortcutModal];

      return modals.some((comp) => comp?.modal?.element?.open);
    }

    _handleGlobalKeydown(e) {
      // 1. Global Toggle (Alt+Q)
      // Use code 'KeyQ' for physical key position independence, or 'q' key property.
      // Exclude AltGr (Ctrl+Alt) to avoid accidental trigger on some keyboard layouts
      const isAltGraph = e.getModifierState ? e.getModifierState('AltGraph') : false;

      if (e.altKey && !e.ctrlKey && !e.metaKey && !isAltGraph && (e.code === 'KeyQ' || e.key === 'q')) {
        // Check if shortcut is enabled
        if (this.config.options.enable_shortcut === false) {
          return;
        }

        // If any modal/panel is open, ignore the shortcut completely
        if (this._isAnyModalOpen()) {
          return;
        }

        e.preventDefault();
        e.stopPropagation();

        if (this.components.textList.element.style.display === 'none') {
          this._showTextList(true);
        } else {
          this._hideTextList();
        }
        return;
      }

      // If list is closed, stop processing navigation keys
      if (this.components.textList.element.style.display === 'none') return;

      // Prevent interference with IME composition (e.g., when focus logic fails and focus remains on input)
      if (e.isComposing) return;

      // Close list on character input
      // Consume the event so no character is entered, then close the list.
      // Note: Space key is excluded here because it is handled explicitly in the switch statement below.
      const isCharKey = !e.ctrlKey && !e.altKey && !e.metaKey && e.key.length === 1 && e.key !== ' ';
      if (isCharKey) {
        e.preventDefault();
        e.stopPropagation();
        this._hideTextList();
        return;
      }

      // 2. Navigation when List is Open
      const isProfileList = this.isProfileListMode;

      // Update navigation mode to keyboard
      if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
        this.isKeyboardNavigating = true;
      }

      switch (e.key) {
        case 'Escape':
          e.preventDefault();
          e.stopPropagation();
          this._hideTextList();
          break;

        case 'ArrowUp':
          e.preventDefault();
          e.stopPropagation();
          if (e.ctrlKey) {
            // Ctrl+Up: Toggle Profile List
            this.toggleProfileList();
            if (this.isProfileListMode) {
              requestAnimationFrame(() => this.components.textList.focusOption(0));
            } else {
              requestAnimationFrame(() => this.components.textList.focusOption(this.lastFocusedIndex));
            }
          } else {
            // Normal Up: Previous Item
            this.components.textList.focusPrev();
          }
          break;

        case 'ArrowDown':
          e.preventDefault();
          e.stopPropagation();
          if (e.ctrlKey) {
            // Ctrl+Down: Toggle Profile List
            this.toggleProfileList();
            if (this.isProfileListMode) {
              requestAnimationFrame(() => this.components.textList.focusOption(0));
            } else {
              requestAnimationFrame(() => this.components.textList.focusOption(this.lastFocusedIndex));
            }
          } else {
            // Normal Down: Next Item
            this.components.textList.focusNext();
          }
          break;

        case 'ArrowLeft':
          e.preventDefault();
          e.stopPropagation();
          if (e.ctrlKey) {
            // Ctrl+Left: Switch Profile
            if (!isProfileList) {
              this._switchProfile(-1).then(() => {
                // Reset focus to first item after switch
                requestAnimationFrame(() => this.components.textList.focusOption(0));
              });
            }
          } else {
            // Normal Left: Switch Category (New Feature)
            if (!isProfileList) {
              this._switchCategory(-1);
            }
          }
          break;

        case 'ArrowRight':
          e.preventDefault();
          e.stopPropagation();
          if (e.ctrlKey) {
            // Ctrl+Right: Switch Profile
            if (!isProfileList) {
              this._switchProfile(1).then(() => {
                // Reset focus to first item after switch
                requestAnimationFrame(() => this.components.textList.focusOption(0));
              });
            }
          } else {
            // Normal Right: Switch Category
            if (!isProfileList) {
              this._switchCategory(1);
            }
          }
          break;

        case 'Tab':
        case ' ':
        case 'Enter':
          e.preventDefault();
          e.stopPropagation();
          if (document.activeElement instanceof HTMLElement && this.components.textList.element.contains(document.activeElement)) {
            document.activeElement.click();
          } else {
            // Handle selection via mouse hover if no element is focused
            const cls = StyleManager.request(StyleDefinitions.getTextList).classes;
            const hoverOption = this.components.textList.element.querySelector(`.${cls.option}:hover`);
            if (hoverOption instanceof HTMLElement) {
              hoverOption.click();
            } else {
              this._hideTextList();
            }
          }
          break;
      }
    }
  }

  // =================================================================================
  // SECTION: Sync Manager
  // =================================================================================

  class SyncManager extends BaseManager {
    constructor(appInstance) {
      super();
      this.app = appInstance;
      this.pendingRemoteConfig = null;
      this.remoteChangeListenerId = null;
    }

    _onInit() {
      this.remoteChangeListenerId = GM_addValueChangeListener(this.app.configKey, async (name, oldValue, newValue, remote) => {
        if (remote) {
          await this._handleRemoteChange(newValue);
        }
      });
    }

    /**
     * @override
     * @protected
     */
    _onDestroy() {
      if (this.remoteChangeListenerId !== null) {
        GM_removeValueChangeListener(this.remoteChangeListenerId);
        this.remoteChangeListenerId = null;
      }
      super._onDestroy();
    }

    onModalClose() {
      if (this.pendingRemoteConfig) {
        Logger.log('', '', 'SyncManager: Modal closed with a pending update. Applying it now.');
        this.app.applyUpdate(this.pendingRemoteConfig);
        this.pendingRemoteConfig = null;
      }
    }

    onSave() {
      // A local save overwrites any pending remote changes.
      this.pendingRemoteConfig = null;
      // Also, clear any visible conflict notifications.
      const activeModal = this.app.uiManager.getActiveModal?.();
      if (activeModal) {
        this._clearConflictNotification(activeModal);
      }
    }

    async _handleRemoteChange(rawValue) {
      Logger.info('SYNC', LOG_STYLES.TEAL, 'SyncManager: Remote config change detected.');
      try {
        const newConfig = await this.app.configManager.decode(rawValue);
        const activeModal = this.app.uiManager.getActiveModal?.();

        if (activeModal) {
          Logger.info('SYNC', LOG_STYLES.TEAL, 'SyncManager: A modal is open. Storing update and displaying conflict notification.');
          this.pendingRemoteConfig = newConfig;
          this._showConflictNotification(activeModal);
        } else {
          Logger.info('SYNC', LOG_STYLES.TEAL, 'SyncManager: No modal open. Applying silent update.');
          this.app.applyUpdate(newConfig);
        }
      } catch (e) {
        Logger.error('SYNC ERROR', LOG_STYLES.RED, 'SyncManager: Failed to handle remote config change:', e);
      }
    }

    _showConflictNotification(modalComponent) {
      if (!modalComponent?.modal) return;
      this._clearConflictNotification(modalComponent); // Clear previous state first

      const messageArea = modalComponent.modal.dom.footerMessage;
      const cls = StyleDefinitions.COMMON_CLASSES;
      const v = StyleDefinitions.VARS;

      if (messageArea) {
        const messageText = h('span', {
          textContent: 'Settings updated in another tab.',
          style: { display: 'flex', alignItems: 'center' },
        });
        const reloadBtn = h('button', {
          id: cls.conflictReloadBtnId,
          className: cls.modalButton,
          textContent: 'Reload UI',
          title: 'Discard local changes and load the settings from the other tab.',
          style: {
            borderColor: `var(${v.TEXT_DANGER})`,
            marginLeft: '12px',
          },
          onclick: () => {
            const reopenContext = modalComponent.getContextForReopen?.();
            modalComponent.close();
            // onModalClose will handle applying the pending update.
            // Request to reopen the modal after a short delay to ensure sync completion.
            setTimeout(() => {
              EventBus.publish(EVENTS.REOPEN_MODAL, reopenContext);
            }, 100);
          },
        });
        messageArea.classList.add(cls.conflictText);
        messageArea.replaceChildren(messageText, reloadBtn);
      }
    }

    _clearConflictNotification(modalComponent) {
      if (!modalComponent?.modal) return;
      const messageArea = modalComponent.modal.dom.footerMessage;
      if (messageArea) {
        messageArea.replaceChildren();
        messageArea.classList.remove(StyleDefinitions.COMMON_CLASSES.conflictText);
      }
    }
  }

  // =================================================================================
  // SECTION: Navigation Monitor
  // Description: Centralizes URL change detection via history API hooks and popstate events.
  // =================================================================================

  class NavigationMonitor {
    constructor() {
      this.originalHistoryMethods = { pushState: null, replaceState: null };
      this._historyWrappers = {};
      this.isInitialized = false;
      this.lastPath = null;
      this._handlePopState = this._handlePopState.bind(this);

      // Debounce the navigation event to allow the DOM to settle and prevent duplicate events
      this.debouncedNavigation = debounce(
        () => {
          EventBus.publish(EVENTS.NAVIGATION);
        },
        CONSTANTS.TIMING.TIMEOUTS.POST_NAVIGATION_DOM_SETTLE,
        true
      );
    }

    init() {
      if (this.isInitialized) return;
      this.isInitialized = true;
      // Capture initial path
      this.lastPath = location.pathname + location.search;
      this._hookHistory();
      window.addEventListener('popstate', this._handlePopState);
    }

    destroy() {
      if (!this.isInitialized) return;
      this._restoreHistory();
      window.removeEventListener('popstate', this._handlePopState);
      this.debouncedNavigation.cancel();
      this.isInitialized = false;
    }

    _hookHistory() {
      // Capture the instance for use in the wrapper
      const instance = this;

      for (const m of ['pushState', 'replaceState']) {
        const orig = history[m];
        this.originalHistoryMethods[m] = orig;

        const wrapper = function (...args) {
          const result = orig.apply(this, args);
          instance._onUrlChange();
          return result;
        };

        this._historyWrappers[m] = wrapper;
        history[m] = wrapper;
      }
    }

    _restoreHistory() {
      for (const m of ['pushState', 'replaceState']) {
        if (this.originalHistoryMethods[m]) {
          if (history[m] === this._historyWrappers[m]) {
            history[m] = this.originalHistoryMethods[m];
          } else {
            Logger.warn('HISTORY HOOK', LOG_STYLES.YELLOW, `history.${m} has been wrapped by another script. Skipping restoration to prevent breaking the chain.`);
          }
          this.originalHistoryMethods[m] = null;
        }
      }
      this._historyWrappers = {};
    }

    _handlePopState() {
      this._onUrlChange();
    }

    _onUrlChange() {
      const currentPath = location.pathname + location.search;

      // Prevent re-triggers if the path hasn't actually changed
      if (currentPath === this.lastPath) {
        return;
      }
      this.lastPath = currentPath;

      EventBus.publish(EVENTS.NAVIGATION_START);
      this.debouncedNavigation();
    }
  }

  // =================================================================================
  // SECTION: DOM Observers and Event Listeners
  // =================================================================================

  class ObserverManager extends BaseManager {
    constructor() {
      super();
      this.activePageObservers = []; // To store cleanup functions
    }

    /**
     * Starts all platform-specific observers by retrieving and executing them
     * from the PlatformAdapter.
     */
    start() {
      // Subscribe to navigation events to handle page changes
      this._subscribe(EVENTS.NAVIGATION, () => this._onNavigation());

      // Perform initial setup by publishing the navigation event.
      EventBus.publish(EVENTS.NAVIGATION);
    }

    /**
     * @override
     * @protected
     */
    _onDestroy() {
      this.activePageObservers.forEach((cleanup) => cleanup());
      this.activePageObservers = [];
      super._onDestroy();
    }

    /**
     * @private
     * @description Handles the logic required when a navigation occurs or the app initializes.
     * Resets observers and sets up page-specific listeners.
     */
    _onNavigation() {
      try {
        Logger.debug('NAVIGATOR', LOG_STYLES.CYAN, 'Navigation detected. Starting lifecycle...');

        // 1. Cleanup previous page resources
        this.activePageObservers.forEach((cleanup) => cleanup());
        this.activePageObservers = [];

        // 2. Initialize Page-Specific Observers
        const observerStarters = PlatformAdapters.Observer.getInitializers();
        for (const startObserver of observerStarters) {
          // Ensure the context of 'this' is the ObserverManager instance
          const cleanup = startObserver.call(this, {});
          if (typeof cleanup === 'function') {
            this.activePageObservers.push(cleanup);
          }
        }
      } catch (e) {
        Logger.error('NAV_HANDLER_ERROR', LOG_STYLES.RED, 'Error during navigation handling:', e);
      }
    }
  }

  // =================================================================================
  // SECTION: Main Application Controller
  // =================================================================================

  /**
   * @class Sentinel
   * @description Detects DOM node insertion using a shared, prefixed CSS animation trick.
   * @property {Map<string, Set<(element: Element) => void>>} listeners
   * @property {Set<string>} rules
   * @property {HTMLElement | null} styleElement
   * @property {CSSStyleSheet | null} sheet
   * @property {string[]} pendingRules
   * @property {WeakMap<CSSRule, string>} ruleSelectors
   */
  class Sentinel {
    /**
     * @param {string} prefix - A unique identifier for this Sentinel instance to avoid CSS conflicts. Required.
     */
    constructor(prefix) {
      if (!prefix) {
        throw new Error('[Sentinel] "prefix" argument is required to avoid CSS conflicts.');
      }

      // Validate prefix for CSS compatibility
      // 1. Must contain only alphanumeric characters, hyphens, or underscores.
      // 2. Cannot start with a digit.
      // 3. Cannot start with a hyphen followed by a digit.
      if (!/^[a-zA-Z0-9_-]+$/.test(prefix) || /^[0-9]|^-[0-9]/.test(prefix)) {
        throw new Error(`[Sentinel] Prefix "${prefix}" is invalid. It must contain only alphanumeric characters, hyphens, or underscores, and cannot start with a digit or a hyphen followed by a digit.`);
      }

      /** @type {Window & { __global_sentinel_instances__?: Record<string, Sentinel> }} */
      const globalScope = window;
      globalScope.__global_sentinel_instances__ ??= {};
      if (globalScope.__global_sentinel_instances__[prefix]) {
        return globalScope.__global_sentinel_instances__[prefix];
      }

      this.prefix = prefix;
      this.isDestroyed = false;
      this.isSuspended = false;
      this._initObserver = null;

      // Use a unique, prefixed animation name shared by all scripts in a project.
      this.animationName = `${prefix}-global-sentinel-animation`;
      this.styleId = `${prefix}-sentinel-global-rules`; // A single, unified style element
      this.listeners = new Map();
      this.rules = new Set(); // Tracks all active selectors
      this.styleElement = null; // Holds the reference to the single style element
      this.sheet = null; // Cache the CSSStyleSheet reference
      this.pendingRules = []; // Queue for rules requested before sheet is ready
      /** @type {WeakMap<CSSRule, string>} */
      this.ruleSelectors = new WeakMap(); // Tracks selector strings associated with CSSRule objects

      this._boundHandleAnimationStart = this._handleAnimationStart.bind(this);

      this._injectStyleElement();
      document.addEventListener('animationstart', this._boundHandleAnimationStart, true);

      globalScope.__global_sentinel_instances__[prefix] = this;
    }

    destroy() {
      if (this.isDestroyed) return;
      this.isDestroyed = true;

      document.removeEventListener('animationstart', this._boundHandleAnimationStart, true);

      if (this._initObserver) {
        this._initObserver.disconnect();
        this._initObserver = null;
      }

      if (this.styleElement) {
        this.styleElement.remove();
        this.styleElement = null;
      }

      this.sheet = null;
      this.listeners.clear();
      this.rules.clear();
      this.pendingRules = [];

      /** @type {Window & { __global_sentinel_instances__?: Record<string, Sentinel> }} */
      const globalScope = window;
      if (globalScope.__global_sentinel_instances__) {
        delete globalScope.__global_sentinel_instances__[this.prefix];
      }
    }

    _injectStyleElement() {
      // Ensure the style element is injected only once per project prefix.
      this.styleElement = document.getElementById(this.styleId);

      if (this.styleElement instanceof HTMLStyleElement) {
        this.styleElement.disabled = this.isSuspended;

        /** @type {HTMLStyleElement} */
        const styleNode = this.styleElement;
        const pollExisting = () => {
          if (this.isDestroyed) return;
          if (styleNode.sheet) {
            this.sheet = styleNode.sheet;
            this._flushPendingRules();
          } else {
            // Poll infinitely until sheet is ready
            setTimeout(pollExisting, 50);
          }
        };
        pollExisting();
        return;
      }

      // Create empty style element
      this.styleElement = h('style', {
        id: this.styleId,
      });
      // CSP Fix: Try to fetch a valid nonce from existing scripts/styles
      // "nonce" property exists on HTMLScriptElement/HTMLStyleElement, not basic Element.
      let nonce;

      // 1. Try to get nonce from scripts collection
      const scripts = document.scripts;
      for (let i = 0; i < scripts.length; i++) {
        if (scripts[i].nonce) {
          nonce = scripts[i].nonce;
          break;
        }
      }

      // 2. Fallback: Using querySelector (content attribute)
      if (!nonce) {
        const style = document.querySelector('style[nonce]');
        const script = document.querySelector('script[nonce]');

        if (style instanceof HTMLStyleElement && style.nonce) {
          nonce = style.nonce;
        } else if (script instanceof HTMLScriptElement && script.nonce) {
          nonce = script.nonce;
        }
      }

      if (nonce) {
        this.styleElement.nonce = nonce;
      }

      if (this.styleElement instanceof HTMLStyleElement) {
        this.styleElement.disabled = this.isSuspended;
      }

      // Try to inject immediately.
      // If the document is not yet ready (e.g. extremely early document-start), wait for the root element.
      const target = document.head || document.documentElement;

      const initSheet = () => {
        if (this.isDestroyed) return;
        if (this.styleElement instanceof HTMLStyleElement) {
          /** @type {HTMLStyleElement} */
          const styleNode = this.styleElement;
          if (styleNode.sheet) {
            this.sheet = styleNode.sheet;
            // Insert the shared keyframes rule at index 0.
            try {
              const keyframes = `@keyframes ${this.animationName} { from { outline: 1px solid transparent;} to { outline: 0px solid transparent; } }`;
              this.sheet.insertRule(keyframes, 0);
            } catch (e) {
              Logger.error('SENTINEL', LOG_STYLES.RED, 'Failed to insert keyframes rule:', e);
            }
            this._flushPendingRules();
          } else {
            // Poll infinitely until sheet is ready
            setTimeout(initSheet, 50);
          }
        }
      };

      if (target) {
        target.appendChild(this.styleElement);
        initSheet();
      } else {
        this._initObserver = new MutationObserver(() => {
          if (this.isDestroyed) return;
          const retryTarget = document.head || document.documentElement;
          if (retryTarget) {
            this._initObserver.disconnect();
            this._initObserver = null;

            retryTarget.appendChild(this.styleElement);
            initSheet();
          }
        });
        this._initObserver.observe(document, { childList: true });
      }
    }

    /**
     * Ensures the style element is connected to the DOM and restores rules if it was removed.
     */
    _ensureStyleGuard() {
      if (this.styleElement && !this.styleElement.isConnected) {
        const target = document.head || document.documentElement;
        if (target) {
          target.appendChild(this.styleElement);
          if (this.styleElement instanceof HTMLStyleElement && this.styleElement.sheet) {
            this.styleElement.disabled = this.isSuspended;
            this.sheet = this.styleElement.sheet;

            try {
              while (this.sheet.cssRules.length > 0) {
                this.sheet.deleteRule(0);
              }
              const keyframes = `@keyframes ${this.animationName} { from { outline: 1px solid transparent; } to { outline: 0px solid transparent; } }`;
              this.sheet.insertRule(keyframes, 0);
            } catch (e) {
              Logger.error('SENTINEL', LOG_STYLES.RED, 'Failed to clear or restore base rules:', e);
            }

            this.pendingRules = [];

            this.rules.forEach((selector) => {
              this._insertRule(selector);
            });
          }
        }
      }
    }

    _flushPendingRules() {
      if (!this.sheet || this.pendingRules.length === 0) return;
      const rulesToInsert = [...this.pendingRules];
      this.pendingRules = [];

      rulesToInsert.forEach((selector) => {
        this._insertRule(selector);
      });
    }

    /**
     * Helper to insert a single rule into the stylesheet
     * @param {string} selector
     */
    _insertRule(selector) {
      try {
        const index = this.sheet.cssRules.length;
        const ruleText = `${selector} { animation-duration: 0.001s; animation-name: ${this.animationName}; }`;
        this.sheet.insertRule(ruleText, index);
        // Associate the inserted rule with the selector via WeakMap for safer removal later.
        // This mimics sentinel.js behavior to handle index shifts and selector normalization.
        const insertedRule = this.sheet.cssRules[index];
        if (insertedRule) {
          this.ruleSelectors.set(insertedRule, selector);
        }
      } catch (e) {
        Logger.error('SENTINEL', LOG_STYLES.RED, `Failed to insert rule for selector "${selector}":`, e);
      }
    }

    _handleAnimationStart(event) {
      if (this.isDestroyed) return;

      // Check if the animation is the one we're listening for.
      if (event.animationName !== this.animationName) return;

      const target = event.target;
      if (!(target instanceof Element)) {
        return;
      }

      // Check if the target element matches any of this instance's selectors.
      for (const [selector, callbacks] of this.listeners.entries()) {
        if (target.matches(selector)) {
          // Use a copy of the callbacks Set in case a callback removes itself.
          [...callbacks].forEach((cb) => {
            try {
              cb(target);
            } catch (e) {
              Logger.error('SENTINEL', LOG_STYLES.RED, `Listener error for selector "${selector}":`, e);
            }
          });
        }
      }
    }

    /**
     * @param {string} selector
     * @param {(element: Element) => void} callback
     */
    on(selector, callback) {
      if (this.isDestroyed) return;
      this._ensureStyleGuard();

      // Add callback to listeners

      if (!this.listeners.has(selector)) {
        this.listeners.set(selector, new Set());
      }
      this.listeners.get(selector).add(callback);
      // If selector is already registered in rules, do nothing
      if (this.rules.has(selector)) return;
      this.rules.add(selector);

      // Apply rule
      if (this.sheet) {
        this._insertRule(selector);
      } else {
        this.pendingRules.push(selector);
      }
    }

    /**
     * @param {string} selector
     * @param {(element: Element) => void} callback
     */
    off(selector, callback) {
      if (this.isDestroyed) return;
      const callbacks = this.listeners.get(selector);
      if (!callbacks) return;

      const wasDeleted = callbacks.delete(callback);
      if (!wasDeleted) {
        return;
        // Callback not found, do nothing.
      }

      if (callbacks.size === 0) {
        // Remove listener and rule
        this.listeners.delete(selector);
        this.rules.delete(selector);

        if (this.sheet) {
          // Iterate backwards to avoid index shifting issues during deletion
          for (let i = this.sheet.cssRules.length - 1; i >= 0; i--) {
            const rule = this.sheet.cssRules[i];
            // Check for recorded selector via WeakMap or fallback to selectorText match
            const recordedSelector = this.ruleSelectors.get(rule);
            if (recordedSelector === selector || (rule instanceof CSSStyleRule && rule.selectorText === selector)) {
              try {
                this.sheet.deleteRule(i);
              } catch (e) {
                Logger.error('SENTINEL', LOG_STYLES.RED, `Failed to delete rule for selector "${selector}":`, e);
              }
              // We assume one rule per selector, so we can break after deletion
              break;
            }
          }
        }
      }
    }

    suspend() {
      if (this.isDestroyed) return;
      this.isSuspended = true;
      if (this.styleElement instanceof HTMLStyleElement) {
        this.styleElement.disabled = true;
      }
      Logger.debug('SENTINEL', LOG_STYLES.CYAN, 'Suspended.');
    }

    resume() {
      if (this.isDestroyed) return;
      this.isSuspended = false;
      if (this.styleElement instanceof HTMLStyleElement) {
        this.styleElement.disabled = false;
      }
      Logger.debug('SENTINEL', LOG_STYLES.CYAN, 'Resumed.');
    }
  }

  // =================================================================================
  // SECTION: Core Functions
  // =================================================================================

  class AppController extends BaseManager {
    constructor(platformDetails) {
      super();
      this.configManager = null;
      this.uiManager = null;
      this.platformDetails = platformDetails;
      this.syncManager = null;
      this.observerManager = new ObserverManager();
    }

    async _onInit() {
      // Inject platform-specific CSS variables immediately
      StyleManager.injectPlatformVariables(this.platformDetails.platformId);

      this.configManager = new ConfigManager();
      await this.configManager.load();

      // Set logger level from config immediately after loading
      Logger.setLevel(this.configManager.get().developer.logger_level);

      this.syncManager = new SyncManager(this);

      this.uiManager = new UIManager(
        this.configManager.get(),
        (newConfig) => this.handleSave(newConfig),
        this.platformDetails,
        () => this.syncManager.onModalClose(),
        (conf) => this.configManager.validateConfigSize(conf) // Pass size validator
      );
      await this.uiManager.init();
      await this.syncManager.init();

      this.observerManager.init();
      this.observerManager.start();
    }

    /**
     * @override
     * @protected
     */
    _onDestroy() {
      // Do not destroy navigationMonitor to keep history hooks active for recovery
      this.uiManager?.destroy();
      this.observerManager?.destroy();
      this.configManager?.destroy();
      this.syncManager?.destroy();
      super._onDestroy();
      Logger.debug('SHUTDOWN', LOG_STYLES.GRAY, 'AppController destroyed (suspended).');
    }

    // Method required by the SyncManager's interface for silent updates
    applyUpdate(newConfig) {
      const completeConfig = ConfigProcessor.process(newConfig);
      this.configManager.config = completeConfig;
      this.uiManager.updateConfig(completeConfig);

      // Apply logger level immediately
      if (completeConfig.developer && completeConfig.developer.logger_level) {
        Logger.setLevel(completeConfig.developer.logger_level);
      }

      // Notify UI components (e.g., SettingsPanel) to update their state
      EventBus.publish(EVENTS.CONFIG_UPDATED, completeConfig);
    }

    // Method required by the SyncManager's interface
    getAppId() {
      return APPID;
    }

    // Getter required by the SyncManager's interface
    get configKey() {
      return CONSTANTS.CONFIG_KEY;
    }

    async handleSave(newConfig) {
      try {
        // 1. Save to storage (Validation and sanitization occur here)
        await this.configManager.save(newConfig);

        // 2. Retrieve the sanitized config from the manager
        // This ensures we are using the valid data that was actually saved.
        const savedConfig = this.configManager.get();

        // 3. Update the UI Manager with the new config
        this.uiManager.updateConfig(savedConfig);

        // 4. Apply the new logger level immediately
        if (savedConfig.developer && savedConfig.developer.logger_level) {
          Logger.setLevel(savedConfig.developer.logger_level);
        }

        // 5. Notify UI components (e.g., SettingsPanel) to refresh their form values
        // This is critical if the save was triggered by one component but others need to stay in sync.
        EventBus.publish(EVENTS.CONFIG_UPDATED, savedConfig);

        // 6. Notify SyncManager of the successful save
        this.syncManager.onSave();
      } catch (err) {
        Logger.error('CONFIG', '', 'Failed to save config:', err);
        throw err; // Re-throw the error for the UI layer to catch
      }
    }
  }

  // =================================================================================
  // SECTION: Lifecycle Manager
  // Description: Centralizes application startup/shutdown logic.
  // =================================================================================

  class LifecycleManager extends BaseManager {
    constructor(platformDetails) {
      super();
      this.platformDetails = platformDetails;
      this.app = new AppController(platformDetails);
      this.navMonitor = new NavigationMonitor();
      this._boundUpdateState = this._updateState.bind(this);
    }

    /**
     * @protected
     * @override
     */
    _onInit() {
      // Start navigation monitoring
      this.navMonitor.init();

      // Trigger 1: DOM Detection
      sentinel.on(this.platformDetails.selectors.INPUT_TARGET, this._boundUpdateState);

      // Trigger 2: Navigation
      this._subscribe(EVENTS.NAVIGATION, this._boundUpdateState);

      // Initial check
      this._updateState();
    }

    /**
     * @protected
     * @override
     */
    _onDestroy() {
      // Stop navigation monitoring
      this.navMonitor.destroy();

      // Cleanup Sentinel listener
      sentinel.off(this.platformDetails.selectors.INPUT_TARGET, this._boundUpdateState);

      // Ensure AppController is destroyed
      if (this.app.isInitialized) {
        this.app.destroy();
      }
      super._onDestroy();
    }

    _updateState() {
      // 1. Check exclusion rules
      if (PlatformAdapters.General.isExcludedPage()) {
        Logger.info('SUSPENDED', LOG_STYLES.YELLOW, 'Excluded page detected. App suspended.');
        if (this.app.isInitialized) {
          this.app.destroy();
        }
        return;
      }

      // 2. Try to launch if valid target exists
      if (!this.app.isInitialized) {
        const target = document.querySelector(this.platformDetails.selectors.INPUT_TARGET);
        if (target) {
          this.app.init();
        }
      }
    }
  }

  // =================================================================================
  // SECTION: Entry Point
  // =================================================================================

  // Exit if already executed
  if (ExecutionGuard.hasExecuted()) return;
  ExecutionGuard.setExecuted();

  // Singleton instance for observing internal DOM node insertions.
  const sentinel = new Sentinel(OWNERID);

  // Main Execution
  const platformDetails = PlatformAdapters.General.getPlatformDetails();
  if (platformDetails) {
    const lifecycleManager = new LifecycleManager(platformDetails);
    lifecycleManager.init();
  }
})();