const state = {
  devices: [],
  selectedDeviceId: null,
  logs: [],
  alarms: [],
  rules: [],
  adminToken: localStorage.getItem('sst_admin_token') || '',
  hiddenLogBeforeId: JSON.parse(localStorage.getItem('sst_hidden_log_before_id') || '{}'),
  devicePreferences: JSON.parse(localStorage.getItem('sst_device_preferences') || '{}'),
  language: localStorage.getItem('sst_language') || ((navigator.language || '').toLowerCase().startsWith('cs') ? 'cs' : 'en'),
};

const $ = (id) => document.getElementById(id);
const defaultBaudrates = [300, 1200, 2400, 4800, 9600, 14400, 19200, 38400, 57600, 115200, 230400];
const defaultDevicePreferences = {
  filters: {
    RX: true,
    TX: true,
    SYS: true,
    INFO: true,
    autoScroll: true,
  },
  command: {
    add_asterisk: false,
    add_cr: false,
    add_lf: true,
    hex: false,
  },
};

const translations = {
  cs: {
    'app.subtitle': 'Webový terminál pro Sakul Serial Terminal',
    'button.refresh': 'Obnovit',
    'button.settings': 'Nastavení',
    'button.clearView': 'Skrýt zobrazení',
    'button.restoreView': 'Obnovit skryté',
    'button.exportLogs': 'Export logu',
    'button.deleteLogs': 'Smazat hlášení',
    'button.sendCommand': 'Odeslat',
    'button.applyConnection': 'Použít',
    'button.saveSettings': 'Uložit nastavení',
    'button.saveRule': 'Uložit pravidlo',
    'button.ack': 'Potvrdit',
    'button.deleteDevice': 'Odstranit zařízení',
    'button.deleteRule': 'Odstranit pravidlo',
    'button.newRule': 'Nové pravidlo',
    'section.devices': 'Zařízení',
    'section.alarms': 'Alarmy',
    'section.rules': 'Pravidla',
    'settings.title': 'Nastavení Web Monitoru',
    'settings.tokens': 'Tokeny',
    'settings.email': 'E-mail',
    'settings.adminToken': 'Admin token',
    'settings.apiToken': 'API token',
    'settings.keepToken': 'Prázdné = ponechat stávající',
    'settings.adminEmail': 'Admin e-mail',
    'settings.emailFrom': 'Odesílatel',
    'settings.logs': 'Logy',
    'settings.retentionDays': 'Uchovávat logy [dní]',
    'terminal.title': 'Terminál',
    'terminal.pickDevice': 'Vyber zařízení vlevo.',
    'terminal.online': 'online',
    'terminal.ready': 'připraven',
    'terminal.offline': 'offline',
    'terminal.lastSeen': 'poslední kontakt',
    'command.placeholder': 'Příkaz k odeslání',
    'connection.disconnect': 'Odpojit',
    'connection.connectCom': 'Připojit COM',
    'connection.connectTcp': 'Připojit TCP',
    'connection.baudrate': 'Baudrate',
    'connection.ipAddress': 'IP adresa',
    'connection.tcpPort': 'TCP port',
    'alarm.none': 'Žádné aktivní alarmy.',
    'rule.none': 'Zatím nejsou nastavena žádná pravidla.',
    'rule.enabledState': 'povoleno',
    'rule.disabledState': 'vypnuto',
    'rule.allDevices': 'všechna zařízení',
    'rule.name': 'Název',
    'rule.namePlaceholder': 'Např. ERROR alarm',
    'rule.deviceId': 'Device ID',
    'rule.devicePlaceholder': 'Prázdné = všechna zařízení',
    'rule.matchType': 'Typ masky',
    'rule.level': 'Úroveň',
    'rule.enabled': 'povoleno',
    'rule.highlight': 'zvýraznit',
    'prompt.adminToken': 'Zadej Admin token ze SST:',
    'prompt.apiToken': 'API token ze SST pro příjem dat:',
    'error.adminMissing': 'Admin token není nastaven.',
    'toast.noToken': 'Nebyl zadán žádný token.',
    'toast.tokensSaved': 'Tokeny byly uloženy na web.',
    'toast.settingsSaved': 'Nastavení bylo uloženo.',
    'toast.deviceDeleted': 'Zařízení bylo odstraněno.',
    'toast.viewRestored': 'Skrytá hlášení jsou znovu zobrazena.',
    'toast.logsDeleted': 'Hlášení byla smazána.',
    'toast.exportEmpty': 'Není vybrané žádné zařízení pro export.',
    'toast.commandQueued': 'Příkaz byl zařazen k odeslání.',
    'toast.connectionQueued': 'Požadavek na přepnutí připojení byl zařazen.',
    'toast.ruleSaved': 'Pravidlo uloženo.',
    'toast.ruleLoaded': 'Pravidlo načteno do formuláře.',
    'confirm.deleteDevice': 'Opravdu odstranit zařízení "{name}" včetně jeho logů a alarmů?',
    'confirm.deleteLogs': 'Opravdu trvale smazat všechna hlášení pro zařízení "{name}"?',
    'confirm.deleteRule': 'Opravdu odstranit definici alarmu "{name}"?',
  },
  en: {
    'app.subtitle': 'Web terminal for Sakul Serial Terminal',
    'button.refresh': 'Refresh',
    'button.settings': 'Settings',
    'button.clearView': 'Hide view',
    'button.restoreView': 'Restore hidden',
    'button.exportLogs': 'Export log',
    'button.deleteLogs': 'Delete logs',
    'button.sendCommand': 'Send',
    'button.applyConnection': 'Apply',
    'button.saveSettings': 'Save settings',
    'button.saveRule': 'Save rule',
    'button.ack': 'Acknowledge',
    'button.deleteDevice': 'Delete device',
    'button.deleteRule': 'Delete rule',
    'button.newRule': 'New rule',
    'section.devices': 'Devices',
    'section.alarms': 'Alarms',
    'section.rules': 'Rules',
    'settings.title': 'Web Monitor settings',
    'settings.tokens': 'Tokens',
    'settings.email': 'E-mail',
    'settings.adminToken': 'Admin token',
    'settings.apiToken': 'API token',
    'settings.keepToken': 'Empty = keep current',
    'settings.adminEmail': 'Admin e-mail',
    'settings.emailFrom': 'Sender',
    'settings.logs': 'Logs',
    'settings.retentionDays': 'Keep logs [days]',
    'terminal.title': 'Terminal',
    'terminal.pickDevice': 'Select a device on the left.',
    'terminal.online': 'online',
    'terminal.ready': 'ready',
    'terminal.offline': 'offline',
    'terminal.lastSeen': 'last seen',
    'command.placeholder': 'Command to send',
    'connection.disconnect': 'Disconnect',
    'connection.connectCom': 'Connect COM',
    'connection.connectTcp': 'Connect TCP',
    'connection.baudrate': 'Baudrate',
    'connection.ipAddress': 'IP address',
    'connection.tcpPort': 'TCP port',
    'alarm.none': 'No active alarms.',
    'rule.none': 'No rules are configured yet.',
    'rule.enabledState': 'enabled',
    'rule.disabledState': 'disabled',
    'rule.allDevices': 'all devices',
    'rule.name': 'Name',
    'rule.namePlaceholder': 'E.g. ERROR alarm',
    'rule.deviceId': 'Device ID',
    'rule.devicePlaceholder': 'Empty = all devices',
    'rule.matchType': 'Match type',
    'rule.level': 'Level',
    'rule.enabled': 'enabled',
    'rule.highlight': 'highlight',
    'prompt.adminToken': 'Enter the Admin token from SST:',
    'prompt.apiToken': 'API token from SST for data receiving:',
    'error.adminMissing': 'Admin token is not set.',
    'toast.noToken': 'No token was entered.',
    'toast.tokensSaved': 'Tokens were saved on the web.',
    'toast.settingsSaved': 'Settings were saved.',
    'toast.deviceDeleted': 'Device was deleted.',
    'toast.viewRestored': 'Hidden messages are visible again.',
    'toast.logsDeleted': 'Messages were deleted.',
    'toast.exportEmpty': 'No device is selected for export.',
    'toast.commandQueued': 'Command was queued for sending.',
    'toast.connectionQueued': 'Connection change request was queued.',
    'toast.ruleSaved': 'Rule saved.',
    'toast.ruleLoaded': 'Rule loaded into the form.',
    'confirm.deleteDevice': 'Delete device "{name}" including its logs and alarms?',
    'confirm.deleteLogs': 'Permanently delete all messages for device "{name}"?',
    'confirm.deleteRule': 'Delete alarm definition "{name}"?',
  },
};

