// Auto-extracted from apps-manager.js (verbatim) — augments AppsManager.prototype. Loaded after the base.
Object.assign(AppsManager.prototype, {
  countryFlagEmoji(name) {
    const map = {
      'Albania':'AL','Algeria':'DZ','Andorra':'AD','Angola':'AO','Argentina':'AR','Armenia':'AM','Australia':'AU','Austria':'AT','Azerbaijan':'AZ',
      'Bahamas':'BS','Bahrain':'BH','Bangladesh':'BD','Belarus':'BY','Belgium':'BE','Belize':'BZ','Bermuda':'BM','Bhutan':'BT','Bolivia':'BO',
      'Bosnia and Herzegovina':'BA','Brazil':'BR','Brunei':'BN','Brunei Darussalam':'BN','Bulgaria':'BG','Cambodia':'KH','Canada':'CA','Chile':'CL',
      'China':'CN','Colombia':'CO','Costa Rica':'CR','Croatia':'HR','Cyprus':'CY','Czech Republic':'CZ','Czechia':'CZ',
      'Denmark':'DK','Dominican Republic':'DO','Ecuador':'EC','Egypt':'EG','El Salvador':'SV','Estonia':'EE','Ethiopia':'ET',
      'Finland':'FI','France':'FR','Georgia':'GE','Germany':'DE','Ghana':'GH','Greece':'GR','Greenland':'GL','Guatemala':'GT',
      'Honduras':'HN','Hong Kong':'HK','Hungary':'HU','Iceland':'IS','India':'IN','Indonesia':'ID','Iran':'IR','Iraq':'IQ',
      'Ireland':'IE','Isle of Man':'IM','Israel':'IL','Italy':'IT','Jamaica':'JM','Japan':'JP','Jordan':'JO','Kazakhstan':'KZ',
      'Kenya':'KE','Kuwait':'KW','Kyrgyzstan':'KG','Laos':'LA','Latvia':'LV','Lebanon':'LB','Liechtenstein':'LI','Lithuania':'LT',
      'Luxembourg':'LU','Macao':'MO','Macau':'MO','North Macedonia':'MK','Macedonia':'MK','Madagascar':'MG','Malaysia':'MY','Malta':'MT',
      'Mexico':'MX','Moldova':'MD','Monaco':'MC','Mongolia':'MN','Montenegro':'ME','Morocco':'MA','Myanmar':'MM','Nepal':'NP',
      'Netherlands':'NL','New Zealand':'NZ','Nicaragua':'NI','Nigeria':'NG','Norway':'NO','Oman':'OM','Pakistan':'PK','Panama':'PA',
      'Papua New Guinea':'PG','Paraguay':'PY','Peru':'PE','Philippines':'PH','Poland':'PL','Portugal':'PT','Puerto Rico':'PR','Qatar':'QA',
      'Romania':'RO','Russia':'RU','Russian Federation':'RU','Saudi Arabia':'SA','Senegal':'SN','Serbia':'RS','Singapore':'SG',
      'Slovakia':'SK','Slovenia':'SI','South Africa':'ZA','South Korea':'KR','Korea, Republic of':'KR','Spain':'ES','Sri Lanka':'LK',
      'Sweden':'SE','Switzerland':'CH','Taiwan':'TW','Tajikistan':'TJ','Thailand':'TH','Trinidad and Tobago':'TT','Tunisia':'TN',
      'Turkey':'TR','Türkiye':'TR','Turkmenistan':'TM','Ukraine':'UA','United Arab Emirates':'AE','UAE':'AE','United Kingdom':'GB','UK':'GB',
      'United States':'US','USA':'US','United States of America':'US','Uruguay':'UY','Uzbekistan':'UZ','Venezuela':'VE','Vietnam':'VN','Viet Nam':'VN'
    };
    const code = map[name];
    if (!code) return '🏳️';
    return String.fromCodePoint(...[...code].map(c => 0x1F1E6 + (c.charCodeAt(0) - 65)));
  },
  openGluetunCountriesModal(fieldId) {
    const hidden = document.getElementById(fieldId);
    const chips = document.getElementById(`${fieldId}-chips`);
    if (!hidden) return;

    const providerEl = (typeof ConfigOptions !== 'undefined' && ConfigOptions.findGluetunProviderEl)
      ? ConfigOptions.findGluetunProviderEl()
      : null;
    const provider = providerEl ? providerEl.value : '';
    const providers = window.gluetunProviders || {};
    const countries = (provider && providers[provider] && Array.isArray(providers[provider].countries))
      ? [...providers[provider].countries].sort((a, b) => a.localeCompare(b))
      : [];

    const current = new Set((hidden.value || '').split(',').map(s => s.trim()).filter(Boolean));

    const existing = document.getElementById('gluetun-countries-modal');
    if (existing) existing.remove();

    const flag = (n) => this.countryFlagEmoji(n);
    const renderChips = (list) => list.length
      ? list.map(c => `<span class="gluetun-country-chip"><span class="gluetun-flag">${flag(c)}</span>${c}</span>`).join('')
      : `<span class="gluetun-country-empty">Any</span>`;

    const fallbackProviderIcon = `data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2338bdf8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M12 22s8-4 8-12V5l-8-3-8 3v5c0 8 8 12 8 12z'/><path d='M9 12l2 2 4-4'/></svg>`;
    const providerLabel = provider ? provider.replace(/\b\w/g, (c) => c.toUpperCase()) : '— none selected —';
    const iconManifest = window.gluetunProviderIcons || {};
    const providerIconUrl = (provider && iconManifest[provider]) || fallbackProviderIcon;

    const bodyHtml = `
      <div class="gluetun-provider-card">
        <div class="gluetun-provider-icon-wrap">
          <img class="gluetun-provider-icon" src="${providerIconUrl}" alt="${provider}" onerror="this.onerror=null; this.src='${fallbackProviderIcon}';">
        </div>
        <div class="gluetun-provider-text">
          <p class="gluetun-provider-label">Provider</p>
          <p class="gluetun-provider-name">${providerLabel}</p>
        </div>
      </div>
      <div class="gluetun-search-card">
        <div class="gluetun-search-row">
          <svg class="gluetun-search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <circle cx="11" cy="11" r="8"></circle>
            <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
          </svg>
          <input type="text" class="gluetun-country-search" placeholder="Filter countries...">
        </div>
        <div class="gluetun-search-actions">
          <button type="button" class="btn btn-secondary gluetun-country-all">Select all</button>
          <button type="button" class="btn btn-secondary gluetun-country-none">Clear</button>
        </div>
      </div>
      <div class="gluetun-country-list">
        ${countries.length === 0
          ? `<p class="gluetun-country-empty-msg">No country list available for this provider. Pick a provider first or wait for the snapshot to load.</p>`
          : countries.map(c => `
            <label class="gluetun-country-item">
              <input type="checkbox" value="${c}" ${current.has(c) ? 'checked' : ''}>
              <span class="gluetun-country-name"><span class="gluetun-flag">${flag(c)}</span>${c}</span>
            </label>`).join('')}
      </div>`;

    const m = window.openEoModal({
      id: 'gluetun-countries-modal',
      title: '🌍  Select VPN Countries',
      body: bodyHtml,
      actions: [
        { label: 'Save', variant: 'primary', onClick: (modal) => {
          const picked = Array.from(modal.contentEl.querySelectorAll('.gluetun-country-item input:checked')).map(cb => cb.value);
          hidden.value = picked.join(',');
          hidden.dispatchEvent(new Event('change', { bubbles: true }));
          if (chips) chips.innerHTML = renderChips(picked);
          modal.close();
        }},
        { label: 'Cancel', variant: 'secondary' }
      ]
    });
    const root = m.contentEl;
    root.querySelector('.gluetun-country-search').addEventListener('input', (e) => {
      const q = e.target.value.toLowerCase();
      root.querySelectorAll('.gluetun-country-item').forEach(item => {
        const label = item.querySelector('.gluetun-country-name').textContent.toLowerCase();
        item.style.display = label.includes(q) ? '' : 'none';
      });
    });
    root.querySelector('.gluetun-country-all').addEventListener('click', () => {
      root.querySelectorAll('.gluetun-country-item').forEach(item => {
        if (item.style.display !== 'none') item.querySelector('input').checked = true;
      });
    });
    root.querySelector('.gluetun-country-none').addEventListener('click', () => {
      root.querySelectorAll('.gluetun-country-item input').forEach(cb => cb.checked = false);
    });
  },
  async openGluetunRouteAppsModal() {
    const existing = document.getElementById('gluetun-route-apps-modal');
    if (existing) existing.remove();

    let allow = [];
    try {
      const r = await fetch('/data/apps/gluetun-eligible-categories.json', { cache: 'no-store' });
      if (r.ok) allow = (await r.json()).categories || [];
    } catch {}
    const allowSet = new Set(allow.map((c) => String(c).toLowerCase()));
    const overrideOn = (typeof ConfigOptions !== 'undefined') && this.checkRequirementEnabled('GLUETUN_FOR_ALL');
    const skip = new Set(['gluetun', 'libreportal', 'traefik', 'fail2ban']);
    const apps = (window.apps || [])
      .filter((a) => a.installed)
      .map((a) => {
        const slug = (a.command || '').split(' ').pop();
        return { ...a, slug };
      })
      .filter((a) => a.slug && !skip.has(a.slug))
      .filter((a) => overrideOn || allowSet.has(String(a.category || '').toLowerCase()))
      .sort((a, b) => (a.name || a.slug).localeCompare(b.name || b.slug));

    const bodyHtml = `
      <p class="eo-modal-section-text">
        Tick an app to send its outbound traffic through the Gluetun VPN. Untick to restore the default network.
        Each change re-runs that app's install task to apply the new compose.
      </p>
      ${apps.length === 0 ? `
        <div class="eo-empty-state info" role="status">
          <div class="eo-empty-state-icon" aria-hidden="true">
            <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <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>
          </div>
          <div class="eo-empty-state-body">
            <p class="eo-empty-state-title">No eligible installed apps</p>
            <p class="eo-empty-state-text">
              Install an app from the curated categories first, or enable the
              <strong>Gluetun For All Apps</strong> requirement to expose every app.
            </p>
          </div>
        </div>
      ` : `
        <div class="gluetun-country-list">
          ${apps.map((a) => {
            const cfgKey = `CFG_${a.slug.toUpperCase()}_NETWORK`;
            const current = (a.config && a.config[cfgKey]) || 'default';
            const checked = current === 'gluetun' ? 'checked' : '';
            const icon = a.icon ? (a.icon.startsWith('/') ? a.icon : '/' + a.icon) : '/core/icons/apps/default.svg';
            return `
              <label class="gluetun-country-item">
                <input type="checkbox" data-slug="${a.slug}" data-current="${current}" ${checked}>
                <span class="gluetun-country-name" style="display:flex; align-items:center; gap:10px;">
                  <img src="${icon}" alt="" style="width:20px; height:20px; object-fit:contain;" onerror="this.onerror=null; this.src='/core/icons/apps/default.svg';">
                  ${a.name || a.slug}
                </span>
              </label>`;
          }).join('')}
        </div>
      `}`;

    const m = window.openEoModal({
      id: 'gluetun-route-apps-modal',
      title: '🛡️  Route apps through Gluetun',
      body: bodyHtml,
      actions: [
        { label: 'Apply', variant: 'primary', onClick: async (modal) => {
          if (apps.length === 0) { modal.close(); return; }
          const root = modal.contentEl;
          const applyBtn = root.querySelectorAll('.eo-modal-footer .btn')[0];
          const changes = [];
          root.querySelectorAll('.gluetun-country-item input[type=checkbox]').forEach((cb) => {
            const desired = cb.checked ? 'gluetun' : 'default';
            if (desired !== cb.dataset.current) changes.push({ slug: cb.dataset.slug, value: desired });
          });
          if (changes.length === 0) { modal.close(); return; }
          applyBtn.disabled = true; applyBtn.textContent = 'Applying…';
          try {
            if (!window.tasksManager?.router) await this.loadTaskSystem?.();
            for (const { slug, value } of changes) {
              const cfgKey = `CFG_${slug.toUpperCase()}_NETWORK`;
              await window.tasksManager.router.routeAction('install', { appName: slug, config: { [cfgKey]: value } });
            }
            this.addSuccessLog?.(`Queued ${changes.length} gluetun routing task(s).`);
            modal.close();
            if (window.appTabbedManager) window.appTabbedManager.switchTab('tasks');
          } catch (err) {
            applyBtn.disabled = false; applyBtn.textContent = 'Apply';
            console.error('Failed to queue gluetun routing tasks', err);
          }
        }},
        { label: 'Cancel', variant: 'secondary' }
      ]
    });
    if (apps.length === 0) {
      const applyBtn = m.contentEl.querySelectorAll('.eo-modal-footer .btn')[0];
      if (applyBtn) applyBtn.disabled = true;
    }
  },
  openMullvadGenerateModal() {
    const existing = document.getElementById('mullvad-generate-modal');
    if (existing) existing.remove();

    const mullvadIcon = (window.gluetunProviderIcons && window.gluetunProviderIcons.mullvad) || '/components/apps/core/icons/vpn/mullvad.svg';
    const bodyHtml = `
      <p class="eo-modal-section-text">
        Enter your 16-digit Mullvad account number. A new WireGuard key will be generated locally
        and registered with Mullvad — this consumes one of your 5 device slots.
      </p>
      <div class="form-group">
        <label for="mullvad-acct" style="display:block; margin-bottom:6px; font-size:13px;">Account number</label>
        <input type="text" id="mullvad-acct" class="form-input" placeholder="1234567890123456" autocomplete="off" inputmode="numeric">
      </div>
      <p class="mullvad-error" style="color: #f87171; font-size: 13px; margin: 8px 0 0 0; display:none;"></p>`;

    const m = window.openEoModal({
      id: 'mullvad-generate-modal',
      size: 'sm',
      icon: mullvadIcon,
      iconAlt: 'Mullvad',
      eyebrow: 'Provider',
      title: 'Generate Mullvad Config',
      body: bodyHtml,
      actions: [
        { label: 'Generate', variant: 'primary', onClick: async (modal) => {
          const root = modal.contentEl;
          const errEl = root.querySelector('.mullvad-error');
          const confirmBtn = root.querySelectorAll('.eo-modal-footer .btn')[0];
          const acctEl = root.querySelector('#mullvad-acct');
          const setError = (msg) => { errEl.textContent = msg || ''; errEl.style.display = msg ? '' : 'none'; };
          const account = (acctEl.value || '').replace(/\s+/g, '');
          if (!/^\d{16}$/.test(account)) { setError('Account number must be 16 digits.'); return; }
          setError('');
          confirmBtn.disabled = true; confirmBtn.textContent = 'Generating…';
          try {
            const res = await fetch('/api/gluetun/mullvad-wireguard', {
              method: 'POST', headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({ accountNumber: account })
            });
            const data = await res.json();
            if (!res.ok || !data.success) {
              setError(data.error || `Request failed (${res.status}).`);
              confirmBtn.disabled = false; confirmBtn.textContent = 'Generate';
              return;
            }
            const findField = (suffix) => (typeof ConfigOptions !== 'undefined' && ConfigOptions.findGluetunFieldEl) ? ConfigOptions.findGluetunFieldEl(suffix) : null;
            const setField = (suffix, value) => {
              const el = findField(suffix); if (!el) return;
              el.value = value;
              el.dispatchEvent(new Event('input', { bubbles: true }));
              el.dispatchEvent(new Event('change', { bubbles: true }));
            };
            setField('WIREGUARD_PRIVATE_KEY', data.privateKey);
            setField('WIREGUARD_ADDRESSES', data.addresses);
            modal.close();
          } catch (err) {
            setError(err.message || 'Network error.');
            confirmBtn.disabled = false; confirmBtn.textContent = 'Generate';
          }
        }},
        { label: 'Cancel', variant: 'secondary' }
      ]
    });
    m.contentEl.querySelector('#mullvad-acct').focus();
  },
  async shouldRecommendGluetun(appName, appData) {
    if (appName === 'gluetun') return false;
    if (this.checkServiceInstalled('gluetun')) return false;
    const overrideOn = this.checkRequirementEnabled?.('GLUETUN_FOR_ALL');
    if (overrideOn) return true;
    if (!this._gluetunEligiblePromise) {
      this._gluetunEligiblePromise = (async () => {
        try {
          const r = await fetch('/data/apps/gluetun-eligible-categories.json', { cache: 'no-store' });
          if (!r.ok) return new Set();
          const j = await r.json();
          return new Set((j.categories || []).map((c) => String(c).toLowerCase()));
        } catch { return new Set(); }
      })();
    }
    const allow = await this._gluetunEligiblePromise;
    const cat = String(appData?.category || '').toLowerCase();
    return allow.has(cat);
  },
  showGluetunRecommendModal(appName, appData) {
    const existing = document.getElementById('gluetun-recommend-modal');
    if (existing) existing.remove();

    const displayName = appData?.name?.split(' - ')[0]?.trim() || appName;
    let icon = appData?.icon || `/core/icons/apps/${appName}.svg`;
    if (icon && !icon.startsWith('/')) icon = '/' + icon;

    const bodyHtml = `
      <div class="eo-empty-state warning" role="status">
        <div class="eo-empty-state-icon">
          <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M12 22s8-4 8-12V5l-8-3-8 3v5c0 8 8 12 8 12z"/>
          </svg>
        </div>
        <div class="eo-empty-state-body">
          <p class="eo-empty-state-title">Apps in this category usually benefit from VPN routing</p>
          <p class="eo-empty-state-text">Without Gluetun, this app's outbound traffic uses your real public IP — exposing activity to ISPs, copyright trackers, and the destination services.</p>
        </div>
      </div>`;

    window.openEoModal({
      id: 'gluetun-recommend-modal',
      size: 'sm',
      icon,
      iconAlt: displayName,
      eyebrow: 'About to install',
      title: displayName,
      desc: 'VPN routing is recommended for this app.',
      body: bodyHtml,
      actions: [
        { label: 'Install Gluetun first', variant: 'primary', onClick: (modal) => {
          modal.close();
          this.showAppDetailWithConfig('gluetun');
        }},
        { label: 'Continue without VPN', variant: 'secondary', onClick: (modal) => {
          modal.close();
          this.executeInstall(appName, false);
        }}
      ]
    });
  },
});
