'use strict';

const PS = {
  status: null,
  config: null,
};

// ─── API helpers ───
async function api(method, path, body) {
  const opts = {
    method,
    headers: body ? (body instanceof FormData ? {} : { 'Content-Type': 'application/json' }) : {},
  };
  if (body && !(body instanceof FormData)) {
    opts.body = JSON.stringify(body);
  } else if (body instanceof FormData) {
    opts.body = body;
  }
  const res = await fetch(path, opts);
  const json = await res.json();
  if (!res.ok || !json.success) {
    throw new Error(json.error || `HTTP ${res.status}`);
  }
  return json.data;
}

const get = (path) => api('GET', path);
const post = (path, body) => api('POST', path, body);
const put = (path, body) => api('PUT', path, body);

function showToast(msg, type = 'info') {
  const t = document.getElementById('toast');
  t.textContent = msg;
  t.className = `toast ${type}`;
  t.classList.remove('hidden');
  clearTimeout(t._timer);
  t._timer = setTimeout(() => t.classList.add('hidden'), 3500);
}

function esc(s) {
  if (s == null) return '';
  const d = document.createElement('div');
  d.textContent = String(s);
  return d.innerHTML;
}

// ─── Segmented buttons ───
function setSegBtn(id, mode) {
  document.querySelectorAll(`#${id} .seg-btn`).forEach(b => {
    b.classList.toggle('active', b.dataset.mode === mode);
  });
}

function getSegBtn(id) {
  const active = document.querySelector(`#${id} .seg-btn.active`);
  return active ? active.dataset.mode : '';
}

// Generic seg btn click handler
document.querySelectorAll('.segmented').forEach(seg => {
  seg.addEventListener('click', e => {
    const btn = e.target.closest('.seg-btn');
    if (!btn || !seg.id) return;
    seg.querySelectorAll('.seg-btn').forEach(b => b.classList.remove('active'));
    btn.classList.add('active');
  });
});

// ─── Config accessors ───
function getProxies() { return PS.config && Array.isArray(PS.config.proxies) ? PS.config.proxies : []; }
function getGroups() { return PS.config && Array.isArray(PS.config['proxy-groups']) ? PS.config['proxy-groups'] : []; }
function getRules() { return PS.config && Array.isArray(PS.config.rules) ? PS.config.rules : []; }
function getProxyProviders() {
  const pp = PS.config && PS.config['proxy-providers'];
  if (!pp || typeof pp !== 'object') return [];
  return Object.entries(pp).map(([name, val]) => ({ name, ...val }));
}
function getRuleProviders() {
  const rp = PS.config && PS.config['rule-providers'];
  if (!rp || typeof rp !== 'object') return [];
  return Object.entries(rp).map(([name, val]) => ({ name, ...val }));
}

// ─── Type labels ───
const PROTO_LABELS = {
  ss: 'Shadowsocks', ssr: 'ShadowsocksR', vmess: 'VMess', vless: 'VLESS',
  trojan: 'Trojan', hysteria: 'Hysteria', hysteria2: 'Hysteria2', tuic: 'TUIC',
  wireguard: 'WireGuard', snell: 'Snell', ssh: 'SSH', anytls: 'AnyTLS',
  mieru: 'Mieru', http: 'HTTP', socks5: 'SOCKS5', direct: 'DIRECT',
};
function typeLabel(t) { return PROTO_LABELS[t] || t; }

const GROUP_LABELS = { select: 'Выбор', 'url-test': 'Автотест', fallback: 'Резерв', 'load-balance': 'Балансировка', relay: 'Relay' };
function groupTypeLabel(t) { return GROUP_LABELS[t] || t; }

// ─── Load / Save ───
async function loadStatus() {
  try {
    PS.status = await get('/api/mihomo/status');
    renderStatus();
  } catch (e) {
    console.error('load status', e);
  }
}

async function loadConfig() {
  try {
    PS.config = await get('/api/mihomo/config');
    renderAll();
  } catch (e) {
    console.error('load config', e);
    PS.config = {};
    renderAll();
    showToast('Ошибка загрузки конфига: ' + e.message, 'error');
  }
  document.getElementById('loading').classList.add('hidden');
}

async function saveFullConfig(restart) {
  try {
    await put('/api/mihomo/config', PS.config);
    showToast('Конфиг сохранён', 'success');
    refreshYAMLPreview();
    if (restart) {
      try {
        await post('/api/mihomo/restart', null);
        showToast('Mihomo перезапущен', 'success');
      } catch (e) {
        showToast('Перезапуск не удался: ' + e.message, 'error');
      }
      await loadStatus();
    }
  } catch (e) {
    showToast('Ошибка сохранения: ' + e.message, 'error');
  }
}

async function refreshYAMLPreview() {
  try {
    const res = await fetch('/api/mihomo/config.yaml');
    if (res.ok) {
      const el = document.getElementById('yamlPreview');
      if (el) el.value = await res.text();
    }
  } catch (e) { /* ignore */ }
}

// ─── Status rendering ───
function renderStatus() {
  const s = PS.status;
  if (!s) return;
  const text = document.getElementById('statusText');
  const headerBadge = document.getElementById('statusBadge');
  const toggle = document.getElementById('serviceActive');
  const toggleText = document.getElementById('serviceToggleText');
  const restartBtn = document.getElementById('restartBtn');
  const coreInfo = document.getElementById('coreInfo');

  if (s.running) {
    text.className = 'svc-badge running'; text.textContent = 'Запущен (PID ' + (s.pid || '?') + ')';
    headerBadge.className = 'svc-badge running'; headerBadge.textContent = 'Запущен';
    toggle.checked = true; toggleText.textContent = 'Запущен'; restartBtn.disabled = false;
  } else {
    text.className = 'svc-badge stopped'; text.textContent = 'Остановлен';
    headerBadge.className = 'svc-badge stopped'; headerBadge.textContent = 'Остановлен';
    toggle.checked = false; toggleText.textContent = 'Остановлен'; restartBtn.disabled = true;
  }

  const corePath = document.getElementById('corePath');
  const coreExists = document.getElementById('coreExists');
  const corePid = document.getElementById('corePid');
  if (corePath) corePath.textContent = s.core_path || '—';
  if (coreExists) coreExists.textContent = s.core_exists ? 'Да' : 'Нет';
  if (corePid) corePid.textContent = s.running && s.pid ? s.pid : '—';

  if (!s.core_exists) {
    coreInfo.classList.remove('hidden');
  } else {
    coreInfo.classList.add('hidden');
  }

  document.getElementById('statusBar').classList.remove('hidden');
  document.getElementById('loading').classList.add('hidden');
}

// ─── Render all ───
function renderAll() {
  renderProxies();
  renderGroups();
  renderRules();
  renderProxyProviders();
  renderRuleProviders();
  fillSettings();
  refreshYAMLPreview();
}

// ─── Render proxies ───
function renderProxies() {
  const list = document.getElementById('proxyList');
  const empty = document.getElementById('proxyEmpty');
  const proxies = getProxies();
  list.innerHTML = '';
  if (proxies.length === 0) { empty.classList.remove('hidden'); return; }
  empty.classList.add('hidden');
  proxies.forEach(p => {
    const card = document.createElement('div');
    card.className = 'proxy-card';
    const server = p.type === 'direct' ? '' :
      p.type === 'wireguard' ? (p.ip || '—') :
      `<div class="info-row"><span class="info-label">Сервер</span><span class="info-val mono">${esc(p.server || '—')}</span></div>
      <div class="info-row"><span class="info-label">Порт</span><span class="info-val mono">${p.port || '—'}</span></div>`;
    const tags = [
      p.udp ? '<span class="tag-active">UDP</span>' : '',
      p.tls ? '<span class="tag-active">TLS</span>' : '',
      p.network ? `<span class="tag-active">${esc(p.network)}</span>` : '',
    ].filter(Boolean).join('');
    card.innerHTML = `
      <div class="proxy-card-header">
        <div style="display:flex;align-items:center;gap:8px">
          <span class="proxy-name">${esc(p.name)}</span>
          <span class="tag-active">${esc(typeLabel(p.type))}</span>
        </div>
        <div style="display:flex;gap:6px">
          <button class="btn btn-ghost btn-sm" data-edit-proxy="${esc(p.name)}">Изменить</button>
          <button class="btn btn-danger btn-sm" data-delete-proxy="${esc(p.name)}">Удалить</button>
        </div>
      </div>
      <div class="proxy-card-info">${server}${tags ? `<div style="display:flex;gap:4px;flex-wrap:wrap;margin-top:4px">${tags}</div>` : ''}</div>`;
    list.appendChild(card);
  });
}

// ─── Render groups ───
function renderGroups() {
  const list = document.getElementById('groupList');
  const empty = document.getElementById('groupEmpty');
  const groups = getGroups();
  list.innerHTML = '';
  if (groups.length === 0) { empty.classList.remove('hidden'); return; }
  empty.classList.add('hidden');
  groups.forEach(g => {
    const card = document.createElement('div');
    card.className = 'proxy-card';
    const pl = (g.proxies || []).slice(0, 5).map(p => esc(p)).join(', ');
    const more = (g.proxies || []).length > 5 ? ` +${(g.proxies || []).length - 5}` : '';
    card.innerHTML = `
      <div class="proxy-card-header">
        <div style="display:flex;align-items:center;gap:8px">
          <span class="proxy-name">${esc(g.name)}</span>
          <span class="tag-gw">${esc(groupTypeLabel(g.type))}</span>
          ${g['include-all'] ? '<span class="tag-active">Все прокси</span>' : ''}
        </div>
        <div style="display:flex;gap:6px">
          <button class="btn btn-ghost btn-sm" data-edit-group="${esc(g.name)}">Изменить</button>
          <button class="btn btn-danger btn-sm" data-delete-group="${esc(g.name)}">Удалить</button>
        </div>
      </div>
      <div class="proxy-card-info">
        <div class="info-row"><span class="info-label">Узлы</span><span class="info-val">${pl || '—'}${more}</span></div>
        ${g.url ? `<div class="info-row"><span class="info-label">URL</span><span class="info-val mono" style="font-size:.75rem">${esc(g.url)}</span></div>` : ''}
        ${g.interval ? `<div class="info-row"><span class="info-label">Интервал</span><span class="info-val">${g.interval}с</span></div>` : ''}
      </div>`;
    list.appendChild(card);
  });
}

// ─── Render rules ───
function renderRules() {
  const list = document.getElementById('rulesList');
  const rules = getRules();
  list.innerHTML = '';
  if (rules.length === 0) { list.innerHTML = '<div class="empty-state">Нет правил маршрутизации</div>'; return; }
  rules.forEach((rule, i) => {
    const parts = rule.split(',');
    const type = parts[0] || '';
    const value = parts[1] || '';
    const target = parts[2] || '';
    const flags = parts.slice(3).join(',');
    const el = document.createElement('div');
    el.className = 'rule-item';
    el.innerHTML = `
      <div class="rule-info">
        <span class="rule-type">${esc(type)}</span>
        <span class="rule-value">${esc(value || (type === 'MATCH' ? '*' : ''))}</span>
        <span class="rule-target">${esc(target)}</span>
        ${flags ? `<span class="rule-flags">${esc(flags)}</span>` : ''}
      </div>
      <div style="display:flex;gap:4px">
        <button class="btn btn-danger btn-sm" data-delete-rule="${i}">✕</button>
      </div>`;
    list.appendChild(el);
  });
}