function t(key, replacements = {}) {
  let text = (translations[state.language] && translations[state.language][key]) || translations.cs[key] || key;
  Object.entries(replacements).forEach(([name, value]) => {
    text = text.replace(`{${name}}`, value);
  });
  return text;
}

function applyTranslations() {
  document.documentElement.lang = state.language;
  $('languageSelect').value = state.language;
  document.querySelectorAll('[data-i18n]').forEach((element) => {
    element.textContent = t(element.dataset.i18n);
  });
  document.querySelectorAll('[data-i18n-placeholder]').forEach((element) => {
    element.placeholder = t(element.dataset.i18nPlaceholder);
  });
  renderDevices();
  renderTerminal();
  renderAlarms();
  renderRules();
}

function escapeText(value) {
  return String(value ?? '').replace(/[&<>"']/g, (ch) => ({
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#039;',
  }[ch]));
}

function getDevicePreferences(deviceId = state.selectedDeviceId) {
  const stored = state.devicePreferences[deviceId] || {};
  return {
    filters: {
      ...defaultDevicePreferences.filters,
      ...(stored.filters || {}),
    },
    command: {
      ...defaultDevicePreferences.command,
      ...(stored.command || {}),
    },
  };
}

function saveDevicePreferences() {
  if (!state.selectedDeviceId) {
    return;
  }

  const filterInputs = [...document.querySelectorAll('.filters input[type="checkbox"][value]')];
  const commandForm = $('commandForm');
  state.devicePreferences[state.selectedDeviceId] = {
    filters: {
      RX: filterInputs.find((input) => input.value === 'RX')?.checked ?? defaultDevicePreferences.filters.RX,
      TX: filterInputs.find((input) => input.value === 'TX')?.checked ?? defaultDevicePreferences.filters.TX,
      SYS: filterInputs.find((input) => input.value === 'SYS')?.checked ?? defaultDevicePreferences.filters.SYS,
      INFO: filterInputs.find((input) => input.value === 'INFO')?.checked ?? defaultDevicePreferences.filters.INFO,
      autoScroll: $('autoScroll')?.checked ?? defaultDevicePreferences.filters.autoScroll,
    },
    command: {
      add_asterisk: commandForm?.elements.add_asterisk?.checked ?? defaultDevicePreferences.command.add_asterisk,
      add_cr: commandForm?.elements.add_cr?.checked ?? defaultDevicePreferences.command.add_cr,
      add_lf: commandForm?.elements.add_lf?.checked ?? defaultDevicePreferences.command.add_lf,
      hex: commandForm?.elements.hex?.checked ?? defaultDevicePreferences.command.hex,
    },
  };
  localStorage.setItem('sst_device_preferences', JSON.stringify(state.devicePreferences));
}

