// Traefik routing panel — only surfaces on the Traefik app's detail page.
// Lists every installed app's TCP, routing-eligible ports in two sections:
//   Recommended → port_recommended=true (defaults to the webui flag for
//                 unmigrated rows)
//   Advanced    → other TCP ports (collapsed behind a toggle)
//
// Apply flow: for each changed row, mutate field 7 (traefik) of the
// CFG_<APP>_PORT_<n> string and dispatch `install` with the config override.
// The existing task router runs the reinstall, which re-resolves the
// compose with the new TRAEFIK_ENABLE label.

class RoutingManager {
  constructor() {
    this.currentApp = null;
    this.changes = new Map(); // key = "<appSlug>|<portIdx>" → bool
  }

  isApplicable(appName) { return appName === 'traefik'; }

  async load(appName) {
    this.currentApp = appName;
    const list = document.getElementById('routing-list');
    if (!list) return;
    if (!this.isApplicable(appName)) {
      list.innerHTML = `<div class="routing-empty">Traefik routing controls live here.</div>`;
      return;
    }
    list.innerHTML = this._render();
    this._wire(list);
  }

  unload() { this.changes.clear(); }

  // Walk every installed app's CFG_<APP>_PORT_<n> rows, classify each one.
  _collectPorts() {
    const out = [];
    const apps = (window.apps || []).filter(a => a && a.installed && a.command);
    for (const app of apps) {
      const slug = (app.command || '').split(' ').pop();
      if (!slug || slug === 'traefik') continue;
      for (let i = 1; i <= 20; i++) {
        const cfgKey = `CFG_${slug.toUpperCase()}_PORT_${i}`;
        const raw = app.config && app.config[cfgKey];
        if (!raw) continue;
        const parts = String(raw).split('|');
        if (parts.length < 8) continue;
        const isNine = parts.length >= 9;
        const isTwelve = parts.length >= 12;
        const webui = isNine ? parts[7] === 'true' : parts[6] === 'true';
        const port = {
          appSlug: slug,
          appName: app.name || slug,
          portIdx: i,
          cfgKey,
          rawValue: String(raw),
          serviceName: parts[1],
          internalPort: (parts[2] || '').split(':')[1] || '',
          access: parts[3] || 'private',
          protocol: parts[4] || 'tcp',
          traefik: isNine ? parts[6] === 'true' : parts[5] === 'true',
          webui,
          recommended: isTwelve ? parts[11] === 'true' : webui,
          description: isNine ? (parts[8] || '') : (parts[7] || '')
        };
        if (port.protocol !== 'tcp') continue;
        out.push(port);
      }
    }
    return out;
  }

  _previewUrl(port, app) {
    const hostName = app && app.config && app.config[`CFG_${port.appSlug.toUpperCase()}_HOST_NAME`];
    const domainIdx = app && app.config && app.config[`CFG_${port.appSlug.toUpperCase()}_DOMAIN`];
    const domain = app && app.config && app.config[`CFG_DOMAIN_${domainIdx || 1}`];
    if (hostName && domain) return `https://${hostName}.${domain}`;
    return `https://${port.appSlug}.<your-domain>`;
  }

  _render() {
    const ports = this._collectPorts();
    const apps = (window.apps || []);
    const byApp = new Map();
    apps.forEach(a => byApp.set((a.command || '').split(' ').pop(), a));

    const primary = ports.filter(p => p.recommended);
    const advanced = ports.filter(p => !p.recommended);

    const renderRow = (p) => {
      const app = byApp.get(p.appSlug);
      const url = this._previewUrl(p, app);
      const iconUrl = `/icons/apps/${encodeURIComponent(p.appSlug)}.svg`;
      const badges = [
        p.webui ? '<span class="routing-badge routing-badge-webui" title="Marked as the webui (click-to-open) port">webui</span>' : '',
        p.access === 'public' ? '<span class="routing-badge routing-badge-public" title="Host port is exposed">public</span>' : ''
      ].join('');
      return `
        <div class="routing-row" data-app="${escapeAttr(p.appSlug)}" data-idx="${p.portIdx}">
          <img class="routing-icon" src="${iconUrl}" alt="${escapeHtml(p.appName)}" onerror="this.style.visibility='hidden'">
          <div class="routing-meta">
            <div class="routing-title">
              <span class="routing-app">${escapeHtml(p.appName)}</span>
              <span class="routing-port-name">${escapeHtml(p.serviceName)}</span>
              <span class="routing-port-num">:${escapeHtml(p.internalPort)}</span>
              ${badges}
            </div>
            <div class="routing-url">${escapeHtml(url)}</div>
          </div>
          <label class="routing-toggle" title="Toggle Traefik routing for this port">
            <input type="checkbox" class="routing-traefik" ${p.traefik ? 'checked' : ''}>
            <span class="routing-toggle-track"></span>
          </label>
        </div>`;
    };

    return `
      <div class="routing-title-block">
        <h3>🛡️ Traefik Routing</h3>
        <p>Toggle which app ports Traefik routes. Each change applied below reinstalls the affected app so the new <code>traefik.enable</code> label takes effect.</p>
      </div>

      <div class="routing-section">
        <div class="routing-section-head">
          <h4>Recommended <span class="routing-count">${primary.length}</span></h4>
          <span class="routing-section-hint">Ports flagged <code>recommended=true</code> in their PORT config.</span>
        </div>
        <div class="routing-table">
          ${primary.length ? primary.map(renderRow).join('') : '<div class="routing-empty">No recommended ports — install an app whose webui port is recommended.</div>'}
        </div>
      </div>

      <div class="routing-section">
        <div class="routing-section-head">
          <h4>Advanced <span class="routing-count">${advanced.length}</span></h4>
          <label class="routing-show-advanced">
            <input type="checkbox" id="routing-show-advanced">
            <span>Show advanced ports</span>
          </label>
        </div>
        <div class="routing-table routing-advanced-table">
          ${advanced.length ? advanced.map(renderRow).join('') : '<div class="routing-empty">No additional TCP ports across installed apps.</div>'}
        </div>
      </div>

      <div class="routing-apply-bar">
        <span class="routing-apply-hint" id="routing-apply-hint">No pending changes.</span>
        <button type="button" class="btn btn-primary routing-apply" disabled>Apply</button>
      </div>
    `;
  }