// ─── Render proxy providers ───
function renderProxyProviders() {
  const list = document.getElementById('proxyProviderList');
  const empty = document.getElementById('proxyProviderEmpty');
  const providers = getProxyProviders();
  list.innerHTML = '';
  if (providers.length === 0) { empty.style.display = ''; return; }
  empty.style.display = 'none';
  providers.forEach(p => {
    const card = document.createElement('div');
    card.className = 'proxy-card';
    card.innerHTML = `
      <div class="proxy-card-header">
        <div style="display:flex;align-items:center;gap:8px">
          <span class="proxy-name">${esc(p.name)}</span>
          <span class="tag-active">${esc(p.type || 'http')}</span>
        </div>
        <div style="display:flex;gap:6px">
          <button class="btn btn-ghost btn-sm" data-edit-pp="${esc(p.name)}">Изменить</button>
          <button class="btn btn-danger btn-sm" data-delete-pp="${esc(p.name)}">Удалить</button>
        </div>
      </div>
      <div class="proxy-card-info">
        ${p.url ? `<div class="info-row"><span class="info-label">URL</span><span class="info-val mono" style="font-size:.75rem">${esc(p.url)}</span></div>` : ''}
        ${p.interval ? `<div class="info-row"><span class="info-label">Интервал</span><span class="info-val">${p.interval}с</span></div>` : ''}
        ${p.filter ? `<div class="info-row"><span class="info-label">Filter</span><span class="info-val mono">${esc(p.filter)}</span></div>` : ''}
      </div>`;
    list.appendChild(card);
  });
}

// ─── Render rule providers ───
function renderRuleProviders() {
  const list = document.getElementById('ruleProviderList');
  const empty = document.getElementById('ruleProviderEmpty');
  const providers = getRuleProviders();
  list.innerHTML = '';
  if (providers.length === 0) { empty.style.display = ''; return; }
  empty.style.display = 'none';
  providers.forEach(p => {
    const card = document.createElement('div');
    card.className = 'proxy-card';
    card.innerHTML = `
      <div class="proxy-card-header">
        <div style="display:flex;align-items:center;gap:8px">
          <span class="proxy-name">${esc(p.name)}</span>
          <span class="tag-active">${esc(p.type || 'http')}</span>
          <span class="tag-gw">${esc(p.behavior || 'classical')}</span>
        </div>
        <div style="display:flex;gap:6px">
          <button class="btn btn-ghost btn-sm" data-edit-rp="${esc(p.name)}">Изменить</button>
          <button class="btn btn-danger btn-sm" data-delete-rp="${esc(p.name)}">Удалить</button>
        </div>
      </div>
      <div class="proxy-card-info">
        ${p.url ? `<div class="info-row"><span class="info-label">URL</span><span class="info-val mono" style="font-size:.75rem">${esc(p.url)}</span></div>` : ''}
        ${p.format ? `<div class="info-row"><span class="info-label">Format</span><span class="info-val">${esc(p.format)}</span></div>` : ''}
      </div>`;
    list.appendChild(card);
  });
}

// ─── Fill settings ───
function fillSettings() {
  if (!PS.config) return;
  const c = PS.config;
  const dns = c.dns || {};
  const geo = c['geox-url'] || {};
  const profile = c.profile || {};

  // Basic
  setSegBtn('modeSwitch', c.mode || 'rule');
  setSegBtn('logLevelSwitch', c['log-level'] || 'info');
  setVal('allowLan', c['allow-lan'] !== false, true);
  setVal('bindAddress', c['bind-address'] || '*');
  setVal('ipv6', c.ipv6 !== false, true);
  setVal('tcpConcurrent', c['tcp-concurrent'] !== false, true);
  setVal('unifiedDelay', !!c['unified-delay'], true);
  setSegBtn('findProcessSwitch', c['find-process-mode'] || 'off');
  setVal('globalUA', c['global-ua'] || '');
  setVal('keepAliveInterval', c['keep-alive-interval'] || '');
  setVal('keepAliveIdle', c['keep-alive-idle'] || '');

  // Ports
  setVal('mixedPort', c['mixed-port'] || 7890);
  setVal('httpPort', c.port || '');
  setVal('socksPort', c['socks-port'] || '');
  setVal('redirPort', c['redir-port'] || '');

  // TProxy
  setVal('tproxyEnabled', !!c['tproxy-port'], true);
  setVal('tproxyPort', c['tproxy-port'] || 7894);

  // DNS
  setVal('dnsEnabled', dns.enable !== false, true);
  setVal('dnsListen', dns.listen || '0.0.0.0:1053');
  setSegBtn('dnsModeSwitch', dns['enhanced-mode'] || 'redir-host');
  setVal('dnsNameserver', (dns.nameserver || []).join('\n'));
  setVal('dnsFallback', (dns.fallback || []).join('\n'));
  setVal('dnsDefaultNS', (dns['default-nameserver'] || []).join('\n'));
  setVal('dnsProxyNS', (dns['proxy-server-nameserver'] || []).join('\n'));
  setVal('dnsFakeIPRange', dns['fake-ip-range'] || '198.18.0.1/16');
  setVal('dnsFakeIPFilter', (dns['fake-ip-filter'] || []).join('\n'));
  const nsp = dns['nameserver-policy'];
  setVal('dnsNameserverPolicy', nsp && typeof nsp === 'object' ?
    Object.entries(nsp).map(([k, v]) => `${k}: ${v}`).join('\n') : '');
  setVal('dnsUseHosts', dns['use-hosts'] !== false, true);
  setVal('dnsUseSystemHosts', dns['use-system-hosts'] !== false, true);

  // GEO
  setVal('geodataMode', !!c['geodata-mode'], true);
  setSegBtn('geodataLoaderSwitch', c['geodata-loader'] || 'memconservative');
  setVal('geoAutoUpdate', !!c['geo-auto-update'], true);
  setVal('geoUpdateInterval', c['geo-update-interval'] || 24);
  setVal('geoxGeoIP', geo.geoip || '');
  setVal('geoxGeoSite', geo.geosite || '');
  setVal('geoxMMDB', geo.mmdb || '');
  setVal('geoxASN', geo.asn || '');

  // External controller
  setVal('externalController', c['external-controller'] || '0.0.0.0:9090');
  setVal('secret', c.secret || '');
  setVal('externalUI', c['external-ui'] || '');
  setVal('externalUIName', c['external-ui-name'] || '');
  setVal('externalUIURL', c['external-ui-url'] || '');

  // Profile
  setVal('profileStoreSelected', profile['store-selected'] !== false, true);
  setVal('profileStoreFakeIP', !!profile['store-fake-ip'], true);
}

function setVal(id, val, isCheck) {
  const el = document.getElementById(id);
  if (!el) return;
  if (isCheck) el.checked = !!val;
  else el.value = val == null ? '' : val;
}

function gVal(id) {
  const el = document.getElementById(id);
  return el ? el.value : '';
}
function gCheck(id) {
  const el = document.getElementById(id);
  return el ? el.checked : false;
}
function gNum(id, def) {
  const v = parseInt(gVal(id));
  return isNaN(v) ? (def || 0) : v;
}
function gLines(id) {
  return gVal(id).split('\n').map(s => s.trim()).filter(Boolean);
}

// ─── Apply settings to config ───
function applySettingsToConfig() {
  if (!PS.config) PS.config = {};
  const c = PS.config;

  c.mode = getSegBtn('modeSwitch');
  c['log-level'] = getSegBtn('logLevelSwitch');
  c['allow-lan'] = gCheck('allowLan');
  c['bind-address'] = gVal('bindAddress') || '*';
  c.ipv6 = gCheck('ipv6');
  c['tcp-concurrent'] = gCheck('tcpConcurrent');
  c['unified-delay'] = gCheck('unifiedDelay');
  c['find-process-mode'] = getSegBtn('findProcessSwitch');
  const ua = gVal('globalUA');
  if (ua) c['global-ua'] = ua; else delete c['global-ua'];
  const kai = gNum('keepAliveInterval', 0);
  if (kai) c['keep-alive-interval'] = kai; else delete c['keep-alive-interval'];
  const kaIdle = gNum('keepAliveIdle', 0);
  if (kaIdle) c['keep-alive-idle'] = kaIdle; else delete c['keep-alive-idle'];

  // Ports
  c['mixed-port'] = gNum('mixedPort', 7890);
  const hp = gNum('httpPort', 0);
  if (hp) c.port = hp; else delete c.port;
  const sp = gNum('socksPort', 0);
  if (sp) c['socks-port'] = sp; else delete c['socks-port'];
  const rp = gNum('redirPort', 0);
  if (rp) c['redir-port'] = rp; else delete c['redir-port'];

  // TProxy
  if (gCheck('tproxyEnabled')) {
    c['tproxy-port'] = gNum('tproxyPort', 7894);
  } else {
    delete c['tproxy-port'];
  }

  // DNS
  const nspRaw = gVal('dnsNameserverPolicy').trim();
  let nsp = null;
  if (nspRaw) {
    nsp = {};
    nspRaw.split('\n').forEach(line => {
      const idx = line.indexOf(':');
      if (idx > 0) { nsp[line.slice(0, idx).trim()] = line.slice(idx + 1).trim(); }
    });
  }
  c.dns = {
    enable: gCheck('dnsEnabled'),
    ipv6: gCheck('ipv6'),
    listen: gVal('dnsListen'),
    'enhanced-mode': getSegBtn('dnsModeSwitch'),
    'fake-ip-range': gVal('dnsFakeIPRange') || '198.18.0.1/16',
    'fake-ip-filter': gLines('dnsFakeIPFilter'),
    'default-nameserver': gLines('dnsDefaultNS'),
    nameserver: gLines('dnsNameserver'),
    fallback: gLines('dnsFallback'),
    'use-hosts': gCheck('dnsUseHosts'),
    'use-system-hosts': gCheck('dnsUseSystemHosts'),
  };
  const proxyNS = gLines('dnsProxyNS');
  if (proxyNS.length) c.dns['proxy-server-nameserver'] = proxyNS;
  if (nsp) c.dns['nameserver-policy'] = nsp;

  // GEO
  c['geodata-mode'] = gCheck('geodataMode');
  c['geodata-loader'] = getSegBtn('geodataLoaderSwitch');
  c['geo-auto-update'] = gCheck('geoAutoUpdate');
  c['geo-update-interval'] = gNum('geoUpdateInterval', 24);
  const geox = {};
  ['geoip', 'geosite', 'mmdb', 'asn'].forEach(k => {
    const el = document.getElementById(`geox${k.charAt(0).toUpperCase() + k.slice(1)}`);
    if (el && el.value.trim()) geox[k] = el.value.trim();
  });
  if (Object.keys(geox).length) c['geox-url'] = geox; else delete c['geox-url'];

  // External controller
  c['external-controller'] = gVal('externalController');
  c.secret = gVal('secret') || '';
  const extUI = gVal('externalUI');
  if (extUI) c['external-ui'] = extUI; else delete c['external-ui'];
  const extUIName = gVal('externalUIName');
  if (extUIName) c['external-ui-name'] = extUIName; else delete c['external-ui-name'];
  const extUIURL = gVal('externalUIURL');
  if (extUIURL) c['external-ui-url'] = extUIURL; else delete c['external-ui-url'];

  // Profile
  c.profile = {
    'store-selected': gCheck('profileStoreSelected'),
    'store-fake-ip': gCheck('profileStoreFakeIP'),
  };
}

// ─── Tab switching ───
document.querySelectorAll('.ptab').forEach(btn => {
  btn.addEventListener('click', () => {
    document.querySelectorAll('.ptab').forEach(b => b.classList.remove('active'));
    document.querySelectorAll('.ptab-content').forEach(c => c.classList.add('hidden'));
    btn.classList.add('active');
    document.getElementById('tab-' + btn.dataset.tab).classList.remove('hidden');
    if (btn.dataset.tab === 'dashboard') {
      updateDashVisibility();
      if (PS.status && PS.status.running) startDashPolling();
    } else {
      stopDashPolling();
    }
  });
});

// ─── Core control ───
document.getElementById('serviceActive').addEventListener('change', async (e) => {
  const action = e.target.checked ? 'start' : 'stop';
  try {
    await post('/api/mihomo/' + action, null);
    showToast('Mihomo ' + (action === 'start' ? 'запущен' : 'остановлен'), 'success');
    await loadStatus();
    updateDashVisibility();
    if (action === 'start') {
      const dashTab = document.querySelector('.ptab[data-tab="dashboard"]');
      if (dashTab && dashTab.classList.contains('active')) startDashPolling();
    } else {
      stopDashPolling();
    }
  } catch (err) {
    showToast('Ошибка: ' + err.message, 'error');
    await loadStatus();
    updateDashVisibility();
  }
});