function applyDevicePreferences(deviceId = state.selectedDeviceId) {
  if (!deviceId) {
    return;
  }

  const preferences = getDevicePreferences(deviceId);
  document.querySelectorAll('.filters input[type="checkbox"][value]').forEach((input) => {
    input.checked = Boolean(preferences.filters[input.value]);
  });

  if ($('autoScroll')) {
    $('autoScroll').checked = Boolean(preferences.filters.autoScroll);
  }

  const commandForm = $('commandForm');
  if (!commandForm) {
    return;
  }

  commandForm.elements.add_asterisk.checked = Boolean(preferences.command.add_asterisk);
  commandForm.elements.add_cr.checked = Boolean(preferences.command.add_cr);
  commandForm.elements.add_lf.checked = Boolean(preferences.command.add_lf);
  commandForm.elements.hex.checked = Boolean(preferences.command.hex);
}

async function api(action, options = {}) {
  const response = await fetch(`api.php?action=${encodeURIComponent(action)}${options.query || ''}`, {
    method: options.method || 'GET',
    headers: {
      'Content-Type': 'application/json',
      ...(options.admin ? { 'X-Api-Token': state.adminToken } : {}),
    },
    body: options.body ? JSON.stringify(options.body) : undefined,
    cache: 'no-store',
  });
  const data = await response.json();
  if (!data.ok) {
    throw new Error(data.error || 'API error');
  }
  return data;
}

async function postApi(action, body, admin = false) {
  return api(action, { method: 'POST', admin, body });
}

function toast(message) {
  const el = $('toast');
  el.textContent = message;
  el.hidden = false;
  clearTimeout(toast._timer);
  toast._timer = setTimeout(() => { el.hidden = true; }, 3500);
}

function formatTime(value) {
  if (!value) return '';
  return value.replace('T', ' ').slice(0, 23);
}

function formatLogData(value) {
  return String(value ?? '').replace(/[\r\n]+$/g, '');
}

function normalizeHexString(value) {
  const cleaned = String(value || '').replace(/[^0-9a-f]/gi, '').toUpperCase();
  return cleaned.replace(/(.{2})(?=.)/g, '$1 ').trim();
}

function isValidHexString(value) {
  const raw = String(value || '');
  const cleaned = raw.replace(/[^0-9a-f]/gi, '');
  return raw.trim() === '' || (cleaned.length > 0 && cleaned.length % 2 === 0);
}

function textToHex(value) {
  return [...new TextEncoder().encode(String(value || ''))]
    .map((byte) => byte.toString(16).toUpperCase().padStart(2, '0'))
    .join(' ');
}

function hexToText(value) {
  const cleaned = String(value || '').replace(/[^0-9a-f]/gi, '');
  if (cleaned.length === 0) return '';
  if (cleaned.length % 2 !== 0) throw new Error('Invalid HEX format.');

  const bytes = new Uint8Array(cleaned.match(/.{2}/g).map((part) => parseInt(part, 16)));
  return new TextDecoder().decode(bytes);
}

