// Auto-extracted from apps-manager.js (verbatim) — augments AppsManager.prototype. Loaded after the base.
Object.assign(AppsManager.prototype, {
  setupSidebar(activeCategory = 'all') {
    const sidebar = document.getElementById('sidebar');
    if (!sidebar) return;
    
    // Clear sidebar
    const container = document.getElementById('dynamic-categories');
    if (!container) return;
    
    container.innerHTML = '';
    
    // Hide loading
    const loading = document.querySelector('.loading-categories');
    if (loading) loading.style.display = 'none';
    
    // Add categories
    this.addCategory('All', 'all');
    this.addCategory('Installed', 'installed');
    
    // Add dynamic categories
    if (window.sidebarCategories) {
      const categoriesArray = Array.isArray(window.sidebarCategories) ? window.sidebarCategories : 
        Object.entries(window.sidebarCategories).map(([key, value]) => ({ id: key, ...value }));
      
      categoriesArray.forEach(cat => {
        this.addCategory(cat.name, cat.id, cat.icon);
      });
    }
    
    // Add back button for app detail
    if (this.currentView === 'app-detail') {
      this.addBackButton();
    }
    
    // Set active category
    this.setActiveCategory(activeCategory);
  },
  addCategory(name, id, icon) {
    const container = document.getElementById('dynamic-categories');
    if (!container) return;

    const div = document.createElement('div');
    div.className = 'category';
    div.setAttribute('data-category', id);

    let iconHtml;
    if (!icon && id === 'all') {
      iconHtml = '<svg class="category-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>';
    } else if (!icon && id === 'installed') {
      iconHtml = '<svg class="category-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>';
    } else {
      let iconPath = icon || `/core/icons/categories/${id}.svg`;
      if (!iconPath.startsWith('/')) iconPath = '/' + iconPath;
      iconHtml = `<img src="${iconPath}" alt="${name}" onerror="this.src='/core/icons/categories/misc.svg'"/>`;
    }

    div.innerHTML = `${iconHtml} ${name}`;

    div.addEventListener('click', (e) => {
      e.preventDefault();
      this.switchCategory(id);
    });

    container.appendChild(div);
  },
  addBackButton() {
    const container = document.getElementById('dynamic-categories');
    if (!container) return;
    
    const div = document.createElement('div');
    div.className = 'category';
    div.innerHTML = '← Back to Apps';
    div.addEventListener('click', () => {
      this.showAppsList('all');
    });
    
    container.appendChild(div);
  },
  setActiveCategory(categoryId) {
    document.querySelectorAll('.category').forEach(cat => {
      cat.classList.remove('active');
    });
    
    const active = document.querySelector(`[data-category="${categoryId}"]`);
    if (active) active.classList.add('active');
  },
  switchCategory(categoryId) {
    //// // console.log(`AppsManager: Switching to category: ${categoryId}`);
    
    // Update URL to reflect current state
    if (categoryId === 'all') {
      history.pushState({}, '', '/apps');
    } else {
      history.pushState({}, '', `/apps/${categoryId}`);
    }
    
    // Direct view update without URL change to avoid conflicts
    this.currentView = 'apps';
    this.currentApp = null;
    
    // Switch to apps view
    this.showView('apps');
    
    // Render apps for new category
    this.renderApps(categoryId);
    
    // Update sidebar for new category
    this.setupSidebar(categoryId);
  },
  renderApps(category) {
    const container = document.getElementById('apps-section');
    if (!container) return;

    container.innerHTML = '';

    // Load and render apps
    this.loadApps(category).then(apps => {
      apps.forEach(app => {
        const card = this.createAppCard(app);
        container.appendChild(card);
      });
      this.populateInlineServiceButtons();
      // Re-apply any active sidebar search so changing category
      // doesn't reveal apps that should be filtered out.
      if (this.appsSearchQuery) this.filterAppsByQuery(this.appsSearchQuery);
    }).catch(error => {
      console.error('Error rendering apps:', error);
    });
  },
  // Client-side substring filter wired to the sidebar search box.
  // Cards carry data-search (built in createAppCard) so this stays
  // a single querySelectorAll + display toggle.
  filterAppsByQuery(query) {
    const q = (query || '').trim().toLowerCase();
    this.appsSearchQuery = q;
    const wrap = document.querySelector('.apps-search');
    if (wrap) wrap.classList.toggle('has-value', !!q);

    const cards = document.querySelectorAll('#apps-section .app-card');
    cards.forEach(card => {
      const hay = card.dataset.search || '';
      card.style.display = (!q || hay.includes(q)) ? '' : 'none';
    });
  },
  clearAppsSearch() {
    const input = document.getElementById('apps-search-input');
    if (input) input.value = '';
    this.filterAppsByQuery('');
    if (input) input.focus();
  },
  createAppCard(app) {
    const card = document.createElement('div');
    card.className = 'app-card';
    if (app.installed) card.classList.add('installed');

    // Searchable text for the sidebar search box. Combined name +
    // description + long description + category, lowercased once here
    // so filterAppsByQuery is a cheap substring match.
    const searchHaystack = [
      app.name,
      app.description,
      app.longDescription,
      app.category,
      this.getCategoryName ? this.getCategoryName(app.category) : ''
    ].filter(Boolean).join(' ').toLowerCase();
    card.dataset.search = searchHaystack;

    const appName = app.command.split(' ').pop();
    let icon = app.icon || '/core/icons/apps/default.svg';
    
    // Ensure absolute path from root
    if (!icon.startsWith('/')) {
      icon = '/' + icon;
    }
    
    const status = app.installed ? 'Installed' : 'Not Installed';
    
    // Get category icon and name
    const categoryIcon = this.getCategoryIcon(app.category);
    const categoryName = this.getCategoryName(app.category);
    
    // Create rich tags like original
    const descriptionTag = app.description ? `<span class="app-tag description-tag"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg> ${app.description}</span>` : '';
    const categoryTag = `<span class="app-tag category-tag clickable" onclick="event.stopPropagation(); appsManager.switchCategory('${app.category}')"><img src="${categoryIcon}"/> ${categoryName}</span>`;
    
    // Format long description with period if missing
    let formattedLongDescription = '';
    if (app.longDescription) {
      formattedLongDescription = app.longDescription;
      if (!formattedLongDescription.endsWith('.') && !formattedLongDescription.endsWith('?') && !formattedLongDescription.endsWith('!')) {
        formattedLongDescription += '.';
      }
    }

    // Service trigger icon (only for installed apps - visibility controlled after services load)
    const serviceTrigger = app.installed ? `
      <div class="service-trigger" id="service-trigger-${appName}" style="display:none;">
        <div class="service-trigger-icon" onclick="event.stopPropagation(); window.toggleServiceTrigger('${appName}')">
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
            <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
            <polyline points="15 3 21 3 21 9"></polyline>
            <line x1="10" y1="14" x2="21" y2="3"></line>
          </svg>
          Open
        </div>
        <div class="service-trigger-popup">
          <div id="service-popup-content-${appName}"></div>
        </div>
      </div>` : '';

    // Multi-instance: instances are hidden from the grid and managed inside the
    // type's detail page. The only grid hint is a subtle count chip on a
    // multi-instance-capable type that already has instances — discovery without
    // clutter. (Capability is read straight off the app's own config.)
    const _cfg = app.config || {};
    const _U = appName.toUpperCase();
    const isMultiCapable = String(_cfg[`CFG_${_U}_MULTI_INSTANCE`]).toLowerCase() === 'true';
    let instanceCountChip = '';
    if (isMultiCapable && Array.isArray(window.apps)) {
      const n = window.apps.filter(a => {
        const s = (a.command || '').split(' ').pop();
        return (a.config || {})[`CFG_${s.toUpperCase()}_INSTANCE_OF`] === appName;
      }).length;
      if (n > 0) {
        instanceCountChip = `<span class="app-tag instance-count-tag" title="${n} instance${n > 1 ? 's' : ''} of this app — manage them via Manage">⧉ ${n} instance${n > 1 ? 's' : ''}</span>`;
      }
    }

    card.innerHTML = `
      <div class="app-card-top" style="cursor: pointer;" onclick="appsManager.showAppDetail('${appName}')">
        <div class="app-card-icon">
          <img src="${icon}" alt="${app.name}" onerror="this.src='/core/icons/apps/default.svg'"/>
        </div>
        <div class="app-card-content">
          <div class="app-card-title" style="cursor: pointer;">${app.name.split(' - ')[0].trim()}</div>
          <div class="app-card-tags">
            ${descriptionTag}
            ${categoryTag}
            ${instanceCountChip}
            <span class="app-tag clickable ${app.installed ? 'installed-tag' : 'not-installed-tag'}" onclick="event.stopPropagation(); appsManager.switchCategory('${app.installed ? 'installed' : 'all'}')">${status}</span>
          </div>
        </div>
      </div>
      ${formattedLongDescription ? `<div class="app-card-long-description">${formattedLongDescription}</div>` : ''}
      <div class="app-card-actions">
        <button class="${app.installed ? 'manage-btn' : 'install-btn'}" onclick="appsManager.showAppDetailWithConfig('${appName}')">
          ${app.installed ? 'Manage' : 'Install'}
        </button>
        ${serviceTrigger}
      </div>
    `;
    
    return card;
  },
  // Populate inline service trigger popups for installed apps
  async populateInlineServiceButtons() {
    if (!window.serviceButtons) return;

    if (window.serviceButtons.services.length === 0) {
      await window.serviceButtons.loadServices();
    }

    const proto = s => ['http', 'https'].includes((s.protocol || '').toLowerCase()) ? s.protocol.toLowerCase() : 'http';

    const popupContents = document.querySelectorAll('[id^="service-popup-content-"]');
    for (const content of popupContents) {
      const appName = content.id.replace('service-popup-content-', '');
      const trigger = document.getElementById(`service-trigger-${appName}`);

      const appServices = window.serviceButtons.services.filter(s => s.app === appName && s.buttonEnabled === true);
      if (appServices.length === 0) continue;

      // Multi-button render via the shared expandServiceLinks() helper.
      const buttons = appServices.flatMap(s => {
        const protectedClass = s.loginRequired ? ' protected' : '';
        const lockIcon = s.loginRequired
          ? `<span class="service-lock-icon" title="Login required for this URL — credentials in Config → General → Logins."><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></span>`
          : '';
        return window.expandServiceLinks(s).map(({ url, label }) => `
          <a href="${url}" target="_blank" rel="noopener noreferrer" class="service-button${protectedClass}" onclick="event.stopPropagation()">
            <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
              <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
              <polyline points="15 3 21 3 21 9"></polyline>
              <line x1="10" y1="14" x2="21" y2="3"></line>
            </svg>
            ${label}
            ${lockIcon}
          </a>
        `);
      }).filter(Boolean).join('');

      if (buttons) {
        content.innerHTML = buttons;
        if (trigger) trigger.style.display = '';
      }
    }
  },

  // ---- Multi-instance family switcher (app-detail header) -------------------
  // An instance is just another app with its own route, so "swapping" instances
  // is real navigation between sibling slugs. This bar renders under the app
  // title for any multi-instance-capable type and its instances: a pill per
  // family member (base + each instance) + an "+ Add" pill, and — when the
  // current app IS an instance — a "Remove instance" action. Reads the family
  // entirely from window.apps via the INSTANCE_OF / MULTI_INSTANCE config.
  renderInstanceFamilyBar(cleanAppName) {
    if (!cleanAppName) return '';
    const apps = window.apps || [];
    const esc = (s) => (typeof escapeHtml === 'function' ? escapeHtml(s) : String(s == null ? '' : s));
    const slugOf = (a) => (a.command || '').split(' ').pop();
    const cfgOf = (slug) => { const a = apps.find(x => slugOf(x) === slug); return (a && a.config) || {}; };
    const U = (s) => String(s).toUpperCase();

    const myCfg = cfgOf(cleanAppName);
    const isInstance = !!myCfg[`CFG_${U(cleanAppName)}_INSTANCE_OF`];
    const type = isInstance ? myCfg[`CFG_${U(cleanAppName)}_INSTANCE_OF`] : cleanAppName;
    const typeCfg = cfgOf(type);
    const isCapable = String(typeCfg[`CFG_${U(type)}_MULTI_INSTANCE`]).toLowerCase() === 'true';
    if (!isCapable) return '';

    const instances = apps
      .map(slugOf)
      .filter(slug => cfgOf(slug)[`CFG_${U(slug)}_INSTANCE_OF`] === type)
      .sort();
    const members = [type, ...instances];

    const typeApp = apps.find(x => slugOf(x) === type);
    const typeTitle = typeApp ? (typeApp.name || type).split(' - ')[0].trim() : type;
    const labelFor = (slug) => slug === type ? typeTitle : slug.slice(type.length + 1);

    const pills = members.map(slug => {
      const active = slug === cleanAppName ? ' active' : '';
      return `<button class="instance-pill${active}" onclick="appsManager.gotoInstance('${esc(slug)}')">${esc(labelFor(slug))}</button>`;
    }).join('');

    const addPill = `<button class="instance-pill instance-pill-add" title="Create another isolated instance" onclick="window.instanceManager && window.instanceManager.openCreateModal('${esc(type)}')">+ Add</button>`;

    const removeBtn = isInstance
      ? `<button class="instance-remove" title="Uninstall and delete this instance" onclick="window.instanceManager && window.instanceManager.confirmRemove('${esc(cleanAppName)}','${esc(type)}')">Remove instance</button>`
      : '';

    return `
      <div class="instance-family-bar">
        <span class="instance-family-label">Instances</span>
        <div class="instance-pills">${pills}${addPill}</div>
        ${removeBtn}
      </div>`;
  },

  // Swap to a sibling instance: real path-based navigation, current tab kept.
  gotoInstance(slug) {
    if (!slug) return;
    const tab = (window.appTabbedManager && window.appTabbedManager.currentTab) || 'config';
    const path = (typeof window.appPath === 'function') ? window.appPath(slug, tab) : `/app/${slug}/${tab}`;
    if (window.librePortalSPA && window.librePortalSPA.navigateTo) {
      window.librePortalSPA.navigateTo(path);
    } else if (window.navigateToRoute) {
      window.navigateToRoute(path.replace(/^\//, ''));
    } else {
      window.location.href = path;
    }
  },
});