document.getElementById('restartBtn').addEventListener('click', async () => {
  try {
    await post('/api/mihomo/restart', null);
    showToast('Mihomo перезапущен', 'success');
    await loadStatus();
  } catch (e) {
    showToast('Ошибка: ' + e.message, 'error');
  }
});

// ─── Settings ───
document.getElementById('settingsForm').addEventListener('submit', e => {
  e.preventDefault();
  applySettingsToConfig();
  saveFullConfig(false);
});
document.getElementById('saveAndRestartBtn').addEventListener('click', () => {
  applySettingsToConfig();
  saveFullConfig(true);
});

// ════════════════════════════════════════════════════════════
// PROXY MODAL
// ════════════════════════════════════════════════════════════

// Protocols needing transport
const TRANSPORT_PROTOS = ['vmess', 'vless', 'trojan'];
// Protocols needing TLS section (always shown)
const TLS_PROTOS = ['ss', 'vmess', 'vless', 'trojan', 'hysteria', 'hysteria2', 'tuic', 'snell', 'anytls', 'http', 'socks5'];
// TLS always on (no toggle)
const TLS_ALWAYS = ['trojan', 'hysteria', 'hysteria2', 'tuic'];
// Protocols with client-fingerprint
const CLIENT_FP_PROTOS = ['vmess', 'vless', 'trojan', 'anytls'];
// Protocols with Reality support (shows Reality section inside TLS)
const REALITY_PROTOS = ['vless', 'vmess'];

// All proto-specific section IDs
const PROTO_SECTIONS = ['pf-ss', 'pf-ssr', 'pf-vmess', 'pf-vless', 'pf-trojan',
  'pf-hysteria', 'pf-hysteria2', 'pf-tuic', 'pf-wireguard', 'pf-snell',
  'pf-ssh', 'pf-anytls', 'pf-mieru', 'pf-http-auth'];

function showEl(id) { const e = document.getElementById(id); if (e) e.classList.remove('hidden'); }
function hideEl(id) { const e = document.getElementById(id); if (e) e.classList.add('hidden'); }

function updateProxyFields() {
  const type = document.getElementById('proxyType').value;

  // Hide all proto sections
  PROTO_SECTIONS.forEach(hideEl);

  // Server/port visibility
  if (type === 'direct') {
    hideEl('pf-server-section');
  } else {
    showEl('pf-server-section');
  }

  // UDP row visibility
  const noUDP = ['wireguard']; // WireGuard has its own udp field
  // actually WireGuard does have udp field in common but let's keep it

  // Show the appropriate proto section
  switch (type) {
    case 'ss':      showEl('pf-ss'); break;
    case 'ssr':     showEl('pf-ssr'); break;
    case 'vmess':   showEl('pf-vmess'); break;
    case 'vless':   showEl('pf-vless'); break;
    case 'trojan':  showEl('pf-trojan'); break;
    case 'hysteria': showEl('pf-hysteria'); break;
    case 'hysteria2': showEl('pf-hysteria2'); break;
    case 'tuic':    showEl('pf-tuic'); break;
    case 'wireguard': showEl('pf-wireguard'); break;
    case 'snell':   showEl('pf-snell'); break;
    case 'ssh':     showEl('pf-ssh'); break;
    case 'anytls':  showEl('pf-anytls'); break;
    case 'mieru':   showEl('pf-mieru'); break;
    case 'http':
    case 'socks5':
      showEl('pf-http-auth');
      // headers only for http
      const hrow = document.getElementById('pf-http-headers-row');
      if (hrow) hrow.classList.toggle('hidden', type !== 'http');
      break;
  }

  // Transport section
  if (TRANSPORT_PROTOS.includes(type)) {
    showEl('pf-transport-section');
    updateTransportOpts(); // show/hide transport sub-opts
    // XHTTP only for VLESS
    const xopt = document.getElementById('pf-xhttp-opts');
    if (xopt) {
      const net = document.getElementById('proxyNetwork').value;
      if (type === 'vless' && net === 'xhttp') xopt.classList.remove('hidden');
      else xopt.classList.add('hidden');
    }
  } else {
    hideEl('pf-transport-section');
  }

  // TLS section
  if (TLS_PROTOS.includes(type)) {
    showEl('pf-tls-section');
    const toggleRow = document.getElementById('pf-tls-toggle-row');
    if (TLS_ALWAYS.includes(type)) {
      // Always on — hide toggle, show fields
      if (toggleRow) toggleRow.classList.add('hidden');
      showEl('pf-tls-fields');
    } else {
      if (toggleRow) toggleRow.classList.remove('hidden');
      const tlsOn = document.getElementById('proxyTLS') && document.getElementById('proxyTLS').checked;
      if (tlsOn) showEl('pf-tls-fields'); else hideEl('pf-tls-fields');
    }
    // Client fingerprint
    const cfpRow = document.getElementById('pf-client-fp-row');
    if (cfpRow) cfpRow.classList.toggle('hidden', !CLIENT_FP_PROTOS.includes(type));
    // Reality section
    const realSec = document.getElementById('pf-reality-section');
    if (realSec) realSec.classList.toggle('hidden', !REALITY_PROTOS.includes(type));
  } else {
    hideEl('pf-tls-section');
  }
}

function updateTransportOpts() {
  const net = document.getElementById('proxyNetwork').value;
  const type = document.getElementById('proxyType').value;
  hideEl('pf-ws-opts'); hideEl('pf-h2-opts'); hideEl('pf-grpc-opts');
  hideEl('pf-http-transport-opts'); hideEl('pf-xhttp-opts');
  switch (net) {
    case 'ws':    showEl('pf-ws-opts'); break;
    case 'h2':    showEl('pf-h2-opts'); break;
    case 'grpc':  showEl('pf-grpc-opts'); break;
    case 'http':  showEl('pf-http-transport-opts'); break;
    case 'xhttp': if (type === 'vless') showEl('pf-xhttp-opts'); break;
  }
}

document.getElementById('proxyType').addEventListener('change', updateProxyFields);
document.getElementById('proxyNetwork').addEventListener('change', updateTransportOpts);

document.getElementById('proxyTLS').addEventListener('change', () => {
  const on = document.getElementById('proxyTLS').checked;
  if (on) showEl('pf-tls-fields'); else hideEl('pf-tls-fields');
});

document.getElementById('ssPlugin').addEventListener('change', () => {
  const plugin = document.getElementById('ssPlugin').value;
  if (!plugin) {
    hideEl('pf-ss-obfs'); hideEl('pf-ss-plugin-opts');
  } else if (plugin === 'obfs') {
    showEl('pf-ss-obfs'); hideEl('pf-ss-plugin-opts');
  } else {
    hideEl('pf-ss-obfs'); showEl('pf-ss-plugin-opts');
    // Set placeholder for plugin opts
    const ta = document.getElementById('ssPluginOpts');
    if (plugin === 'v2ray-plugin' || plugin === 'gost-plugin') {
      ta.placeholder = 'mode: websocket\ntls: true\nhost: example.com\npath: /';
    } else if (plugin === 'shadow-tls') {
      ta.placeholder = 'host: cloud.tencent.com\npassword: shadow_tls_password\nversion: 3';
    } else if (plugin === 'restls') {
      ta.placeholder = 'host: www.microsoft.com\npassword: your_password\nversion-hint: tls13';
    } else if (plugin === 'kcptun') {
      ta.placeholder = 'key: secret\ncrypt: aes\nmode: fast';
    }
  }
});

document.getElementById('snellObfsMode').addEventListener('change', () => {
  const mode = document.getElementById('snellObfsMode').value;
  const row = document.getElementById('pf-snell-obfs-host');
  if (row) row.classList.toggle('hidden', !mode);
});

// TUIC version switch — visibility handled after generic seg handler
document.getElementById('tuicVersionSwitch').addEventListener('click', e => {
  const btn = e.target.closest('.seg-btn');
  if (!btn) return;
  // Let the generic handler set active class, then update visibility
  setTimeout(() => {
    const v = getSegBtn('tuicVersionSwitch') || '5';
    const tokenRow = document.getElementById('pf-tuic-token');
    const v5auth = document.getElementById('pf-tuic-v5-auth');
    if (v === '4') { if (tokenRow) tokenRow.classList.remove('hidden'); if (v5auth) v5auth.classList.add('hidden'); }
    else { if (tokenRow) tokenRow.classList.add('hidden'); if (v5auth) v5auth.classList.remove('hidden'); }
  }, 0);
});

// ─── Open proxy modal ───
let editProxyName = null;