function getDeviceStatus(device) {
  if (!device) return 'offline';
  if (device.runtime_status) return device.runtime_status;
  return device.online ? 'online' : 'offline';
}

function getDeviceLastSeenValue(device) {
  const value = Date.parse(String(device?.last_seen || '').replace(' ', 'T'));
  return Number.isFinite(value) ? value : 0;
}

function getPreferredDeviceId(devices) {
  if (!devices.length) return null;

  const statusPriority = {
    online: 0,
    ready: 1,
    offline: 2,
  };

  return [...devices]
    .sort((a, b) => {
      const statusDiff = (statusPriority[getDeviceStatus(a)] ?? 3) - (statusPriority[getDeviceStatus(b)] ?? 3);
      if (statusDiff !== 0) return statusDiff;
      return getDeviceLastSeenValue(b) - getDeviceLastSeenValue(a);
    })[0].device_id;
}

function getDeviceAvailablePorts(device) {
  if (!device || !device.available_ports) return [];
  if (Array.isArray(device.available_ports)) return device.available_ports;

  try {
    const ports = JSON.parse(device.available_ports);
    return Array.isArray(ports) ? ports : [];
  } catch {
    return [];
  }
}

function getDeviceAvailableBaudrates(device) {
  if (!device || !device.available_baudrates) return defaultBaudrates;
  let baudrates = [];

  if (Array.isArray(device.available_baudrates)) {
    baudrates = device.available_baudrates;
  } else {
    try {
      const parsed = JSON.parse(device.available_baudrates);
      baudrates = Array.isArray(parsed) ? parsed : [];
    } catch {
      baudrates = [];
    }
  }

  baudrates = baudrates
    .map((value) => Number(value))
    .filter((value) => Number.isFinite(value) && value > 0);

  return baudrates.length > 0 ? [...new Set(baudrates)].sort((a, b) => a - b) : defaultBaudrates;
}

function renderDevices() {
  $('deviceCount').textContent = state.devices.length;
  $('devicesList').innerHTML = state.devices.map((device) => {
    const status = getDeviceStatus(device);
    return `
    <article class="device ${device.device_id === state.selectedDeviceId ? 'selected' : ''}" data-device-id="${escapeText(device.device_id)}">
      <span class="status ${escapeText(status)}"></span>
      <button class="device-main" type="button" data-select-device="${escapeText(device.device_id)}">
        <strong>${escapeText(device.device_name || device.device_id)}</strong>
        <small>${escapeText(device.device_id)}<br>${escapeText(device.port || '-')}, ${escapeText(device.app_version || '')}</small>
      </button>
      <button class="device-delete" type="button" title="${escapeText(t('button.deleteDevice'))}" aria-label="${escapeText(t('button.deleteDevice'))} ${escapeText(device.device_name || device.device_id)}" data-delete-device="${escapeText(device.device_id)}">×</button>
    </article>
  `;
  }).join('');

  document.querySelectorAll('[data-select-device]').forEach((button) => {
    button.addEventListener('click', () => selectDevice(button.dataset.selectDevice));
  });

  document.querySelectorAll('[data-delete-device]').forEach((button) => {
    button.addEventListener('click', () => deleteDevice(button.dataset.deleteDevice));
  });

}

function renderTerminal() {
  const device = state.devices.find((item) => item.device_id === state.selectedDeviceId);
  const status = getDeviceStatus(device);
  $('selectedDeviceTitle').textContent = device ? (device.device_name || device.device_id) : t('terminal.title');
  $('selectedDeviceMeta').textContent = device
    ? `${device.device_id} | ${t(`terminal.${status}`)} | ${t('terminal.lastSeen')} ${formatTime(device.last_seen)} | ${device.hostname || ''}`
    : t('terminal.pickDevice');

  const enabled = new Set([...document.querySelectorAll('.filters input[type="checkbox"][value]:checked')].map((item) => item.value));
  const showInfoPrefix = enabled.has('INFO');
  const hiddenBeforeId = Number(state.hiddenLogBeforeId[state.selectedDeviceId] || 0);
  const lines = state.logs
    .filter((log) => Number(log.id || 0) > hiddenBeforeId)
    .filter((log) => enabled.has(log.direction))
    .map((log) => {
      const data = formatLogData(log.data);
      const level = String(log.level || '').toUpperCase();
      if (level === 'INFO' && !showInfoPrefix) {
        return data;
      }

      return `[${formatTime(log.created_at)}] ${log.direction.padEnd(3)} ${level.padEnd(7)} ${data}`;
    })
    .join('\n');
  $('terminal').textContent = lines;
  renderConnectionForm(device);
  $('terminal').className = 'terminal';

  if ($('autoScroll').checked) {
    $('terminal').scrollTop = $('terminal').scrollHeight;
  }
}

