<script lang="ts">
  import { onMount } from 'svelte';
  import Modal from '$lib/Modal.svelte';
  import FlashBanner from '$lib/FlashBanner.svelte';
  import {
    accessLabelById,
    auditLogSummary,
    certLabel,
    certLabelById,
    domainListLabel,
    ownerDisplay,
    providerLabel,
    redirLabel,
    sslCertLabel,
    streamLabel,
    streamStatusDisplay,
  } from '$lib/labels';

  let token = $state(localStorage.getItem('pm_token') || '');
  let user = $state<any>(null);
  let currentTab = $state('dashboard');
  let loading = $state(false);
  let error = $state('');
  let formError = $state('');
  let success = $state('');

  // Data states
  let dashboard = $state<any>({});
  let proxyHosts = $state<any[]>([]);
  let certificates = $state<any[]>([]);
  let accessLists = $state<any[]>([]);
  let auditLogs = $state<any[]>([]);
  let streams = $state<any[]>([]);
  let redirs = $state<any[]>([]);
  let deads = $state<any[]>([]);
  let settingsData = $state<any>({});
  let appVersion = $state('');

  // Form states for new items (simple)
  let newProxy = $state({ 
    id: 0,
    domainNames: '', 
    forwardScheme: 'http', 
    forwardHost: '127.0.0.1', 
    forwardPort: 80, 
    accessListId: 0,
    certificateId: 0, 
    sslForced: false, 
    blockExploits: false, 
    hstsEnabled: false, 
    enabled: true,
    allowWebsocketUpgrade: false,
    trustForwardedProto: false,
    cachingEnabled: false,
    http2Support: false,
    hstsSubdomains: false,
    locations: [] as any[], // [{path, forwardScheme, forwardHost, forwardPort, forwardPath}]
    requestHeaders: [] as { name: string; value: string }[],
    responseHeaders: [] as { name: string; value: string }[],
    hideHeaders: [] as string[],
    createdOn: ''
  });
  let newCert = $state({ domainNames: '', provider: 'letsencrypt' });
  let newCustomCert = $state({ niceName: '', domainNames: '', cert: '', key: '' });
  let newAccess = $state({ id: 0, name: '', clients: '127.0.0.1/32 allow', items: 'user:pass', createdOn: '', satisfyAny: false, passAuth: false });
  let newStream = $state({ id: 0, incomingPort: 9000, forwardingHost: '127.0.0.1', forwardingPort: 9000, tcpForwarding: true, udpForwarding: false, certificateId: 0, enabled: true, createdOn: '' });
  let newRedir = $state({ id: 0, domainNames: '', forwardDomainName: 'example.com', forwardHttpCode: 301, forwardScheme: 'https', preservePath: false, enabled: true, createdOn: '' });
  let newDead = $state({ id: 0, domainNames: '', enabled: true, customHtml: '', createdOn: '' });

  // single admin password (default "password" until changed; forced on first login)
  let mustChangePassword = $state(false);
  let showChangePw = $state(false);
  let pwForm = $state({ current: '', new: '', confirm: '' });
  let changingPw = $state(false);

  // Modal state for form-based CRUD (streams/redir/dead) and confirm dialogs
  let openFormModal = $state(false);
  let formModalType = $state<string | null>(null);
  let formModalTitle = $state('');
  let confirmState = $state({ open: false, message: '', onConfirm: null as any });

  const API = ''; // same origin

  function saveToken(t: string) {
    token = t;
    localStorage.setItem('pm_token', t);
  }

  function clearAuth() {
    token = '';
    user = null;
    mustChangePassword = false;
    showChangePw = false;
    pwForm = { current: '', new: '', confirm: '' };
    localStorage.removeItem('pm_token');
    currentTab = 'dashboard';
    openFormModal = false;
    formModalType = null;
    proxyFormTab = 'details';
    searchProxy = ''; searchCerts = ''; searchAccess = ''; searchStreams = ''; searchRedir = ''; searchDead = ''; searchAudit = '';
    confirmState.open = false;
    confirmState.message = '';
    confirmState.onConfirm = null;
    showUserMenu = false;
    showHostsMenu = false;
  }

  async function apiFetch(path: string, opts: RequestInit = {}) {
    const headers: any = { 'Content-Type': 'application/json', ...(opts.headers || {}) };
    if (token) headers['Authorization'] = `Bearer ${token}`;
    const res = await fetch(API + path, { ...opts, headers });
    if (res.status === 401) {
      clearAuth();
      throw new Error('Session expired, please login again');
    }
    const txt = await res.text();
    if (!res.ok) {
      throw new Error(txt || res.statusText);
    }
    if (!txt) return null;
    return JSON.parse(txt);
  }

  // shared helper to wrap async side-effect handlers (toggles, loads, refreshes) for consistent error surfacing
  async function safeCall(fn: () => Promise<void>, okMsg?: string) {
    error = '';
    try {
      await fn();
      if (okMsg) success = okMsg;
    } catch (err: any) {
      error = err.message || 'Failed';
    }
  }

  async function login(e: Event) {
    e.preventDefault();
    error = ''; success = ''; loading = true;
    const form = e.target as HTMLFormElement;
    const password = (form.querySelector('#password') as HTMLInputElement).value || '';
    try {
      const data = await apiFetch('/api/login', { method: 'POST', body: JSON.stringify({ password }) });
      saveToken(data.token);
      user = data.user;
      mustChangePassword = !!data.mustChangePassword;
      showChangePw = mustChangePassword;
      success = mustChangePassword ? 'Login successful — please set a new admin password' : 'Logged in';
      pwForm = { current: '', new: '', confirm: '' };
      if (!mustChangePassword) {
        await loadAll();
        currentTab = 'dashboard';
      }
    } catch (err: any) {
      error = err.message || 'Login failed';
    } finally { loading = false; }
  }

  async function changeAdminPassword(e?: Event) {
    if (e) e.preventDefault();
    error = ''; success = ''; changingPw = true;
    if (!mustChangePassword) showChangePw = true; // keep form visible for manual change
    if (pwForm.new !== pwForm.confirm) {
      error = 'New passwords do not match';
      changingPw = false;
      return;
    }
    if (!pwForm.new || pwForm.new.length < 4) {
      error = 'New password must be at least 4 characters';
      changingPw = false;
      return;
    }
    try {
      const body: any = { newPassword: pwForm.new };
      if (!mustChangePassword || pwForm.current) {
        body.currentPassword = pwForm.current || (mustChangePassword ? 'password' : '');
      }
      await apiFetch('/api/users/me/password', { method: 'POST', body: JSON.stringify(body) });
      success = 'Password changed successfully';
      mustChangePassword = false;
      showChangePw = false;
      pwForm = { current: '', new: '', confirm: '' };
      // after forced first change, load the app
      await loadAll();
      currentTab = 'dashboard';
    } catch (err: any) {
      error = err.message || 'Failed to change password';
    } finally {
      changingPw = false;
    }
  }

  async function loadDashboard() {
    dashboard = await apiFetch('/api/dashboard');
  }

  async function loadProxyHosts() {
    proxyHosts = (await apiFetch('/api/proxy-hosts')) ?? [];
  }

  async function loadCerts() {
    certificates = (await apiFetch('/api/certificates')) ?? [];
  }

  async function loadAccess() {
    accessLists = (await apiFetch('/api/access-lists')) ?? [];
  }

  async function loadAudit() {
    auditLogs = (await apiFetch('/api/audit-logs')) ?? [];
  }

  async function loadStreams() { streams = (await apiFetch('/api/streams')) ?? []; }
  async function loadRedirs() { redirs = (await apiFetch('/api/redirection-hosts')) ?? []; }
  async function loadDeads() { deads = (await apiFetch('/api/dead-hosts')) ?? []; }

  async function loadSettings() {
    settingsData = await apiFetch('/api/settings');
  }

  async function loadVersion() {
    try {
      const data = await apiFetch('/api/version');
      appVersion = data?.version || '';
    } catch {
      appVersion = '';
    }
  }

  // search states for list views (client-side filter)
  let searchProxy = $state('');
  let searchCerts = $state('');
  let searchAccess = $state('');
  let searchStreams = $state('');
  let searchRedir = $state('');
  let searchDead = $state('');
  let searchAudit = $state('');

  // internal tab for proxy form modal (Details / Custom Locations / SSL / Headers)
  let proxyFormTab = $state<'details'|'locations'|'ssl'|'headers'>('details');

  function parseLegacyAdvancedConfig(adv: string) {
    const requestHeaders: { name: string; value: string }[] = [];
    const responseHeaders: { name: string; value: string }[] = [];
    const hideHeaders: string[] = [];
    if (!adv) return { requestHeaders, responseHeaders, hideHeaders };
    for (const raw of adv.replace(/;/g, '\n').split('\n')) {
      const line = raw.trim();
      if (!line || line.startsWith('#')) continue;
      const lower = line.toLowerCase();
      const parts = line.split(/\s+/);
      if (lower.startsWith('proxy_set_header ') && parts.length >= 3) {
        requestHeaders.push({ name: parts[1], value: parts.slice(2).join(' ') });
      } else if (lower.startsWith('add_header ') && parts.length >= 3) {
        const val = parts.slice(2).join(' ').replace(/^["']|["';]+$/g, '');
        responseHeaders.push({ name: parts[1], value: val });
      } else if (lower.startsWith('proxy_hide_header ') && parts.length >= 2) {
        hideHeaders.push(parts[1]);
      }
    }
    return { requestHeaders, responseHeaders, hideHeaders };
  }

  function headerFieldsFromHost(h: any) {
    const requestHeaders = (h.requestHeaders || []).map((r: any) => ({ name: r.name || '', value: r.value || '' }));
    const responseHeaders = (h.responseHeaders || []).map((r: any) => ({ name: r.name || '', value: r.value || '' }));
    const hideHeaders = [...(h.hideHeaders || [])];
    if (!requestHeaders.length && !responseHeaders.length && !hideHeaders.length && h.advancedConfig) {
      return parseLegacyAdvancedConfig(h.advancedConfig);
    }
    return { requestHeaders, responseHeaders, hideHeaders };
  }

  // user menu and hosts dropdown state
  let showUserMenu = $state(false);
  let showHostsMenu = $state(false);

  // client-side filtered lists (per spec, no backend change)
  const filteredProxy = $derived(
    proxyHosts.filter((h: any) => {
      const q = searchProxy.toLowerCase();
      if (!q) return true;
      const domains = (h.domainNames || []).join(' ').toLowerCase();
      return domains.includes(q) || (h.forwardHost || '').toLowerCase().includes(q);
    })
  );
  const filteredCerts = $derived(
    certificates.filter((c: any) => {
      const q = searchCerts.toLowerCase();
      if (!q) return true;
      const domains = (c.domainNames || []).join(' ').toLowerCase();
      const nice = (c.niceName || '').toLowerCase();
      return domains.includes(q) || nice.includes(q) || (c.provider || '').toLowerCase().includes(q);
    })
  );
  const filteredAccess = $derived(
    accessLists.filter((al: any) => {
      const q = searchAccess.toLowerCase();
      if (!q) return true;
      return (al.name || '').toLowerCase().includes(q);
    })
  );
  const filteredStreams = $derived(
    streams.filter((s: any) => {
      const q = searchStreams.toLowerCase();
      if (!q) return true;
      return String(s.incomingPort).toLowerCase().includes(q) || (s.forwardingHost || '').toLowerCase().includes(q);
    })
  );
  const filteredRedirs = $derived(
    redirs.filter((r: any) => {
      const q = searchRedir.toLowerCase();
      if (!q) return true;
      const doms = (r.domainNames || []).join(' ').toLowerCase();
      return doms.includes(q) || (r.forwardDomainName || '').toLowerCase().includes(q);
    })
  );
  const filteredDeads = $derived(
    deads.filter((d: any) => {
      const q = searchDead.toLowerCase();
      if (!q) return true;
      return (d.domainNames || []).join(' ').toLowerCase().includes(q);
    })
  );
  const filteredAudit = $derived(
    auditLogs.filter((l: any) => {
      const q = searchAudit.toLowerCase();
      if (!q) return true;
      const summary = auditLogSummary(l, user).toLowerCase();
      return summary.includes(q)
        || (l.action || '').toLowerCase().includes(q)
        || (l.objectType || '').toLowerCase().includes(q)
        || String(l.objectId || '').includes(q)
        || String(l.userId || '').includes(q);
    })
  );

  const owner = $derived(ownerDisplay(user));

  function isCertInUse(certId: number): boolean {
    if (!certId) return false;
    return proxyHosts.some((h: any) => h.certificateId === certId) || streams.some((s: any) => s.certificateId === certId);
  }

  async function loadAll() {
    if (!token) return;
    loading = true; error = '';
    try {
      await Promise.all([
        loadDashboard(),
        loadProxyHosts(),
        loadCerts(),
        loadAccess(),
        loadAudit(),
        loadStreams(),
        loadRedirs(),
        loadDeads(),
        loadSettings(),
        loadVersion()
      ]);
      if (!user) {
        try { user = await apiFetch('/api/users/me'); } catch {}
      }
    } catch (err: any) {
      error = err.message;
    } finally { loading = false; }
  }

  async function toggleHost(id: number, enable: boolean) {
    const path = `/api/proxy-hosts/${id}/${enable ? 'enable' : 'disable'}`;
    await safeCall(async () => {
      await apiFetch(path, { method: 'POST' });
      await loadProxyHosts();
      await loadDashboard();
    }, `Host ${enable ? 'enabled' : 'disabled'}`);
  }

  async function deleteHost(id: number) {
    await safeCall(async () => {
      await apiFetch(`/api/proxy-hosts/${id}`, { method: 'DELETE' });
      await loadProxyHosts();
      await loadCerts();
      await loadAccess();
      await loadDashboard();
    }, 'Host deleted');
  }

  function sourceDomainConflict(domains: string[], existing: any[], selfId: number): string | null {
    const domainKeys = domains.map(d => d.toLowerCase());
    if (new Set(domainKeys).size !== domainKeys.length) {
      return 'Duplicate source domains are not allowed';
    }
    for (const item of existing) {
      if (selfId && item.id === selfId) continue;
      for (const d of (item.domainNames || [])) {
        if (domainKeys.includes(String(d).toLowerCase())) {
          return `Source domain already in use: ${d}`;
        }
      }
    }
    return null;
  }

  function streamPortConflict(port: number, selfId: number): string | null {
    for (const s of streams) {
      if (selfId && s.id === selfId) continue;
      if (s.incomingPort === port) {
        return `Incoming port already in use: ${port}`;
      }
    }
    return null;
  }

  async function createProxy(e: Event) {
    e.preventDefault();
    formError = ''; error = ''; success = '';
    const domains = newProxy.domainNames.split(',').map(s => s.trim()).filter(Boolean);
    if (!domains.length) { formError = 'Enter at least one domain'; return; }
    const domainErr = sourceDomainConflict(domains, proxyHosts, newProxy.id);
    if (domainErr) { formError = domainErr; proxyFormTab = 'details'; return; }
    let certificateId = newProxy.certificateId || 0;
    let autoCertMsg = '';
    if (certificateId === -1) {
      // Convenience: auto-request a new LE cert for the domains entered here (one-flow like original NPM).
      // Mirrors the separate certs tab flow but uses the proxy's domains and attaches the result.
      try {
        const certPayload = { provider: 'letsencrypt', domainNames: domains };
        const created = await apiFetch('/api/certificates', { method: 'POST', body: JSON.stringify(certPayload) });
        certificateId = created?.id || 0;
        autoCertMsg = 'Certificate requested and ';
        await loadCerts();
      } catch (err: any) {
        formError = err.message || 'Failed to request LE certificate';
        return;
      }
    }
    const locs = (newProxy.locations || []).filter(l => l.path).map(l => ({
      path: l.path,
      forwardScheme: l.forwardScheme || 'http',
      forwardHost: l.forwardHost || '',
      forwardPort: parseInt(String(l.forwardPort)) || 80,
      forwardPath: l.forwardPath || ''
    }));
    const payload: any = {
      domainNames: domains,
      forwardScheme: newProxy.forwardScheme,
      forwardHost: newProxy.forwardHost,
      forwardPort: parseInt(String(newProxy.forwardPort)) || 80,
      accessListId: newProxy.accessListId || 0,
      certificateId,
      sslForced: newProxy.sslForced,
      blockExploits: newProxy.blockExploits,
      hstsEnabled: newProxy.hstsEnabled,
      enabled: newProxy.enabled,
      allowWebsocketUpgrade: newProxy.allowWebsocketUpgrade,
      trustForwardedProto: newProxy.trustForwardedProto,
      cachingEnabled: newProxy.cachingEnabled,
      http2Support: newProxy.http2Support,
      hstsSubdomains: newProxy.hstsSubdomains,
      locations: locs,
      requestHeaders: (newProxy.requestHeaders || []).filter(r => (r.name || '').trim()).map(r => ({ name: r.name.trim(), value: r.value || '' })),
      responseHeaders: (newProxy.responseHeaders || []).filter(r => (r.name || '').trim()).map(r => ({ name: r.name.trim(), value: r.value || '' })),
      hideHeaders: (newProxy.hideHeaders || []).map(s => s.trim()).filter(Boolean),
      advancedConfig: '',
      createdOn: newProxy.createdOn || ''
    };
    const method = newProxy.id ? 'PUT' : 'POST';
    const url = newProxy.id ? `/api/proxy-hosts/${newProxy.id}` : '/api/proxy-hosts';
    try {
      await apiFetch(url, { method, body: JSON.stringify(payload) });
      success = autoCertMsg + (newProxy.id ? 'Proxy host updated' : 'Proxy host created');
      resetProxyForm();
      await loadProxyHosts();
      await loadCerts();
      await loadAccess();
      await loadDashboard();
    } catch (err: any) {
      formError = err.message || 'Failed';
    }
  }

  function resetProxyForm() {
    newProxy = { 
      id: 0,
      domainNames: '', 
      forwardScheme: 'http', 
      forwardHost: '127.0.0.1', 
      forwardPort: 80, 
      accessListId: 0,
      certificateId: 0, 
      sslForced: false, 
      blockExploits: false, 
      hstsEnabled: false, 
      enabled: true,
      allowWebsocketUpgrade: false,
      trustForwardedProto: false,
      cachingEnabled: false,
      http2Support: false,
      hstsSubdomains: false,
      locations: [] as any[],
      requestHeaders: [] as { name: string; value: string }[],
      responseHeaders: [] as { name: string; value: string }[],
      hideHeaders: [] as string[],
      createdOn: ''
    };
  }

  function closeForm() {
    if (formModalType === 'proxy' && !newProxy.id) {
      resetProxyForm();
    }
    proxyFormTab = 'details';
    formModalType = null;
    formError = '';
    openFormModal = false;
  }

  function editHost(h: any) {
    const headers = headerFieldsFromHost(h);
    newProxy = { 
      id: h.id,
      domainNames: (h.domainNames || []).join(', '), 
      forwardScheme: h.forwardScheme || 'http', 
      forwardHost: h.forwardHost || '', 
      forwardPort: h.forwardPort || 80, 
      accessListId: h.accessListId || 0,
      certificateId: h.certificateId || 0, 
      sslForced: !!h.sslForced, 
      blockExploits: !!h.blockExploits, 
      hstsEnabled: !!h.hstsEnabled, 
      enabled: !!h.enabled,
      allowWebsocketUpgrade: !!h.allowWebsocketUpgrade,
      trustForwardedProto: !!h.trustForwardedProto,
      cachingEnabled: !!h.cachingEnabled,
      http2Support: !!h.http2Support,
      hstsSubdomains: !!h.hstsSubdomains,
      locations: (h.locations || []).map((l: any) => ({...l})),
      requestHeaders: headers.requestHeaders,
      responseHeaders: headers.responseHeaders,
      hideHeaders: headers.hideHeaders,
      createdOn: h.createdOn || ''
    };
    // switch to proxy tab if not
    currentTab = 'proxy';
  }

  async function createCert(e: Event) {
    e.preventDefault();
    formError = ''; error = ''; success = '';
    const domains = newCert.domainNames.split(',').map(s => s.trim()).filter(Boolean);
    if (!domains.length) { formError = 'Enter domains'; return; }
    const payload = { provider: newCert.provider, domainNames: domains };
    try {
      await apiFetch('/api/certificates', { method: 'POST', body: JSON.stringify(payload) });
      success = 'Certificate requested';
      newCert = { domainNames: '', provider: 'letsencrypt' };
      await loadCerts();
      await loadProxyHosts();
      await loadStreams();
      await loadDashboard();
    } catch (err: any) {
      formError = err.message || 'Failed';
    }
  }

  async function createCustomCert(e: Event) {
    e.preventDefault();
    formError = ''; error = ''; success = '';
    const domains = newCustomCert.domainNames.split(',').map(s => s.trim()).filter(Boolean);
    if (!domains.length || !newCustomCert.cert || !newCustomCert.key) { formError = 'Domains + cert + key required'; return; }
    const payload = {
      provider: 'other',
      niceName: newCustomCert.niceName || domains[0],
      domainNames: domains,
      meta: { certificate: newCustomCert.cert, certificate_key: newCustomCert.key, cert: newCustomCert.cert, key: newCustomCert.key }
    };
    try {
      await apiFetch('/api/certificates', { method: 'POST', body: JSON.stringify(payload) });
      success = 'Custom certificate uploaded';
      newCustomCert = { niceName: '', domainNames: '', cert: '', key: '' };
      await loadCerts();
      await loadProxyHosts();
      await loadStreams();
      await loadDashboard();
    } catch (err: any) {
      formError = err.message || 'Failed';
    }
  }

  async function deleteCert(id: number) {
    await safeCall(async () => {
      await apiFetch(`/api/certificates/${id}`, { method: 'DELETE' });
      await loadCerts();
      await loadProxyHosts();
      await loadStreams();
      await loadDashboard();
    }, 'Certificate deleted');
  }

  async function renewCert(id: number) {
    error = ''; success = '';
    try {
      await apiFetch(`/api/certificates/${id}/renew`, { method: 'POST' });
      success = 'Certificate renewal requested (LE will re-issue)';
      await loadCerts();
      await loadProxyHosts();
      await loadStreams();
      await loadDashboard();
    } catch (err: any) {
      error = err.message || 'Renew failed';
    }
  }

  async function createAccess(e: Event) {
    e.preventDefault();
    formError = ''; error = ''; success = '';
    const name = (newAccess.name || '').trim();
    if (!name) { formError = 'Enter a name for the access list'; return; }
    // very simple parse for demo
    const clientsStr = newAccess.clients;
    const clients = clientsStr.split(',').map((p: string) => {
      const [addr, dir = 'allow'] = p.trim().split(' ');
      return { address: addr, directive: dir };
    });
    const itemsStr = newAccess.items;
    const items = itemsStr.split(',').map((p: string) => {
      const [u, pw] = p.trim().split(':');
      return { username: u, password: pw || 'pass' };
    });
    const payload: any = { name, clients, items, satisfyAny: newAccess.satisfyAny, passAuth: newAccess.passAuth, createdOn: newAccess.createdOn || '' };
    const method = newAccess.id ? 'PUT' : 'POST';
    const url = newAccess.id ? `/api/access-lists/${newAccess.id}` : '/api/access-lists';
    try {
      await apiFetch(url, { method, body: JSON.stringify(payload) });
      success = newAccess.id ? 'Access list updated' : 'Access list created';
      resetAccessForm();
      await loadAccess();
      await loadProxyHosts(); // for Proxy Hosts count in access table if viewing it
    } catch (err: any) {
      formError = err.message || 'Failed';
    }
  }

  function resetAccessForm() {
    newAccess = { id: 0, name: '', clients: '127.0.0.1/32 allow', items: 'user:pass', createdOn: '', satisfyAny: false, passAuth: false };
  }

  function editAccess(al: any) {
    const clientsStr = (al.clients || []).map((c: any) => `${c.address} ${c.directive}`).join(', ');
    const itemsStr = (al.items || []).map((i: any) => `${i.username}:${i.password || ''}`).join(', ');
    newAccess = { id: al.id, name: al.name || '', clients: clientsStr || '127.0.0.1/32 allow', items: itemsStr || 'user:pass', createdOn: al.createdOn || '', satisfyAny: !!al.satisfyAny, passAuth: !!al.passAuth };
  }

  async function deleteAccess(id: number) {
    await safeCall(async () => {
      await apiFetch(`/api/access-lists/${id}`, { method: 'DELETE' });
      await loadAccess();
      await loadProxyHosts(); // for symmetry with createAccess + Proxy Hosts count / name lookups in proxy table if viewing it
    }, 'Access list deleted');
  }

  async function createStream(e: Event) {
    e.preventDefault();
    formError = ''; error = ''; success = '';
    const payload: any = {
      incomingPort: parseInt(String(newStream.incomingPort)) || 0,
      forwardingHost: newStream.forwardingHost,
      forwardingPort: parseInt(String(newStream.forwardingPort)) || 0,
      tcpForwarding: newStream.tcpForwarding,
      udpForwarding: newStream.udpForwarding,
      certificateId: newStream.certificateId || 0,
      enabled: newStream.enabled,
      createdOn: newStream.createdOn || ''
    };
    if (!payload.incomingPort) { formError = 'Incoming port required'; return; }
    const portErr = streamPortConflict(payload.incomingPort, newStream.id);
    if (portErr) { formError = portErr; return; }
    const method = newStream.id ? 'PUT' : 'POST';
    const url = newStream.id ? `/api/streams/${newStream.id}` : '/api/streams';
    try {
      await apiFetch(url, { method, body: JSON.stringify(payload) });
      success = newStream.id ? 'Stream updated' : 'Stream created' + (payload.certificateId ? ' (with SSL termination)' : '');
      resetStreamForm();
      await loadStreams();
      await loadCerts();
      await loadDashboard();
    } catch (err: any) {
      formError = err.message || 'Failed';
    }
  }

  function resetStreamForm() {
    newStream = { id: 0, incomingPort: 9000, forwardingHost: '127.0.0.1', forwardingPort: 9000, tcpForwarding: true, udpForwarding: false, certificateId: 0, enabled: true, createdOn: '' };
  }

  function editStream(s: any) {
    newStream = { id: s.id || 0, incomingPort: s.incomingPort || 9000, forwardingHost: s.forwardingHost || '', forwardingPort: s.forwardingPort || 9000, tcpForwarding: !!s.tcpForwarding, udpForwarding: !!s.udpForwarding, certificateId: s.certificateId || 0, enabled: !!s.enabled, createdOn: s.createdOn || '' };
  }

  async function deleteStream(id: number) {
    await safeCall(async () => {
      await apiFetch(`/api/streams/${id}`, { method: 'DELETE' });
      await loadStreams();
      await loadCerts();
      await loadDashboard();
    }, 'Stream deleted');
  }

  async function createRedir(e: Event) {
    e.preventDefault();
    formError = ''; error = ''; success = '';
    const domains = newRedir.domainNames.split(',').map(s => s.trim()).filter(Boolean);
    if (!domains.length) { formError = 'Enter domains'; return; }
    const domainErr = sourceDomainConflict(domains, redirs, newRedir.id);
    if (domainErr) { formError = domainErr; return; }
    const payload: any = {
      domainNames: domains,
      forwardDomainName: newRedir.forwardDomainName,
      forwardHttpCode: newRedir.forwardHttpCode || 301,
      forwardScheme: newRedir.forwardScheme || 'https',
      preservePath: newRedir.preservePath,
      enabled: newRedir.enabled,
      createdOn: newRedir.createdOn || ''
    };
    const method = newRedir.id ? 'PUT' : 'POST';
    const url = newRedir.id ? `/api/redirection-hosts/${newRedir.id}` : '/api/redirection-hosts';
    try {
      await apiFetch(url, { method, body: JSON.stringify(payload) });
      success = newRedir.id ? 'Redirection host updated' : 'Redirection host created';
      resetRedirForm();
      await loadRedirs();
      await loadDashboard();
    } catch (err: any) {
      formError = err.message || 'Failed';
    }
  }

  function resetRedirForm() {
    newRedir = { id: 0, domainNames: '', forwardDomainName: 'example.com', forwardHttpCode: 301, forwardScheme: 'https', preservePath: false, enabled: true, createdOn: '' };
  }

  function editRedir(r: any) {
    newRedir = {
      id: r.id || 0,
      domainNames: (r.domainNames || []).join(', '),
      forwardDomainName: r.forwardDomainName || 'example.com',
      forwardHttpCode: r.forwardHttpCode || 301,
      forwardScheme: r.forwardScheme || 'https',
      preservePath: !!r.preservePath,
      enabled: !!r.enabled,
      createdOn: r.createdOn || ''
    };
  }

  async function deleteRedir(id: number) {
    await safeCall(async () => {
      await apiFetch(`/api/redirection-hosts/${id}`, { method: 'DELETE' });
      await loadRedirs();
      await loadDashboard();
    }, 'Redirection host deleted');
  }

  async function createDead(e: Event) {
    e.preventDefault();
    formError = ''; error = ''; success = '';
    const domains = newDead.domainNames.split(',').map(s => s.trim()).filter(Boolean);
    if (!domains.length) { formError = 'Enter domains'; return; }
    const domainErr = sourceDomainConflict(domains, deads, newDead.id);
    if (domainErr) { formError = domainErr; return; }
    const payload: any = {
      domainNames: domains,
      enabled: newDead.enabled,
      meta: newDead.customHtml ? { html: newDead.customHtml } : {},
      createdOn: newDead.createdOn || ''
    };
    const method = newDead.id ? 'PUT' : 'POST';
    const url = newDead.id ? `/api/dead-hosts/${newDead.id}` : '/api/dead-hosts';
    try {
      await apiFetch(url, { method, body: JSON.stringify(payload) });
      success = newDead.id ? '404 host updated' : '404 host created';
      resetDeadForm();
      await loadDeads();
      await loadDashboard();
    } catch (err: any) {
      formError = err.message || 'Failed';
    }
  }

  function resetDeadForm() {
    newDead = { id: 0, domainNames: '', enabled: true, customHtml: '', createdOn: '' };
  }

  function editDead(d: any) {
    newDead = {
      id: d.id || 0,
      domainNames: (d.domainNames || []).join(', '),
      enabled: !!d.enabled,
      customHtml: (d.meta && d.meta.html) || '',
      createdOn: d.createdOn || ''
    };
  }

  async function deleteDead(id: number) {
    await safeCall(async () => {
      await apiFetch(`/api/dead-hosts/${id}`, { method: 'DELETE' });
      await loadDeads();
      await loadDashboard();
    }, '404 host deleted');
  }

  async function toggleRedir(id: number, en: boolean) {
    await safeCall(async () => {
      await apiFetch(`/api/redirection-hosts/${id}/${en?'enable':'disable'}`, {method:'POST'});
      await loadRedirs();
    }, `Host ${en ? 'enabled' : 'disabled'}`);
  }
  async function toggleDead(id: number, en: boolean) {
    await safeCall(async () => {
      await apiFetch(`/api/dead-hosts/${id}/${en?'enable':'disable'}`, {method:'POST'});
      await loadDeads();
    }, `Host ${en ? 'enabled' : 'disabled'}`);
  }
  async function toggleStream(id: number, en: boolean) {
    await safeCall(async () => {
      await apiFetch(`/api/streams/${id}/${en?'enable':'disable'}`, {method:'POST'});
      await loadStreams();
    }, `Host ${en ? 'enabled' : 'disabled'}`);
  }

  // init - onMount avoids Svelte 5 "captures the initial value" warning from top-level if on $state
  onMount(() => {
    if (token) {
      loadAll();
    }
  });

  function logout() {
    clearAuth();
    proxyHosts = []; certificates = []; /* etc reset if want */
  }

  function switchTab(tab: string) {
    showHostsMenu = false;
    showUserMenu = false;
    currentTab = tab;
    openFormModal = false;
    formModalType = null;
    proxyFormTab = 'details';
    searchProxy = ''; searchCerts = ''; searchAccess = ''; searchStreams = ''; searchRedir = ''; searchDead = ''; searchAudit = '';
    confirmState.open = false;
    confirmState.message = '';
    confirmState.onConfirm = null;
    if (tab === 'dashboard') safeCall(loadDashboard);
    if (tab === 'proxy') { safeCall(async () => { await loadProxyHosts(); await loadCerts(); await loadAccess(); }); }
    if (tab === 'certs') safeCall(async () => { await loadCerts(); await loadProxyHosts(); await loadStreams(); });
    if (tab === 'access') safeCall(async () => { await loadAccess(); await loadProxyHosts(); });
    if (tab === 'audit') safeCall(loadAudit);
    if (tab === 'streams') { safeCall(async () => { await loadStreams(); await loadCerts(); }); }
    if (tab === 'redir') safeCall(async () => { await loadRedirs(); await loadDashboard(); });
    if (tab === 'dead') safeCall(async () => { await loadDeads(); await loadDashboard(); });
    if (tab === 'settings') safeCall(loadSettings);
  }

  function showConfirm(message: string, onConfirm: () => void | Promise<void>) {
    confirmState.message = message;
    confirmState.onConfirm = onConfirm;
    confirmState.open = true;
  }

  function handleConfirm() {
    const fn = confirmState.onConfirm;
    confirmState.open = false;
    confirmState.message = '';
    confirmState.onConfirm = null;
    if (fn) {
      Promise.resolve(fn()).catch((e: any) => { error = e?.message || 'Action failed'; });
    }
  }

  function openNewStream() {
    resetStreamForm();
    formError = '';
    formModalType = 'stream';
    formModalTitle = 'Add Stream';
    openFormModal = true;
  }
  function editStreamAndOpen(s: any) {
    editStream(s);
    formError = '';
    formModalType = 'stream';
    formModalTitle = `Edit Stream ${streamLabel(newStream)}`;
    openFormModal = true;
  }

  function openNewRedir() {
    resetRedirForm();
    formError = '';
    formModalType = 'redir';
    formModalTitle = 'Add Redirection Host';
    openFormModal = true;
  }
  function editRedirAndOpen(r: any) {
    editRedir(r);
    formError = '';
    formModalType = 'redir';
    formModalTitle = `Edit ${redirLabel(newRedir)}`;
    openFormModal = true;
  }

  function openNewDead() {
    resetDeadForm();
    formError = '';
    formModalType = 'dead';
    formModalTitle = 'Add 404 Host';
    openFormModal = true;
  }
  function editDeadAndOpen(d: any) {
    editDead(d);
    formError = '';
    formModalType = 'dead';
    formModalTitle = `Edit 404 Host ${domainListLabel(newDead.domainNames, `#${newDead.id}`)}`;
    openFormModal = true;
  }

  function openNewProxy() {
    resetProxyForm();
    proxyFormTab = 'details';
    formError = '';
    formModalType = 'proxy';
    formModalTitle = 'Add Proxy Host';
    openFormModal = true;
  }
  function editHostAndOpen(h: any) {
    editHost(h);
    proxyFormTab = 'details';
    formError = '';
    formModalType = 'proxy';
    const domains = (newProxy.domainNames || '').split(',').map((s: string) => s.trim()).filter(Boolean);
    formModalTitle = `Edit Proxy Host ${domainListLabel(domains, `#${newProxy.id}`)}`;
    openFormModal = true;
  }

  function openNewCert() {
    newCert = { domainNames: '', provider: 'letsencrypt' };
    formError = '';
    formModalType = 'cert';
    formModalTitle = 'Add Certificate';
    openFormModal = true;
  }

  function openNewCustomCert() {
    newCustomCert = { niceName: '', domainNames: '', cert: '', key: '' };
    formError = '';
    formModalType = 'customCert';
    formModalTitle = 'Add Custom Certificate';
    openFormModal = true;
  }

  function openNewAccess() {
    resetAccessForm();
    formError = '';
    formModalType = 'access';
    formModalTitle = 'Add Access List';
    openFormModal = true;
  }
  function editAccessAndOpen(al: any) {
    editAccess(al);
    formError = '';
    formModalType = 'access';
    formModalTitle = `Edit Access List ${newAccess.name || `#${newAccess.id}`}`;
    openFormModal = true;
  }
</script>

<div class="min-h-screen bg-zinc-950 text-zinc-200">
  <!-- Top banner matching live: logo + title, right icons + user avatar menu -->
  <header class="border-b border-zinc-800 bg-zinc-900">
    <div class="max-w-6xl mx-auto px-4 py-2 flex items-center justify-between">
      <div class="flex items-center gap-3">
        <div class="w-8 h-8 bg-emerald-500 rounded flex items-center justify-center text-black font-bold">HP</div>
        <div class="font-semibold">Helix Proxy</div>
      </div>
      {#if token}
        <div class="flex items-center gap-1 text-sm">
          <!-- icon buttons (notifications/help placeholders to match live) -->
          <button title="Notifications" class="p-1 text-zinc-400 hover:text-zinc-200">
            <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" /></svg>
          </button>
          <button title="Help" class="p-1 text-zinc-400 hover:text-zinc-200">
            <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" /></svg>
          </button>
          <!-- user avatar + dropdown menu -->
          <div class="relative">
            <button
              onclick={() => showUserMenu = !showUserMenu}
              class="w-6 h-6 bg-emerald-600 rounded-full flex items-center justify-center text-[10px] text-white font-medium focus:outline-none"
              title="Open user menu"
              aria-haspopup="menu"
              aria-expanded={showUserMenu}
            >
              A
            </button>
            {#if showUserMenu}
              <div class="absolute right-0 mt-1 w-48 bg-zinc-900 border border-zinc-700 rounded shadow-xl text-sm z-50 py-1">
                <div class="px-3 py-1.5 text-[11px] text-zinc-400 border-b border-zinc-800">{user?.email || 'admin'}<div class="text-[10px] text-emerald-400">Administrator</div></div>
                <button onclick={() => { showUserMenu=false; pwForm = { current: '', new: '', confirm: '' }; showChangePw = true; }} class="block w-full text-left px-3 py-1 hover:bg-zinc-800 text-xs">Change password</button>
                <button onclick={() => { showUserMenu=false; logout(); }} class="block w-full text-left px-3 py-1 hover:bg-zinc-800 text-xs text-red-400">Logout</button>
              </div>
            {/if}
          </div>
        </div>
      {/if}
    </div>
  </header>

  {#if !token}
    <!-- Login -->
    <div class="max-w-md mx-auto mt-16 p-8 bg-zinc-900 border border-zinc-800 rounded-xl">
      <h1 class="text-2xl font-semibold mb-6">Sign in</h1>

      <form onsubmit={login} class="space-y-4">
        <div>
          <label for="password" class="text-xs text-zinc-400 block mb-1">Password</label>
          <input id="password" type="password" placeholder="Password" class="w-full bg-zinc-950 border border-zinc-800 rounded px-3 py-2 text-sm focus:outline-none focus:border-emerald-500" />
        </div>
        <button type="submit" disabled={loading} class="w-full bg-emerald-600 hover:bg-emerald-500 disabled:opacity-60 py-2 rounded font-medium text-sm transition">
          {loading ? 'Signing in...' : 'Sign in'}
        </button>
      </form>

      <FlashBanner bind:message={error} variant="error" compact class="mt-4" />
      <FlashBanner bind:message={success} variant="success" compact class="mt-4" />
    </div>
  {:else}
    <!-- Main App -->
    <div class="max-w-6xl mx-auto px-4 py-6">
      {#if !mustChangePassword}
      <!-- Secondary nav bar matching live NPM: Dashboard, Hosts(dropdown), Access Lists, Certificates, Audit Logs, Settings -->
      <div class="flex items-center gap-x-5 text-sm border-b border-zinc-800 pb-2 mb-6 -mx-1 px-1 text-zinc-300">
        <button onclick={() => switchTab('dashboard')} class="{currentTab === 'dashboard' ? 'text-emerald-400 font-medium' : 'hover:text-white'}">Dashboard</button>
        <!-- Hosts dropdown -->
        <div class="relative">
          <button
            onclick={() => showHostsMenu = !showHostsMenu}
            class="flex items-center gap-0.5 {['proxy','redir','streams','dead'].includes(currentTab) ? 'text-emerald-400 font-medium' : 'hover:text-white'}"
            aria-haspopup="menu"
            aria-expanded={showHostsMenu}
          >
            Hosts <span class="text-xs">▾</span>
          </button>
          {#if showHostsMenu}
            <div class="absolute left-0 mt-1 w-44 bg-zinc-900 border border-zinc-700 rounded shadow-xl z-50 py-1 text-xs">
              <button onclick={() => { switchTab('proxy'); }} class="block w-full text-left px-3 py-1 hover:bg-zinc-800 {currentTab==='proxy' ? 'text-emerald-400' : ''}">Proxy Hosts</button>
              <button onclick={() => { switchTab('redir'); }} class="block w-full text-left px-3 py-1 hover:bg-zinc-800 {currentTab==='redir' ? 'text-emerald-400' : ''}">Redirection Hosts</button>
              <button onclick={() => { switchTab('streams'); }} class="block w-full text-left px-3 py-1 hover:bg-zinc-800 {currentTab==='streams' ? 'text-emerald-400' : ''}">Streams</button>
              <button onclick={() => { switchTab('dead'); }} class="block w-full text-left px-3 py-1 hover:bg-zinc-800 {currentTab==='dead' ? 'text-emerald-400' : ''}">404 Hosts</button>
            </div>
          {/if}
        </div>
        <button onclick={() => switchTab('access')} class="{currentTab === 'access' ? 'text-emerald-400 font-medium' : 'hover:text-white'}">Access Lists</button>
        <button onclick={() => switchTab('certs')} class="{currentTab === 'certs' ? 'text-emerald-400 font-medium' : 'hover:text-white'}">Certificates</button>
        <button onclick={() => switchTab('audit')} class="{currentTab === 'audit' ? 'text-emerald-400 font-medium' : 'hover:text-white'}">Audit Logs</button>
        <button onclick={() => switchTab('settings')} class="{currentTab === 'settings' ? 'text-emerald-400 font-medium' : 'hover:text-white'}">Settings</button>
      </div>
      {/if}

      {#if loading}<div class="text-xs text-zinc-500 mb-2">Loading...</div>{/if}
      <FlashBanner bind:message={error} variant="error" class="mb-4" />
      <FlashBanner bind:message={success} variant="success" class="mb-4" />

      {#if mustChangePassword || showChangePw}
        <div class="max-w-lg mx-auto mb-6 p-5 bg-zinc-900 border border-emerald-600 rounded-xl">
          <div class="font-medium mb-1">{mustChangePassword ? 'First login: set a new admin password' : 'Change Admin Password'}</div>
          <p class="text-xs text-zinc-400 mb-3">{mustChangePassword ? 'The default "password" must be changed before using the manager.' : 'Enter your current password and choose a new one.'}</p>
          <form onsubmit={changeAdminPassword} class="space-y-2 text-sm">
            {#if !mustChangePassword}
              <input bind:value={pwForm.current} type="password" placeholder="Current password" title="Your current admin password" class="w-full bg-zinc-950 border border-zinc-700 rounded px-3 py-1.5" />
            {/if}
            <input bind:value={pwForm.new} type="password" placeholder="New password (min. 4 characters)" title="At least 4 characters; cannot be the default password" class="w-full bg-zinc-950 border border-zinc-700 rounded px-3 py-1.5" />
            <input bind:value={pwForm.confirm} type="password" placeholder="Confirm new password" class="w-full bg-zinc-950 border border-zinc-700 rounded px-3 py-1.5" />
            <div class="flex gap-2">
              <button type="submit" disabled={changingPw} class="bg-emerald-600 hover:bg-emerald-500 px-4 py-1 rounded text-sm flex-1">{changingPw ? 'Saving...' : (mustChangePassword ? 'Set Password & Enter' : 'Update Password')}</button>
              {#if !mustChangePassword}
                <button type="button" onclick={() => { showChangePw = false; pwForm = {current:'',new:'',confirm:''}; }} class="px-3 py-1 rounded border border-zinc-700 text-xs">Cancel</button>
              {/if}
            </div>
          </form>
        </div>
      {/if}

      <!-- DASHBOARD -->
      {#if currentTab === 'dashboard'}
        <div class="mb-2 font-semibold">Dashboard</div>
        <div class="grid grid-cols-2 md:grid-cols-4 gap-4">
          <button type="button" onclick={() => switchTab('proxy')} class="bg-zinc-900 border border-zinc-800 hover:border-emerald-700 rounded-xl p-4 flex gap-3 items-start cursor-pointer text-left w-full focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-emerald-500">
            <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 text-emerald-400 mt-0.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" /></svg>
            <div>
              <div class="text-3xl font-semibold">{dashboard.proxy_hosts ?? 0}</div>
              <div class="text-xs text-zinc-400">Proxy Hosts</div>
            </div>
          </button>
          <button type="button" onclick={() => switchTab('redir')} class="bg-zinc-900 border border-zinc-800 hover:border-emerald-700 rounded-xl p-4 flex gap-3 items-start cursor-pointer text-left w-full focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-emerald-500">
            <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 text-emerald-400 mt-0.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" /></svg>
            <div>
              <div class="text-3xl font-semibold">{dashboard.redirection_hosts ?? 0}</div>
              <div class="text-xs text-zinc-400">Redirection Hosts</div>
            </div>
          </button>
          <button type="button" onclick={() => switchTab('streams')} class="bg-zinc-900 border border-zinc-800 hover:border-emerald-700 rounded-xl p-4 flex gap-3 items-start cursor-pointer text-left w-full focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-emerald-500">
            <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 text-emerald-400 mt-0.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" /></svg>
            <div>
              <div class="text-3xl font-semibold">{dashboard.streams ?? 0}</div>
              <div class="text-xs text-zinc-400">Streams</div>
            </div>
          </button>
          <button type="button" onclick={() => switchTab('dead')} class="bg-zinc-900 border border-zinc-800 hover:border-emerald-700 rounded-xl p-4 flex gap-3 items-start cursor-pointer text-left w-full focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-emerald-500">
            <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 text-emerald-400 mt-0.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>
            <div>
              <div class="text-3xl font-semibold">{dashboard.dead_hosts ?? 0}</div>
              <div class="text-xs text-zinc-400">404 Hosts</div>
            </div>
          </button>
        </div>
        <!-- note moved to settings per spec -->
      {/if}

      <!-- PROXY HOSTS -->
      {#if currentTab === 'proxy'}
        <!-- per-page header with search, refresh icon, Add button matching live -->
        <div class="flex items-center justify-between mb-3">
          <div class="font-semibold text-base">Proxy Hosts</div>
          <div class="flex items-center gap-2">
            <div class="relative">
              <input bind:value={searchProxy} placeholder="Search..." class="bg-zinc-950 border border-zinc-700 rounded pl-8 pr-2 py-1 text-xs w-40 focus:outline-none focus:border-emerald-600" />
              <svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5 absolute left-2 top-1.5 text-zinc-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>
            </div>
            <button title="Refresh" onclick={() => safeCall(async () => { await loadProxyHosts(); await loadCerts(); await loadAccess(); await loadDashboard(); })} class="p-1 text-zinc-400 hover:text-white bg-zinc-800 rounded">
              <svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.058 11H1M12 3v2m0 16v2m9-9H15m-6 0a8 8 0 01-.937-1.5" /></svg>
            </button>
            <button onclick={openNewProxy} class="text-xs px-3 py-1 bg-emerald-600 hover:bg-emerald-500 rounded flex items-center gap-1">
              <svg xmlns="http://www.w3.org/2000/svg" class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" /></svg>
              Add Proxy Host
            </button>
          </div>
        </div>

        <div class="bg-zinc-900 border border-zinc-800 rounded-xl overflow-hidden mb-6">
          <table class="w-full text-sm">
            <thead class="bg-zinc-950 text-xs text-zinc-400">
              <tr>
                <th class="text-left p-3 font-normal">Owner</th>
                <th class="text-left p-3 font-normal">Source ↓</th>
                <th class="text-left p-3 font-normal">Destination</th>
                <th class="text-left p-3 font-normal">SSL</th>
                <th class="text-left p-3 font-normal">Access</th>
                <th class="p-3 font-normal">Status</th>
                <th class="p-3 w-8"></th>
              </tr>
            </thead>
            <tbody class="divide-y divide-zinc-800">
              {#each filteredProxy as h}
                <tr class="hover:bg-zinc-950/50">
                  <td class="p-3">
                    <div class="flex items-center gap-2 text-xs">
                      <div class="w-5 h-5 bg-emerald-600 text-white rounded-full text-[10px] flex items-center justify-center shrink-0">{owner.initial}</div>
                      <span>{owner.label}</span>
                    </div>
                  </td>
                  <td class="p-3 text-xs">
                    <button onclick={() => editHostAndOpen(h)} class="text-emerald-400 hover:underline font-mono">{(h.domainNames || []).join(', ') || '-'}</button>
                    <div class="text-[10px] text-zinc-500">Created: {h.createdOn || '—'}</div>
                  </td>
                  <td class="p-3 text-xs font-mono text-zinc-300">{h.forwardScheme}://{h.forwardHost}:{h.forwardPort}</td>
                  <td class="p-3 text-xs">
                    {sslCertLabel(h.certificateId, certificates, h.sslForced)}
                  </td>
                  <td class="p-3 text-xs">{h.accessListId ? accessLabelById(h.accessListId, accessLists) : 'Public'}</td>
                  <td class="p-3"><span class="text-[10px] px-2 py-0.5 rounded-full {h.enabled ? 'bg-emerald-900 text-emerald-400' : 'bg-zinc-700 text-zinc-400'}">{h.enabled ? 'Online' : 'Offline'}</span></td>
                  <td class="p-3 text-right">
                    <button title="Edit" onclick={() => editHostAndOpen(h)} class="text-zinc-400 hover:text-white p-0.5"><svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" /></svg></button>
                    <button title="{h.enabled ? 'Disable' : 'Enable'}" onclick={() => toggleHost(h.id, !h.enabled)} class="text-zinc-400 hover:text-white p-0.5 ml-0.5 text-[10px]">{h.enabled ? '⏻' : '⏻'}</button>
                    <button title="Delete" onclick={() => showConfirm(`Delete proxy host for ${(h.domainNames||[]).join(', ') || h.id}?`, () => deleteHost(h.id))} class="text-red-400 hover:text-red-300 p-0.5 ml-0.5">×</button>
                  </td>
                </tr>
              {:else}
                <tr><td colspan="7" class="p-0"><div class="py-12 text-center">
                  <div class="text-lg">There are no Proxy Hosts</div>
                  <div class="text-sm text-zinc-400">Why don't you create one?</div>
                  <button onclick={openNewProxy} class="mt-3 px-3 py-1 bg-emerald-600 hover:bg-emerald-500 text-sm rounded">Add Proxy Host</button>
                </div></td></tr>
              {/each}
            </tbody>
          </table>
        </div>
      {/if}

      <!-- CERTIFICATES -->
      {#if currentTab === 'certs'}
        <div class="flex items-center justify-between mb-3">
          <div class="font-semibold text-base">Certificates</div>
          <div class="flex items-center gap-2">
            <div class="relative">
              <input bind:value={searchCerts} placeholder="Search..." class="bg-zinc-950 border border-zinc-700 rounded pl-8 pr-2 py-1 text-xs w-40 focus:outline-none focus:border-emerald-600" />
              <svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5 absolute left-2 top-1.5 text-zinc-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>
            </div>
            <button title="Refresh" onclick={() => safeCall(async () => { await loadCerts(); await loadProxyHosts(); await loadStreams(); })} class="p-1 text-zinc-400 hover:text-white bg-zinc-800 rounded">
              <svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.058 11H1M12 3v2m0 16v2m9-9H15m-6 0a8 8 0 01-.937-1.5" /></svg>
            </button>
            <button onclick={openNewCert} class="text-xs px-3 py-1 bg-emerald-600 hover:bg-emerald-500 rounded flex items-center gap-1">
              <svg xmlns="http://www.w3.org/2000/svg" class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" /></svg>
              Add Certificate
            </button>
            <button onclick={openNewCustomCert} class="text-xs px-2 py-1 bg-emerald-700 hover:bg-emerald-600 rounded">Upload Custom</button>
          </div>
        </div>
        <div class="bg-zinc-900 border border-zinc-800 rounded-xl overflow-hidden mb-4 text-sm">
          <table class="w-full">
            <thead class="bg-zinc-950"><tr class="text-xs text-zinc-400"><th class="text-left p-3 font-normal">Owner</th><th class="text-left p-3 font-normal">Name ↓</th><th class="text-left p-3 font-normal">Provider</th><th class="p-3 font-normal">Expires</th><th class="p-3 font-normal">Status</th><th class="p-3"></th></tr></thead>
            <tbody class="text-xs divide-y divide-zinc-800">
              {#each filteredCerts as c}
                <tr class="hover:bg-zinc-950/50">
                  <td class="p-3">
                    <div class="flex items-center gap-2 text-xs">
                      <div class="w-5 h-5 bg-emerald-600 text-white rounded-full text-[10px] flex items-center justify-center shrink-0">{owner.initial}</div>
                      <span>{owner.label}</span>
                    </div>
                  </td>
                  <td class="p-3 font-mono">{certLabel(c)}<div class="text-[10px] text-zinc-500">Created: {c.createdOn || '—'}</div></td>
                  <td class="p-3">{providerLabel(c.provider)}</td>
                  <td class="p-3">{c.expiresOn || '-'}</td>
                  <td class="p-3"><span class="text-[10px] px-2 py-0.5 rounded-full {isCertInUse(c.id) ? 'bg-emerald-900 text-emerald-400' : 'bg-zinc-700 text-zinc-400'}">{isCertInUse(c.id) ? 'In Use' : 'Available'}</span></td>
                  <td class="p-3 text-right">
                    <button title="Renew" onclick={() => renewCert(c.id)} class="text-zinc-400 hover:text-white p-0.5">
                      <!-- renew icon (refresh arrows) to match icon-button style of other lists (no more text "renew") -->
                      <svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.058 11H1M12 3v2m0 16v2m9-9H15m-6 0a8 8 0 01-.937-1.5" /></svg>
                    </button>
                    <button title="Delete" onclick={() => showConfirm(`Delete certificate "${certLabel(c)}"? (hosts using it will lose SSL)`, () => deleteCert(c.id))} class="text-red-400 hover:text-red-300 p-0.5 ml-0.5">×</button>
                  </td>
                </tr>
              {:else}
                <tr><td colspan="6" class="p-0"><div class="py-12 text-center">
                  <div class="text-lg">There are no Certificates</div>
                  <div class="text-sm text-zinc-400">Why don't you create one?</div>
                  <button onclick={openNewCert} class="mt-3 px-3 py-1 bg-emerald-600 hover:bg-emerald-500 text-sm rounded">Add Certificate</button>
                </div></td></tr>
              {/each}
            </tbody>
          </table>
        </div>

      {/if}

      <!-- ACCESS LISTS -->
      {#if currentTab === 'access'}
        <div class="flex items-center justify-between mb-3">
          <div class="font-semibold text-base">Access Lists</div>
          <div class="flex items-center gap-2">
            <div class="relative">
              <input bind:value={searchAccess} placeholder="Search..." class="bg-zinc-950 border border-zinc-700 rounded pl-8 pr-2 py-1 text-xs w-40 focus:outline-none focus:border-emerald-600" />
              <svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5 absolute left-2 top-1.5 text-zinc-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>
            </div>
            <button title="Refresh" onclick={() => safeCall(async () => { await loadAccess(); await loadProxyHosts(); })} class="p-1 text-zinc-400 hover:text-white bg-zinc-800 rounded">
              <svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.058 11H1M12 3v2m0 16v2m9-9H15m-6 0a8 8 0 01-.937-1.5" /></svg>
            </button>
            <button onclick={openNewAccess} class="text-xs px-3 py-1 bg-emerald-600 hover:bg-emerald-500 rounded flex items-center gap-1">
              <svg xmlns="http://www.w3.org/2000/svg" class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" /></svg>
              Add Access List
            </button>
          </div>
        </div>
        <div class="bg-zinc-900 border border-zinc-800 rounded-xl overflow-hidden mb-4 text-sm">
          <table class="w-full">
            <thead class="bg-zinc-950"><tr class="text-xs text-zinc-400"><th class="text-left p-3 font-normal">Owner</th><th class="text-left p-3 font-normal">Name ↓</th><th class="p-3 font-normal">Authorization</th><th class="p-3 font-normal">Access</th><th class="p-3 font-normal">Satisfy</th><th class="p-3 font-normal">Proxy Hosts</th><th class="p-3"></th></tr></thead>
            <tbody class="text-xs divide-y divide-zinc-800">
              {#each filteredAccess as al}
                <tr class="hover:bg-zinc-950/50">
                  <td class="p-3">
                    <div class="flex items-center gap-2 text-xs">
                      <div class="w-5 h-5 bg-emerald-600 text-white rounded-full text-[10px] flex items-center justify-center shrink-0">{owner.initial}</div>
                      <span>{owner.label}</span>
                    </div>
                  </td>
                  <td class="p-3 font-mono">{al.name || '-'}<div class="text-[10px] text-zinc-500">Created: {al.createdOn || '—'}</div></td>
                  <td class="p-3">{(al.items || []).length} User</td>
                  <td class="p-3">{(al.clients || []).length} Rule</td>
                  <td class="p-3">{al.satisfyAny ? 'Any' : 'All'}</td>
                  <td class="p-3">{proxyHosts.filter((p:any)=>p.accessListId === al.id).length}</td>
                  <td class="p-3 text-right space-x-1">
                    <button title="Edit" onclick={() => editAccessAndOpen(al)} class="text-zinc-400 hover:text-white p-0.5"><svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5 inline" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" /></svg></button>
                    <button title="Delete" onclick={() => showConfirm(`Delete access list "${al.name}"?`, () => deleteAccess(al.id))} class="text-red-400 hover:text-red-300 p-0.5">×</button>
                  </td>
                </tr>
              {:else}
                <tr><td colspan="7" class="p-0"><div class="py-12 text-center">
                  <div class="text-lg">There are no Access Lists</div>
                  <div class="text-sm text-zinc-400">Why don't you create one?</div>
                  <button onclick={openNewAccess} class="mt-3 px-3 py-1 bg-emerald-600 hover:bg-emerald-500 text-sm rounded">Add Access List</button>
                </div></td></tr>
              {/each}
            </tbody>
          </table>
        </div>
      {/if}

      <!-- STREAMS (dedicated tab, full table + modal CRUD) -->
      {#if currentTab === 'streams'}
        <div class="flex items-center justify-between mb-3">
          <div class="font-semibold text-base">Streams</div>
          <div class="flex items-center gap-2">
            <div class="relative">
              <input bind:value={searchStreams} placeholder="Search..." class="bg-zinc-950 border border-zinc-700 rounded pl-8 pr-2 py-1 text-xs w-40 focus:outline-none focus:border-emerald-600" />
              <svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5 absolute left-2 top-1.5 text-zinc-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>
            </div>
            <button title="Refresh" onclick={() => safeCall(async () => { await loadStreams(); await loadCerts(); })} class="p-1 text-zinc-400 hover:text-white bg-zinc-800 rounded">
              <svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.058 11H1M12 3v2m0 16v2m9-9H15m-6 0a8 8 0 01-.937-1.5" /></svg>
            </button>
            <button onclick={openNewStream} class="text-xs px-3 py-1 bg-emerald-600 hover:bg-emerald-500 rounded flex items-center gap-1">
              <svg xmlns="http://www.w3.org/2000/svg" class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" /></svg>
              Add Stream
            </button>
          </div>
        </div>
        <div class="bg-zinc-900 border border-zinc-800 rounded-xl overflow-hidden mb-6">
          <table class="w-full text-sm">
            <thead class="bg-zinc-950 text-xs text-zinc-400">
              <tr>
                <th class="text-left p-3 font-normal">Owner</th>
                <th class="text-left p-3 font-normal">Incoming ↓</th>
                <th class="text-left p-3 font-normal">Forward</th>
                <th class="p-3 font-normal">SSL</th>
                <th class="p-3 font-normal">Status</th>
                <th class="p-3 w-8"></th>
              </tr>
            </thead>
            <tbody class="divide-y divide-zinc-800">
              {#each filteredStreams as s}
                {@const status = streamStatusDisplay(s)}
                <tr class="hover:bg-zinc-950/50">
                  <td class="p-3">
                    <div class="flex items-center gap-2 text-xs">
                      <div class="w-5 h-5 bg-emerald-600 text-white rounded-full text-[10px] flex items-center justify-center shrink-0">{owner.initial}</div>
                      <span>{owner.label}</span>
                    </div>
                  </td>
                  <td class="p-3 text-xs font-mono">{s.incomingPort}<div class="text-[10px] text-zinc-500">Created: {s.createdOn || '—'}</div></td>
                  <td class="p-3 text-xs font-mono">{s.forwardingHost}:{s.forwardingPort}</td>
                  <td class="p-3 text-xs">{sslCertLabel(s.certificateId, certificates)} {s.tcpForwarding ? 'TCP' : ''}{s.udpForwarding ? ' UDP' : ''}</td>
                  <td class="p-3"><span class="text-[10px] px-2 py-0.5 rounded-full {status.className}" title={status.title}>{status.label}</span></td>
                  <td class="p-3 text-right">
                    <button title="Edit" onclick={() => editStreamAndOpen(s)} class="text-zinc-400 hover:text-white p-0.5"><svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" /></svg></button>
                    <button title="{s.enabled ? 'Disable' : 'Enable'}" onclick={() => toggleStream(s.id, !s.enabled)} class="text-zinc-400 hover:text-white p-0.5 ml-0.5 text-[10px]">⏻</button>
                    <button title="Delete" onclick={() => showConfirm(`Delete stream ${streamLabel(s)}?`, () => deleteStream(s.id))} class="text-red-400 hover:text-red-300 p-0.5 ml-0.5">×</button>
                  </td>
                </tr>
              {:else}
                <tr><td colspan="6" class="p-0"><div class="py-12 text-center">
                  <div class="text-lg">There are no Streams</div>
                  <div class="text-sm text-zinc-400">Why don't you create one?</div>
                  <button onclick={openNewStream} class="mt-3 px-3 py-1 bg-emerald-600 hover:bg-emerald-500 text-sm rounded">Add Stream</button>
                </div></td></tr>
              {/each}
            </tbody>
          </table>
        </div>
      {/if}

      <!-- REDIRECTION HOSTS (dedicated tab) -->
      {#if currentTab === 'redir'}
        <div class="flex items-center justify-between mb-3">
          <div class="font-semibold text-base">Redirection Hosts</div>
          <div class="flex items-center gap-2">
            <div class="relative">
              <input bind:value={searchRedir} placeholder="Search..." class="bg-zinc-950 border border-zinc-700 rounded pl-8 pr-2 py-1 text-xs w-40 focus:outline-none focus:border-emerald-600" />
              <svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5 absolute left-2 top-1.5 text-zinc-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>
            </div>
            <button title="Refresh" onclick={() => safeCall(async () => { await loadRedirs(); await loadDashboard(); })} class="p-1 text-zinc-400 hover:text-white bg-zinc-800 rounded">
              <svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.058 11H1M12 3v2m0 16v2m9-9H15m-6 0a8 8 0 01-.937-1.5" /></svg>
            </button>
            <button onclick={openNewRedir} class="text-xs px-3 py-1 bg-emerald-600 hover:bg-emerald-500 rounded flex items-center gap-1">
              <svg xmlns="http://www.w3.org/2000/svg" class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" /></svg>
              Add Redirection Host
            </button>
          </div>
        </div>
        <div class="bg-zinc-900 border border-zinc-800 rounded-xl overflow-hidden mb-6">
          <table class="w-full text-sm">
            <thead class="bg-zinc-950 text-xs text-zinc-400">
              <tr>
                <th class="text-left p-3 font-normal">Owner</th>
                <th class="text-left p-3 font-normal">Source ↓</th>
                <th class="text-left p-3 font-normal">Destination</th>
                <th class="p-3 font-normal">Status</th>
                <th class="p-3 w-8"></th>
              </tr>
            </thead>
            <tbody class="divide-y divide-zinc-800">
              {#each filteredRedirs as r}
                <tr class="hover:bg-zinc-950/50">
                  <td class="p-3">
                    <div class="flex items-center gap-2 text-xs">
                      <div class="w-5 h-5 bg-emerald-600 text-white rounded-full text-[10px] flex items-center justify-center shrink-0">{owner.initial}</div>
                      <span>{owner.label}</span>
                    </div>
                  </td>
                  <td class="p-3 text-xs">
                    <button onclick={() => editRedirAndOpen(r)} class="text-emerald-400 hover:underline font-mono">{(r.domainNames || []).join(', ') || '-'}</button>
                    <div class="text-[10px] text-zinc-500">Created: {r.createdOn || '—'}</div>
                  </td>
                  <td class="p-3 text-xs font-mono">{r.forwardDomainName} ({r.forwardHttpCode} {r.forwardScheme})</td>
                  <td class="p-3"><span class="text-[10px] px-2 py-0.5 rounded-full {r.enabled ? 'bg-emerald-900 text-emerald-400' : 'bg-zinc-700 text-zinc-400'}">{r.enabled ? 'Online' : 'Offline'}</span></td>
                  <td class="p-3 text-right">
                    <button title="Edit" onclick={() => editRedirAndOpen(r)} class="text-zinc-400 hover:text-white p-0.5"><svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" /></svg></button>
                    <button title="{r.enabled ? 'Disable' : 'Enable'}" onclick={() => toggleRedir(r.id, !r.enabled)} class="text-zinc-400 hover:text-white p-0.5 ml-0.5 text-[10px]">⏻</button>
                    <button title="Delete" onclick={() => showConfirm(`Delete redirection ${redirLabel(r)}?`, () => deleteRedir(r.id))} class="text-red-400 hover:text-red-300 p-0.5 ml-0.5">×</button>
                  </td>
                </tr>
              {:else}
                <tr><td colspan="5" class="p-0"><div class="py-12 text-center">
                  <div class="text-lg">There are no Redirection Hosts</div>
                  <div class="text-sm text-zinc-400">Why don't you create one?</div>
                  <button onclick={openNewRedir} class="mt-3 px-3 py-1 bg-emerald-600 hover:bg-emerald-500 text-sm rounded">Add Redirection Host</button>
                </div></td></tr>
              {/each}
            </tbody>
          </table>
        </div>
      {/if}

      <!-- DEAD HOSTS (dedicated tab) -- shown as 404 Hosts to align terminology per review (vars/form/api stay 'dead'/'dead-host') -->
      {#if currentTab === 'dead'}
        <div class="flex items-center justify-between mb-3">
          <div class="font-semibold text-base">404 Hosts</div>
          <div class="flex items-center gap-2">
            <div class="relative">
              <input bind:value={searchDead} placeholder="Search..." class="bg-zinc-950 border border-zinc-700 rounded pl-8 pr-2 py-1 text-xs w-40 focus:outline-none focus:border-emerald-600" />
              <svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5 absolute left-2 top-1.5 text-zinc-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>
            </div>
            <button title="Refresh" onclick={() => safeCall(async () => { await loadDeads(); await loadDashboard(); })} class="p-1 text-zinc-400 hover:text-white bg-zinc-800 rounded">
              <svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.058 11H1M12 3v2m0 16v2m9-9H15m-6 0a8 8 0 01-.937-1.5" /></svg>
            </button>
            <button onclick={openNewDead} class="text-xs px-3 py-1 bg-emerald-600 hover:bg-emerald-500 rounded flex items-center gap-1">
              <svg xmlns="http://www.w3.org/2000/svg" class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" /></svg>
              Add 404 Host
            </button>
          </div>
        </div>
        <div class="bg-zinc-900 border border-zinc-800 rounded-xl overflow-hidden mb-6">
          <table class="w-full text-sm">
            <thead class="bg-zinc-950 text-xs text-zinc-400">
              <tr>
                <th class="text-left p-3 font-normal">Owner</th>
                <th class="text-left p-3 font-normal">Source ↓</th>
                <th class="text-left p-3 font-normal">Custom HTML</th>
                <th class="p-3 font-normal">Status</th>
                <th class="p-3 w-8"></th>
              </tr>
            </thead>
            <tbody class="divide-y divide-zinc-800">
              {#each filteredDeads as d}
                <tr class="hover:bg-zinc-950/50">
                  <td class="p-3">
                    <div class="flex items-center gap-2 text-xs">
                      <div class="w-5 h-5 bg-emerald-600 text-white rounded-full text-[10px] flex items-center justify-center shrink-0">{owner.initial}</div>
                      <span>{owner.label}</span>
                    </div>
                  </td>
                  <td class="p-3 text-xs">
                    <button onclick={() => editDeadAndOpen(d)} class="text-emerald-400 hover:underline font-mono">{(d.domainNames || []).join(', ') || '-'}</button>
                    <div class="text-[10px] text-zinc-500">Created: {d.createdOn || '—'}</div>
                  </td>
                  <td class="p-3 text-xs">{d.meta && d.meta.html ? 'yes (per-host)' : 'default 404'}</td>
                  <td class="p-3"><span class="text-[10px] px-2 py-0.5 rounded-full {d.enabled ? 'bg-emerald-900 text-emerald-400' : 'bg-zinc-700 text-zinc-400'}">{d.enabled ? 'Online' : 'Offline'}</span></td>
                  <td class="p-3 text-right">
                    <button title="Edit" onclick={() => editDeadAndOpen(d)} class="text-zinc-400 hover:text-white p-0.5"><svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" /></svg></button>
                    <button title="{d.enabled ? 'Disable' : 'Enable'}" onclick={() => toggleDead(d.id, !d.enabled)} class="text-zinc-400 hover:text-white p-0.5 ml-0.5 text-[10px]">⏻</button>
                    <button title="Delete" onclick={() => showConfirm(`Delete 404 host ${domainListLabel(d.domainNames, `#${d.id}`)}?`, () => deleteDead(d.id))} class="text-red-400 hover:text-red-300 p-0.5 ml-0.5">×</button>
                  </td>
                </tr>
              {:else}
                <tr><td colspan="5" class="p-0"><div class="py-12 text-center">
                  <div class="text-lg">There are no 404 Hosts</div>
                  <div class="text-sm text-zinc-400">Why don't you create one?</div>
                  <button onclick={openNewDead} class="mt-3 px-3 py-1 bg-emerald-600 hover:bg-emerald-500 text-sm rounded">Add 404 Host</button>
                </div></td></tr>
              {/each}
            </tbody>
          </table>
        </div>
      {/if}

      <!-- AUDIT -->
      {#if currentTab === 'audit'}
        <div class="flex items-center justify-between mb-3">
          <div class="font-semibold text-base">Audit Logs (recent first)</div>
          <div class="relative">
            <input bind:value={searchAudit} placeholder="Search..." class="bg-zinc-950 border border-zinc-700 rounded pl-8 pr-2 py-1 text-xs w-40 focus:outline-none focus:border-emerald-600" />
            <svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5 absolute left-2 top-1.5 text-zinc-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>
          </div>
        </div>
        <div class="bg-zinc-900 border border-zinc-800 rounded-xl p-3 text-xs font-mono space-y-1 max-h-[420px] overflow-auto">
          {#each filteredAudit.slice().reverse() as log}
            <div class="flex gap-2"><span class="text-zinc-500 w-40 shrink-0">{log.createdOn}</span> <span>{auditLogSummary(log, user)}</span></div>
          {:else}
            <div class="text-zinc-500">No logs yet</div>
          {/each}
        </div>
      {/if}

      <!-- SETTINGS -->
      {#if currentTab === 'settings'}
        <div class="font-semibold mb-3">Settings</div>
        <div class="bg-zinc-900 border border-zinc-800 rounded-xl p-4 text-sm max-w-md">
          <div class="mb-2" title="Behavior for requests that do not match any configured host">Default site</div>
          <select bind:value={settingsData.default_site} class="bg-zinc-950 border border-zinc-700 rounded px-3 py-2 w-full mb-2">
            <option value="congratulations">Welcome page</option>
            <option value="404">404 Not Found</option>
            <option value="444">Close connection</option>
            <option value="redirect">Redirect</option>
          </select>
          {#if settingsData.default_site === 'redirect'}
            <input bind:value={settingsData.default_site_redirect} placeholder="https://example.com" title="URL to redirect unmatched hosts to" class="bg-zinc-950 border border-zinc-700 rounded px-3 py-2 w-full mb-2" />
          {/if}
          <div class="mt-2">
            <label class="text-xs text-zinc-400 block mb-1" title="Email used when requesting new Let's Encrypt certificates">Let's Encrypt email</label>
            <input bind:value={settingsData.letsencrypt_email} placeholder="admin@example.com" title="Email used when requesting new Let's Encrypt certificates" class="bg-zinc-950 border border-zinc-700 rounded px-3 py-1 w-full mb-2 text-xs" />
          </div>
          <button onclick={async () => {
            try {
              await apiFetch('/api/settings', {method:'POST', body: JSON.stringify(settingsData)});
              success = 'Settings saved';
              await loadSettings();
            } catch (err: any) {
              error = err.message || 'Failed';
            }
          }} class="bg-emerald-600 px-4 py-1 rounded">Save Settings</button>
        </div>
      {/if}

      <!-- Modals for form CRUD (proxy/streams/redir/dead/certs/access) and confirms. Replaces inline cramped forms + confirm()/prompt(). Placed at end of logged-in app div (after tab contents) so always rendered/available when authenticated (no conditional per tab). -->
      <Modal bind:open={openFormModal} bind:error={formError} title={formModalTitle} onClose={() => closeForm()}>
        {#if formModalType === 'stream'}
          <form onsubmit={async (e) => { await createStream(e); if (!formError) closeForm(); }} class="space-y-3 text-sm">
            <div class="flex gap-2">
              <input type="number" bind:value={newStream.incomingPort} placeholder="Incoming port" title="Port this proxy listens on" class="w-28 bg-zinc-950 border border-zinc-700 rounded px-2 py-1" />
              <input bind:value={newStream.forwardingHost} placeholder="Forward host" title="Target hostname or IP" class="flex-1 bg-zinc-950 border border-zinc-700 rounded px-2 py-1" />
              <input type="number" bind:value={newStream.forwardingPort} placeholder="Port" class="w-20 bg-zinc-950 border border-zinc-700 rounded px-2 py-1" />
            </div>
            <div class="flex gap-3 items-center flex-wrap text-xs">
              <label class="flex items-center gap-1" title="Enable TCP forwarding"><input type="checkbox" bind:checked={newStream.tcpForwarding} /> TCP</label>
              <label class="flex items-center gap-1" title="Enable UDP forwarding"><input type="checkbox" bind:checked={newStream.udpForwarding} /> UDP</label>
              <select bind:value={newStream.certificateId} class="bg-zinc-950 border border-zinc-700 rounded px-2 py-1 text-xs" title="Optional SSL certificate for incoming connections">
                <option value={0}>no SSL</option>
                {#each certificates as c}<option value={c.id}>{certLabel(c)}</option>{/each}
              </select>
              <label class="flex items-center gap-1"><input type="checkbox" bind:checked={newStream.enabled} /> enabled</label>
            </div>
            <div class="flex gap-2 pt-2">
              <button type="submit" class="bg-emerald-600 hover:bg-emerald-500 px-4 py-1 rounded text-sm flex-1">Save</button>
              <button type="button" onclick={() => closeForm()} class="px-3 py-1 rounded border border-zinc-700 text-sm">Cancel</button>
            </div>
          </form>
        {:else if formModalType === 'redir'}
          <form onsubmit={async (e) => { await createRedir(e); if (!formError) closeForm(); }} class="space-y-3 text-sm">
            <input bind:value={newRedir.domainNames} placeholder="Source domains, comma separated" class="w-full bg-zinc-950 border border-zinc-700 rounded px-2 py-1" />
            <input bind:value={newRedir.forwardDomainName} placeholder="Target domain" class="w-full bg-zinc-950 border border-zinc-700 rounded px-2 py-1" />
            <div class="flex gap-2">
              <input type="number" bind:value={newRedir.forwardHttpCode} placeholder="301" title="HTTP redirect status code" class="w-20 bg-zinc-950 border border-zinc-700 rounded px-2 py-1" />
              <select bind:value={newRedir.forwardScheme} class="flex-1 bg-zinc-950 border border-zinc-700 rounded px-2 py-1">
                <option value="https">https</option><option value="http">http</option>
              </select>
              <label class="flex items-center text-xs gap-1 px-2" title="Append the original request path to the redirect target"><input type="checkbox" bind:checked={newRedir.preservePath} /> Preserve path</label>
            </div>
            <div class="flex items-center gap-2 text-xs">
              <label class="flex items-center gap-1"><input type="checkbox" bind:checked={newRedir.enabled} /> enabled</label>
            </div>
            <div class="flex gap-2 pt-2">
              <button type="submit" class="bg-emerald-600 hover:bg-emerald-500 px-4 py-1 rounded text-sm flex-1">Save</button>
              <button type="button" onclick={() => closeForm()} class="px-3 py-1 rounded border border-zinc-700 text-sm">Cancel</button>
            </div>
          </form>
        {:else if formModalType === 'dead'}
          <form onsubmit={async (e) => { await createDead(e); if (!formError) closeForm(); }} class="space-y-3 text-sm">
            <input bind:value={newDead.domainNames} placeholder="Source domains, comma separated" class="w-full bg-zinc-950 border border-zinc-700 rounded px-2 py-1" />
            <textarea bind:value={newDead.customHtml} placeholder="Custom 404 HTML (optional)" title="Leave empty to use the default 404 page" class="w-full h-20 bg-zinc-950 border border-zinc-700 rounded px-2 py-1 text-xs font-mono"></textarea>
            <div class="flex items-center gap-2 text-xs">
              <label class="flex items-center gap-1"><input type="checkbox" bind:checked={newDead.enabled} /> enabled</label>
            </div>
            <div class="flex gap-2 pt-2">
              <button type="submit" class="bg-emerald-600 hover:bg-emerald-500 px-4 py-1 rounded text-sm flex-1">Save</button>
              <button type="button" onclick={() => closeForm()} class="px-3 py-1 rounded border border-zinc-700 text-sm">Cancel</button>
            </div>
          </form>
        {:else if formModalType === 'proxy'}
          <form onsubmit={async (e) => { await createProxy(e); if (!formError) closeForm(); }} class="space-y-3 text-sm">
            <!-- internal tabs: Details / Custom Locations / SSL / Headers -->
            <div class="flex border-b border-zinc-700 -mx-1 text-xs mb-2">
              <button type="button" onclick={() => proxyFormTab='details'} class="px-3 py-1 {proxyFormTab==='details' ? 'border-b-2 border-emerald-500 text-emerald-400 font-medium' : 'text-zinc-400 hover:text-zinc-200'}">Details</button>
              <button type="button" onclick={() => proxyFormTab='locations'} class="px-3 py-1 {proxyFormTab==='locations' ? 'border-b-2 border-emerald-500 text-emerald-400 font-medium' : 'text-zinc-400 hover:text-zinc-200'}">Custom Locations</button>
              <button type="button" onclick={() => proxyFormTab='ssl'} class="px-3 py-1 {proxyFormTab==='ssl' ? 'border-b-2 border-emerald-500 text-emerald-400 font-medium' : 'text-zinc-400 hover:text-zinc-200'}">SSL</button>
              <button type="button" onclick={() => proxyFormTab='headers'} class="px-3 py-1 {proxyFormTab==='headers' ? 'border-b-2 border-emerald-500 text-emerald-400 font-medium' : 'text-zinc-400 hover:text-zinc-200'}">Headers</button>
            </div>

            {#if proxyFormTab === 'details'}
              <input bind:value={newProxy.domainNames} placeholder="Domain names, comma separated" class="w-full bg-zinc-950 border border-zinc-700 rounded px-2 py-1" />
              <div class="flex gap-2">
                <select bind:value={newProxy.forwardScheme} class="bg-zinc-950 border border-zinc-700 rounded px-2 py-1 flex-1 text-sm" title="Upstream scheme">
                  <option value="http">http</option><option value="https">https</option>
                </select>
                <input bind:value={newProxy.forwardHost} placeholder="Forward hostname or IP" class="bg-zinc-950 border border-zinc-700 rounded px-2 py-1 flex-1" />
                <input type="number" bind:value={newProxy.forwardPort} placeholder="Port" class="bg-zinc-950 border border-zinc-700 rounded px-2 py-1 w-20" />
              </div>
              <div>
                <select bind:value={newProxy.accessListId} class="bg-zinc-950 border border-zinc-700 rounded px-2 py-1 text-xs w-full" title="Restrict access to selected clients and credentials">
                  <option value={0}>Public</option>
                  {#each accessLists as al}
                    <option value={al.id}>{al.name}</option>
                  {/each}
                </select>
              </div>
              <div class="mt-3 pt-3 border-t border-zinc-800">
                <label class="flex items-center gap-2 text-xs">
                  <input type="checkbox" bind:checked={newProxy.enabled} />
                  <span class="font-medium">Enabled</span>
                </label>
              </div>
              <div class="mt-2">
                <div class="text-xs font-medium mb-1">Options</div>
                <div class="flex items-center gap-3 text-xs flex-wrap">
                  <label class="flex items-center gap-1" title="Cache static assets"><input type="checkbox" bind:checked={newProxy.cachingEnabled} /> Cache assets</label>
                  <label class="flex items-center gap-1" title="Block common exploit patterns"><input type="checkbox" bind:checked={newProxy.blockExploits} /> Block exploits</label>
                  <label class="flex items-center gap-1" title="Allow websocket upgrade requests"><input type="checkbox" bind:checked={newProxy.allowWebsocketUpgrade} /> Websockets</label>
                  <label class="flex items-center gap-1" title="Trust X-Forwarded-Proto from upstream"><input type="checkbox" bind:checked={newProxy.trustForwardedProto} /> Trust forwarded proto</label>
                  <label class="flex items-center gap-1" title="Enable HTTP/2 to upstream"><input type="checkbox" bind:checked={newProxy.http2Support} /> HTTP/2</label>
                </div>
              </div>
            {:else if proxyFormTab === 'locations'}
              <div class="space-y-2">
                <div class="text-xs flex items-center gap-2">
                  Custom locations
                  <button type="button" title="Add location" onclick={() => { newProxy.locations = [...(newProxy.locations || []), {path: '', forwardScheme: 'http', forwardHost: '', forwardPort: 80, forwardPath: ''}]; }} class="text-emerald-400 text-[10px] underline">+ add</button>
                </div>
                {#each (newProxy.locations as any[]) as loc, i}
                  <div class="border border-zinc-800 rounded p-2 space-y-1.5 text-xs">
                    <input bind:value={loc.path} placeholder="Path (/api)" title="URL path prefix for this location" class="w-full min-w-0 bg-zinc-950 border border-zinc-700 rounded px-2 py-1" />
                    <div class="flex gap-1.5">
                      <select bind:value={loc.forwardScheme} title="Upstream scheme" class="shrink-0 bg-zinc-950 border border-zinc-700 rounded px-1 py-1">
                        <option value="http">http</option><option value="https">https</option>
                      </select>
                      <input bind:value={loc.forwardHost} placeholder="Forward host" title="Upstream hostname or IP" class="flex-1 min-w-0 bg-zinc-950 border border-zinc-700 rounded px-2 py-1" />
                      <input type="number" bind:value={loc.forwardPort} placeholder="Port" title="Upstream port" class="w-16 shrink-0 bg-zinc-950 border border-zinc-700 rounded px-2 py-1" />
                    </div>
                    <div class="flex gap-1.5">
                      <input bind:value={loc.forwardPath} placeholder="Rewrite path (optional)" title="Replace matched path prefix with this value" class="flex-1 min-w-0 bg-zinc-950 border border-zinc-700 rounded px-2 py-1" />
                      <button type="button" title="Remove location" onclick={() => { newProxy.locations = (newProxy.locations || []).filter((_,j) => j !== i); }} class="shrink-0 text-red-400 px-2">×</button>
                    </div>
                  </div>
                {/each}
              </div>
            {:else if proxyFormTab === 'ssl'}
              <select bind:value={newProxy.certificateId} class="bg-zinc-950 border border-zinc-700 rounded px-2 py-1 text-xs w-full" title="SSL certificate for these domains">
                <option value={0}>No certificate</option>
                <option value={-1} title="Request a new Let's Encrypt certificate for the domains on this host">Request new Let's Encrypt certificate</option>
                {#each certificates as c}
                  <option value={c.id}>{certLabel(c)}</option>
                {/each}
              </select>
              <div class="flex items-center gap-3 text-xs flex-wrap mt-2">
                <label class="flex items-center gap-1" title="Redirect HTTP to HTTPS"><input type="checkbox" bind:checked={newProxy.sslForced} /> Force SSL</label>
                <label class="flex items-center gap-1" title="Send Strict-Transport-Security header"><input type="checkbox" bind:checked={newProxy.hstsEnabled} /> HSTS</label>
                <label class="flex items-center gap-1" title="Include subdomains in HSTS"><input type="checkbox" bind:checked={newProxy.hstsSubdomains} /> HSTS subdomains</label>
              </div>
            {:else if proxyFormTab === 'headers'}
              <div class="space-y-3">
                <div>
                  <div class="text-xs mb-1 flex items-center gap-2">
                    <span title="Headers sent to the upstream server">Request headers</span>
                    <button type="button" title="Add request header" onclick={() => { newProxy.requestHeaders = [...(newProxy.requestHeaders || []), { name: '', value: '' }]; }} class="text-emerald-400 text-[10px] underline">+ add</button>
                  </div>
                  {#each (newProxy.requestHeaders || []) as hdr, i}
                    <div class="grid grid-cols-[1fr_1fr_auto] gap-1 mb-1 text-xs">
                      <input bind:value={hdr.name} placeholder="Header name" class="bg-zinc-950 border border-zinc-700 rounded px-1 py-0.5" />
                      <input bind:value={hdr.value} placeholder="Value" class="bg-zinc-950 border border-zinc-700 rounded px-1 py-0.5" />
                      <button type="button" title="Remove header" onclick={() => { newProxy.requestHeaders = (newProxy.requestHeaders || []).filter((_, j) => j !== i); }} class="text-red-400 px-1">×</button>
                    </div>
                  {/each}
                </div>
                <div>
                  <div class="text-xs mb-1 flex items-center gap-2">
                    <span title="Headers added to responses sent to clients">Response headers</span>
                    <button type="button" title="Add response header" onclick={() => { newProxy.responseHeaders = [...(newProxy.responseHeaders || []), { name: '', value: '' }]; }} class="text-emerald-400 text-[10px] underline">+ add</button>
                  </div>
                  {#each (newProxy.responseHeaders || []) as hdr, i}
                    <div class="grid grid-cols-[1fr_1fr_auto] gap-1 mb-1 text-xs">
                      <input bind:value={hdr.name} placeholder="Header name" class="bg-zinc-950 border border-zinc-700 rounded px-1 py-0.5" />
                      <input bind:value={hdr.value} placeholder="Value" class="bg-zinc-950 border border-zinc-700 rounded px-1 py-0.5" />
                      <button type="button" title="Remove header" onclick={() => { newProxy.responseHeaders = (newProxy.responseHeaders || []).filter((_, j) => j !== i); }} class="text-red-400 px-1">×</button>
                    </div>
                  {/each}
                </div>
                <div>
                  <div class="text-xs mb-1 flex items-center gap-2">
                    <span title="Upstream response headers to remove before sending to clients">Hide headers</span>
                    <button type="button" title="Add hidden header" onclick={() => { newProxy.hideHeaders = [...(newProxy.hideHeaders || []), '']; }} class="text-emerald-400 text-[10px] underline">+ add</button>
                  </div>
                  {#each (newProxy.hideHeaders || []) as name, i}
                    <div class="grid grid-cols-[1fr_auto] gap-1 mb-1 text-xs">
                      <input bind:value={newProxy.hideHeaders[i]} placeholder="Header name" class="bg-zinc-950 border border-zinc-700 rounded px-1 py-0.5" />
                      <button type="button" title="Remove" onclick={() => { newProxy.hideHeaders = (newProxy.hideHeaders || []).filter((_, j) => j !== i); }} class="text-red-400 px-1">×</button>
                    </div>
                  {/each}
                </div>
              </div>
            {/if}

            <div class="flex gap-2 pt-2">
              <button type="submit" class="bg-emerald-600 hover:bg-emerald-500 px-4 py-1 rounded text-sm flex-1">Save</button>
              <button type="button" onclick={() => closeForm()} class="px-3 py-1 rounded border border-zinc-700 text-sm">Cancel</button>
            </div>
          </form>
        {:else if formModalType === 'cert'}
          <form onsubmit={async (e) => { await createCert(e); if (!formError) closeForm(); }} class="space-y-3 text-sm">
            <input bind:value={newCert.domainNames} placeholder="example.com, www.example.com" title="Domains must resolve publicly for Let's Encrypt validation" class="w-full bg-zinc-950 border border-zinc-700 rounded px-2 py-1" />
            <div class="flex gap-2 pt-2">
              <button type="submit" class="bg-emerald-600 hover:bg-emerald-500 px-4 py-1 rounded text-sm flex-1">Save</button>
              <button type="button" onclick={() => closeForm()} class="px-3 py-1 rounded border border-zinc-700 text-sm">Cancel</button>
            </div>
          </form>
        {:else if formModalType === 'customCert'}
          <form onsubmit={async (e) => { await createCustomCert(e); if (!formError) closeForm(); }} class="space-y-3 text-sm">
            <input bind:value={newCustomCert.niceName} placeholder="Display name (optional)" class="w-full bg-zinc-950 border border-zinc-700 rounded px-2 py-1" />
            <input bind:value={newCustomCert.domainNames} placeholder="Domains, comma separated" class="w-full bg-zinc-950 border border-zinc-700 rounded px-2 py-1" />
            <textarea bind:value={newCustomCert.cert} placeholder="-----BEGIN CERTIFICATE-----\n..." class="w-full h-20 bg-zinc-950 border border-zinc-700 rounded px-2 py-1 text-xs font-mono"></textarea>
            <textarea bind:value={newCustomCert.key} placeholder="-----BEGIN PRIVATE KEY-----\n..." class="w-full h-20 bg-zinc-950 border border-zinc-700 rounded px-2 py-1 text-xs font-mono"></textarea>
            <div class="flex gap-2 pt-2">
              <button type="submit" class="bg-emerald-600 hover:bg-emerald-500 px-4 py-1 rounded text-sm flex-1">Save</button>
              <button type="button" onclick={() => closeForm()} class="px-3 py-1 rounded border border-zinc-700 text-sm">Cancel</button>
            </div>
          </form>
        {:else if formModalType === 'access'}
          <form onsubmit={async (e) => { await createAccess(e); if (!formError) closeForm(); }} class="space-y-3 text-sm">
            <input bind:value={newAccess.name} placeholder="Name" class="w-full bg-zinc-950 border border-zinc-700 rounded px-2 py-1" />
            <input bind:value={newAccess.clients} placeholder="127.0.0.1/32 allow, 10.0.0.0/8 deny" title="Client CIDR rules with allow or deny" class="w-full bg-zinc-950 border border-zinc-700 rounded px-2 py-1 text-xs" />
            <input bind:value={newAccess.items} placeholder="user:password, admin:secret" title="Basic auth credentials" class="w-full bg-zinc-950 border border-zinc-700 rounded px-2 py-1 text-xs" />
            <div class="flex items-center gap-3 text-xs">
              <label class="flex items-center gap-1" title="Any matching client rule is sufficient"><input type="checkbox" bind:checked={newAccess.satisfyAny} /> Satisfy any</label>
              <label class="flex items-center gap-1" title="Forward authorization to the upstream"><input type="checkbox" bind:checked={newAccess.passAuth} /> Pass auth</label>
            </div>
            <div class="flex gap-2 pt-2">
              <button type="submit" class="bg-emerald-600 hover:bg-emerald-500 px-4 py-1 rounded text-sm flex-1">Save</button>
              <button type="button" onclick={() => closeForm()} class="px-3 py-1 rounded border border-zinc-700 text-sm">Cancel</button>
            </div>
          </form>
        {:else}
          <div class="text-xs text-zinc-500">No form selected.</div>
        {/if}
      </Modal>

      <Modal bind:open={confirmState.open} title="Confirm" onClose={() => { confirmState.message = ''; confirmState.onConfirm = null; }}>
        <p class="text-sm mb-4 text-zinc-300">{confirmState.message}</p>
        <div class="flex gap-2 justify-end">
          <button type="button" onclick={() => { confirmState.open = false; confirmState.message = ''; confirmState.onConfirm = null; }} class="px-4 py-1.5 text-sm rounded border border-zinc-700 hover:bg-zinc-800">Cancel</button>
          <button type="button" onclick={handleConfirm} class="px-4 py-1.5 text-sm rounded bg-red-600 hover:bg-red-500">Confirm Delete</button>
        </div>
      </Modal>

      <!-- footer matching live reference -->
      <footer class="text-[10px] text-zinc-500 mt-8 py-4 border-t border-zinc-800 flex flex-wrap gap-x-4">
        <a href="https://github.com/rrd/helix-proxy" class="hover:text-zinc-300">GitHub</a>
        <span>© 2026</span>
        <span>{appVersion || 'dev'}</span>
      </footer>
    </div>
  {/if}
</div>

<style>
  /* minimal overrides if needed */
</style>