function openProxyModal(proxy) {
  editProxyName = proxy ? proxy.name : null;
  document.getElementById('proxyModalTitle').textContent = proxy ? 'Редактировать прокси' : 'Добавить прокси';
  document.getElementById('proxyEditName').value = proxy ? proxy.name : '';

  const p = proxy || {};
  const type = p.type || 'ss';
  document.getElementById('proxyType').value = type;
  document.getElementById('proxyName').value = p.name || '';
  document.getElementById('proxyServer').value = p.server || '';
  document.getElementById('proxyPort').value = p.port || '';

  // Common advanced
  document.getElementById('proxyUDP').checked = p.udp !== false;
  document.getElementById('proxyIPVersion').value = p['ip-version'] || '';
  document.getElementById('proxyInterface').value = p['interface-name'] || '';
  document.getElementById('proxyRoutingMark').value = p['routing-mark'] || '';
  document.getElementById('proxyTFO').checked = !!p.tfo;
  document.getElementById('proxyMPTCP').checked = !!p.mptcp;
  document.getElementById('proxyDialerProxy').value = p['dialer-proxy'] || '';

  // TLS
  document.getElementById('proxyTLS').checked = !!p.tls;
  document.getElementById('proxySNI').value = p.servername || p.sni || '';
  document.getElementById('proxyFingerprint').value = p.fingerprint || '';
  document.getElementById('proxyALPN').value = Array.isArray(p.alpn) ? p.alpn.join('\n') : (p.alpn || '');
  document.getElementById('proxySkipCertVerify').checked = !!p['skip-cert-verify'];
  document.getElementById('proxyClientFP').value = p['client-fingerprint'] || '';

  // Reality
  const ro = p['reality-opts'] || {};
  document.getElementById('realityPublicKey').value = ro['public-key'] || '';
  document.getElementById('realityShortID').value = ro['short-id'] || '';
  document.getElementById('realityX25519mlkem').checked = !!ro['support-x25519mlkem768'];

  // ECH
  const ech = p['ech-opts'] || {};
  document.getElementById('echEnable').checked = !!ech.enable;
  document.getElementById('echConfig').value = ech.config || '';
  document.getElementById('echQuerySNI').value = ech['query-server-name'] || '';

  // Transport
  document.getElementById('proxyNetwork').value = p.network || '';
  const ws = p['ws-opts'] || {};
  document.getElementById('wsPath').value = ws.path || '';
  document.getElementById('wsHost').value = (ws.headers && ws.headers.Host) || '';
  document.getElementById('wsMaxEarlyData').value = ws['max-early-data'] || '';
  document.getElementById('wsEarlyDataHeader').value = ws['early-data-header-name'] || '';
  document.getElementById('wsV2rayUpgrade').checked = !!ws['v2ray-http-upgrade'];
  document.getElementById('wsV2rayUpgradeFO').checked = !!ws['v2ray-http-upgrade-fast-open'];

  const h2 = p['h2-opts'] || {};
  document.getElementById('h2Host').value = Array.isArray(h2.host) ? h2.host.join('\n') : (h2.host || '');
  document.getElementById('h2Path').value = h2.path || '';

  const grpc = p['grpc-opts'] || {};
  document.getElementById('grpcServiceName').value = grpc['grpc-service-name'] || '';
  document.getElementById('grpcUserAgent').value = grpc['grpc-user-agent'] || '';

  const http = p['http-opts'] || {};
  document.getElementById('httpTransportMethod').value = http.method || 'GET';
  document.getElementById('httpTransportPath').value = Array.isArray(http.path) ? http.path.join('\n') : (http.path || '');

  const xhttp = p['xhttp-opts'] || {};
  document.getElementById('xhttpPath').value = xhttp.path || '';
  document.getElementById('xhttpHost').value = xhttp.host || '';
  document.getElementById('xhttpMode').value = xhttp.mode || '';
  document.getElementById('xhttpNoGRPC').checked = !!xhttp['no-grpc-header'];
  document.getElementById('xhttpPaddingBytes').value = xhttp['x-padding-bytes'] || '';

  // ── Protocol fields ──
  // SS
  document.getElementById('ssCipher').value = p.cipher || 'auto';
  document.getElementById('ssPassword').value = p.password || '';
  document.getElementById('ssUOT').checked = !!p['udp-over-tcp'];
  setSegBtn('ssUOTVersionSwitch', String(p['udp-over-tcp-version'] || '1'));
  document.getElementById('ssPlugin').value = p.plugin || '';
  if (p.plugin === 'obfs') {
    document.getElementById('ssObfsMode').value = (p['plugin-opts'] || {}).mode || 'tls';
    document.getElementById('ssObfsHost').value = (p['plugin-opts'] || {}).host || '';
  } else if (p.plugin && p['plugin-opts']) {
    // Serialize plugin-opts to YAML-like text
    document.getElementById('ssPluginOpts').value = objToYAML(p['plugin-opts']);
  }

  // SSR
  document.getElementById('ssrCipher').value = p.cipher || 'none';
  document.getElementById('ssrPassword').value = p.password || '';
  document.getElementById('ssrProtocol').value = p.protocol || 'origin';
  document.getElementById('ssrProtocolParam').value = p['protocol-param'] || '';
  document.getElementById('ssrObfs').value = p.obfs || 'plain';
  document.getElementById('ssrObfsParam').value = p['obfs-param'] || '';

  // VMess
  document.getElementById('vmessUUID').value = p.uuid || '';
  document.getElementById('vmessAlterID').value = p.alterId != null ? p.alterId : 0;
  document.getElementById('vmessCipher').value = p.cipher || 'auto';

  // VLESS
  document.getElementById('vlessUUID').value = p.uuid || '';
  document.getElementById('vlessFlow').value = p.flow || '';
  document.getElementById('vlessEncryption').value = p.encryption || 'none';

  // Trojan
  document.getElementById('trojanPassword').value = p.password || '';

  // Hysteria v1
  document.getElementById('hyAuthStr').value = p['auth-str'] || '';
  document.getElementById('hyProtocol').value = p.protocol || 'udp';
  document.getElementById('hyUp').value = p.up || '';
  document.getElementById('hyDown').value = p.down || '';
  document.getElementById('hyObfs').value = p.obfs || '';
  document.getElementById('hyPorts').value = p.ports || '';
  document.getElementById('hyFastOpen').checked = !!p['fast-open'];

  // Hysteria2
  document.getElementById('hy2Password').value = p.password || '';
  document.getElementById('hy2Up').value = p.up || '';
  document.getElementById('hy2Down').value = p.down || '';
  document.getElementById('hy2ObfsType').value = p.obfs || '';
  document.getElementById('hy2ObfsPassword').value = p['obfs-password'] || '';
  document.getElementById('hy2Ports').value = p.ports || '';
  document.getElementById('hy2HopInterval').value = p['hop-interval'] || '';

  // TUIC
  const tuicV = p.token ? '4' : '5';
  setSegBtn('tuicVersionSwitch', tuicV);
  document.getElementById('pf-tuic-token').classList.toggle('hidden', tuicV !== '4');
  document.getElementById('pf-tuic-v5-auth').classList.toggle('hidden', tuicV !== '5');
  document.getElementById('tuicToken').value = p.token || '';
  document.getElementById('tuicUUID').value = p.uuid || '';
  document.getElementById('tuicPassword').value = p.password || '';
  document.getElementById('tuicIP').value = p.ip || '';
  document.getElementById('tuicHeartbeat').value = p['heartbeat-interval'] || '';
  document.getElementById('tuicUDPMode').value = p['udp-relay-mode'] || 'native';
  document.getElementById('tuicCongestion').value = p['congestion-controller'] || '';
  document.getElementById('tuicMaxUDP').value = p['max-udp-relay-packet-size'] || '';
  document.getElementById('tuicReqTimeout').value = p['request-timeout'] || '';
  document.getElementById('tuicMaxStreams').value = p['max-open-streams'] || '';
  document.getElementById('tuicDisableSNI').checked = !!p['disable-sni'];
  document.getElementById('tuicReduceRTT').checked = !!p['reduce-rtt'];
  document.getElementById('tuicFastOpen').checked = !!p['fast-open'];

  // WireGuard
  document.getElementById('wgPrivateKey').value = p['private-key'] || '';
  document.getElementById('wgIP').value = p.ip || '';
  document.getElementById('wgIPv6').value = p.ipv6 || '';
  // In simple mode, use top-level server/port/public-key
  document.getElementById('wgPublicKey').value = p['public-key'] || '';
  document.getElementById('wgAllowedIPs').value = Array.isArray(p['allowed-ips']) ? p['allowed-ips'].join(', ') : (p['allowed-ips'] || '0.0.0.0/0');
  document.getElementById('wgPreSharedKey').value = p['pre-shared-key'] || '';
  document.getElementById('wgReserved').value = Array.isArray(p.reserved) ? p.reserved.join(',') : (p.reserved || '');
  document.getElementById('wgMTU').value = p.mtu || '';
  document.getElementById('wgKeepalive').value = p['persistent-keepalive'] || '';
  document.getElementById('wgRemoteDNS').checked = !!p['remote-dns-resolve'];
  document.getElementById('wgDNS').value = Array.isArray(p.dns) ? p.dns.join(', ') : (p.dns || '');
  document.getElementById('wgDialerProxy').value = p['dialer-proxy'] || '';

  // Snell
  document.getElementById('snellPSK').value = p.psk || '';
  document.getElementById('snellVersion').value = p.version || '3';
  const snellObfsMode = (p['obfs-opts'] || {}).mode || '';
  document.getElementById('snellObfsMode').value = snellObfsMode;
  document.getElementById('snellObfsHost').value = (p['obfs-opts'] || {}).host || '';
  const snellHostRow = document.getElementById('pf-snell-obfs-host');
  if (snellHostRow) snellHostRow.classList.toggle('hidden', !snellObfsMode);

  // SSH
  document.getElementById('sshUsername').value = p.username || '';
  document.getElementById('sshPassword').value = p.password || '';
  document.getElementById('sshPrivateKey').value = p['private-key'] || '';
  document.getElementById('sshPrivateKeyPass').value = p['private-key-passphrase'] || '';
  document.getElementById('sshHostKey').value = Array.isArray(p['host-key']) ? p['host-key'].join('\n') : '';
  document.getElementById('sshHostKeyAlgos').value = Array.isArray(p['host-key-algorithms']) ? p['host-key-algorithms'].join('\n') : '';

  // AnyTLS
  document.getElementById('anytlsPassword').value = p.password || '';

  // Mieru
  document.getElementById('mieruUsername').value = p.username || '';
  document.getElementById('mieruPassword').value = p.password || '';
  if (p.transport) document.getElementById('mieruTransport').value = p.transport;

  // HTTP/SOCKS5
  document.getElementById('httpUsername').value = p.username || '';
  document.getElementById('httpPassword').value = p.password || '';
  if (p.headers && typeof p.headers === 'object') {
    document.getElementById('httpHeaders').value = Object.entries(p.headers)
      .map(([k, v]) => `${k}: ${Array.isArray(v) ? v.join(', ') : v}`).join('\n');
  } else {
    document.getElementById('httpHeaders').value = '';
  }

  updateProxyFields();
  // Trigger plugin-related visibility
  document.getElementById('ssPlugin').dispatchEvent(new Event('change'));
  document.getElementById('proxyModal').classList.remove('hidden');
}

function objToYAML(obj, indent) {
  if (!obj || typeof obj !== 'object') return String(obj || '');
  indent = indent || '';
  return Object.entries(obj).map(([k, v]) => {
    if (v && typeof v === 'object') {
      return `${indent}${k}:\n${objToYAML(v, indent + '  ')}`;
    }
    return `${indent}${k}: ${v}`;
  }).join('\n');
}

function parseYAMLToObj(text) {
  const result = {};
  if (!text) return result;
  text.split('\n').forEach(line => {
    const idx = line.indexOf(':');
    if (idx > 0) {
      const k = line.slice(0, idx).trim();
      const v = line.slice(idx + 1).trim();
      if (k) result[k] = v;
    }
  });
  return result;
}

function closeProxyModal() {
  document.getElementById('proxyModal').classList.add('hidden');
}

document.getElementById('addProxyBtn').addEventListener('click', () => openProxyModal(null));
document.getElementById('closeProxyModal').addEventListener('click', closeProxyModal);
document.getElementById('cancelProxyBtn').addEventListener('click', closeProxyModal);
document.getElementById('proxyModalBackdrop').addEventListener('click', closeProxyModal);