function renderConnectionForm(device) {
  const form = $('connectionForm');
  if (!form) return;

  const ports = getDeviceAvailablePorts(device);
  const baudrates = getDeviceAvailableBaudrates(device);
  const currentPort = device && String(device.connection_type || '').toUpperCase() === 'COM'
    ? String(device.port || '')
    : '';
  const selected = form.elements.com_port.value || currentPort;
  const selectedBaudrate = Number(form.elements.baudrate.value || device?.baudrate || 115200);
  const portOptions = ports.length > 0 ? ports : (currentPort ? [currentPort] : []);

  form.elements.com_port.innerHTML = portOptions
    .map((port) => `<option value="${escapeText(port)}">${escapeText(port)}</option>`)
    .join('');

  if (selected && portOptions.includes(selected)) {
    form.elements.com_port.value = selected;
  }

  form.elements.baudrate.innerHTML = baudrates
    .map((baudrate) => `<option value="${baudrate}">${baudrate}</option>`)
    .join('');

  if (baudrates.includes(selectedBaudrate)) {
    form.elements.baudrate.value = String(selectedBaudrate);
  } else if (baudrates.includes(115200)) {
    form.elements.baudrate.value = '115200';
  }

  if (device && String(device.connection_type || '').toUpperCase() === 'TCP' && device.port) {
    const parts = String(device.port).split(':');
    if (!form.elements.ip_address.value) form.elements.ip_address.value = parts[0] || '';
    if (!form.elements.tcp_port.value) form.elements.tcp_port.value = parts[1] || '';
  }
}

function renderAlarms() {
  $('alarmCount').textContent = state.alarms.length;
  $('alarmsList').innerHTML = state.alarms.length === 0
    ? `<p class="muted">${escapeText(t('alarm.none'))}</p>`
    : state.alarms.map((alarm) => `
      <article class="alarm-card">
        <strong>${escapeText(alarm.device_id)}</strong>
        <small>${formatTime(alarm.created_at)}</small>
        <p>${escapeText(alarm.message)}</p>
        <button data-ack="${alarm.id}" type="button">${escapeText(t('button.ack'))}</button>
      </article>
    `).join('');

  document.querySelectorAll('[data-ack]').forEach((button) => {
    button.addEventListener('click', async () => {
      await ensureAdminToken();
      await api('ack-alarm', { method: 'POST', admin: true, body: { id: Number(button.dataset.ack), acknowledged_by: 'web' } });
      await loadAlarms();
    });
  });
}

function renderRules() {
  $('rulesList').innerHTML = state.rules.length === 0
    ? `<p class="muted">${escapeText(t('rule.none'))}</p>`
    : state.rules.map((rule) => `
      <article class="rule-card">
        <button class="rule-card-main" type="button" data-edit-rule="${escapeText(rule.id)}">
          <strong>${escapeText(rule.name)}</strong>
          <small>${escapeText(rule.enabled === 1 || rule.enabled === '1' ? t('rule.enabledState') : t('rule.disabledState'))} | ${escapeText(rule.match_type)} | ${escapeText(rule.device_id || t('rule.allDevices'))}</small>
          <code>${escapeText(rule.pattern)}</code>
        </button>
        <button class="rule-delete" type="button" title="${escapeText(t('button.deleteRule'))}" aria-label="${escapeText(t('button.deleteRule'))} ${escapeText(rule.name)}" data-delete-rule="${escapeText(rule.id)}">×</button>
      </article>
    `).join('');

  document.querySelectorAll('[data-edit-rule]').forEach((button) => {
    button.addEventListener('click', () => loadRuleToForm(Number(button.dataset.editRule)));
  });

  document.querySelectorAll('[data-delete-rule]').forEach((button) => {
    button.addEventListener('click', () => deleteRule(Number(button.dataset.deleteRule)));
  });
}

async function loadDevices() {
  const data = await api('devices');
  state.devices = data.devices || [];
  const selectedStillExists = state.devices.some((device) => device.device_id === state.selectedDeviceId);
  if (!selectedStillExists) {
    state.selectedDeviceId = getPreferredDeviceId(state.devices);
  }
  applyDevicePreferences();
  renderDevices();
}

async function loadLogs() {
  if (!state.selectedDeviceId) {
    state.logs = [];
    renderTerminal();
    return;
  }
  const data = await api('logs', { query: `&device_id=${encodeURIComponent(state.selectedDeviceId)}&limit=300` });
  state.logs = data.logs || [];
  renderTerminal();
}

async function loadAlarms() {
  const data = await api('alarms');
  state.alarms = data.alarms || [];
  renderAlarms();
}

