const BaseMixin = {
  data() {
    return {
      isSmallWindow: false,
      isMediumWindow: false,
      // sidebarOpen and sidebarState moved to components
      isEnterPressed: false,
      searchQuery: '',
      enableDeepSearch: false,

      // Sorting
      selectedSort: 'nameA',

      // Context Menu States
      folderContextMenuVisible: false,
      folderContextMenuPosition: { x: 0, y: 0 },
      separatorContextMenuVisible: false,
      separatorContextMenuPosition: { x: 0, y: 0 },
      contentContextMenuVisible: false,
      contentContextMenuPosition: { x: 0, y: 0 },
      itemContextMenuVisible: false,
      itemContextMenuPosition: { x: 0, y: 0 },
      onlineContextMenuVisible: false,
      onlineContextMenuPosition: { x: 0, y: 0 },

      // Common Selection States
      selectedFolderPath: null,
      recentFolderPath: null,
      selectedType: null,
      favorites: [],
      recent: [],

      // View Options
      viewOptions: [
        // 只保留大缩略图 / 小缩略图两种视图模式
        { mode: 'small', title: 'smallThumbnails', icon: 'apps' },
        { mode: 'large', title: 'largeThumbnails', icon: 'view_grid' },
      ],
      currentViewIndex: 0,
      viewMode: 'thumb',
      thumbnailSize: 100,
      // 卡片固定为详细模式（不再提供简洁模式，避免 localStorage 空值误为简洁）
      showDetails: true,

      // User Options Modal
      userOptionsMessage: null,
      userOptionsType: '',
      userOptions: [
        {
          key: "option1",
          label: "Option 1",
          showLabel: true,
          type: "text",
          value: "",
          disabled: false,
          validationType: "string",
          messageError: null,
        },
      ],
      hasOptionsError: false,

      targetMaterialFolderPath: null,
    };
  },

  computed: {
    currentView() {
      return this.viewOptions[this.currentViewIndex];
    },

    currentStyle() {
      // 只使用缩略图网格布局
      return {
        gridTemplateColumns: `repeat(auto-fill, ${this.thumbnailSize}px)`,
        display: 'grid'
      };
    }
  },

  mounted() {
    this.checkWindowSize();
    window.addEventListener('resize', this.checkWindowSize);
    document.addEventListener('click', this.hideContextMenu);
    document.addEventListener('scroll', this.handleScroll);
    document.addEventListener('keydown', this.handleKeydown);

    // Sidebar initialization moved to components
  },

  beforeDestroy() {
    document.removeEventListener('click', this.hideContextMenu);
    document.removeEventListener('scroll', this.handleScroll);
    document.removeEventListener('keydown', this.handleKeydown);
    window.removeEventListener('resize', this.checkWindowSize);
  },

  methods: {
    translated(key) {
      return this.$root.translated(key);
    },

    // Validate file name for invalid characters (Windows and Mac)
    // Windows: \ / : * ? " < > |
    // Mac: : /
    // Returns true if valid, false if contains invalid characters
    validateFileName(fileName, oldItemName) {
      const invalidCharsPattern = /[\\/:*?"<>|]/;
      if (invalidCharsPattern.test(fileName)) {
        const message = this.$root.translated('invalidFileNameCharsAlert');
        this.$root.showMessageModal(message, 'error', 1500);
        // Must wait for the modal auto close before focusing back to input
        setTimeout(() => {
          this.$root.$emit('focus-item-input', oldItemName);
        }, 1700);
        return false;
      }
      return true;
    },

    checkWindowSize() {
      this.isSmallWindow = window.innerWidth < 365;
      this.isMediumWindow = window.innerWidth < 480;
    },

    hideContextMenu() {
      this.folderContextMenuVisible = false;
      this.separatorContextMenuVisible = false;
      this.contentContextMenuVisible = false;
      this.itemContextMenuVisible = false;
      this.onlineContextMenuVisible = false;
    },

    handleScroll() {
      this.hideContextMenu();
    },

    changeView() {
      this.currentViewIndex = (this.currentViewIndex + 1) % this.viewOptions.length;
      this.applyViewOptions(this.currentView.mode);
    },

    applyViewOptions(mode) {
      // 根据视图模式在 documentElement 上打标记，用于 CSS 调整悬停按钮大小
      try {
        const root = document.documentElement;
        root.classList.remove('view-small', 'view-large');
        if (mode === 'small') {
          root.classList.add('view-small');
        } else if (mode === 'large') {
          root.classList.add('view-large');
        }
      } catch (e) {
        // 忽略 HtmlDialog 尚未准备好的异常
      }

      if (mode === 'small') {
        this.viewMode = 'thumb';
        this.thumbnailSize = 100;
      } else if (mode === 'large') {
        this.viewMode = 'thumb';
        this.thumbnailSize = 200;
      }

      // 切换缩略图大小时保持详细模式（不恢复任何「简洁」偏好）
      this.showDetails = true;
      this.saveViewOptions();
      handleTooltip(this);
    },

    clearSearch() {
      this.$emit('update:searchQuery', '');
      // Clear deep search results if method exists (Model component)
      if (typeof this.clearDeepSearch === 'function') {
        this.clearDeepSearch();
      }
    },

    toggleDeepSearch() {
      this.enableDeepSearch = !this.enableDeepSearch;
    },

    toggleSidebar(value) {
      // Direct assignment
      this.sidebarOpen = !this.sidebarOpen;
      this.sidebarState = this.sidebarOpen ? 'sidebar-open' : 'sidebar-closed';

      localStorage.setItem(sidebarKey, this.sidebarOpen);

      updateDynamicWidth(value);
    },

    /** 仅切换二级目录列显示/隐藏（顶部汉堡菜单调用，不收起左侧文件夹列表） */
    toggleSubfolderColumn() {
      if (typeof this.subfolderColumnVisible !== 'boolean') return;
      this.subfolderColumnVisible = !this.subfolderColumnVisible;
      if (typeof subfolderColumnVisibleKey !== 'undefined') {
        localStorage.setItem(subfolderColumnVisibleKey, this.subfolderColumnVisible);
      }
    },

    updateExtensionImagesPath(path) {
      this.folderThumbnailPath = path + '/folder_thumbnail.png';
      this.dynamicIconPath = path + '/dynamic_icon.png';
      this.liveIconPath = path + '/live_icon.png';
    },

    createNewFolder(path, folderName) {
      const itemInfo = {
        itemPath: path + '/' + folderName,
        itemName: folderName,
        itemType: 'folder',
        itemSize: 0,
        thumbnailPath: this.folderThumbnailPath
      };
      sketchup.createNewFolder(itemInfo.itemPath);
    },

    // Get the modal ID based on explorerName (Model or Material)
    getUserOptionsModalId() {
      const type = this.$root.currentTab ? this.$root.currentTab : 'model';
      return `#${type}UserOptionsModal`;
    },

    focusUserOptionsInput(key) {
      const modalId = this.getUserOptionsModalId(); // returns "#modelUserOptionsModal"
      const type = this.$root.currentTab ? this.$root.currentTab : 'model';
      $(modalId).one('shown.bs.modal', () => {
        const inputElement = document.getElementById(`${type}_userOption_${key}`);

        if (inputElement) {
          inputElement.focus();
          inputElement.select();
        }
      });
    },

    initCreateNewFolderOptions(type) {
      this.userOptionsMessage = null;
      this.userOptionsType = 'createNewFolder';
      this.userOptions = [
        {
          key: "folderName",
          label: this.translated('folderName'),
          showLabel: true,
          type: "text",
          value: this.translated('newFolder'),
          disabled: false,
          validationType: "string",
          messageError: null,
        }
      ];

      this.showUserOptionsModal('folderName');
    },

    initRenameItems(type) {
      this.userOptionsMessage = this.translated('renameItemsMessage');
      this.userOptionsType = type + 'RenameItems';
      const prefix = localStorage.getItem(recentPrefixKey) || 'Prefix_';
      this.userOptions = [
        {
          key: "label_group",
          label: this.translated("name"),
          showLabel: false,
          type: "group",
          group: "horizontal",
          disabled: false,
          gap: "4px",
          children: [
            {
              key: "prefix",
              label: this.translated('prefix'),
              showLabel: true,
              type: "text",
              value: prefix,
              disabled: false,
              validationType: "string",
              messageError: null,
            },
            {
              key: "incrementType",
              label: this.translated('incrementType'),
              showLabel: true,
              type: "select",
              options: [
                { value: '1', label: '1, 2, 3...' },
                { value: '01', label: '01, 02, 03...' },
                { value: '001', label: '001, 002, 003...' },
              ],
              value: '01',
              disabled: false,
            }
          ]
        }
      ];

      this.showUserOptionsModal('prefix');
    },

    initInsertModels() {
      this.userOptionsType = 'insertModels';
      const unit = this.$root.modelUnits === 'metric' ? '(mm)' : '(in)';
      const label = this.translated('distanceBetweenModels') + ' ' + unit;
      const value = this.$root.modelUnits === 'metric' ? 1000 : 39;
      this.userOptions = [
        {
          key: "distance",
          label: label,
          showLabel: true,
          type: "number",
          value: value,
          disabled: false,
          validationType: "numberPositive",
          messageError: null,
        }
      ];

      this.$root.showModal(this.getUserOptionsModalId(), "wait");

      // Focus the input after modal is fully shown
      this.focusUserOptionsInput('distance');
    },

    initAddMaterialOptions(totalImages) {
      this.$root.modalTitle = this.translated('textureSizeOptions');
      this.userOptionsMessage = this.translated('convertImageMaterialNotice').replace('{totalImages}', totalImages);
      this.userOptionsType = 'importMaterial';

      const unit = this.$root.modelUnits === 'metric' ? '(mm)' : '(in)';
      const defaultSize = this.$root.modelUnits === 'metric' ? 600 : 12;
      this.userOptions = [
        {
          key: "textureWidth",
          label: this.translated('textureWidth') + ` ${unit}`,
          showLabel: true,
          type: "number",
          value: defaultSize,
          disabled: false,
          validationType: "numberPositive",
          messageError: null,
        },
        {
          key: "textureHeight",
          label: this.translated('textureHeight') + ` ${unit}`,
          showLabel: true,
          type: "number",
          value: defaultSize,
          disabled: false,
          validationType: "numberPositive",
          messageError: null,
        }

      ];

      this.showUserOptionsModal('textureWidth');
    },

    initResizeTexturesOptions() {
      this.userOptionsMessage = this.translated('resizeTexturesMessage');
      this.userOptionsType = 'resizeTextures';
      this.userOptions = [
        {
          key: "size",
          label: this.translated('size') + ' (px)',
          showLabel: true,
          type: "number",
          value: 512,
          disabled: false,
          validationType: "numberPositive",
          messageError: null,
        }
      ];

      this.showUserOptionsModal('size');
    },

    showUserOptionsModal(focusKey = null) {
      // Reset
      this.hasOptionsError = false;

      // Show modal
      this.$root.showModal(this.getUserOptionsModalId(), "wait");

      // Focus input if key provided
      if (focusKey) {
        this.focusUserOptionsInput(focusKey);
      }
    },

    onUserOptionChange(index, element, value, childKey = null) {
      if (!this.userOptions[index]) return;

      // Handle child options in group
      if (childKey && this.userOptions[index].type === 'group' && this.userOptions[index].children) {
        const childOption = this.userOptions[index].children.find(child => child.key === childKey);
        if (childOption) {
          childOption.value = value;
          childOption.messageError = null;

          // Clear error state from the element if validation exists
          if (element && childOption.validationType) {
            this.clearValidationError(element);
          }
        }
        return; // Exit after handling child option
      }

      this.userOptions[index].value = value;

      // Clear validation error when user changes value
      if (this.userOptions[index]) {
        this.userOptions[index].messageError = null;
      }

      // Clear error state
      this.clearValidationError(element);
    },

    handleUserOptionsConfirm() {
      // Reset validation state
      this.hasOptionsError = false;

      // Get the current modal to scope queries
      const modalId = this.getUserOptionsModalId();
      const modalElement = document.querySelector(modalId);
      if (!modalElement) return;

      // Loop through userOptions to validate each option
      this.userOptions.forEach((option, index) => {
        if (option.validationType && (option.type === 'input' || option.type === 'number' || option.type === 'text')) {
          const element = modalElement.querySelector(`[data-option-index="${index}"]`);
          if (element) {
            const validation = this.validateInput(index, element, option.validationType);
            if (!validation.isValid) {
              this.hasOptionsError = true;
            }
          }
        }

        // Check existing messageError for regular options
        if (option.messageError) {
          this.hasOptionsError = true;
        }

        // Validate and check child options for group types
        if (option.type === 'group' && option.children) {
          option.children.forEach((child, childIndex) => {
            // Validate child options with validationType
            if (child.validationType && (child.type === 'input' || child.type === 'number' || child.type === 'text')) {
              const childElement = modalElement.querySelector(`[data-option-index="${index}"][data-child-key="${child.key}"]`);
              if (childElement) {
                const validation = this.validateInput(index, childElement, child.validationType, child.key);
                if (!validation.isValid) {
                  this.hasOptionsError = true;
                }
              }
            }

            // Check existing messageError for child options
            if (child.messageError) {
              this.hasOptionsError = true;
            }
          });
        }
      });

      // If there is any error, do not proceed
      if (this.hasOptionsError) {
        const message = this.translated("pleaseFixValidationErrors");
        this.$root.showMessageModal(message, "error");
        return;
      }

      // Process user options
      this.processUserOptions();
      this.$root.hideModal(this.getUserOptionsModalId());
    },

    handleUserOptionsCancel() {
      // Reset all validation errors
      this.userOptions.forEach((option) => {
        option.messageError = null;

        // Reset child options validation errors
        if (option.type === 'group' && option.children) {
          option.children.forEach(child => {
            child.messageError = null;
          });
        }
      });

      // Hide modal
      this.$root.hideModal(this.getUserOptionsModalId());

      // Reset states
      this.hasOptionsError = false;
      this.$root.modalAction1 = null;
      this.$root.modalAction2 = null;
      this.$root.modalTitle = "";
    },

    processUserOptions() {
      // Create an object with the user option values
      const optionValues = {};

      this.userOptions.forEach((option) => {
        if (option.type === 'group' && option.children) {
          // Handle group options - collect values from children
          option.children.forEach(child => {
            optionValues[child.key] = child.value;
          });
        } else {
          // Handle regular option
          optionValues[option.key] = option.value;
        }
      });

      let showProcessingMessage = false;

      switch (true) {
        case this.userOptionsType === 'insertModels':
          sketchup.insertModels(this.selectedItems, optionValues);
          showProcessingMessage = true;
          break;

        case this.userOptionsType === 'modelRenameItems':
          if (this.selectedType === 'in-model') {
            sketchup.renameModels(this.selectedItems, optionValues);
          } else {
            sketchup.renameModelItems(this.activeFolderPath, this.selectedItems, optionValues);
          }
          showProcessingMessage = true;
          break;

        case this.userOptionsType === 'materialRenameItems':
          if (this.selectedType === 'in-model') {
            sketchup.renameMaterials(this.selectedItems, optionValues);
          } else {
            sketchup.renameMaterialItems(this.activeFolderPath, this.selectedItems, optionValues);
          }
          showProcessingMessage = true;
          break;

        case this.userOptionsType === 'createNewFolder':
          this.createNewFolder(this.activeFolderPath, optionValues['folderName']);
          break;

        case this.userOptionsType === 'importMaterial':
          const folderPath = this.targetMaterialFolderPath;
          sketchup.importMaterial(folderPath, optionValues);
          showProcessingMessage = true;
          break;

        case this.userOptionsType === 'resizeTextures':
          sketchup.resizeTextures(this.selectedItems, this.selectedType, optionValues);
          showProcessingMessage = true;
          break;

        default:
          break;
      }

      // Save recent prefix if applicable
      if (optionValues['prefix'] !== undefined) {
        localStorage.setItem(recentPrefixKey, optionValues['prefix']);
      }

      // Show processing message
      if (showProcessingMessage) {
        const message = this.translated('processing');
        this.$root.showMessageModal(message, 'updating', 'wait');
      }

      // Clear user options after processing
      this.userOptions = [];
      this.userOptionsType = '';
      this.selectedItems = [];
    },

    // ===== VALIDATION HANDLERS =====

    // Event handlers for blur validation (on focus loss)
    handleInputValidation(index, element, validationType, childKey = null) {
      if (!element) return;

      // Validate immediately on blur (no need for debouncing)
      const validationResult = this.validateInput(index, element, validationType, childKey);
      this.hasOptionsError = !validationResult.isValid;
    },

    validateInput(index, element, validationType, childKey = null) {
      if (!element) return { isValid: true };

      const value = element.value;
      let validation = { isValid: true };

      // Remove existing error styling
      element.classList.remove("is-invalid");

      switch (validationType) {
        case "stringEmpty":
          validation = this.validateStringInput(index, value, true, childKey); // Allow empty, max 50 chars
          break;
        case "string":
          validation = this.validateStringInput(index, value, false, childKey); // No empty, max 50 chars
          break;
        case "number":
          validation = this.validateNumberInput(index, value, true, true, childKey); // allow zero, allow negative
          break;
        case "numberPositive":
          validation = this.validateNumberInput(index, value, true, false, childKey); // allow zero, disallow negative
          break;
        default:
          return { isValid: true };
      }

      // Show validation result
      if (!validation.isValid) {
        this.showValidationError(element, validation.message);

        // Keep original invalid value in input so user can see what they typed
        if (validation.originalValue !== undefined) {
          element.value = validation.originalValue;
        }

        return validation;
      } else {
        // Don't set hasOptionsError here, as other fields might still have errors
        return validation;
      }
    },

    validateStringInput(index, value, allowEmpty = false, childKey = null) {
      const trimmedValue = value.trim();

      let isValid = true;
      let message = null;

      // Check if value is empty (when not allowed)
      if (!allowEmpty && !trimmedValue) {
        message = this.translated("cannotBeEmpty");
        isValid = false;
      }

      // Check name length (max 50 characters)
      if (trimmedValue.length > 50) {
        message = this.translated("nameTooLong");
        isValid = false;
      }

      // Set messageError for the appropriate option or child option
      if (!isValid && message) {
        if (childKey && this.userOptions[index] && this.userOptions[index].type === 'group' && this.userOptions[index].children) {
          const childOption = this.userOptions[index].children.find(child => child.key === childKey);
          if (childOption) {
            childOption.messageError = message;
          }
        } else if (this.userOptions[index]) {
          this.userOptions[index].messageError = message;
        }
      }

      return { isValid: isValid };
    },

    validateNumberInput(index, value, allowZero = false, allowNegative = false, childKey = null) {
      const trimmedValue = value.toString().trim();

      let isValid = true;
      let message = null;

      // Check if value contains only valid number characters (digits, decimal point, minus sign)
      // This regex allows: optional minus, digits, optional decimal point and more digits
      const numberRegex = /^-?(\d+\.?\d*|\.\d+)$/;
      if (!numberRegex.test(trimmedValue)) {
        message = this.translated("mustBeNumber");
        isValid = false;
      }

      // Convert to number after validation
      const numericValue = parseFloat(trimmedValue);

      // Double check for NaN (shouldn't happen with regex, but safety check)
      if (isNaN(numericValue)) {
        message = this.translated("mustBeNumber");
        isValid = false;
      }

      // Check if value is zero (when not allowed)
      if (!allowZero && numericValue === 0) {
        message = this.translated("cannotBeZero");
        isValid = false;
      }

      // Check if value is negative (when not allowed)
      if (!allowNegative && numericValue < 0) {
        message = this.translated("cannotBeNegative");
        isValid = false;
      }

      // Check if value is empty
      if (!trimmedValue) {
        message = this.translated("cannotBeEmpty");
        isValid = false;
      }

      // Set messageError for the appropriate option or child option
      if (!isValid && message) {
        if (childKey && this.userOptions[index] && this.userOptions[index].type === 'group' && this.userOptions[index].children) {
          const childOption = this.userOptions[index].children.find(child => child.key === childKey);
          if (childOption) {
            childOption.messageError = message;
          }
        } else if (this.userOptions[index]) {
          this.userOptions[index].messageError = message;
        }
      }

      if (isValid) {
        this.hasOptionsError = false;
      }

      return { isValid: isValid };
    },

    showValidationError(element, message) {
      if (!element) return;

      // Add error styling to input
      element.classList.add("is-invalid");

      // Show error message
      // this.showMessageModal(message, "error", 1000);
    },

    clearValidationError(element) {
      if (!element) return;

      // Remove error styling from input
      element.classList.remove("is-invalid");
    },

    handleKeydown(event) {
      const isInputFocused  = document.activeElement.tagName === 'INPUT' ||
                              document.activeElement.tagName === 'TEXTAREA' ||
                              document.activeElement.isContentEditable;

      if (event.key === 'Backspace' && !isInputFocused) {
        event.preventDefault();
        this.handleParentClick();
      }
    }
  }
};