document.getElementById('saveProxyBtn').addEventListener('click', () => {
  const type = document.getElementById('proxyType').value;
  const name = document.getElementById('proxyName').value.trim();

  if (!name) { showToast('Имя прокси обязательно', 'error'); return; }

  const proxy = { name, type };

  // Server/port
  if (type !== 'direct') {
    proxy.server = gVal('proxyServer').trim();
    const port = parseInt(gVal('proxyPort'));
    if (port) proxy.port = port;
  }

  // Common advanced
  proxy.udp = gCheck('proxyUDP');
  const ipVer = gVal('proxyIPVersion');
  if (ipVer) proxy['ip-version'] = ipVer;
  const iface = gVal('proxyInterface').trim();
  if (iface) proxy['interface-name'] = iface;
  const mark = parseInt(gVal('proxyRoutingMark'));
  if (mark) proxy['routing-mark'] = mark;
  if (gCheck('proxyTFO')) proxy.tfo = true;
  if (gCheck('proxyMPTCP')) proxy.mptcp = true;
  const dialerProxy = gVal('proxyDialerProxy').trim();
  if (dialerProxy) proxy['dialer-proxy'] = dialerProxy;

  // TLS
  const hasTLS = TLS_PROTOS.includes(type);
  const tlsAlways = TLS_ALWAYS.includes(type);
  const tlsEnabled = tlsAlways || (hasTLS && gCheck('proxyTLS'));

  if (hasTLS) {
    if (!tlsAlways) proxy.tls = gCheck('proxyTLS');
    if (tlsEnabled) {
      const sni = gVal('proxySNI').trim();
      if (sni) {
        if (type === 'vmess' || type === 'vless') proxy.servername = sni;
        else proxy.sni = sni;
      }
      const fp = gVal('proxyFingerprint').trim();
      if (fp) proxy.fingerprint = fp;
      const alpn = gLines('proxyALPN');
      if (alpn.length) proxy.alpn = alpn;
      if (gCheck('proxySkipCertVerify')) proxy['skip-cert-verify'] = true;
      if (CLIENT_FP_PROTOS.includes(type)) {
        const cfp = gVal('proxyClientFP');
        if (cfp) proxy['client-fingerprint'] = cfp;
      }
      // Reality
      if (REALITY_PROTOS.includes(type)) {
        const rPubKey = gVal('realityPublicKey').trim();
        const rShortID = gVal('realityShortID').trim();
        if (rPubKey || rShortID) {
          proxy['reality-opts'] = {};
          if (rPubKey) proxy['reality-opts']['public-key'] = rPubKey;
          if (rShortID) proxy['reality-opts']['short-id'] = rShortID;
          if (gCheck('realityX25519mlkem')) proxy['reality-opts']['support-x25519mlkem768'] = true;
        }
      }
      // ECH
      if (gCheck('echEnable')) {
        proxy['ech-opts'] = { enable: true };
        const echCfg = gVal('echConfig').trim();
        if (echCfg) proxy['ech-opts'].config = echCfg;
        const echQ = gVal('echQuerySNI').trim();
        if (echQ) proxy['ech-opts']['query-server-name'] = echQ;
      }
    }
  }

  // Transport
  if (TRANSPORT_PROTOS.includes(type)) {
    const net = gVal('proxyNetwork');
    if (net) {
      proxy.network = net;
      switch (net) {
        case 'ws': {
          const wsOpts = {};
          const wsPath = gVal('wsPath').trim();
          if (wsPath) wsOpts.path = wsPath;
          const wsHost = gVal('wsHost').trim();
          if (wsHost) wsOpts.headers = { Host: wsHost };
          const wsMaxED = parseInt(gVal('wsMaxEarlyData'));
          if (wsMaxED) wsOpts['max-early-data'] = wsMaxED;
          const wsEDH = gVal('wsEarlyDataHeader').trim();
          if (wsEDH) wsOpts['early-data-header-name'] = wsEDH;
          if (gCheck('wsV2rayUpgrade')) wsOpts['v2ray-http-upgrade'] = true;
          if (gCheck('wsV2rayUpgradeFO')) wsOpts['v2ray-http-upgrade-fast-open'] = true;
          if (Object.keys(wsOpts).length) proxy['ws-opts'] = wsOpts;
          break;
        }
        case 'h2': {
          const h2Opts = {};
          const h2host = gLines('h2Host');
          if (h2host.length) h2Opts.host = h2host;
          const h2path = gVal('h2Path').trim();
          if (h2path) h2Opts.path = h2path;
          if (Object.keys(h2Opts).length) proxy['h2-opts'] = h2Opts;
          break;
        }
        case 'grpc': {
          const grpcOpts = {};
          const gsn = gVal('grpcServiceName').trim();
          if (gsn) grpcOpts['grpc-service-name'] = gsn;
          const gua = gVal('grpcUserAgent').trim();
          if (gua) grpcOpts['grpc-user-agent'] = gua;
          if (Object.keys(grpcOpts).length) proxy['grpc-opts'] = grpcOpts;
          break;
        }
        case 'http': {
          const httpOpts = {};
          const method = gVal('httpTransportMethod');
          if (method) httpOpts.method = method;
          const paths = gLines('httpTransportPath');
          if (paths.length) httpOpts.path = paths;
          if (Object.keys(httpOpts).length) proxy['http-opts'] = httpOpts;
          break;
        }
        case 'xhttp': {
          if (type === 'vless') {
            const xopts = {};
            const xp = gVal('xhttpPath').trim();
            if (xp) xopts.path = xp;
            const xh = gVal('xhttpHost').trim();
            if (xh) xopts.host = xh;
            const xm = gVal('xhttpMode');
            if (xm) xopts.mode = xm;
            if (gCheck('xhttpNoGRPC')) xopts['no-grpc-header'] = true;
            const xpad = gVal('xhttpPaddingBytes').trim();
            if (xpad) xopts['x-padding-bytes'] = xpad;
            if (Object.keys(xopts).length) proxy['xhttp-opts'] = xopts;
          }
          break;
        }
      }
    }
  }

  // ── Protocol-specific ──
  switch (type) {
    case 'ss': {
      proxy.cipher = gVal('ssCipher');
      proxy.password = gVal('ssPassword');
      if (gCheck('ssUOT')) {
        proxy['udp-over-tcp'] = true;
        const uotVer = parseInt(getSegBtn('ssUOTVersionSwitch') || '1');
        if (uotVer === 2) proxy['udp-over-tcp-version'] = 2;
      }
      const plugin = gVal('ssPlugin');
      if (plugin) {
        proxy.plugin = plugin;
        if (plugin === 'obfs') {
          proxy['plugin-opts'] = { mode: gVal('ssObfsMode') || 'tls' };
          const oh = gVal('ssObfsHost').trim();
          if (oh) proxy['plugin-opts'].host = oh;
        } else {
          const optsText = gVal('ssPluginOpts').trim();
          if (optsText) proxy['plugin-opts'] = parseYAMLToObj(optsText);
        }
      }
      break;
    }
    case 'ssr': {
      proxy.cipher = gVal('ssrCipher');
      proxy.password = gVal('ssrPassword');
      proxy.protocol = gVal('ssrProtocol');
      const pp = gVal('ssrProtocolParam').trim();
      if (pp) proxy['protocol-param'] = pp;
      proxy.obfs = gVal('ssrObfs');
      const op = gVal('ssrObfsParam').trim();
      if (op) proxy['obfs-param'] = op;
      break;
    }
    case 'vmess': {
      proxy.uuid = gVal('vmessUUID').trim();
      proxy.alterId = gNum('vmessAlterID', 0);
      proxy.cipher = gVal('vmessCipher');
      break;
    }
    case 'vless': {
      proxy.uuid = gVal('vlessUUID').trim();
      const flow = gVal('vlessFlow');
      if (flow) proxy.flow = flow;
      proxy.encryption = gVal('vlessEncryption') || 'none';
      break;
    }
    case 'trojan': {
      proxy.password = gVal('trojanPassword');
      break;
    }
    case 'hysteria': {
      proxy['auth-str'] = gVal('hyAuthStr');
      proxy.protocol = gVal('hyProtocol') || 'udp';
      const up = gVal('hyUp').trim();
      if (up) proxy.up = up;
      const down = gVal('hyDown').trim();
      if (down) proxy.down = down;
      const obfs = gVal('hyObfs').trim();
      if (obfs) proxy.obfs = obfs;
      const ports = gVal('hyPorts').trim();
      if (ports) proxy.ports = ports;
      if (gCheck('hyFastOpen')) proxy['fast-open'] = true;
      break;
    }
    case 'hysteria2': {
      proxy.password = gVal('hy2Password');
      const up2 = gVal('hy2Up').trim();
      if (up2) proxy.up = up2;
      const down2 = gVal('hy2Down').trim();
      if (down2) proxy.down = down2;
      const obfsType = gVal('hy2ObfsType');
      if (obfsType) {
        proxy.obfs = obfsType;
        const obfsPass = gVal('hy2ObfsPassword').trim();
        if (obfsPass) proxy['obfs-password'] = obfsPass;
      }
      const ports2 = gVal('hy2Ports').trim();
      if (ports2) proxy.ports = ports2;
      const hopInt = parseInt(gVal('hy2HopInterval'));
      if (hopInt) proxy['hop-interval'] = hopInt;
      break;
    }
    case 'tuic': {
      const tuicV = getSegBtn('tuicVersionSwitch') || '5';
      if (tuicV === '4') {
        proxy.token = gVal('tuicToken').trim();
      } else {
        proxy.uuid = gVal('tuicUUID').trim();
        proxy.password = gVal('tuicPassword');
      }
      const tip = gVal('tuicIP').trim();
      if (tip) proxy.ip = tip;
      const hb = parseInt(gVal('tuicHeartbeat'));
      if (hb) proxy['heartbeat-interval'] = hb;
      proxy['udp-relay-mode'] = gVal('tuicUDPMode') || 'native';
      const cong = gVal('tuicCongestion');
      if (cong) proxy['congestion-controller'] = cong;
      const maxUDP = parseInt(gVal('tuicMaxUDP'));
      if (maxUDP) proxy['max-udp-relay-packet-size'] = maxUDP;
      const reqTOut = parseInt(gVal('tuicReqTimeout'));
      if (reqTOut) proxy['request-timeout'] = reqTOut;
      const maxStr = parseInt(gVal('tuicMaxStreams'));
      if (maxStr) proxy['max-open-streams'] = maxStr;
      if (gCheck('tuicDisableSNI')) proxy['disable-sni'] = true;
      if (gCheck('tuicReduceRTT')) proxy['reduce-rtt'] = true;
      if (gCheck('tuicFastOpen')) proxy['fast-open'] = true;
      break;
    }
    case 'wireguard': {
      proxy['private-key'] = gVal('wgPrivateKey').trim();
      const wip = gVal('wgIP').trim();
      if (wip) proxy.ip = wip;
      const wipv6 = gVal('wgIPv6').trim();
      if (wipv6) proxy.ipv6 = wipv6;
      proxy['public-key'] = gVal('wgPublicKey').trim();
      const allowedIPs = gVal('wgAllowedIPs').split(',').map(s => s.trim()).filter(Boolean);
      proxy['allowed-ips'] = allowedIPs.length ? allowedIPs : ['0.0.0.0/0'];
      const psk = gVal('wgPreSharedKey').trim();
      if (psk) proxy['pre-shared-key'] = psk;
      const wgRes = gVal('wgReserved').trim();
      if (wgRes) proxy.reserved = wgRes;
      const wgMTU = parseInt(gVal('wgMTU'));
      if (wgMTU) proxy.mtu = wgMTU;
      const wgKA = parseInt(gVal('wgKeepalive'));
      if (wgKA) proxy['persistent-keepalive'] = wgKA;
      if (gCheck('wgRemoteDNS')) {
        proxy['remote-dns-resolve'] = true;
        const wgDNS = gVal('wgDNS').split(',').map(s => s.trim()).filter(Boolean);
        if (wgDNS.length) proxy.dns = wgDNS;
      }
      const wgDP = gVal('wgDialerProxy').trim();
      if (wgDP) proxy['dialer-proxy'] = wgDP;
      break;
    }
    case 'snell': {
      proxy.psk = gVal('snellPSK');
      proxy.version = parseInt(gVal('snellVersion')) || 3;
      const snellMode = gVal('snellObfsMode');
      if (snellMode) {
        proxy['obfs-opts'] = { mode: snellMode };
        const sh = gVal('snellObfsHost').trim();
        if (sh) proxy['obfs-opts'].host = sh;
      }
      break;
    }
    case 'ssh': {
      proxy.username = gVal('sshUsername').trim();
      const sshPass = gVal('sshPassword');
      if (sshPass) proxy.password = sshPass;
      const sshPK = gVal('sshPrivateKey').trim();
      if (sshPK) proxy['private-key'] = sshPK;
      const sshPKP = gVal('sshPrivateKeyPass').trim();
      if (sshPKP) proxy['private-key-passphrase'] = sshPKP;
      const sshHK = gLines('sshHostKey');
      if (sshHK.length) proxy['host-key'] = sshHK;
      const sshHKA = gLines('sshHostKeyAlgos');
      if (sshHKA.length) proxy['host-key-algorithms'] = sshHKA;
      break;
    }
    case 'anytls': {
      proxy.password = gVal('anytlsPassword');
      break;
    }
    case 'mieru': {
      proxy.username = gVal('mieruUsername').trim();
      proxy.password = gVal('mieruPassword');
      proxy.transport = gVal('mieruTransport');
      break;
    }
    case 'http':
    case 'socks5': {
      const uname = gVal('httpUsername').trim();
      if (uname) proxy.username = uname;
      const upass = gVal('httpPassword');
      if (upass) proxy.password = upass;
      if (type === 'http') {
        const hdrText = gVal('httpHeaders').trim();
        if (hdrText) {
          const hdrs = {};
          hdrText.split('\n').forEach(line => {
            const idx = line.indexOf(':');
            if (idx > 0) {
              hdrs[line.slice(0, idx).trim()] = [line.slice(idx + 1).trim()];
            }
          });
          if (Object.keys(hdrs).length) proxy.headers = hdrs;
        }
      }
      break;
    }
    case 'direct':
      // nothing extra needed
      break;
  }

  // Save to config
  const proxies = getProxies();
  if (editProxyName) {
    const idx = proxies.findIndex(p => p.name === editProxyName);
    if (idx >= 0) {
      const oldName = proxies[idx].name;
      proxies[idx] = proxy;
      if (proxy.name !== oldName) {
        getGroups().forEach(g => {
          if (g.proxies) g.proxies = g.proxies.map(pn => pn === oldName ? proxy.name : pn);
        });
      }
    }
  } else {
    if (proxies.some(p => p.name === proxy.name)) {
      showToast('Прокси с таким именем уже существует', 'error');
      return;
    }
    proxies.push(proxy);
  }
  PS.config.proxies = proxies;
  closeProxyModal();
  renderAll();
  saveFullConfig(false);
});