async function loadRules() {
  const data = await api('rules');
  state.rules = data.rules || [];
  renderRules();
}

async function refreshAll() {
  try {
    await loadDevices();
    await Promise.all([loadLogs(), loadAlarms(), loadRules()]);
  } catch (error) {
    toast(error.message);
  }
}

async function hardRefreshApp() {
  try {
    if ('serviceWorker' in navigator) {
      const registrations = await navigator.serviceWorker.getRegistrations();
      await Promise.all(registrations.map((registration) => registration.unregister()));
    }

    if ('caches' in window) {
      const cacheNames = await caches.keys();
      await Promise.all(cacheNames.map((name) => caches.delete(name)));
    }
  } catch (error) {
    console.warn('Hard refresh failed, reloading anyway.', error);
  }

  const url = new URL(window.location.href);
  url.searchParams.set('reload', Date.now().toString());
  window.location.replace(url.toString());
}

async function selectDevice(deviceId) {
  if (!deviceId) return;
  state.selectedDeviceId = deviceId;
  applyDevicePreferences(deviceId);
  renderDevices();
  await loadLogs();
}

async function deleteDevice(deviceId) {
  const device = state.devices.find((item) => item.device_id === deviceId);
  const name = device ? (device.device_name || device.device_id) : deviceId;

  if (!confirm(t('confirm.deleteDevice', { name }))) {
    return;
  }

  try {
    await ensureAdminToken();
    await api('delete-device', { method: 'POST', admin: true, body: { device_id: deviceId } });

    if (state.selectedDeviceId === deviceId) {
      state.selectedDeviceId = null;
      state.logs = [];
    }

    await refreshAll();
    toast(t('toast.deviceDeleted'));
  } catch (error) {
    toast(error.message);
  }
}

async function deleteRule(ruleId) {
  const rule = state.rules.find((item) => Number(item.id) === Number(ruleId));
  const name = rule ? rule.name : String(ruleId);

  if (!confirm(t('confirm.deleteRule', { name }))) {
    return;
  }

  try {
    await ensureAdminToken();
    await api('delete-rule', { method: 'POST', admin: true, body: { id: Number(ruleId) } });
    await loadRules();
  } catch (error) {
    toast(error.message);
  }
}

function loadRuleToForm(ruleId) {
  const rule = state.rules.find((item) => Number(item.id) === Number(ruleId));
  if (!rule) {
    return;
  }

  const form = $('ruleForm');
  form.elements.id.value = rule.id || '';
  form.elements.name.value = rule.name || '';
  form.elements.device_id.value = rule.device_id || '';
  form.elements.match_type.value = rule.match_type || 'contains';
  form.elements.pattern.value = rule.pattern || '';
  form.elements.highlight_level.value = rule.highlight_level || 'warning';
  form.elements.cooldown_seconds.value = Number(rule.cooldown_seconds || 1800);
  form.elements.enabled.checked = rule.enabled === 1 || rule.enabled === '1' || rule.enabled === true;
  form.elements.action_highlight.checked = rule.action_highlight === 1 || rule.action_highlight === '1' || rule.action_highlight === true;
  form.elements.action_alarm.checked = rule.action_alarm === 1 || rule.action_alarm === '1' || rule.action_alarm === true;
  form.elements.action_email.checked = rule.action_email === 1 || rule.action_email === '1' || rule.action_email === true;
  form.scrollIntoView({ behavior: 'smooth', block: 'start' });
  toast(t('toast.ruleLoaded'));
}

function resetRuleForm() {
  const form = $('ruleForm');
  form.reset();
  form.elements.id.value = '';
  form.elements.enabled.checked = true;
  form.elements.action_highlight.checked = true;
  form.elements.action_alarm.checked = true;
  form.elements.cooldown_seconds.value = 1800;
}

function clearCurrentView() {
  if (state.selectedDeviceId) {
    const maxId = state.logs.reduce((max, log) => Math.max(max, Number(log.id || 0)), 0);
    state.hiddenLogBeforeId[state.selectedDeviceId] = maxId;
    localStorage.setItem('sst_hidden_log_before_id', JSON.stringify(state.hiddenLogBeforeId));
  }

  renderTerminal();
}

function restoreCurrentView() {
  if (!state.selectedDeviceId) {
    return;
  }

  delete state.hiddenLogBeforeId[state.selectedDeviceId];
  localStorage.setItem('sst_hidden_log_before_id', JSON.stringify(state.hiddenLogBeforeId));
  renderTerminal();
  toast(t('toast.viewRestored'));
}