  _wire(root) {
    root.querySelectorAll('.routing-traefik').forEach(cb => {
      cb.addEventListener('change', () => this._trackChange(cb));
    });
    const showAdv = root.querySelector('#routing-show-advanced');
    const advTable = root.querySelector('.routing-advanced-table');
    if (showAdv && advTable) {
      const sync = () => { advTable.classList.toggle('routing-advanced-open', showAdv.checked); };
      showAdv.addEventListener('change', sync);
      sync();
    }
    root.querySelector('.routing-apply')?.addEventListener('click', () => this._apply());
  }

  _trackChange(cb) {
    const row = cb.closest('.routing-row');
    if (!row) return;
    const key = `${row.dataset.app}|${row.dataset.idx}`;
    // If the new value matches the original (clicked twice), drop the entry.
    const orig = row.querySelector('.routing-traefik').defaultChecked;
    if (cb.checked === orig) this.changes.delete(key);
    else this.changes.set(key, cb.checked);

    const root = document.getElementById('routing-list');
    const hint = root.querySelector('#routing-apply-hint');
    const applyBtn = root.querySelector('.routing-apply');
    const n = this.changes.size;
    if (n === 0) {
      hint.textContent = 'No pending changes.';
      applyBtn.disabled = true;
    } else {
      const apps = new Set([...this.changes.keys()].map(k => k.split('|')[0]));
      hint.textContent = `${n} change${n === 1 ? '' : 's'} across ${apps.size} app${apps.size === 1 ? '' : 's'} will reinstall.`;
      applyBtn.disabled = false;
    }
  }

  async _apply() {
    if (this.changes.size === 0) return;
    if (!window.tasksManager || !window.tasksManager.router) {
      console.error('Tasks router not available');
      return;
    }
    const apps = window.apps || [];
    const byApp = new Map();
    apps.forEach(a => byApp.set((a.command || '').split(' ').pop(), a));

    // Group changes by app so each app reinstalls once with all its port edits.
    const grouped = new Map();
    for (const [key, newTraefik] of this.changes) {
      const [slug, idxStr] = key.split('|');
      const idx = parseInt(idxStr, 10);
      const app = byApp.get(slug);
      if (!app) continue;
      const cfgKey = `CFG_${slug.toUpperCase()}_PORT_${idx}`;
      const raw = String(app.config[cfgKey] || '');
      const parts = raw.split('|');
      if (parts.length < 8) continue;
      // Field 7 is traefik in 9+col, field 6 in 8-col legacy.
      if (parts.length >= 9) parts[6] = newTraefik ? 'true' : 'false';
      else parts[5] = newTraefik ? 'true' : 'false';
      const newValue = parts.join('|');
      if (!grouped.has(slug)) grouped.set(slug, {});
      grouped.get(slug)[cfgKey] = newValue;
    }

    const slugs = [...grouped.keys()];
    for (const slug of slugs) {
      try {
        await window.tasksManager.router.routeAction('install', {
          appName: slug,
          config: grouped.get(slug)
        });
      } catch (e) {
        console.error(`Routing apply failed for ${slug}:`, e);
      }
    }

    this.changes.clear();
    if (window.appTabbedManager && typeof window.appTabbedManager.switchTab === 'function') {
      window.appTabbedManager.switchTab('tasks');
    }
  }
}

function escapeHtml(s) {
  return String(s ?? '')
    .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}
function escapeAttr(s) { return escapeHtml(s); }

window.routingManager = new RoutingManager();