// Proxy list delegated events
document.getElementById('proxyList').addEventListener('click', e => {
  const editBtn = e.target.closest('[data-edit-proxy]');
  const delBtn = e.target.closest('[data-delete-proxy]');
  if (editBtn) {
    const proxy = getProxies().find(p => p.name === editBtn.dataset.editProxy);
    if (proxy) openProxyModal(proxy);
  } else if (delBtn) {
    const name = delBtn.dataset.deleteProxy;
    if (confirm(`Удалить прокси "${name}"?`)) {
      PS.config.proxies = getProxies().filter(p => p.name !== name);
      getGroups().forEach(g => { if (g.proxies) g.proxies = g.proxies.filter(pn => pn !== name); });
      renderAll(); saveFullConfig(false);
    }
  }
});

// ════════════════════════════════════════════════════════════
// GROUP MODAL
// ════════════════════════════════════════════════════════════

function updateGroupFields() {
  const type = document.getElementById('groupType').value;
  const urlField = document.getElementById('groupURLField');
  const lbField = document.getElementById('groupLBStrategyDiv');
  const needsURL = ['url-test', 'fallback', 'load-balance'].includes(type);
  urlField.classList.toggle('hidden', !needsURL);
  lbField.classList.toggle('hidden', type !== 'load-balance');
}

document.getElementById('groupType').addEventListener('change', updateGroupFields);

let editGroupName = null;

function openGroupModal(group) {
  editGroupName = group ? group.name : null;
  document.getElementById('groupEditName').value = group ? group.name : '';
  document.getElementById('groupModalTitle').textContent = group ? 'Редактировать группу' : 'Добавить группу';
  document.getElementById('groupName').value = group ? group.name : '';
  document.getElementById('groupType').value = group ? group.type : 'select';
  document.getElementById('groupURL').value = group ? (group.url || 'https://www.gstatic.com/generate_204') : 'https://www.gstatic.com/generate_204';
  document.getElementById('groupInterval').value = group ? (group.interval || 300) : 300;
  document.getElementById('groupTimeout').value = group ? (group.timeout || 5000) : 5000;
  document.getElementById('groupTolerance').value = group ? (group.tolerance || 50) : 50;
  document.getElementById('groupMaxFailed').value = group ? (group['max-failed-times'] || 5) : 5;
  document.getElementById('groupLazy').checked = group ? (group.lazy !== false) : true;
  document.getElementById('groupIncludeAll').checked = group ? (group['include-all'] || false) : false;
  document.getElementById('groupFilter').value = group ? (group.filter || '') : '';
  document.getElementById('groupExcludeFilter').value = group ? (group['exclude-filter'] || '') : '';
  document.getElementById('groupExcludeType').value = group ? (group['exclude-type'] || '') : '';
  document.getElementById('groupExpectedStatus').value = group ? (group['expected-status'] || '') : '';
  document.getElementById('groupLBStrategy').value = group ? (group.strategy || 'round-robin') : 'round-robin';
  document.getElementById('groupDisableUDP').checked = group ? (group['disable-udp'] || false) : false;

  const checkboxes = document.getElementById('groupProxyCheckboxes');
  checkboxes.innerHTML = '';
  const selected = group ? (group.proxies || []) : [];
  const allItems = ['DIRECT', 'REJECT', ...getProxies().map(p => p.name), ...getGroups().filter(g => !group || g.name !== group.name).map(g => g.name)];
  allItems.forEach(name => {
    const label = document.createElement('label');
    label.className = 'checkbox-label';
    label.style.cssText = 'font-size:.85rem;padding:4px 0;display:flex;align-items:center;gap:6px';
    const cb = document.createElement('input');
    cb.type = 'checkbox'; cb.value = name;
    if (selected.includes(name)) cb.checked = true;
    label.appendChild(cb);
    label.appendChild(document.createTextNode(name));
    checkboxes.appendChild(label);
  });

  updateGroupFields();
  document.getElementById('groupModal').classList.remove('hidden');
}

function closeGroupModal() { document.getElementById('groupModal').classList.add('hidden'); }

document.getElementById('addGroupBtn').addEventListener('click', () => openGroupModal(null));
document.getElementById('closeGroupModal').addEventListener('click', closeGroupModal);
document.getElementById('cancelGroupBtn').addEventListener('click', closeGroupModal);
document.getElementById('groupModalBackdrop').addEventListener('click', closeGroupModal);

document.getElementById('saveGroupBtn').addEventListener('click', () => {
  const selectedProxies = [];
  document.querySelectorAll('#groupProxyCheckboxes input[type="checkbox"]:checked').forEach(cb => selectedProxies.push(cb.value));

  const type = document.getElementById('groupType').value;
  const hasURL = ['url-test', 'fallback', 'load-balance'].includes(type);
  const group = {
    name: document.getElementById('groupName').value.trim(),
    type,
    proxies: selectedProxies,
    'include-all': gCheck('groupIncludeAll'),
    'disable-udp': gCheck('groupDisableUDP'),
  };

  if (hasURL) {
    group.url = gVal('groupURL').trim();
    group.interval = gNum('groupInterval', 300);
    group.timeout = gNum('groupTimeout', 5000);
    group.lazy = gCheck('groupLazy');
    const maxFailed = gNum('groupMaxFailed', 0);
    if (maxFailed) group['max-failed-times'] = maxFailed;
  }
  if (type === 'url-test') {
    const tol = gNum('groupTolerance', 0);
    if (tol) group.tolerance = tol;
  }
  if (type === 'load-balance') {
    group.strategy = gVal('groupLBStrategy') || 'round-robin';
  }
  const filter = gVal('groupFilter').trim();
  if (filter) group.filter = filter;
  const exFilter = gVal('groupExcludeFilter').trim();
  if (exFilter) group['exclude-filter'] = exFilter;
  const exType = gVal('groupExcludeType').trim();
  if (exType) group['exclude-type'] = exType;
  const expStatus = gVal('groupExpectedStatus').trim();
  if (expStatus) group['expected-status'] = expStatus;

  if (!group.name) { showToast('Имя группы обязательно', 'error'); return; }

  const groups = getGroups();
  if (editGroupName) {
    const idx = groups.findIndex(g => g.name === editGroupName);
    if (idx >= 0) groups[idx] = group;
  } else {
    if (groups.some(g => g.name === group.name)) { showToast('Группа с таким именем уже существует', 'error'); return; }
    groups.push(group);
  }
  PS.config['proxy-groups'] = groups;
  closeGroupModal(); renderAll(); saveFullConfig(false);
});

document.getElementById('groupList').addEventListener('click', e => {
  const editBtn = e.target.closest('[data-edit-group]');
  const delBtn = e.target.closest('[data-delete-group]');
  if (editBtn) {
    const group = getGroups().find(g => g.name === editBtn.dataset.editGroup);
    if (group) openGroupModal(group);
  } else if (delBtn) {
    const name = delBtn.dataset.deleteGroup;
    if (confirm(`Удалить группу "${name}"?`)) {
      PS.config['proxy-groups'] = getGroups().filter(g => g.name !== name);
      PS.config.rules = getRules().map(r => r.endsWith(',' + name) ? r.replace(',' + name, ',DIRECT') : r);
      renderAll(); saveFullConfig(false);
    }
  }
});

// ════════════════════════════════════════════════════════════
// RULE MODAL
// ════════════════════════════════════════════════════════════

const NO_RESOLVE_TYPES = ['IP-CIDR', 'IP-CIDR6', 'IP-SUFFIX', 'IP-ASN', 'GEOIP', 'SRC-IP-CIDR', 'SRC-IP-SUFFIX', 'SRC-IP-ASN', 'SRC-GEOIP'];

function updateRuleFields() {
  const type = document.getElementById('ruleType').value;
  const valueDiv = document.getElementById('ruleValueField');
  const noResolveDiv = document.getElementById('ruleNoResolveDiv');
  valueDiv.classList.toggle('hidden', type === 'MATCH');
  noResolveDiv.classList.toggle('hidden', !NO_RESOLVE_TYPES.includes(type));

  const sel = document.getElementById('ruleProxy');
  const cur = sel.value;
  sel.innerHTML = '<option value="DIRECT">DIRECT</option><option value="REJECT">REJECT</option><option value="REJECT-DROP">REJECT-DROP</option>';
  getGroups().forEach(g => {
    const opt = document.createElement('option');
    opt.value = g.name; opt.textContent = g.name;
    sel.appendChild(opt);
  });
  getProxies().filter(p => p.type !== 'direct').forEach(p => {
    const opt = document.createElement('option');
    opt.value = p.name; opt.textContent = p.name;
    sel.appendChild(opt);
  });
  if ([...sel.options].some(o => o.value === cur)) sel.value = cur;
}

document.getElementById('ruleType').addEventListener('change', updateRuleFields);

function openRuleModal() {
  document.getElementById('ruleType').value = 'DOMAIN-SUFFIX';
  document.getElementById('ruleValue').value = '';
  document.getElementById('ruleNoResolve').checked = true;
  updateRuleFields();
  document.getElementById('ruleModal').classList.remove('hidden');
}

function closeRuleModal() { document.getElementById('ruleModal').classList.add('hidden'); }

document.getElementById('addRuleBtn').addEventListener('click', openRuleModal);
document.getElementById('addBlockBtn').addEventListener('click', () => {
  openRuleModal();
  document.getElementById('ruleType').value = 'DOMAIN-KEYWORD';
  document.getElementById('ruleProxy').value = 'REJECT';
  updateRuleFields();
});
document.getElementById('closeRuleModal').addEventListener('click', closeRuleModal);
document.getElementById('cancelRuleBtn').addEventListener('click', closeRuleModal);
document.getElementById('ruleModalBackdrop').addEventListener('click', closeRuleModal);

document.getElementById('saveRuleBtn').addEventListener('click', () => {
  const type = document.getElementById('ruleType').value;
  const value = gVal('ruleValue').trim();
  const proxy = gVal('ruleProxy');
  const noResolve = gCheck('ruleNoResolve');

  if (type !== 'MATCH' && !value) { showToast('Введите значение правила', 'error'); return; }

  let rule;
  if (type === 'MATCH') {
    rule = `MATCH,${proxy}`;
  } else {
    rule = `${type},${value},${proxy}`;
    if (NO_RESOLVE_TYPES.includes(type) && noResolve) rule += ',no-resolve';
  }

  const rules = getRules();
  rules.push(rule);
  PS.config.rules = rules;
  closeRuleModal(); renderAll(); saveFullConfig(false);
});

document.getElementById('rulesList').addEventListener('click', e => {
  const delBtn = e.target.closest('[data-delete-rule]');
  if (delBtn) {
    const idx = parseInt(delBtn.dataset.deleteRule);
    if (!isNaN(idx)) {
      const rules = getRules();
      rules.splice(idx, 1);
      PS.config.rules = rules;
      renderAll(); saveFullConfig(false);
    }
  }
});

// ════════════════════════════════════════════════════════════
// PROXY PROVIDERS
// ════════════════════════════════════════════════════════════

let editPPName = null;

function openPPModal(provider) {
  editPPName = provider ? provider.name : null;
  document.getElementById('ppModalTitle').textContent = provider ? 'Редактировать провайдер' : 'Добавить провайдер прокси';
  document.getElementById('ppEditName').value = provider ? provider.name : '';
  document.getElementById('ppName').value = provider ? provider.name : '';
  document.getElementById('ppType').value = provider ? (provider.type || 'http') : 'http';
  document.getElementById('ppURL').value = provider ? (provider.url || '') : '';
  document.getElementById('ppPath').value = provider ? (provider.path || '') : '';
  document.getElementById('ppInterval').value = provider ? (provider.interval || 3600) : 3600;
  document.getElementById('ppProxy').value = provider ? (provider.proxy || '') : '';
  const hc = (provider && provider['health-check']) || {};
  document.getElementById('ppHCEnable').checked = hc.enable !== false;
  document.getElementById('ppHCURL').value = hc.url || 'https://www.gstatic.com/generate_204';
  document.getElementById('ppHCInterval').value = hc.interval || 300;
  document.getElementById('ppHCTimeout').value = hc.timeout || 5000;
  document.getElementById('ppHCLazy').checked = hc.lazy !== false;
  document.getElementById('ppFilter').value = provider ? (provider.filter || '') : '';
  document.getElementById('ppExcludeFilter').value = provider ? (provider['exclude-filter'] || '') : '';
  document.getElementById('ppExcludeType').value = provider ? (provider['exclude-type'] || '') : '';
  const ov = (provider && provider.override) || {};
  document.getElementById('ppOverridePrefix').value = ov['additional-prefix'] || '';
  document.getElementById('ppOverrideSuffix').value = ov['additional-suffix'] || '';
  document.getElementById('ppOverrideSkipCert').checked = !!ov['skip-cert-verify'];
  document.getElementById('ppOverrideUDP').checked = !!ov.udp;
  document.getElementById('ppOverrideIPVersion').value = ov['ip-version'] || '';
  updatePPTypeFields();
  document.getElementById('proxyProviderModal').classList.remove('hidden');
}