async function deleteCurrentLogs() {
  if (!state.selectedDeviceId) {
    return;
  }

  const device = state.devices.find((item) => item.device_id === state.selectedDeviceId);
  const name = device ? (device.device_name || device.device_id) : state.selectedDeviceId;

  if (!confirm(t('confirm.deleteLogs', { name }))) {
    return;
  }

  try {
    await ensureAdminToken();
    await api('delete-logs', { method: 'POST', admin: true, body: { device_id: state.selectedDeviceId } });
    delete state.hiddenLogBeforeId[state.selectedDeviceId];
    localStorage.setItem('sst_hidden_log_before_id', JSON.stringify(state.hiddenLogBeforeId));
    state.logs = [];
    renderTerminal();
    await loadAlarms();
    toast(t('toast.logsDeleted'));
  } catch (error) {
    toast(error.message);
  }
}

async function exportCurrentLogs() {
  if (!state.selectedDeviceId) {
    toast(t('toast.exportEmpty'));
    return;
  }

  try {
    const url = `api.php?action=export-logs&device_id=${encodeURIComponent(state.selectedDeviceId)}&format=txt&limit=5000`;
    const response = await fetch(url, { cache: 'no-store' });
    if (!response.ok) {
      throw new Error(`Export failed: ${response.status}`);
    }

    const blob = await response.blob();
    const disposition = response.headers.get('Content-Disposition') || '';
    const match = disposition.match(/filename="([^"]+)"/i);
    const fileName = match ? match[1] : `${state.selectedDeviceId}_logs.txt`;
    const objectUrl = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = objectUrl;
    link.download = fileName;
    document.body.appendChild(link);
    link.click();
    link.remove();
    URL.revokeObjectURL(objectUrl);
  } catch (error) {
    toast(error.message);
  }
}

async function sendWebCommand(event) {
  event.preventDefault();

  if (!state.selectedDeviceId) {
    return;
  }

  const form = event.currentTarget;
  const data = new FormData(form);
  saveDevicePreferences();
  let command = String(data.get('command') || '');
  if (!command) {
    return;
  }

  const hexMode = data.has('hex');
  if (hexMode) {
    if (!isValidHexString(command)) {
      toast('Invalid HEX format.');
      return;
    }

    command = normalizeHexString(command);
    form.elements.command.value = command;
  }

  try {
    await ensureAdminToken();
    await api('send-command', {
      method: 'POST',
      admin: true,
      body: {
        device_id: state.selectedDeviceId,
        command,
        format: hexMode ? 'hex' : 'text',
        add_cr: data.has('add_cr'),
        add_lf: data.has('add_lf'),
        add_asterisk: data.has('add_asterisk'),
      },
    });

    toast(t('toast.commandQueued'));
  } catch (error) {
    toast(error.message);
  }
}

async function sendConnectionCommand(event) {
  event.preventDefault();

  if (!state.selectedDeviceId) {
    return;
  }

  const form = event.currentTarget;
  const data = new FormData(form);
  const action = String(data.get('action') || 'disconnect');
  const body = {
    device_id: state.selectedDeviceId,
    action,
  };

  if (action === 'connect_com') {
    body.connection_type = 'COM';
    body.port = String(data.get('com_port') || '').trim();
    body.baudrate = Number(data.get('baudrate') || 115200);
    if (!body.port) {
      toast('COM port is required.');
      return;
    }
  } else if (action === 'connect_tcp') {
    body.connection_type = 'TCP';
    body.ip_address = String(data.get('ip_address') || '').trim();
    body.tcp_port = Number(data.get('tcp_port') || 0);
    if (!body.ip_address) {
      toast(t('connection.ipAddress'));
      return;
    }
  }

  try {
    await ensureAdminToken();
    await api('send-connection-command', {
      method: 'POST',
      admin: true,
      body,
    });

    toast(t('toast.connectionQueued'));
  } catch (error) {
    toast(error.message);
  }
}

function connectionActionChanged(event) {
  const form = event.currentTarget.form;
  const action = event.currentTarget.value;
  form.elements.com_port.disabled = action !== 'connect_com';
  form.elements.baudrate.disabled = action !== 'connect_com';
  form.elements.ip_address.disabled = action !== 'connect_tcp';
  form.elements.tcp_port.disabled = action !== 'connect_tcp';
}

function commandHexChanged(event) {
  const input = $('commandForm').elements.command;

  try {
    input.value = event.target.checked
      ? textToHex(input.value)
      : hexToText(input.value);
  } catch (error) {
    toast(error.message);
    event.target.checked = true;
  }
}

async function ensureAdminToken() {
  if (state.adminToken) return;
  const token = prompt(t('prompt.adminToken'));
  if (!token) throw new Error(t('error.adminMissing'));
  state.adminToken = token;
  localStorage.setItem('sst_admin_token', token);
}

