const ModelComponent = {
  mixins: [BaseMixin],
  props: {
    searchQuery: { type: String, default: '' }
  },
  data() {
    return {
      // Override mixin's sidebarOpen to ensure reactivity
      sidebarOpen: true,
      sidebarState: 'sidebar-open',

      defaultSource: ['https://3dwarehouse.sketchup.com/', '3D Warehouse'],
      onlineSources: [],
      sortOrderFolders: 1,
      // Flag to control auto-sorting: true = sort on load, false = manual sort only
      autoSortEnabled: true,
      modelFolders: [],
      modelFolder: {
        folderPath: '',
        originFolderName: '',
        customFolderName: '',
        folderItems: ''
      },
      modelItems: [],
      modelItem: {
        itemPath: '',
        itemName: '',
        itemType: '',
        itemSize: '',
        thumbnailPath: ''
      },
      isOnline: false,

      currentUrl: '',
      iframeBlocked: false,
      selectedOnlineName: '',
      selectedOnlineUrl: '',

      dynamicIconPath: null,
      liveIconPath: null,
      activeModelPath: [],

      folderThumbnailPath: null,
      recentFolderPath: null,
      recentChildFolder: null,
      activeModel: true,
      activeFolderPath: null,
      selectedFolderPath: null,
      /** 左侧选中目录路径，中间列树固定显示该目录的子树，不随点击中间列而改变 */
      treeRootPath: null,
      /** 左侧选中目录下的内容（用于中间列树），仅在与 treeRootPath 一致时由 updateItems 更新 */
      treeRootItems: null,
      /** 仅用于 updateItems：区分当前 browse 是否来自左侧栏点击，避免点击二级目录时把 treeRoot 改成子目录 */
      _browseFromLeft: false,
      selectedType: null,
      selectedItemType: null,
      actionFolderPath: null,

      selectedModels: [], // From SketchUp selection
      selectedItem: null,
      selectedItems: [],
      nameExists: false,

      isDrive: false,
      isSketchupModel: false,
      folderEmpty: false,
      showSupport: false,

      showAllModels: true,
      firstShowAllModel: true,

      // Deep Search
      deepSearchResults: [],
      isDeepSearching: false,

      // 左侧边栏可调宽度（拖拽分割线）
      sidebarWidth: typeof SIDEBAR_WIDTH_DEFAULT !== 'undefined' ? SIDEBAR_WIDTH_DEFAULT : 230,
      _resizeStartX: 0,
      _resizeStartWidth: 0,
      // 二级目录列可调宽度（与右侧内容区间分割线）
      subfolderColumnWidth: typeof SUBFOLDER_WIDTH_DEFAULT !== 'undefined' ? SUBFOLDER_WIDTH_DEFAULT : 160,
      _resizeSubfolderStartX: 0,
      _resizeSubfolderStartWidth: 0,
      // 二级目录列显示/隐藏（顶部汉堡菜单只控制此项，不收起左侧文件夹列表）
      subfolderColumnVisible: true,
    };
  },

  computed: {
    /** 中间列树使用的数据：固定为左侧选中目录下的内容，不随点击中间列而变成子目录 */
    subfolderTreeItems() {
      if (this.treeRootItems != null) return this.treeRootItems;
      return this.modelItems || [];
    },
    filteredItems() {
      let filtered = this.modelItems;

      if (this.selectedType === 'favorites') {
        filtered = filtered.filter(itemInfo => this.favorites.includes(itemInfo.itemPath));
      }

      // Deep search mode: merge results from subfolders
      if (this.enableDeepSearch && this.searchQuery && this.deepSearchResults.length > 0) {
        // Get current folder items paths to avoid duplicates
        const currentPaths = new Set(filtered.map(item => item.itemPath));

        // Add deep search results that are not already in current items
        const uniqueDeepResults = this.deepSearchResults.filter(
          item => !currentPaths.has(item.itemPath)
        );

        // Merge: current folder items + deep search results from subfolders
        filtered = [...filtered, ...uniqueDeepResults];

        // Filter by search query
        const query = this.searchQuery.toLowerCase();
        filtered = filtered.filter(itemInfo => 
          itemInfo.itemName && itemInfo.itemName.toLowerCase().includes(query)
        );
      } else if (this.searchQuery) {
        // Normal search: filter current items only
        const query = this.searchQuery.toLowerCase();
        filtered = filtered.filter(itemInfo => itemInfo.itemName.toLowerCase().includes(query));
      }

      // 右侧只显示文件，文件夹全部在中间列二级目录显示
      filtered = filtered.filter(itemInfo => !(itemInfo.itemType && String(itemInfo.itemType).includes('folder')));

      return filtered;
    },

  },

  mounted() {
    // Load sidebar state BEFORE mixin's mounted runs
    const sidebarSaved = localStorage.getItem(sidebarKey);
    if (sidebarSaved !== null) {
      const sidebar = sidebarSaved === 'true';
      this.sidebarOpen = sidebar;
      this.sidebarState = sidebar ? 'sidebar-open' : 'sidebar-closed';
    }
    const savedWidth = localStorage.getItem(sidebarWidthKey);
    if (savedWidth) {
      const w = parseInt(savedWidth, 10);
      if (!isNaN(w) && w >= (typeof SIDEBAR_WIDTH_MIN !== 'undefined' ? SIDEBAR_WIDTH_MIN : 180) && w <= (typeof SIDEBAR_WIDTH_MAX !== 'undefined' ? SIDEBAR_WIDTH_MAX : 420)) {
        this.sidebarWidth = w;
      }
    }
    const savedSubfolderWidth = localStorage.getItem(subfolderColumnWidthKey);
    if (savedSubfolderWidth) {
      const w = parseInt(savedSubfolderWidth, 10);
      if (!isNaN(w) && w >= (typeof SUBFOLDER_WIDTH_MIN !== 'undefined' ? SUBFOLDER_WIDTH_MIN : 120) && w <= (typeof SUBFOLDER_WIDTH_MAX !== 'undefined' ? SUBFOLDER_WIDTH_MAX : 280)) {
        this.subfolderColumnWidth = w;
      }
    }
    const savedSubfolderVisible = localStorage.getItem(subfolderColumnVisibleKey);
    if (savedSubfolderVisible !== null) {
      this.subfolderColumnVisible = savedSubfolderVisible === 'true';
    }

    // Load online state（在线功能已移除，强制本地模式）
    this.isOnline = false;
    this.switchMode('local');

    // Load recent URL
    const recentUrl = localStorage.getItem(recentModelUrlKey);
    this.currentUrl = recentUrl || this.defaultSource[0];

    // Load local storage
    this.loadDetailsState();
    this.loadViewOptions();
    this.loadShowAllState();

    // Update dynamic width
    updateDynamicWidth('model-app');

    // Drag & Sort - Initialize Sortable
    this.$nextTick(() => {
      this.initSortableLocal();
      this.initSortableOnline();
    });
  },

  methods: {
    initSortableLocal() {
      const sortableLocal = document.getElementById('sortableLocalModel');
      if (sortableLocal && !sortableLocal._sortableInstance) {
        sortableLocal._sortableInstance = new Sortable(sortableLocal, {
          animation: 150,
          onEnd: () => {
            this.handleSortLocal();
          }
        });
      }
    },

    initSortableOnline() {
      const sortableOnline = document.getElementById('sortableOnlineModel');
      if (sortableOnline && !sortableOnline._sortableInstance) {
        sortableOnline._sortableInstance = new Sortable(sortableOnline, {
          animation: 150,
          onEnd: () => {
            this.handleSortOnline();
          }
        });
      }
    },

    disableSortableLocal() {
      const sortableLocal = document.getElementById('sortableLocalModel');
      if (sortableLocal && sortableLocal._sortableInstance) {
        sortableLocal._sortableInstance.option('disabled', true);
      }
    },

    enableSortableLocal() {
      const sortableLocal = document.getElementById('sortableLocalModel');
      if (sortableLocal && sortableLocal._sortableInstance) {
        sortableLocal._sortableInstance.option('disabled', false);
      }
    },

    // Overridden from Mixin or specific to Model
    saveViewOptions() {
      const viewOptionsToSave = {
        currentViewIndex: this.currentViewIndex,
        thumbnailSize: this.thumbnailSize
      };
      // 使用全局视图设置键，四个页面同步缩略图视图
      localStorage.setItem(viewOptionsKey, JSON.stringify(viewOptionsToSave));
    },

    loadViewOptions() {
      // 从全局视图设置键加载，保持与其它页面同步
      const savedOptions = localStorage.getItem(viewOptionsKey);
      if (savedOptions) {
        const options = JSON.parse(savedOptions);
        this.currentViewIndex = options.currentViewIndex !== undefined ? options.currentViewIndex : 1;
        this.thumbnailSize = options.thumbnailSize || 100;
        this.applyViewOptions(this.viewOptions[this.currentViewIndex].mode);
      }
    },

    switchMode(value) {
      // 在线功能已移除，始终使用本地模式
      this.isOnline = false;
      const onlineState = 'local';
      localStorage.setItem(modelOnlineStateKey, onlineState);
    },

    startResizeSidebar(e) {
      this._resizeStartX = e.clientX;
      this._resizeStartWidth = this.sidebarWidth;
      document.addEventListener('mousemove', this._onResizeSidebarMove);
      document.addEventListener('mouseup', this._onResizeSidebarUp);
      e.preventDefault();
    },
    _onResizeSidebarMove(e) {
      const minW = typeof SIDEBAR_WIDTH_MIN !== 'undefined' ? SIDEBAR_WIDTH_MIN : 180;
      const maxW = typeof SIDEBAR_WIDTH_MAX !== 'undefined' ? SIDEBAR_WIDTH_MAX : 420;
      let w = this._resizeStartWidth + (e.clientX - this._resizeStartX);
      w = Math.max(minW, Math.min(maxW, w));
      this.sidebarWidth = w;
    },
    _onResizeSidebarUp() {
      document.removeEventListener('mousemove', this._onResizeSidebarMove);
      document.removeEventListener('mouseup', this._onResizeSidebarUp);
      if (typeof localStorage !== 'undefined' && typeof sidebarWidthKey !== 'undefined') {
        localStorage.setItem(sidebarWidthKey, String(this.sidebarWidth));
      }
    },

    startResizeSubfolderColumn(e) {
      this._resizeSubfolderStartX = e.clientX;
      this._resizeSubfolderStartWidth = this.subfolderColumnWidth;
      document.addEventListener('mousemove', this._onResizeSubfolderMove);
      document.addEventListener('mouseup', this._onResizeSubfolderUp);
      e.preventDefault();
    },
    _onResizeSubfolderMove(e) {
      const minW = typeof SUBFOLDER_WIDTH_MIN !== 'undefined' ? SUBFOLDER_WIDTH_MIN : 120;
      const maxW = typeof SUBFOLDER_WIDTH_MAX !== 'undefined' ? SUBFOLDER_WIDTH_MAX : 280;
      let w = this._resizeSubfolderStartWidth + (e.clientX - this._resizeSubfolderStartX);
      w = Math.max(minW, Math.min(maxW, w));
      this.subfolderColumnWidth = w;
    },
    _onResizeSubfolderUp() {
      document.removeEventListener('mousemove', this._onResizeSubfolderMove);
      document.removeEventListener('mouseup', this._onResizeSubfolderUp);
      if (typeof localStorage !== 'undefined' && typeof subfolderColumnWidthKey !== 'undefined') {
        localStorage.setItem(subfolderColumnWidthKey, String(this.subfolderColumnWidth));
      }
    },

    sortFolders() {
      // Toggle between ascending (1) and descending (-1)
      this.sortOrderFolders = this.sortOrderFolders * -1;
      
      this.modelFolders.sort((a, b) => {
        if (a.customFolderName && b.customFolderName) {
          return this.sortOrderFolders === 1
            ? a.customFolderName.localeCompare(b.customFolderName)
            : b.customFolderName.localeCompare(a.customFolderName);
        } else if (a.customFolderName) {
          return -1;
        } else if (b.customFolderName) {
          return 1;
        }
        return 0;
      });

      const newOrder = this.modelFolders.map(folder => [folder.folderPath, folder.customFolderName]);
      sketchup.updateModelFolderOrder(JSON.stringify(newOrder));

      // Reinitialize Sortable after Vue re-renders the list
      this.$nextTick(() => {
        const sortableLocal = document.getElementById('sortableLocalModel');
        if (sortableLocal) {
          if (sortableLocal._sortableInstance) {
            sortableLocal._sortableInstance.destroy();
            sortableLocal._sortableInstance = null;
          }
          this.initSortableLocal();
        }
      });
    },

    sortedBy(value) {
      if (!this.modelItems || this.modelItems.length === 0) return;

      this.selectedSort = value;

      const folders = this.modelItems.filter(item => item.itemType && item.itemType.includes('folder'));
      const nonFolders = this.modelItems.filter(item => item.itemType && !item.itemType.includes('folder'));

      const sortFunction = (a, b) => {
        if (value === 'nameA') {
          return a.itemName.localeCompare(b.itemName, undefined, { numeric: true, sensitivity: 'base' });
        } else if (value === 'nameZ') {
          return b.itemName.localeCompare(a.itemName, undefined, { numeric: true, sensitivity: 'base' });
        } else if (value === 'sizeMin') {
          return this.parseSize(a.itemSize) - this.parseSize(b.itemSize);
        } else if (value === 'sizeMax') {
          return this.parseSize(b.itemSize) - this.parseSize(a.itemSize);
        }
        return 0;
      };

      folders.sort(sortFunction);
      nonFolders.sort(sortFunction);

      this.modelItems = [...folders, ...nonFolders];
      localStorage.setItem(modelSortedByKey, value);
    },

    parseSize(size) {
      // Handle number type directly
      if (typeof size === 'number') {
        return size;
      }

      if (typeof size !== 'string') {
        return null;
      }

      const trimmed = size.trim();

      // Handle "-" format (folders containing subfolders)
      if (trimmed === '-') {
        return -1; // Return -1 to sort these folders at the beginning or end
      }

      // Handle "10.0 MB" format (file size)
      if (size.includes('MB')) {
        return parseFloat(size.replace(' MB', '').trim());
      }

      // Handle pure number string (item count in folders, e.g., "49", "500")
      const num = parseInt(trimmed, 10);
      if (!isNaN(num)) {
        return num;
      }

      return null;
    },

    updateOnlineSources(data) {
      if (data) {
        this.onlineSources = data;
        if (this.onlineSources.length === 0 ) {
          this.currentUrl = this.defaultSource[0];
          localStorage.setItem(recentModelUrlKey, this.defaultSource[0]);
        }
      }
    },

    openAddSource() {
      $("#modelSourceOnline").modal("show");
    },

    addSourceOnline() {
      const addNameInput = document.getElementById('modelAddNameInput');
      const nameValue = addNameInput.value.trim();
      const addUrlInput = document.getElementById('modelAddUrlInput');
      const urlValue = addUrlInput.value.trim();

      const urlPattern = new RegExp('^(https?:\\/\\/)?' + 
        '((([a-zA-Z0-9\\-]+\\.)+[a-zA-Z]{2,})|' + 
        '((\\d{1,3}\\.){3}\\d{1,3}))' + 
        '(\\:\\d+)?(\\/[-a-zA-Z0-9%_.~+]*)*' + 
        '(\\?[;&a-zA-Z0-9%_.~+=-]*)?' + 
        '(\\#[-a-zA-Z0-9_]*)?$', 'i');

      if (!nameValue) {
        alert('Source Name is required.');
        return;
      }

      if (!urlPattern.test(urlValue)) {
        alert('This is not a valid URL. Please try again!');
        return;
      }

      canEmbedInIframe(urlValue).then(canEmbed => {
        if (!canEmbed) {
          const message = this.$root.translated('siteBlocked');
          const action = () => { $("#modelSourceOnline").modal("show"); };
          this.$root.showNoticeModal(message, null, action);
        } else {
          const newSource = [urlValue, nameValue];
          if (!Array.isArray(this.onlineSources)) {
            this.onlineSources = [];
          }

          this.onlineSources.push(newSource);
          this.currentUrl = urlValue;

          localStorage.setItem(recentModelUrlKey, urlValue);
          $("#modelSourceOnline").modal("hide");
          sketchup.updateModelOnlineSources(this.onlineSources);
        }
      });
    },

    browseUrl(urlValue) {
      this.currentUrl = urlValue;
      localStorage.setItem(recentModelUrlKey, urlValue || this.defaultSource[0]);
    },

    browseCollection(collection) {
      sketchup.browseModelCollection(collection);
      this.selectedFolderPath = null;
      this.recentFolderPath = null;
      this.selectedType = collection;
      this.clearDeepSearch(); // Clear deep search when changing collection
      this.switchMode('local');
    },

    addCollection() {
      sketchup.addModelCollection();
      this.recentFolderPath = null;
      this.selectedFolderPath = null;
      this.selectedType = null;
    },

    handleParentClick() {
      if (this.selectedType === 'in-model') {
        sketchup.parentModelActivePath();
      } else {
        this.parentFolder();
      }
    },

    handleItemClick(itemInfo) {
      if (itemInfo.itemType !== 'folder') {
        // Update selected models
        this.selectedModels = [itemInfo.itemName];

        // Insert the model
        sketchup.insertModel(itemInfo.itemPath, itemInfo.itemType);

        // Update recent count
        const allRecent = document.getElementById('allModelRecent');
        if (allRecent) {
          const currentValue = parseInt(allRecent.textContent, 10) || 0;
          if (currentValue < 10) {
            allRecent.textContent = currentValue + 1;
          }
        }
      } else {
        this.browseFolder(itemInfo.itemPath, false);
        this.updateRecentChildFolder(itemInfo.itemPath);
      }
    },

    updateThumbnails() {
      const message = this.$root.translated('updateThumbnailsWaitingMessage');
      this.$root.showMessageModal(message, 'updating');
      sketchup.updateModelThumbnails();
    },

    updateRecentChildFolder(path) {
      localStorage.setItem(recentModelChildFolderKey, path);
    },

    updateRecentFolder(recentFolder) {
      this.recentFolderPath = recentFolder;
      this.selectedType = recentFolder;
    },

    updateFolders(newModelFolders) {
      if (!Array.isArray(newModelFolders)) return;

      // 如果 Ruby 返回的集合列表为空，说明所有一级目录都删光了，
      // 此时要同步清空中间二级目录树和当前浏览目录，避免残留旧缓存。
      if (newModelFolders.length === 0) {
        this.modelFolders = [];
        this.selectedFolderPath = null;
        this.treeRootPath = null;
        this.treeRootItems = null;
        this.modelItems = [];
        this.folderEmpty = true;
        return;
      }

      // Initialize reactive properties for each folder
      newModelFolders.forEach(folder => {
        if (!folder.hasOwnProperty('isFolderEditing')) {
          this.$set(folder, 'isFolderEditing', false);
        }
        if (!folder.hasOwnProperty('newName')) {
          this.$set(folder, 'newName', '');
        }
      });

      // Always replace with new data from Ruby
      this.modelFolders = newModelFolders;

      // 如果当前没有选中任何目录（例如首次加载或切换到该页），自动选中第一个一级目录并同步浏览
      const hasSelection = !!(this.selectedFolderPath || this.treeRootPath);
      const isCollectionType = this.selectedType === 'in-model' || this.selectedType === 'favorites' || this.selectedType === 'recent';
      if (!hasSelection && !isCollectionType && newModelFolders.length > 0) {
        const firstPath = newModelFolders[0].folderPath;
        if (firstPath) {
          this.treeRootPath = firstPath;
          this.browseFolder(firstPath, false, true);
        }
      }

      // Re-initialize Sortable after Vue re-renders the list
      this.$nextTick(() => {
        // Destroy old Sortable instance if exists
        const sortableLocal = document.getElementById('sortableLocalModel');
        if (sortableLocal && sortableLocal._sortableInstance) {
          sortableLocal._sortableInstance.destroy();
          sortableLocal._sortableInstance = null;
        }
        this.initSortableLocal();
      });
    },

    updateItems(newModelItems) {
      if (newModelItems && newModelItems.length > 0) {
        // Initialize reactive properties for each item
        newModelItems.forEach(item => {
          if (!item.hasOwnProperty('isItemEditing')) {
            this.$set(item, 'isItemEditing', false);
          }
          if (!item.hasOwnProperty('newName')) {
            this.$set(item, 'newName', '');
          }
        });

        this.modelItems = newModelItems;
        this.folderEmpty = false;
      } else {
        this.modelItems = newModelItems || [];
        this.folderEmpty = true;
      }
      // 仅当来自左侧栏点击时把当前路径当作树根，避免点击二级目录后把树根改成子目录导致中间列变成「暂无子文件夹」
      if (this.treeRootPath == null && this.selectedFolderPath && this._browseFromLeft) this.treeRootPath = this.selectedFolderPath;
      if (this.selectedFolderPath === this.treeRootPath) {
        this.treeRootItems = this.modelItems && this.modelItems.length ? this.modelItems.slice() : [];
      }

      // Only auto-sort when loading new items (not when adding/renaming)
      if (this.autoSortEnabled) {
        const sortedSaved = localStorage.getItem(modelSortedByKey) || 'nameA';
        this.sortedBy(sortedSaved);
      }

      // 当前目录若无文件仅有子文件夹，则自动显示下一级（第一个子文件夹）内容
      const items = newModelItems || [];
      const files = items.filter(item => !(item.itemType && String(item.itemType).includes('folder')));
      const folders = items.filter(item => item.itemType && String(item.itemType).includes('folder'));
      if (files.length === 0 && folders.length > 0) {
        this.$nextTick(() => { this.browseFolder(folders[0].itemPath, false); });
      }

      handleTooltip(this);
    },

    // Force refresh all thumbnails by adding timestamp (used after update_thumbnails)
    forceRefreshThumbnails() {
      forceHideAllModals();
      const timestamp = '?t=' + Date.now();
      this.modelItems.forEach(item => {
        if (item.thumbnailPath) {
          // Remove old timestamp if exists, then add new one
          const basePath = item.thumbnailPath.split('?')[0];
          this.$set(item, 'thumbnailPath', basePath + timestamp);
        }
      });
    },

    // Targeted update for a single renamed item (avoid full refresh)
    updateRenamedItem(result) {
      if (!result) return;

      const { oldPath, newPath, newName, thumbnailPath } = result;
      const itemInfo = this.modelItems.find(item => item.itemPath === oldPath);

      if (itemInfo) {
        // Update item properties reactively
        this.$set(itemInfo, 'itemPath', newPath);
        this.$set(itemInfo, 'itemName', newName);
        this.$set(itemInfo, 'thumbnailPath', thumbnailPath + '?t=' + Date.now());
        this.$set(itemInfo, 'isItemEditing', false);

        // Update favorites if the old path was in favorites
        const favIndex = this.favorites.indexOf(oldPath);
        if (favIndex > -1) {
          this.favorites.splice(favIndex, 1, newPath);
          sketchup.updateModelFavorites(newPath, itemInfo.itemType);
        }
      }
    },

    // Add a single new item to the end of the list (avoid full refresh)
    addNewItem(itemData) {
      if (!itemData) return;

      // Add timestamp to thumbnail to prevent caching
      if (itemData.thumbnailPath) {
        itemData.thumbnailPath = itemData.thumbnailPath + '?t=' + Date.now();
      }

      // Initialize reactive properties
      this.$set(itemData, 'isItemEditing', false);
      this.$set(itemData, 'newName', '');

      // Simply append to the end of the list (no auto-sorting)
      this.modelItems.push(itemData);
    },

    // Find the correct position to insert new item based on current sort order
    findInsertIndex(newItem) {
      const sortValue = this.selectedSort || 'nameA';
      const isFolder = newItem.itemType && newItem.itemType.includes('folder');
      
      // Separate folders and non-folders
      let startIndex = 0;
      let endIndex = this.modelItems.length;
      
      if (isFolder) {
        // Find the end of folders section
        endIndex = this.modelItems.findIndex(item => 
          !item.itemType || !item.itemType.includes('folder')
        );
        if (endIndex === -1) endIndex = this.modelItems.length;
      } else {
        // Find the start of non-folders section
        startIndex = this.modelItems.findIndex(item => 
          !item.itemType || !item.itemType.includes('folder')
        );
        if (startIndex === -1) startIndex = this.modelItems.length;
      }

      // Find correct position within the section
      for (let i = startIndex; i < endIndex; i++) {
        const compareItem = this.modelItems[i];
        let shouldInsertBefore = false;

        if (sortValue === 'nameA') {
          shouldInsertBefore = newItem.itemName.localeCompare(
            compareItem.itemName, undefined, { numeric: true, sensitivity: 'base' }
          ) < 0;
        } else if (sortValue === 'nameZ') {
          shouldInsertBefore = newItem.itemName.localeCompare(
            compareItem.itemName, undefined, { numeric: true, sensitivity: 'base' }
          ) > 0;
        } else if (sortValue === 'sizeMin') {
          shouldInsertBefore = this.parseSize(newItem.itemSize) < this.parseSize(compareItem.itemSize);
        } else if (sortValue === 'sizeMax') {
          shouldInsertBefore = this.parseSize(newItem.itemSize) > this.parseSize(compareItem.itemSize);
        }

        if (shouldInsertBefore) {
          return i;
        }
      }

      return endIndex;
    },

    updateTotalInModel(total) {
      const inModel = document.getElementById('modelInModel');
      if (inModel) {
        inModel.textContent = total;
      }
    },

    updateFavorites(favorites) {
      this.favorites = favorites;
      const allFavorites = document.getElementById('allModelFavorites');
      if (allFavorites) {
        allFavorites.textContent = favorites.length;
      }
    },

    toggleFavorite(itemInfo) {
      if (itemInfo.itemType !== 'folder') {
        const index = this.favorites.indexOf(itemInfo.itemPath);
        if (index > -1) {
          this.favorites.splice(index, 1);
        } else {
          this.favorites.push(itemInfo.itemPath);
        }

        const allFavorites = document.getElementById('allModelFavorites');
        if (allFavorites) {
          allFavorites.textContent = this.favorites.length;
        }

        sketchup.updateModelFavorites(itemInfo.itemPath, itemInfo.itemType);
      } else {
        sketchup.addModelCollection(itemInfo.itemPath);
      }
    },

    updateRecent(recent) {
      this.recent = recent;
      const allRecent = document.getElementById('allModelRecent');
      if (allRecent) {
        allRecent.textContent = recent.length > 10 ? 10 : recent.length;
      }
    },

    parentFolder() {
      const recentChildFolder = localStorage.getItem(recentModelChildFolderKey);
      sketchup.browseModelFolder(recentChildFolder, true);
    },

    browseFolder(folderPath, parent, fromLeft) {
      this._browseFromLeft = !!fromLeft;
      sketchup.browseModelFolder(folderPath, parent);
      this.selectedFolderPath = folderPath;
      this.recentFolderPath = null;
      this.selectedType = null;
      this.clearDeepSearch(); // Clear deep search when changing folder
      this.switchMode('local');
    },
    /** 仅左侧栏点击目录时调用，用于固定中间列树的根 */
    onLeftBrowseFolder(folderPath) {
      this.treeRootPath = folderPath;
      this.browseFolder(folderPath, false, true);
    },

    selectedFolder(folderPath) {
      this.selectedFolderPath = folderPath;
    },

    showCollectionContextMenu(event, folderPath, folderName) {
      // Hide other context menus
      this.hideContextMenu();

      // Display context menu
      this.folderContextMenuVisible = true;

      const menuHeight = 216;
      const dialogHeight = window.innerHeight;
      const buffer = 10;

      // Get position Y of menu
      let calculatedY = event.clientY;

      // Distance from click pos to dialog bottom
      const distanceToBottom = dialogHeight - event.clientY;

      // If menu height is over dialog bottom, re-position Y
      if (distanceToBottom < menuHeight + buffer) {
        calculatedY = dialogHeight - menuHeight - buffer;
      }

      // Update new position of menu
      this.folderContextMenuPosition.x = event.clientX;
      this.folderContextMenuPosition.y = calculatedY;

      // Save selected folder
      this.actionFolderPath = folderPath;
      this.selectedFolderName = folderName;
    },

    collectionActions(action) {
      if (action === 'rename') {
        this.renameCollection(this.actionFolderPath);
      } else if (action === 'remove') {
        this.removeCollection(this.actionFolderPath);
      } else if (action === 'add_separator') {
        this.addSeparator(this.actionFolderPath);
      } else if (action === 'open_location') {
        sketchup.openFolder(this.actionFolderPath);
      };
      this.folderContextMenuVisible = false;
    },

    renameCollection(folderPath) {
      // Close all other editing folders first
      this.modelFolders.forEach(folder => {
        if (folder.isFolderEditing) {
          this.$set(folder, 'isFolderEditing', false);
        }
      });

      const folderInfo = this.modelFolders.find(folder => folder.folderPath === folderPath);
      if (folderInfo) {
        // Disable Sortable to allow text selection in input
        this.disableSortableLocal();

        this.$set(folderInfo, 'newName', folderInfo.customFolderName);
        this.$set(folderInfo, 'isFolderEditing', true);

        this.$nextTick(() => {
          // Emit event to sidebar to focus the input
          this.$root.$emit('focus-folder-input', folderInfo.folderPath);
        });
      }
    },

    saveNewCollectionName(folderPath, oldFolderName) {
      const folderInfo = this.modelFolders.find(folder => folder.folderPath === folderPath);

      if (folderInfo && folderInfo.isFolderEditing) {
        let editedName;
        let newFolderName;
        if (folderInfo.newName) {
          newFolderName = folderInfo.newName.trim();
          if (newFolderName !== '' && newFolderName !== oldFolderName) {
            editedName = newFolderName;
          } else {
            editedName = folderInfo.customFolderName;
          }
        } else {
          editedName = folderInfo.originFolderName;
        };

        this.$set(folderInfo, 'isFolderEditing', false);

        // Re-enable Sortable after editing is done
        this.enableSortableLocal();

        sketchup.renameModelCollection(folderPath, editedName);
      }
    },

    removeCollection(folderPath) {
      // 如果当前二级目录树的根或当前选中目录就在被删除的集合下，先清空本地状态
      const base = (folderPath || '').toString().replace(/\\/g, '/').replace(/\/+$/, '');
      const selected = (this.selectedFolderPath || '').toString().replace(/\\/g, '/');
      const treeRoot = (this.treeRootPath || '').toString().replace(/\\/g, '/');

      if (base && (selected === base || selected.startsWith(base + '/') || treeRoot === base || treeRoot.startsWith(base + '/'))) {
        this.selectedFolderPath = null;
        this.treeRootPath = null;
        this.treeRootItems = null;
        this.modelItems = [];
        this.folderEmpty = true;
      }

      sketchup.removeModelCollection(folderPath);
    },

    addSeparator(folderPath) {
      // Find the folder element in DOM
      const list = document.getElementById('sortableLocalModel');
      const items = list.getElementsByTagName('li');
      let targetItem = null;

      for (let i = 0; i < items.length; i++) {
        if (items[i].getAttribute('data-folder-path') === folderPath) {
          targetItem = items[i];
          break;
        }
      }

      if (targetItem) {
        // Create separator element directly in DOM
        const separatorLi = document.createElement('li');
        separatorLi.setAttribute('data-folder-path', '|-----|');
        separatorLi.className = 'li-separator';
        separatorLi.innerHTML = `
          <div class="dropdown-divider custom-dropdown-divider"></div>
          <span class="folder-name"></span>
        `;

        // Add context menu handler
        separatorLi.addEventListener('contextmenu', (event) => {
          event.stopPropagation();
          event.preventDefault();
          this.showSeparatorContextMenu(event);
        });

        // Insert after target item
        targetItem.parentNode.insertBefore(separatorLi, targetItem.nextSibling);

        // Save the new order to Ruby
        this.handleSortLocal();
      }
    },

    removeSeparator() {
      const separator = this.separatorElement;
      if (separator) {
        separator.remove();
        this.$nextTick(() => {
          this.handleSortLocal();
        });
      }
      this.separatorContextMenuVisible = false;
    },

    addSelected(folderPath) {
      sketchup.addModelSelected(folderPath);
    },

    showItemContextMenu(event, itemInfo) {
      this.selectedItemType = itemInfo.itemType !== 'folder' ? 'item' : 'folder';

      // Hide other context menus
      this.hideContextMenu();

      // Check item type and save selected item BEFORE showing menu
      this.isSketchupModel = itemInfo.itemType !== 'folder';
      this.selectedItem = itemInfo;

      // Display context menu
      this.itemContextMenuVisible = true;

      // Set temporary position at top-left corner to measure true dimensions
      // (placing at click position may cause menu to be clipped by viewport,
      // resulting in getBoundingClientRect returning truncated dimensions)
      this.itemContextMenuPosition = {
        x: 0,
        y: 0
      };

      // Use $nextTick to ensure that the menu is rendered before calculating its dimensions
      this.$nextTick(() => {
        // Query the actual menu element from DOM
        const menuElements = document.querySelectorAll('.context-menu.custom-menu');
        let menuElement = null;

        // Find the visible item context menu
        menuElements.forEach(el => {
          if (el.style.display !== 'none' && window.getComputedStyle(el).display !== 'none') {
            const hasItemContent = el.querySelector('.custom-span');
            if (hasItemContent && this.itemContextMenuVisible) {
              menuElement = el;
            }
          }
        });

        if (!menuElement) return;

        // Calculate real menu dimensions (now measured without viewport clipping)
        const menuRect = menuElement.getBoundingClientRect();
        const menuWidth = menuRect.width;
        const menuHeight = menuRect.height;

        // Get the viewport dimensions
        const dialogWidth = window.innerWidth;
        const dialogHeight = window.innerHeight;
        const buffer = 10;

        // Get position of menu
        let calculatedX = event.clientX;
        let calculatedY = event.clientY;

        // Distance from click pos to dialog right & bottom
        const distanceToRight = dialogWidth - event.clientX;
        const distanceToBottom = dialogHeight - event.clientY;

        // If menu width is over dialog right, re-position X
        if (distanceToRight < menuWidth + buffer) {
          calculatedX = dialogWidth - menuWidth - buffer;
        }

        // If menu height is over dialog bottom, re-position Y
        if (distanceToBottom < menuHeight + buffer) {
          calculatedY = dialogHeight - menuHeight - buffer;
        }

        // Ensure menu doesn't go beyond left or top edge
        if (calculatedX < buffer) {
          calculatedX = buffer;
        }
        if (calculatedY < buffer) {
          calculatedY = buffer;
        }

        // Update new position of menu
        this.itemContextMenuPosition.x = calculatedX;
        this.itemContextMenuPosition.y = calculatedY;
      });
    },

    getSelectedItems() {
      this.selectedItems = [this.selectedItem.itemPath];
      return this.selectedItems;
    },

    handleQuickAction(payload) {
      const { action, itemInfo } = payload || {};
      if (!itemInfo || itemInfo.itemType === 'folder') return;

      this.selectedItem = itemInfo;

      if (action === 'insert_origin') {
        this.handleItemClick(itemInfo);
      } else if (action === 'replace_selected') {
        // 模型页面“拾空填充”：调用后端 placeModelInScene -> AssetLibrary.place_model
        sketchup.placeModelInScene(itemInfo.itemPath);
      } else if (action === 'replace_instances') {
        sketchup.replaceModelSelected(itemInfo.itemPath, itemInfo.itemType, false, true);
      } else if (action === 'replace_match') {
        // 模型页面“取面放置”：调用后端 placeModelOnFace -> YueMuPro::SelectFacePlaceTool
        sketchup.placeModelOnFace(itemInfo.itemPath);
      }
    },

    itemActions(action) {
      if (action === 'insert_origin') {
        this.handleItemClick(this.selectedItem);

      } else if (action === 'replace_selected') {
        sketchup.placeModelInScene(this.selectedItem.itemPath);

      } else if (action === 'replace_instances') {
        sketchup.replaceModelSelected(this.selectedItem.itemPath, this.selectedItem.itemType, false, true);

      } else if (action === 'replace_match') {
        sketchup.placeModelOnFace(this.selectedItem.itemPath);

      } else if (action === 'add_selected') {
        this.addSelected(this.selectedItem.itemPath);

      } else if (action === 'remove_recent') {
        sketchup.removeRecentModels(this.getSelectedItems());

      } else if (action === 'open') {
        sketchup.openFile(this.selectedItem.itemPath);

      } else if (action === 'save_as') {
        sketchup.saveModels(this.getSelectedItems());

      } else if (action === 'rename') {
        this.renameItem(this.selectedItem);

      } else if (action === 'delete') {
        this.getSelectedItems();
        this.confirmDeleteItems(this.selectedItem.itemType);

      } else if (action === 'purge_unused') {
        sketchup.purgeUnusedModels();

      } else if (action === 'open_location') {
        sketchup.openFileLocation(this.selectedItem.itemPath);

      } else if (action === 'find') {
        sketchup.findModelItem(this.getSelectedItems())
      }

      // Hide context menu
      this.itemContextMenuVisible = false;
    },

    renameItem(itemInfo) {
      // Close all other editing items first
      this.modelItems.forEach(item => {
        if (item.isItemEditing) {
          this.$set(item, 'isItemEditing', false);
        }
      });

      this.$set(itemInfo, 'newName', itemInfo.itemName.replace('.skp', ''));
      this.$set(itemInfo, 'isItemEditing', true);

      this.$nextTick(() => {
        // Emit event to content component to focus the input
        this.$root.$emit('focus-item-input', itemInfo.itemName);
      });
    },

    cancelRenameItem(itemPath, oldItemName) {
      const itemInfo = this.modelItems.find(item => item.itemPath === itemPath);
      if (itemInfo && itemInfo.isItemEditing) {
        this.$set(itemInfo, 'itemName', oldItemName);
        this.$set(itemInfo, 'isItemEditing', false);
      }
    },

    saveNewItemName(itemPath, oldItemName) {
      const itemInfo = this.modelItems.find(item => item.itemPath === itemPath);

      if (itemInfo && itemInfo.isItemEditing) {
        if (itemInfo.newName) {
          const newItemName = itemInfo.newName.trim();

          // Check for invalid characters in file name (Windows and Mac)
          if (!this.validateFileName(newItemName, oldItemName)) {
            return;
          }

          // Check same name with any other items
          this.nameExists = this.modelItems.some(item =>
            item.itemName.toLowerCase().replace('.skp', '') === newItemName.toLowerCase() &&
            item.itemPath !== itemPath
          );

          // Show alert if name exists
          if (this.nameExists) {
            // Keep editing mode but show error message only once
            if (!itemInfo.showingError) {
              this.$set(itemInfo, 'showingError', true);
              const message = this.$root.translated('duplicateNameExistsAlert');
              this.$root.showMessageModal(message, 'error', 1000);
              // Wait for modal to close before focusing back to input and reset error flag
              setTimeout(() => {
                this.$set(itemInfo, 'showingError', false);
                this.$root.$emit('focus-item-input', oldItemName);
              }, 1200);
            }
            return;
          }

          if (newItemName !== '' && newItemName !== oldItemName) {
            const item = [itemInfo, oldItemName, newItemName];
            sketchup.renameModelItem(item);

            // Note: Don't update itemPath or thumbnailPath here.
            // Ruby will call Model.refresh(true) which triggers updateItems()
            // with the correct new paths including properly renamed thumbnail.
          }
        }

        this.$set(itemInfo, 'isItemEditing', false);
      }
    },

    // type: 'skp', 'folder', 'dynamic', 'live', 'model'
    confirmDeleteItems(type) {
      let message;
      let action1 = () => null;
      let action2;

      if (type === 'skp') {
        message = this.translated('confirmDeleteMessage');
        action2 = () => sketchup.deleteModelItems(this.selectedItems);
      } else if (type === 'folder') {
        message = this.translated('confirmDeleteFolderMessage');
        action2 = () => sketchup.deleteFolders(this.selectedItems);
      } else {
        message = this.translated('confirmDeleteMessage');
        action2 = () => sketchup.deleteModelInModel(this.selectedItems);
      }

      this.$root.showNoticeModal(message, action1, action2);
    },

    updateDeleteItems(itemPaths) {
      itemPaths.forEach(itemPath => {
        // Remove from modelItems
        const index = this.modelItems.findIndex(item => item.itemPath === itemPath);
        if (index > -1) {
          this.modelItems.splice(index, 1);
        }

        // Remove from favorites
        const favIndex = this.favorites.indexOf(itemPath);
        if (favIndex > -1) {
          this.favorites.splice(favIndex, 1);
        }
      });

      // Update favorites count in UI
      const allFavorites = document.getElementById('allModelFavorites');
      if (allFavorites) {
        allFavorites.textContent = this.favorites.length;
      }
    },

    showOnlineContextMenu(event, source) {
      // Hide other context menus
      this.hideContextMenu();

      // Save selected item BEFORE showing menu
      this.selectedOnlineName = source[1];
      this.selectedOnlineUrl = source[0];

      // Display context menu
      this.onlineContextMenuVisible = true;

      // Use $nextTick to ensure that the menu is rendered before calculating its dimensions
      this.$nextTick(() => {
        // Query the actual menu element from DOM (last one should be online menu)
        const menuElements = document.querySelectorAll('.context-menu.custom-menu');
        let menuElement = null;

        // The online menu should be the last visible one
        for (let i = menuElements.length - 1; i >= 0; i--) {
          if (menuElements[i].style.display !== 'none' && this.onlineContextMenuVisible) {
            menuElement = menuElements[i];
            break;
          }
        }

        if (!menuElement) return;

        // Calculate real menu dimensions
        const menuRect = menuElement.getBoundingClientRect();
        const menuWidth = menuRect.width;
        const menuHeight = menuRect.height;

        // Get the viewport dimensions
        const dialogWidth = window.innerWidth;
        const dialogHeight = window.innerHeight;
        const buffer = 10;

        // Get position of menu
        let calculatedX = event.clientX;
        let calculatedY = event.clientY;

        // Distance from click pos to dialog right & bottom
        const distanceToRight = dialogWidth - event.clientX;
        const distanceToBottom = dialogHeight - event.clientY;

        // If menu width is over dialog right, re-position X
        if (distanceToRight < menuWidth + buffer) {
          calculatedX = dialogWidth - menuWidth - buffer;
        }

        // If menu height is over dialog bottom, re-position Y
        if (distanceToBottom < menuHeight + buffer) {
          calculatedY = dialogHeight - menuHeight - buffer;
        }

        // Update new position of menu
        this.onlineContextMenuPosition.x = calculatedX;
        this.onlineContextMenuPosition.y = calculatedY;
      });
    },

    onlineActions(action) {
      if (action === 'rename') {
        this.renameOnlineSource(this.selectedOnlineUrl, this.selectedOnlineName);
      } else if (action === 'delete') {
        this.deleteOnlineSource(this.selectedOnlineUrl);
      }
      this.onlineContextMenuVisible = false;
    },

    renameOnlineSource(url, name) {
      const source = this.onlineSources.find(s => s[0] === url);
      if (source) {
        this.$set(source, 'newName', name);
        this.$set(source, 'isOnlineEditing', true);

        // Gọi method của online-container component để focus input
        this.$nextTick(() => {
          if (this.$refs.onlineContainer) {
            this.$refs.onlineContainer.focusInput(name);
          }
        });
      }
    },

    saveNewOnlineSourceName(url, oldName) {
      const source = this.onlineSources.find(s => s[0] === url);
      if (source) {
        let newName = source.newName ? source.newName.trim() : oldName;
        if (!newName) newName = oldName;

        source[1] = newName;
        this.$set(source, 'isOnlineEditing', false);
        sketchup.updateModelOnlineSources(this.onlineSources);
      }
    },

    deleteOnlineSource(url) {
      this.onlineSources = this.onlineSources.filter(s => s[0] !== url);
      if (this.currentUrl === url) {
        this.currentUrl = this.defaultSource[0];
        localStorage.setItem(recentModelUrlKey, this.defaultSource[0]);
      }
      sketchup.updateModelOnlineSources(this.onlineSources);
    },

    showSeparatorContextMenu(event) {
      // Hide other context menus
      this.hideContextMenu();

      // Display context menu
      this.separatorContextMenuVisible = true;

      // Define separator
      this.separatorElement = event.currentTarget.closest('li');

      const menuHeight = 60;
      const dialogHeight = window.innerHeight;
      const buffer = 10;

      // Get position Y of menu
      let calculatedY = event.clientY;

      // Distance from click pos to dialog bottom
      const distanceToBottom = dialogHeight - event.clientY;

      // If menu height is over dialog bottom, re-position Y
      if (distanceToBottom < menuHeight + buffer) {
        calculatedY = dialogHeight - menuHeight - buffer;
      }

      // Update new position of menu
      this.separatorContextMenuPosition.x = event.clientX;
      this.separatorContextMenuPosition.y = calculatedY;
    },

    showContentContextMenu(event) {
      if (this.selectedType === 'favorites' || this.selectedType === 'recent') {
        return;
      }

      // Hide other context menus
      this.hideContextMenu();

      // Display context menu
      this.contentContextMenuVisible = true;

      // Use $nextTick to ensure that the menu is rendered before calculating its dimensions
      this.$nextTick(() => {
        // Query the actual menu element from DOM
        const menuElements = document.querySelectorAll('.context-menu.custom-menu');
        let menuElement = null;
        
        // Find visible content menu
        for (let i = menuElements.length - 1; i >= 0; i--) {
          if (menuElements[i].style.display !== 'none' && this.contentContextMenuVisible) {
            menuElement = menuElements[i];
            break;
          }
        }

        if (!menuElement) return;

        // Calculate real menu dimensions
        const menuRect = menuElement.getBoundingClientRect();
        const menuWidth = menuRect.width;
        const menuHeight = menuRect.height;

        // Get the viewport dimensions
        const dialogWidth = window.innerWidth;
        const dialogHeight = window.innerHeight;
        const buffer = 10;

        // Get position of menu
        let calculatedX = event.clientX;
        let calculatedY = event.clientY;

        // Distance from click pos to dialog right & bottom
        const distanceToRight = dialogWidth - event.clientX;
        const distanceToBottom = dialogHeight - event.clientY;

        // If menu width is over dialog right, re-position X
        if (distanceToRight < menuWidth + buffer) {
          calculatedX = dialogWidth - menuWidth - buffer;
        }

        // If menu height is over dialog bottom, re-position Y
        if (distanceToBottom < menuHeight + buffer) {
          calculatedY = dialogHeight - menuHeight - buffer;
        }

        // Update new position of menu
        this.contentContextMenuPosition.x = calculatedX;
        this.contentContextMenuPosition.y = calculatedY;
      });
    },

    contentActions(action) {
      if (action === 'add_selected') {
        this.addSelected(this.activeFolderPath);
      } else if (action === 'purge_unused') {
        sketchup.purgeUnusedModels();
      }
      this.contentContextMenuVisible = false;
    },

    handleSortLocal() {
      const list = document.getElementById('sortableLocalModel');
      const items = list.getElementsByTagName('li');
      const newOrder = [];

      for (let i = 0; i < items.length; i++) {
        const folderPath = items[i].getAttribute('data-folder-path');
        const element = items[i].querySelector('.folder-name');
        const folderName = (element && element.textContent) || '';

        if (folderPath) {
          newOrder.push([folderPath, folderName]);
        }
      }
      // Only save to Ruby, don't update Vue data to avoid conflict with Sortable
      sketchup.updateModelFolderOrder(JSON.stringify(newOrder));
    },

    handleSortOnline() {
      const list = document.getElementById('sortableOnlineModel');
      const items = list.getElementsByTagName('button');
      const newOrder = [];

      if (this.defaultSource.length > 0) {
        // Keep default source if needed, but usually we just sort the user added ones
      }

      for (let i = 0; i < items.length; i++) {
        const url = items[i].getAttribute('data-url');
        const name = items[i].textContent.trim();
        if (url) {
          newOrder.push([url, name]);
        }
      }

      this.onlineSources = newOrder;
      sketchup.updateModelOnlineSources(this.onlineSources);
    },

    updateActive(root, path) {
      this.activeModel = root;
      if (path) {
        this.activeModelPath = path;
      } else {
        this.activeModelPath = []
      }
    },

    loadDetailsState() {
      this.showDetails = true;
    },

    saveDetailsState(_newValue) {
      this.showDetails = true;
    },

    loadShowAllState() {
      const showAllState = localStorage.getItem(modelShowAllStateKey);
      this.showAllModels = showAllState !== 'false';
    },

    updateShowAll(value) {
      this.showAllModels = value;
      localStorage.setItem(modelShowAllStateKey, this.showAllModels);
      handleTooltip(this);
    },

    toggleShowAllModels() {
      // User is trying to enable showAllModels (currently it's false)
      if (!this.showAllModels) {
        // Show warning only on first time
        if (this.firstShowAllModel) {
          const message = this.$root.translated('showAllModelsWarning');
          // action1 = Cancel (keep showAllModels as false)
          const action1 = () => this.changeShowAll('false');
          // action2 = Confirm (set showAllModels to true)
          const action2 = () => this.changeShowAll('true');
          this.$root.showNoticeModal(message, action1, action2);
          this.firstShowAllModel = false;
        } else {
          // Not first time, just enable it
          this.changeShowAll('true');
        }
      } else {
        // User is trying to disable showAllModels (currently it's true)
        this.changeShowAll('false');
      }
    },

    changeShowAll(value) {
      if (value === 'true') {
        this.showAllModels = true;
        localStorage.setItem(modelShowAllStateKey, 'true');
      } else if (value === 'false') {
        this.showAllModels = false;
        localStorage.setItem(modelShowAllStateKey, 'false');
      }

      sketchup.showAllModels(this.showAllModels);
    },

    onEnterKey(folderPath, folderName) {
      this.saveNewCollectionName(folderPath, folderName);
    },

    onBlur(folderPath, folderName) {
      this.$nextTick(() => {
        this.saveNewCollectionName(folderPath, folderName);
      });
    },

    cancelRename(folderPath) {
      const folderInfo = this.modelFolders.find(folder => folder.folderPath === folderPath);
      if (folderInfo && folderInfo.isFolderEditing) {
        // Restore original name and exit edit mode
        this.$set(folderInfo, 'newName', folderInfo.customFolderName);
        this.$set(folderInfo, 'isFolderEditing', false);

        // Re-enable Sortable
        this.enableSortableLocal();
      }
    },

    goToRoot() {
      sketchup.parentModelActivePath('root');
    },

    goToParent(index) {
      sketchup.parentModelActivePath(this.activeModelPath[index]);
    },

    updateSelected(value) {
      this.selectedModels = value;
    },

    updateActiveFolder(value) {
      this.activeFolderPath = value;
    },

    updateIsDrive(value) {
      this.isDrive = value;

      // if (!this.isDrive) return;

      // const message = this.$root.translated('isDriveNotice');
      // this.$root.showMessageModal(message, 'info', 1500);
    },

    // Deep Search Methods
    handleDeepSearch() {
      if (this.isDrive) {
        const message = this.$root.translated('deepSearchDriveNotSupported');
        this.$root.showMessageModal(message, 'info', 2000);
        return;
      }

      if (!this.enableDeepSearch) return;
      if (!this.searchQuery || !this.searchQuery.trim()) return;
      if (!this.activeFolderPath) {
        const message = this.$root.translated('selectFolderFirst') || 'Please select a folder first';
        this.$root.showMessageModal(message, 'info', 1500);
        return;
      }

      // Don't search in special collections
      if (this.selectedType === 'in-model' || this.selectedType === 'favorites' || this.selectedType === 'recent') {
        return;
      }

      // sketchup.checkFolderSize(this.activeFolderPath);
      const message = this.$root.translated('deepSearching');
      this.$root.showMessageModal(message, 'deepSearching', 'wait');

      // Delay to allow modal to render
      setTimeout(() => {
        // this.setDeepSearchLoading(true);
        this.performDeepSearch();
      }, 500);
    },

    performDeepSearch() {
      sketchup.deepSearchModel(this.searchQuery.trim(), this.activeFolderPath);
    },

    updateDeepSearchResults(items) {
      this.deepSearchResults = items || [];

      if (this.deepSearchResults.length === 0) {
        const message1 = this.$root.translated('noDeepSearchResults');
        const message2 = this.$root.translated('searchWithinSubfolders');
        const message = `${message1}<br><br>${message2}`;
        this.$root.showNoticeModal(message);
      } else {
        forceHideAllModals();
      }

      this.isDeepSearching = false;
    },

    setDeepSearchLoading(isLoading) {
      this.isDeepSearching = isLoading;
    },

    clearDeepSearch() {
      this.deepSearchResults = [];
      this.isDeepSearching = false;
    },
  },

  template: /*html*/`
    <div
      :class="['d-flex', 'library-container', sidebarOpen ? 'sidebar-open' : 'sidebar-closed']"
      id="model-app"
    >
      <div
        class="sidebar-wrapper"
        :style="{ width: sidebarOpen ? (sidebarWidth + 'px') : '0' }"
      >
        <side-bar
          type="model"
        :isOnline="isOnline"
        :selectedType="selectedType"
        :folders="modelFolders"
        :sortOrderFolders="sortOrderFolders"
        :recentFolderPath="recentFolderPath"
        :selectedFolderPath="selectedFolderPath"
        :treeRootPath="treeRootPath || ''"
        @switch-mode="switchMode"
        @browse-collection="browseCollection"
        @sort-folders="sortFolders"
        @add-collection="addCollection"
        @browse-folder="onLeftBrowseFolder"
        @folder-context-menu="(payload) => showCollectionContextMenu(payload.event, payload.folderPath, payload.folderName)"
        @separator-context-menu="showSeparatorContextMenu"
        @hide-context-menu="hideContextMenu"
        @folder-enter="(payload) => onEnterKey(payload.folderPath, payload.folderName)"
        @folder-blur="(payload) => onBlur(payload.folderPath, payload.folderName)"
        @folder-esc="(payload) => cancelRename(payload.folderPath)"
        ></side-bar>
        <div
          v-show="sidebarOpen"
          class="resize-handle"
          @mousedown.prevent="startResizeSidebar"
          title="拖拽调节宽度"
        ></div>
      </div>

      <div
        v-if="!isOnline && subfolderColumnVisible"
        class="subfolder-column-wrapper"
        :style="{ width: subfolderColumnWidth + 'px' }"
      >
        <subfolder-column
          type="model"
          :visible="true"
          :items="subfolderTreeItems"
          :selected-folder-path="selectedFolderPath"
          @browse-subfolder="(path) => browseFolder(path, false)"
        ></subfolder-column>
        <div
          class="subfolder-resize-handle"
          @mousedown.prevent="startResizeSubfolderColumn"
          title="拖拽调节宽度"
        ></div>
      </div>

      <online-container
        ref="onlineContainer"
        type="model"
        :isOnline="isOnline"
        :defaultSource="defaultSource"
        :onlineSources="onlineSources"
        :currentUrl="currentUrl"
        @browse-url="browseUrl"
        @show-online-context-menu="(payload) => showOnlineContextMenu(payload.event, payload.source)"
        @save-online-source-name="(payload) => saveNewOnlineSourceName(payload.url, payload.oldName)"
        @open-add-source="openAddSource"
      ></online-container>

      <div v-if="!isOnline" class="local-container flex-grow-1 subfolder-main-content">
        <local-panel
          type="model"
          :sidebarOpen="sidebarOpen"
          :isSmallWindow="isSmallWindow"
          :selectedType="selectedType"
          :isDrive="isDrive"
          :activeModel="activeModel"
          :showAllModels="showAllModels"
          :isMediumWindow="isMediumWindow"
          :viewMode="viewMode"
          :showDetails="showDetails"
          :currentView="currentView"
          :selectedSort="selectedSort"
          :activeFolderPath="activeFolderPath || ''"
          :selectedFolderPath="selectedFolderPath || ''"
          :treeRootPath="treeRootPath || ''"
          @handle-parent-click="handleParentClick"
          @browse-folder-path="(path) => browseFolder(path, false)"
          @add-selected="addSelected(activeFolderPath)"
          @details-change="saveDetailsState"
          @change-view="changeView"
          @update-thumbnails="updateThumbnails"
          @sort-by="sortedBy"
        ></local-panel>

        <model-content
          :isOnline="isOnline"
          :selectedType="selectedType"
          :showAllModels="showAllModels"
          :activeModelPath="activeModelPath"
          :viewMode="viewMode"
          :currentStyle="currentStyle"
          :folderEmpty="folderEmpty"
          :showDetails="showDetails"
          :sidebarOpen="sidebarOpen"
          :isSmallWindow="isSmallWindow"
          :isMediumWindow="isMediumWindow"
          :filteredItems="filteredItems"
          :selectedModels="selectedModels"
          :favorites="favorites"
          :dynamicIconPath="dynamicIconPath"
          :liveIconPath="liveIconPath"
          @content-context-menu="showContentContextMenu"
          @item-context-menu="(payload) => showItemContextMenu(payload.event, payload.itemInfo)"
          @item-click="handleItemClick"
          @update-selected="updateSelected"
          @update-active-folder="updateActiveFolder"
          @toggle-show-all-models="toggleShowAllModels"
          @toggle-favorite="toggleFavorite"
          @quick-action="handleQuickAction"
          @hide-context-menu="hideContextMenu"
          @save-new-item-name="(payload) => saveNewItemName(payload.itemPath, payload.itemName)"
          @cancel-rename-item="(payload) => cancelRenameItem(payload.itemPath, payload.itemName)"
          @go-to-root="goToRoot"
          @go-to-parent="goToParent"
        ></model-content>
      </div>

      <model-context-menus
        :showDetails="showDetails"
        :folderContextMenuVisible="folderContextMenuVisible"
        :folderContextMenuPosition="folderContextMenuPosition"
        :separatorContextMenuVisible="separatorContextMenuVisible"
        :separatorContextMenuPosition="separatorContextMenuPosition"
        :contentContextMenuVisible="contentContextMenuVisible"
        :contentContextMenuPosition="contentContextMenuPosition"
        :selectedType="selectedType"
        :itemContextMenuVisible="itemContextMenuVisible"
        :itemContextMenuPosition="itemContextMenuPosition"
        :isSketchupModel="isSketchupModel"
        :selectedItemType="selectedItemType"
        :sidebarOpen="sidebarOpen"
        :isMediumWindow="isMediumWindow"
        :onlineContextMenuVisible="onlineContextMenuVisible"
        :onlineContextMenuPosition="onlineContextMenuPosition"
        @collection-actions="collectionActions"
        @content-actions="contentActions"
        @item-actions="itemActions"
        @online-actions="onlineActions"
        @remove-separator="removeSeparator"
      ></model-context-menus>

      <user-options-modal
        type="model"
        :userOptionsMessage="userOptionsMessage"
        :userOptions="userOptions"
      ></user-options-modal>

      <add-source-modal
        type="model"
        @add-source="addSourceOnline"
      ></add-source-modal>
    </div>
  `
};