function updatePPTypeFields() {
  const type = document.getElementById('ppType').value;
  const urlRow = document.getElementById('pp-url-row');
  const intRow = document.getElementById('pp-interval-row');
  if (urlRow) urlRow.classList.toggle('hidden', type === 'file' || type === 'inline');
  if (intRow) intRow.classList.toggle('hidden', type === 'inline');
}

document.getElementById('ppType').addEventListener('change', updatePPTypeFields);

function closePPModal() { document.getElementById('proxyProviderModal').classList.add('hidden'); }

document.getElementById('addProxyProviderBtn').addEventListener('click', () => openPPModal(null));
document.getElementById('closePPModal').addEventListener('click', closePPModal);
document.getElementById('cancelPPBtn').addEventListener('click', closePPModal);
document.getElementById('ppModalBackdrop').addEventListener('click', closePPModal);

document.getElementById('savePPBtn').addEventListener('click', () => {
  const name = gVal('ppName').trim();
  if (!name) { showToast('Имя провайдера обязательно', 'error'); return; }

  const provider = {
    type: gVal('ppType'),
  };
  const url = gVal('ppURL').trim();
  if (url) provider.url = url;
  const path = gVal('ppPath').trim();
  if (path) provider.path = path;
  const interval = gNum('ppInterval', 0);
  if (interval) provider.interval = interval;
  const proxy = gVal('ppProxy').trim();
  if (proxy) provider.proxy = proxy;

  if (gCheck('ppHCEnable')) {
    provider['health-check'] = {
      enable: true,
      url: gVal('ppHCURL') || 'https://www.gstatic.com/generate_204',
      interval: gNum('ppHCInterval', 300),
      timeout: gNum('ppHCTimeout', 5000),
      lazy: gCheck('ppHCLazy'),
    };
  }

  const filter = gVal('ppFilter').trim();
  if (filter) provider.filter = filter;
  const exFilter = gVal('ppExcludeFilter').trim();
  if (exFilter) provider['exclude-filter'] = exFilter;
  const exType = gVal('ppExcludeType').trim();
  if (exType) provider['exclude-type'] = exType;

  const override = {};
  const prefix = gVal('ppOverridePrefix').trim();
  if (prefix) override['additional-prefix'] = prefix;
  const suffix = gVal('ppOverrideSuffix').trim();
  if (suffix) override['additional-suffix'] = suffix;
  if (gCheck('ppOverrideSkipCert')) override['skip-cert-verify'] = true;
  if (gCheck('ppOverrideUDP')) override.udp = true;
  const ipVer = gVal('ppOverrideIPVersion');
  if (ipVer) override['ip-version'] = ipVer;
  if (Object.keys(override).length) provider.override = override;

  if (!PS.config['proxy-providers']) PS.config['proxy-providers'] = {};
  if (editPPName && editPPName !== name) {
    delete PS.config['proxy-providers'][editPPName];
  }
  PS.config['proxy-providers'][name] = provider;
  closePPModal(); renderAll(); saveFullConfig(false);
});

document.getElementById('proxyProviderList').addEventListener('click', e => {
  const editBtn = e.target.closest('[data-edit-pp]');
  const delBtn = e.target.closest('[data-delete-pp]');
  if (editBtn) {
    const name = editBtn.dataset.editPp;
    const pp = PS.config['proxy-providers'] && PS.config['proxy-providers'][name];
    if (pp) openPPModal({ name, ...pp });
  } else if (delBtn) {
    const name = delBtn.dataset.deletePp;
    if (confirm(`Удалить провайдер "${name}"?`)) {
      if (PS.config['proxy-providers']) delete PS.config['proxy-providers'][name];
      renderAll(); saveFullConfig(false);
    }
  }
});

// ════════════════════════════════════════════════════════════
// RULE PROVIDERS
// ════════════════════════════════════════════════════════════

let editRPName = null;

function openRPModal(provider) {
  editRPName = provider ? provider.name : null;
  document.getElementById('rpModalTitle').textContent = provider ? 'Редактировать провайдер правил' : 'Добавить провайдер правил';
  document.getElementById('rpEditName').value = provider ? provider.name : '';
  document.getElementById('rpName').value = provider ? provider.name : '';
  document.getElementById('rpType').value = provider ? (provider.type || 'http') : 'http';
  document.getElementById('rpBehavior').value = provider ? (provider.behavior || 'domain') : 'domain';
  document.getElementById('rpFormat').value = provider ? (provider.format || 'yaml') : 'yaml';
  document.getElementById('rpURL').value = provider ? (provider.url || '') : '';
  document.getElementById('rpPath').value = provider ? (provider.path || '') : '';
  document.getElementById('rpInterval').value = provider ? (provider.interval || 600) : 600;
  document.getElementById('rpProxy').value = provider ? (provider.proxy || '') : '';
  updateRPTypeFields();
  document.getElementById('ruleProviderModal').classList.remove('hidden');
}

function updateRPTypeFields() {
  const type = document.getElementById('rpType').value;
  const urlRow = document.getElementById('rp-url-row');
  const intRow = document.getElementById('rp-interval-row');
  if (urlRow) urlRow.classList.toggle('hidden', type !== 'http');
  if (intRow) intRow.classList.toggle('hidden', type === 'inline');
}

document.getElementById('rpType').addEventListener('change', updateRPTypeFields);

function closeRPModal() { document.getElementById('ruleProviderModal').classList.add('hidden'); }

document.getElementById('addRuleProviderBtn').addEventListener('click', () => openRPModal(null));
document.getElementById('closeRPModal').addEventListener('click', closeRPModal);
document.getElementById('cancelRPBtn').addEventListener('click', closeRPModal);
document.getElementById('rpModalBackdrop').addEventListener('click', closeRPModal);

document.getElementById('saveRPBtn').addEventListener('click', () => {
  const name = gVal('rpName').trim();
  if (!name) { showToast('Имя провайдера обязательно', 'error'); return; }

  const provider = {
    type: gVal('rpType'),
    behavior: gVal('rpBehavior') || 'domain',
    format: gVal('rpFormat') || 'yaml',
  };
  const url = gVal('rpURL').trim();
  if (url) provider.url = url;
  const path = gVal('rpPath').trim();
  if (path) provider.path = path;
  const interval = gNum('rpInterval', 0);
  if (interval) provider.interval = interval;
  const proxy = gVal('rpProxy').trim();
  if (proxy) provider.proxy = proxy;

  if (!PS.config['rule-providers']) PS.config['rule-providers'] = {};
  if (editRPName && editRPName !== name) {
    delete PS.config['rule-providers'][editRPName];
  }
  PS.config['rule-providers'][name] = provider;
  closeRPModal(); renderAll(); saveFullConfig(false);
});

document.getElementById('ruleProviderList').addEventListener('click', e => {
  const editBtn = e.target.closest('[data-edit-rp]');
  const delBtn = e.target.closest('[data-delete-rp]');
  if (editBtn) {
    const name = editBtn.dataset.editRp;
    const rp = PS.config['rule-providers'] && PS.config['rule-providers'][name];
    if (rp) openRPModal({ name, ...rp });
  } else if (delBtn) {
    const name = delBtn.dataset.deleteRp;
    if (confirm(`Удалить провайдер "${name}"?`)) {
      if (PS.config['rule-providers']) delete PS.config['rule-providers'][name];
      renderAll(); saveFullConfig(false);
    }
  }
});

// ════════════════════════════════════════════════════════════
// CORE UPLOAD
// ════════════════════════════════════════════════════════════

document.getElementById('uploadCoreForm').addEventListener('submit', async e => {
  e.preventDefault();
  const fileInput = document.getElementById('coreFile');
  if (!fileInput.files[0]) { showToast('Выберите файл', 'error'); return; }
  const fd = new FormData();
  fd.append('core', fileInput.files[0]);
  try {
    const result = await fetch('/api/mihomo/upload-core', { method: 'POST', body: fd });
    const json = await result.json();
    if (!json.success) throw new Error(json.error || 'Upload failed');
    showToast('Ядро загружено: ' + (json.data.arch || json.data.path), 'success');
    await loadStatus();
  } catch (e) {
    showToast('Ошибка: ' + e.message, 'error');
  }
});

// ════════════════════════════════════════════════════════════
// YAML EDITOR
// ════════════════════════════════════════════════════════════

document.getElementById('yamlLoadBtn').addEventListener('click', async () => {
  try {
    const res = await fetch('/api/mihomo/config.yaml');
    if (res.status === 404) { document.getElementById('yamlEditor').value = '# Config not found.'; return; }
    document.getElementById('yamlEditor').value = await res.text();
    showToast('Конфиг загружен', 'info');
  } catch (e) { showToast('Ошибка: ' + e.message, 'error'); }
});

document.getElementById('yamlSaveBtn').addEventListener('click', async () => {
  const content = document.getElementById('yamlEditor').value;
  try {
    const res = await fetch('/api/mihomo/config.yaml', {
      method: 'PUT', headers: { 'Content-Type': 'text/yaml' }, body: content,
    });
    const json = await res.json();
    if (!json.success) throw new Error(json.error || 'Save failed');
    showToast('Конфиг сохранён', 'success');
    await loadConfig();
  } catch (e) { showToast('Ошибка: ' + e.message, 'error'); }
});

// ════════════════════════════════════════════════════════════
// LOGS
// ════════════════════════════════════════════════════════════

let logPollTimer = null;
let lastLogCount = 0;

async function fetchLogs() {
  try {
    const lines = await get('/api/mihomo/logs');
    const el = document.getElementById('logOutput');
    if (lines.length !== lastLogCount) {
      lastLogCount = lines.length;
      el.textContent = lines.join('\n');
      el.scrollTop = el.scrollHeight;
    }
  } catch (e) { /* ignore */ }
}

function startLogPoll() {
  if (logPollTimer) return;
  fetchLogs();
  logPollTimer = setInterval(fetchLogs, 500);
}

function stopLogPoll() {
  if (logPollTimer) { clearInterval(logPollTimer); logPollTimer = null; }
}

document.querySelectorAll('.ptab').forEach(btn => {
  btn.addEventListener('click', () => {
    if (btn.dataset.tab === 'logs') { lastLogCount = 0; startLogPoll(); }
    else stopLogPoll();
  });
});

document.getElementById('clearLogsBtn').addEventListener('click', () => {
  document.getElementById('logOutput').textContent = '';
  lastLogCount = 0;
});

// ════════════════════════════════════════════════════════════
// DASHBOARD (LIVE MIHOMO API)
// ════════════════════════════════════════════════════════════

const DA = {
  ws: null,
  trafficUp: 0,
  trafficDown: 0,
  totalUp: 0,
  totalDown: 0,
  memory: 0,
  connections: [],
  proxies: {},
  groups: {},
  version: '',
  mode: '',
  running: false,
};

function mihomoAPI(path, opts) {
  return fetch('/api/mihomo/api/' + path.replace(/^\/+/, ''), opts).then(r => {
    if (!r.ok) throw new Error('HTTP ' + r.status);
    return r.json();
  });
}