async function openSettings() {
  try {
    await loadSettings();
    $('settingsOverlay').hidden = false;
  } catch (error) {
    if (!state.adminToken) {
      try {
        await ensureAdminToken();
        await loadSettings();
        $('settingsOverlay').hidden = false;
        return;
      } catch (tokenError) {
        toast(tokenError.message);
        return;
      }
    }

    toast(error.message);
  }
}

function closeSettings() {
  $('settingsOverlay').hidden = true;
}

async function loadSettings() {
  const data = await api('settings', { method: 'POST', admin: Boolean(state.adminToken), body: {} });
  const settings = data.settings || {};
  const form = $('settingsForm');

  form.elements.admin_token.value = '';
  form.elements.api_token.value = '';
  form.elements.admin_email.value = settings.admin_email || '';
  form.elements.email_from.value = settings.email_from || '';
  form.elements.log_retention_days.value = Number(settings.log_retention_days ?? 30);
}

async function saveSettings(event) {
  event.preventDefault();

  const form = event.currentTarget;
  const data = Object.fromEntries(new FormData(form).entries());
  const previousAdminToken = state.adminToken;
  const newAdminToken = (data.admin_token || '').trim();
  const authToken = previousAdminToken || newAdminToken;

  try {
    state.adminToken = authToken;
    await api('save-settings', {
      method: 'POST',
      admin: Boolean(authToken),
      body: {
        admin_token: newAdminToken,
        api_token: (data.api_token || '').trim(),
        admin_email: (data.admin_email || '').trim(),
        email_from: (data.email_from || '').trim(),
        log_retention_days: Number(data.log_retention_days || 30),
      },
    });

    if (newAdminToken) {
      state.adminToken = newAdminToken;
      localStorage.setItem('sst_admin_token', newAdminToken);
    } else if (previousAdminToken) {
      state.adminToken = previousAdminToken;
      localStorage.setItem('sst_admin_token', previousAdminToken);
    }

    form.elements.admin_token.value = '';
    form.elements.api_token.value = '';
    toast(t('toast.settingsSaved'));
  } catch (error) {
    state.adminToken = previousAdminToken;
    if (previousAdminToken) {
      localStorage.setItem('sst_admin_token', previousAdminToken);
    } else {
      localStorage.removeItem('sst_admin_token');
    }
    toast(error.message);
  }
}

function bindEvents() {
  $('languageSelect').addEventListener('change', (event) => {
    state.language = event.target.value;
    localStorage.setItem('sst_language', state.language);
    applyTranslations();
  });
  $('refreshButton').addEventListener('click', hardRefreshApp);
  $('settingsButton').addEventListener('click', openSettings);
  $('closeSettingsButton').addEventListener('click', closeSettings);
  $('settingsOverlay').addEventListener('click', (event) => {
    if (event.target === $('settingsOverlay')) {
      closeSettings();
    }
  });
  $('settingsForm').addEventListener('submit', saveSettings);
  $('clearViewButton').addEventListener('click', clearCurrentView);
  $('restoreViewButton').addEventListener('click', restoreCurrentView);
  $('exportLogsButton').addEventListener('click', exportCurrentLogs);
  $('deleteLogsButton').addEventListener('click', deleteCurrentLogs);
  $('commandForm').addEventListener('submit', sendWebCommand);
  ['add_asterisk', 'add_cr', 'add_lf'].forEach((name) => {
    $('commandForm').elements[name].addEventListener('change', saveDevicePreferences);
  });
  $('commandForm').elements.hex.addEventListener('change', (event) => {
    commandHexChanged(event);
    saveDevicePreferences();
  });
  $('connectionForm').addEventListener('submit', sendConnectionCommand);
  $('connectionForm').elements.action.addEventListener('change', connectionActionChanged);
  connectionActionChanged({ currentTarget: $('connectionForm').elements.action });
  document.querySelectorAll('.filters input').forEach((input) => {
    input.addEventListener('change', () => {
      saveDevicePreferences();
      renderTerminal();
    });
  });
  $('ruleForm').addEventListener('submit', async (event) => {
    event.preventDefault();
    try {
      await ensureAdminToken();
      const form = new FormData(event.currentTarget);
      const body = Object.fromEntries(form.entries());
      body.enabled = form.has('enabled');
      body.action_highlight = form.has('action_highlight');
      body.action_alarm = form.has('action_alarm');
      body.action_email = form.has('action_email');
      body.cooldown_seconds = Number(body.cooldown_seconds || 1800);
      await api('save-rule', { method: 'POST', admin: true, body });
      resetRuleForm();
      await loadRules();
      toast(t('toast.ruleSaved'));
    } catch (error) {
      toast(error.message);
    }
  });
  $('newRuleButton').addEventListener('click', resetRuleForm);
}

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('service-worker.js').catch(() => {});
}

bindEvents();
applyTranslations();
refreshAll();
setInterval(refreshAll, 3000);