function mihomoPut(path, body) {
  return mihomoAPI(path, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });
}

function mihomoDel(path) {
  return mihomoAPI(path, { method: 'DELETE' });
}

function formatBytes(b) {
  if (b < 1024) return b.toFixed(0) + ' B';
  if (b < 1048576) return (b / 1024).toFixed(1) + ' KB';
  if (b < 1073741824) return (b / 1048576).toFixed(1) + ' MB';
  return (b / 1073741824).toFixed(2) + ' GB';
}

function formatBytesPerSec(b) {
  return formatBytes(b) + '/s';
}

function closeDashWS() {
  if (DA.ws) {
    try { DA.ws.close(); } catch(e) {}
    DA.ws = null;
  }
}

function openDashWS() {
  closeDashWS();
  const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
  const url = proto + '//' + location.host + '/api/mihomo/ws/traffic';
  try {
    const ws = new WebSocket(url);
    ws.onmessage = (e) => {
      try {
        const d = JSON.parse(e.data);
        DA.trafficUp = d.up || 0;
        DA.trafficDown = d.down || 0;
      } catch(err) {}
    };
    ws.onerror = () => {};
    ws.onclose = () => { DA.ws = null; };
    DA.ws = ws;
  } catch(e) {}
}

let dashTrafficInterval = null;
let dashConnsInterval = null;

async function loadDashInitial() {
  try {
    const vdata = await mihomoAPI('version');
    DA.version = vdata.version || '?';
  } catch(e) { DA.version = '?'; }

  try {
    const cfg = await mihomoAPI('configs');
    DA.mode = cfg.mode || 'rule';
    renderDashMode();
  } catch(e) {}

  await loadDashProxies();
  await loadDashConnections();
  renderDash();
}

async function loadDashProxies() {
  try {
    const data = await mihomoAPI('proxies');
    const all = data.proxies || data || {};
    DA.proxies = {};
    DA.groups = {};
    for (const [name, p] of Object.entries(all)) {
      if ((p.all && p.all.length > 0) || p.type === 'Selector' || p.type === 'URLTest' || p.type === 'Fallback' || p.type === 'LoadBalance' || p.type === 'Relay') {
        DA.groups[name] = p;
      } else {
        DA.proxies[name] = p;
      }
    }
  } catch(e) { console.error('dash proxies load', e); }
}

async function loadDashConnections() {
  try {
    const data = await mihomoAPI('connections');
    DA.connections = Object.values(data.connections || {});
    renderDashConnections();
  } catch(e) { DA.connections = []; }
}

async function dashPingProxy(name) {
  try {
    await mihomoAPI('proxies/' + encodeURIComponent(name) + '/delay?timeout=5000&url=https://www.gstatic.com/generate_204');
    await loadDashProxies();
    renderDashGroups();
  } catch(e) {
    showToast('Пинг ' + name + ': ошибка', 'error');
  }
}

async function dashPingAll() {
  showToast('Пинг всех прокси...', 'info');
  const names = Object.keys(DA.proxies);
  let done = 0;
  for (const name of names) {
    mihomoAPI('proxies/' + encodeURIComponent(name) + '/delay?timeout=5000&url=https://www.gstatic.com/generate_204')
      .then(() => { done++; })
      .catch(() => { done++; })
      .finally(() => {
        if (done === names.length) {
          loadDashProxies().then(() => renderDashGroups());
          showToast('Пинг завершён', 'success');
        }
      });
  }
}

async function dashSwitchProxy(groupName, proxyName) {
  try {
    await mihomoPut('proxies/' + encodeURIComponent(groupName), { name: proxyName });
    await loadDashProxies();
    renderDashGroups();
    showToast(groupName + ' → ' + proxyName, 'success');
  } catch(e) {
    showToast('Ошибка переключения: ' + e.message, 'error');
  }
}

async function dashSetMode(mode) {
  try {
    await mihomoPut('configs', { mode });
    DA.mode = mode;
    renderDashMode();
    showToast('Режим: ' + mode, 'success');
  } catch(e) {
    showToast('Ошибка смены режима: ' + e.message, 'error');
  }
}

async function dashCloseConnection(id) {
  try {
    await mihomoDel('connections/' + encodeURIComponent(id));
    await loadDashConnections();
  } catch(e) {}
}

async function dashCloseAllConnections() {
  try {
    await mihomoDel('connections');
    DA.connections = [];
    renderDashConnections();
    showToast('Все соединения закрыты', 'success');
  } catch(e) {}
}

function renderDashMode() {
  const seg = document.getElementById('dashModeSwitch');
  if (!seg) return;
  seg.querySelectorAll('.seg-btn').forEach(b => {
    b.classList.toggle('active', b.dataset.mode === DA.mode);
  });
}

document.addEventListener('click', (e) => {
  const btn = e.target.closest('#dashModeSwitch .seg-btn');
  if (btn) dashSetMode(btn.dataset.mode);
});

function renderDash() {
  document.getElementById('dashUp').textContent = formatBytesPerSec(DA.trafficUp);
  document.getElementById('dashDown').textContent = formatBytesPerSec(DA.trafficDown);
  document.getElementById('dashUpTotal').textContent = formatBytes(DA.totalUp);
  document.getElementById('dashDownTotal').textContent = formatBytes(DA.totalDown);
  document.getElementById('dashMem').textContent = formatBytes(DA.memory);
  document.getElementById('dashVersion').textContent = DA.version;

  renderDashGroups();
  renderDashConnections();
}

function renderDashGroups() {
  const list = document.getElementById('dashGroupList');
  if (!list) return;
  list.innerHTML = '';
  const groupEntries = Object.entries(DA.groups);
  if (groupEntries.length === 0) {
    list.innerHTML = '<div class="empty-state">Нет групп прокси. Добавьте группы во вкладке «Группы».</div>';
    return;
  }
  groupEntries.sort((a, b) => {
    const order = ['Selector', 'URLTest', 'Fallback', 'LoadBalance', 'Relay'];
    return (order.indexOf(a[1].type) || 99) - (order.indexOf(b[1].type) || 99);
  });
  for (const [name, g] of groupEntries) {
    const card = document.createElement('div');
    card.className = 'dash-group-card';
    const members = (g.all || []).map(m => {
      const p = DA.proxies[m] || DA.groups[m] || {};
      const delay = p.history && p.history.length ? p.history[p.history.length - 1].delay : undefined;
      const dead = delay === undefined || delay === 0;
      let delayText = '—';
      let delayClass = 'dash-delay-unknown';
      if (!dead) {
        delayText = delay + 'ms';
        delayClass = delay < 200 ? 'dash-delay-good' : delay < 500 ? 'dash-delay-medium' : 'dash-delay-slow';
      }
      const isActive = g.now === m;
      const type = p.type || '';
      return `<div class="dash-proxy-item${isActive ? ' dash-proxy-active' : ''}" data-group="${esc(name)}" data-proxy="${esc(m)}">
        <span class="dash-proxy-name">${esc(m)}</span>
        <span class="dash-proxy-type">${esc(type)}</span>
        <span class="dash-delay ${delayClass}">${delayText}</span>
      </div>`;
    }).join('');
    card.innerHTML = `
      <div class="dash-group-header">
        <div class="dash-group-title">
          <span class="dash-group-name">${esc(name)}</span>
          <span class="tag-gw">${esc(g.type || '')}</span>
        </div>
        <button class="btn btn-ghost btn-sm dash-ping-group-btn" data-group="${esc(name)}">Пинг</button>
      </div>
      <div class="dash-proxy-list">${members || '<div class="empty-state" style="padding:8px">Нет нод</div>'}</div>`;
    list.appendChild(card);
  }
}

document.addEventListener('click', (e) => {
  const proxyItem = e.target.closest('.dash-proxy-item');
  if (proxyItem) {
    const groupName = proxyItem.dataset.group;
    const proxyName = proxyItem.dataset.proxy;
    dashSwitchProxy(groupName, proxyName);
  }
  const pingBtn = e.target.closest('.dash-ping-group-btn');
  if (pingBtn) {
    const groupName = pingBtn.dataset.group;
    const group = DA.groups[groupName];
    if (group && group.all) {
      for (const name of group.all) {
        mihomoAPI('proxies/' + encodeURIComponent(name) + '/delay?timeout=5000&url=https://www.gstatic.com/generate_204').catch(() => {});
      }
      showToast('Пинг группы ' + groupName + '...', 'info');
      setTimeout(() => loadDashProxies().then(() => renderDashGroups()), 3000);
    }
  }
  if (e.target.closest('#dashPingAllBtn')) {
    dashPingAll();
  }
  if (e.target.closest('#dashCloseAllConnsBtn')) {
    dashCloseAllConnections();
  }
  const closeConnBtn = e.target.closest('.dash-conn-close');
  if (closeConnBtn) {
    dashCloseConnection(closeConnBtn.dataset.id);
  }
});

function renderDashConnections() {
  const tbody = document.getElementById('dashConnBody');
  const countEl = document.getElementById('dashConnCount');
  if (!tbody) return;
  if (countEl) countEl.textContent = DA.connections.length;
  tbody.innerHTML = '';
  const conns = DA.connections.slice(0, 100);
  for (const c of conns) {
    const meta = c.metadata || {};
    const host = (meta.host || meta.destinationIP || '') + (meta.destinationPort ? ':' + meta.destinationPort : '');
    const chain = (c.chains || []).reverse().join(' → ') || '—';
    const tr = document.createElement('tr');
    tr.innerHTML = `
      <td class="dash-conn-host" title="${esc(host)}">${esc(host.length > 40 ? host.slice(0, 40) + '...' : host)}</td>
      <td>${esc(meta.network || '')}</td>
      <td class="dash-conn-chain" title="${esc(chain)}">${esc(chain.length > 30 ? chain.slice(0, 30) + '...' : chain)}</td>
      <td class="dash-conn-traffic">${formatBytes(c.upload || 0)}</td>
      <td class="dash-conn-traffic">${formatBytes(c.download || 0)}</td>
      <td><button class="btn-icon dash-conn-close" data-id="${esc(c.id || '')}" title="Закрыть">✕</button></td>`;
    tbody.appendChild(tr);
  }
  if (conns.length === 0) {
    tbody.innerHTML = '<tr><td colspan="6" class="empty-state" style="padding:12px">Нет активных соединений</td></tr>';
  }
}

function startDashPolling() {
  stopDashPolling();
  loadDashInitial();
  openDashWS();
  dashTrafficInterval = setInterval(() => {
    DA.totalUp += DA.trafficUp;
    DA.totalDown += DA.trafficDown;
    document.getElementById('dashUp').textContent = formatBytesPerSec(DA.trafficUp);
    document.getElementById('dashDown').textContent = formatBytesPerSec(DA.trafficDown);
    document.getElementById('dashUpTotal').textContent = formatBytes(DA.totalUp);
    document.getElementById('dashDownTotal').textContent = formatBytes(DA.totalDown);
    document.getElementById('dashMem').textContent = formatBytes(DA.memory);
  }, 1000);
  dashConnsInterval = setInterval(loadDashConnections, 3000);
}

function stopDashPolling() {
  if (dashTrafficInterval) { clearInterval(dashTrafficInterval); dashTrafficInterval = null; }
  if (dashConnsInterval) { clearInterval(dashConnsInterval); dashConnsInterval = null; }
  closeDashWS();
}

function updateDashVisibility() {
  const online = document.getElementById('dashOnline');
  const offline = document.getElementById('dashOffline');
  if (!online || !offline) return;
  if (PS.status && PS.status.running) {
    online.classList.remove('hidden');
    offline.classList.add('hidden');
  } else {
    online.classList.add('hidden');
    offline.classList.remove('hidden');
    stopDashPolling();
  }
}

// ════════════════════════════════════════════════════════════
// INIT
// ════════════════════════════════════════════════════════════

(async () => {
  try { await loadStatus(); } catch (e) { console.error('status', e); }
  try { await loadConfig(); } catch (e) { console.error('config', e); }
  updateDashVisibility();
  if (PS.status && PS.status.running) startDashPolling();
})();
