/**
 * Device discovery — add device modal, network/serial scanning, device type switching.
 */

import {
    _discoveryScanRunning, set_discoveryScanRunning,
    _discoveryCache, set_discoveryCache,
    csptCache,
} from '../core/state.ts';
import { API_BASE, fetchWithAuth, isSerialDevice, isMockDevice, isMqttDevice, isWsDevice, isOpenrgbDevice, isDmxDevice, isEspnowDevice, isHueDevice, isUsbhidDevice, isSpiDevice, isChromaDevice, isGameSenseDevice, escapeHtml } from '../core/api.ts';
import { devicesCache } from '../core/state.ts';
import { t } from '../core/i18n.ts';
import { showToast, desktopFocus } from '../core/ui.ts';
import { Modal } from '../core/modal.ts';
import { _computeMaxFps, _renderFpsHint } from './devices.ts';
import { getDeviceTypeIcon, ICON_RADIO, ICON_GLOBE, ICON_CPU, ICON_KEYBOARD, ICON_MOUSE, ICON_HEADPHONES, ICON_PLUG, ICON_TARGET_ICON, ICON_ACTIVITY, ICON_TEMPLATE } from '../core/icons.ts';
import { EntitySelect } from '../core/entity-palette.ts';
import { IconSelect, showTypePicker } from '../core/icon-select.ts';

class AddDeviceModal extends Modal {
    constructor() { super('add-device-modal'); }

    snapshotValues() {
        return {
            name: (document.getElementById('device-name') as HTMLInputElement).value,
            type: (document.getElementById('device-type') as HTMLSelectElement).value,
            url: (document.getElementById('device-url') as HTMLInputElement).value,
            serialPort: (document.getElementById('device-serial-port') as HTMLSelectElement).value,
            ledCount: (document.getElementById('device-led-count') as HTMLInputElement).value,
            baudRate: (document.getElementById('device-baud-rate') as HTMLSelectElement).value,
            ledType: (document.getElementById('device-led-type') as HTMLSelectElement)?.value || 'rgb',
            sendLatency: (document.getElementById('device-send-latency') as HTMLInputElement)?.value || '0',
            zones: JSON.stringify(_getCheckedZones('device-zone-list')),
            zoneMode: _getZoneMode(),
            csptId: (document.getElementById('device-css-processing-template') as HTMLSelectElement)?.value || '',
            dmxProtocol: (document.getElementById('device-dmx-protocol') as HTMLSelectElement)?.value || 'artnet',
            dmxStartUniverse: (document.getElementById('device-dmx-start-universe') as HTMLInputElement)?.value || '0',
            dmxStartChannel: (document.getElementById('device-dmx-start-channel') as HTMLInputElement)?.value || '1',
        };
    }
}

const addDeviceModal = new AddDeviceModal();

/* ── Icon-grid type selector ──────────────────────────────────── */

const DEVICE_TYPE_KEYS = ['wled', 'adalight', 'ambiled', 'mqtt', 'ws', 'openrgb', 'dmx', 'espnow', 'hue', 'usbhid', 'spi', 'chroma', 'gamesense', 'mock'];

function _buildDeviceTypeItems() {
    return DEVICE_TYPE_KEYS.map(key => ({
        value: key,
        icon: getDeviceTypeIcon(key),
        label: t(`device.type.${key}`),
        desc: t(`device.type.${key}.desc`),
    }));
}

let _deviceTypeIconSelect: any = null;
let _csptEntitySelect: any = null;

function _ensureDeviceTypeIconSelect() {
    const sel = document.getElementById('device-type');
    if (!sel) return;
    if (_deviceTypeIconSelect) { _deviceTypeIconSelect.updateItems(_buildDeviceTypeItems()); return; }
    _deviceTypeIconSelect = new IconSelect({ target: sel, items: _buildDeviceTypeItems(), columns: 3 } as any);
}

function _ensureCsptEntitySelect() {
    const sel = document.getElementById('device-css-processing-template');
    if (!sel) return;
    const templates = csptCache.data || [];
    // Populate native <select> options
    sel.innerHTML = `<option value="">${t('common.none_no_cspt')}</option>` +
        templates.map((tp: any) => `<option value="${tp.id}">${tp.name}</option>`).join('');
    if (_csptEntitySelect) _csptEntitySelect.destroy();
    if (templates.length > 0) {
        _csptEntitySelect = new EntitySelect({
            target: sel,
            getItems: () => (csptCache.data || []).map((tp: any) => ({
                value: tp.id,
                label: tp.name,
                icon: ICON_TEMPLATE,
                desc: '',
            })),
            placeholder: t('palette.search'),
            allowNone: true,
            noneLabel: t('common.none_no_cspt'),
        } as any);
    }
}

/* ── Icon-grid DMX protocol selector ─────────────────────────── */

function _buildDmxProtocolItems() {
    return [
        { value: 'artnet', icon: ICON_RADIO, label: 'Art-Net', desc: t('device.dmx_protocol.artnet.desc') },
        { value: 'sacn',   icon: ICON_GLOBE, label: 'sACN (E1.31)', desc: t('device.dmx_protocol.sacn.desc') },
    ];
}

const _dmxProtocolIconSelects: Record<string, any> = {};

export function ensureDmxProtocolIconSelect(selectId: any) {
    const sel = document.getElementById(selectId);
    if (!sel) return;
    if (_dmxProtocolIconSelects[selectId]) {
        _dmxProtocolIconSelects[selectId].updateItems(_buildDmxProtocolItems());
        return;
    }
    _dmxProtocolIconSelects[selectId] = new IconSelect({
        target: sel,
        items: _buildDmxProtocolItems(),
        columns: 2,
    } as any);
}

export function destroyDmxProtocolIconSelect(selectId: any) {
    if (_dmxProtocolIconSelects[selectId]) {
        _dmxProtocolIconSelects[selectId].destroy();
        delete _dmxProtocolIconSelects[selectId];
    }
}

/* ── Icon-grid SPI LED chipset selector ──────────────────────── */

function _buildSpiLedTypeItems() {
    return [
        { value: 'WS2812B',    icon: ICON_CPU, label: 'WS2812B',       desc: t('device.spi.led_type.ws2812b.desc') },
        { value: 'WS2812',     icon: ICON_CPU, label: 'WS2812',        desc: t('device.spi.led_type.ws2812.desc') },
        { value: 'WS2811',     icon: ICON_CPU, label: 'WS2811',        desc: t('device.spi.led_type.ws2811.desc') },
        { value: 'SK6812',     icon: ICON_CPU, label: 'SK6812 (RGB)',   desc: t('device.spi.led_type.sk6812.desc') },
        { value: 'SK6812_RGBW', icon: ICON_CPU, label: 'SK6812 (RGBW)', desc: t('device.spi.led_type.sk6812_rgbw.desc') },
    ];
}

const _spiLedTypeIconSelects: Record<string, any> = {};

export function ensureSpiLedTypeIconSelect(selectId: any) {
    const sel = document.getElementById(selectId);
    if (!sel) return;
    if (_spiLedTypeIconSelects[selectId]) {
        _spiLedTypeIconSelects[selectId].updateItems(_buildSpiLedTypeItems());
        return;
    }
    _spiLedTypeIconSelects[selectId] = new IconSelect({
        target: sel,
        items: _buildSpiLedTypeItems(),
        columns: 3,
    } as any);
}

export function destroySpiLedTypeIconSelect(selectId: any) {
    if (_spiLedTypeIconSelects[selectId]) {
        _spiLedTypeIconSelects[selectId].destroy();
        delete _spiLedTypeIconSelects[selectId];
    }
}

/* ── Icon-grid GameSense peripheral type selector ────────────── */

function _buildGameSenseDeviceTypeItems() {
    return [
        { value: 'keyboard',  icon: ICON_KEYBOARD,   label: t('device.gamesense.peripheral.keyboard'),  desc: t('device.gamesense.peripheral.keyboard.desc') },
        { value: 'mouse',     icon: ICON_MOUSE,       label: t('device.gamesense.peripheral.mouse'),     desc: t('device.gamesense.peripheral.mouse.desc') },
        { value: 'headset',   icon: ICON_HEADPHONES,  label: t('device.gamesense.peripheral.headset'),   desc: t('device.gamesense.peripheral.headset.desc') },
        { value: 'mousepad',  icon: ICON_PLUG,        label: t('device.gamesense.peripheral.mousepad'),  desc: t('device.gamesense.peripheral.mousepad.desc') },
        { value: 'indicator', icon: ICON_ACTIVITY,    label: t('device.gamesense.peripheral.indicator'), desc: t('device.gamesense.peripheral.indicator.desc') },
    ];
}

const _gameSenseDeviceTypeIconSelects: Record<string, any> = {};

export function ensureGameSenseDeviceTypeIconSelect(selectId: any) {
    const sel = document.getElementById(selectId);
    if (!sel) return;
    if (_gameSenseDeviceTypeIconSelects[selectId]) {
        _gameSenseDeviceTypeIconSelects[selectId].updateItems(_buildGameSenseDeviceTypeItems());
        return;
    }
    _gameSenseDeviceTypeIconSelects[selectId] = new IconSelect({
        target: sel,
        items: _buildGameSenseDeviceTypeItems(),
        columns: 3,
    } as any);
}

export function destroyGameSenseDeviceTypeIconSelect(selectId: any) {
    if (_gameSenseDeviceTypeIconSelects[selectId]) {
        _gameSenseDeviceTypeIconSelects[selectId].destroy();
        delete _gameSenseDeviceTypeIconSelects[selectId];
    }
}

export function onDeviceTypeChanged() {
    const deviceType = (document.getElementById('device-type') as HTMLSelectElement).value;
    if (_deviceTypeIconSelect) _deviceTypeIconSelect.setValue(deviceType);
    const urlGroup = document.getElementById('device-url-group') as HTMLElement;
    const urlInput = document.getElementById('device-url') as HTMLInputElement;
    const serialGroup = document.getElementById('device-serial-port-group') as HTMLElement;
    const serialSelect = document.getElementById('device-serial-port') as HTMLSelectElement;
    const ledCountGroup = document.getElementById('device-led-count-group') as HTMLElement;
    const discoverySection = document.getElementById('discovery-section') as HTMLElement;
    const baudRateGroup = document.getElementById('device-baud-rate-group') as HTMLElement;
    const ledTypeGroup = document.getElementById('device-led-type-group') as HTMLElement;
    const sendLatencyGroup = document.getElementById('device-send-latency-group') as HTMLElement;

    // URL label / hint / placeholder — adapt per device type
    const urlLabel = document.getElementById('device-url-label') as HTMLElement;
    const urlHint = document.getElementById('device-url-hint') as HTMLElement;

    const zoneGroup = document.getElementById('device-zone-group') as HTMLElement;
    const scanBtn = document.getElementById('scan-network-btn') as HTMLButtonElement;
    const dmxProtocolGroup = document.getElementById('device-dmx-protocol-group') as HTMLElement;
    const dmxStartUniverseGroup = document.getElementById('device-dmx-start-universe-group') as HTMLElement;
    const dmxStartChannelGroup = document.getElementById('device-dmx-start-channel-group') as HTMLElement;

    // Hide zone group + mode group by default (shown only for openrgb)
    if (zoneGroup) zoneGroup.style.display = 'none';
    const zoneModeGroup = document.getElementById('device-zone-mode-group') as HTMLElement;
    if (zoneModeGroup) zoneModeGroup.style.display = 'none';

    // Hide DMX fields by default
    if (dmxProtocolGroup) dmxProtocolGroup.style.display = 'none';
    if (dmxStartUniverseGroup) dmxStartUniverseGroup.style.display = 'none';
    if (dmxStartChannelGroup) dmxStartChannelGroup.style.display = 'none';

    // Hide new device type fields by default
    _showEspnowFields(false);
    _showHueFields(false);
    _showSpiFields(false);
    _showChromaFields(false);
    _showGameSenseFields(false);

    if (isMqttDevice(deviceType)) {
        // MQTT: show URL (topic), LED count; hide serial/baud/led-type/latency/discovery
        urlGroup.style.display = '';
        urlInput.setAttribute('required', '');
        serialGroup.style.display = 'none';
        serialSelect.removeAttribute('required');
        ledCountGroup.style.display = '';
        baudRateGroup.style.display = 'none';
        if (ledTypeGroup) ledTypeGroup.style.display = 'none';
        if (sendLatencyGroup) sendLatencyGroup.style.display = 'none';
        if (discoverySection) discoverySection.style.display = 'none';
        if (scanBtn) scanBtn.style.display = 'none';
        // Relabel URL field as "Topic"
        if (urlLabel) urlLabel.textContent = t('device.mqtt_topic');
        if (urlHint) urlHint.textContent = t('device.mqtt_topic.hint');
        urlInput.placeholder = t('device.mqtt_topic.placeholder') || 'mqtt://ledgrab/device/living-room';
    } else if (isMockDevice(deviceType)) {
        urlGroup.style.display = 'none';
        urlInput.removeAttribute('required');
        serialGroup.style.display = 'none';
        serialSelect.removeAttribute('required');
        ledCountGroup.style.display = '';
        baudRateGroup.style.display = 'none';
        if (ledTypeGroup) ledTypeGroup.style.display = '';
        if (sendLatencyGroup) sendLatencyGroup.style.display = '';
        if (discoverySection) discoverySection.style.display = 'none';
        if (scanBtn) scanBtn.style.display = 'none';
    } else if (isWsDevice(deviceType)) {
        urlGroup.style.display = 'none';
        urlInput.removeAttribute('required');
        serialGroup.style.display = 'none';
        serialSelect.removeAttribute('required');
        ledCountGroup.style.display = '';
        baudRateGroup.style.display = 'none';
        if (ledTypeGroup) ledTypeGroup.style.display = 'none';
        if (sendLatencyGroup) sendLatencyGroup.style.display = 'none';
        if (discoverySection) discoverySection.style.display = 'none';
        if (scanBtn) scanBtn.style.display = 'none';
    } else if (isSerialDevice(deviceType)) {
        urlGroup.style.display = 'none';
        urlInput.removeAttribute('required');
        serialGroup.style.display = '';
        serialSelect.setAttribute('required', '');
        ledCountGroup.style.display = '';
        baudRateGroup.style.display = '';
        if (ledTypeGroup) ledTypeGroup.style.display = 'none';
        if (sendLatencyGroup) sendLatencyGroup.style.display = 'none';
        if (scanBtn) scanBtn.style.display = 'none';
        // Hide discovery list — serial port dropdown replaces it
        if (discoverySection) discoverySection.style.display = 'none';
        // Populate from cache or show placeholder (lazy-load on focus)
        if (deviceType in _discoveryCache) {
            _populateSerialPortDropdown(_discoveryCache[deviceType]);
        } else {
            serialSelect.innerHTML = '';
            const opt = document.createElement('option');
            opt.value = '';
            opt.textContent = t('device.serial_port.hint') || 'Click to discover ports...';
            opt.disabled = true;
            opt.selected = true;
            serialSelect.appendChild(opt);
        }
        updateBaudFpsHint();
    } else if (isDmxDevice(deviceType)) {
        // DMX: show URL (IP address), LED count, DMX-specific fields; hide serial/baud/discovery
        urlGroup.style.display = '';
        urlInput.setAttribute('required', '');
        serialGroup.style.display = 'none';
        serialSelect.removeAttribute('required');
        ledCountGroup.style.display = '';
        baudRateGroup.style.display = 'none';
        if (ledTypeGroup) ledTypeGroup.style.display = 'none';
        if (sendLatencyGroup) sendLatencyGroup.style.display = 'none';
        if (discoverySection) discoverySection.style.display = 'none';
        if (scanBtn) scanBtn.style.display = 'none';
        // Show DMX-specific fields
        if (dmxProtocolGroup) dmxProtocolGroup.style.display = '';
        if (dmxStartUniverseGroup) dmxStartUniverseGroup.style.display = '';
        if (dmxStartChannelGroup) dmxStartChannelGroup.style.display = '';
        ensureDmxProtocolIconSelect('device-dmx-protocol');
        // Relabel URL field as IP Address
        if (urlLabel) urlLabel.textContent = t('device.dmx.url');
        if (urlHint) urlHint.textContent = t('device.dmx.url.hint');
        urlInput.placeholder = t('device.dmx.url.placeholder') || '192.168.1.50';
    } else if (isOpenrgbDevice(deviceType)) {
        urlGroup.style.display = '';
        urlInput.setAttribute('required', '');
        serialGroup.style.display = 'none';
        serialSelect.removeAttribute('required');
        ledCountGroup.style.display = 'none';
        baudRateGroup.style.display = 'none';
        if (ledTypeGroup) ledTypeGroup.style.display = 'none';
        if (sendLatencyGroup) sendLatencyGroup.style.display = 'none';
        if (scanBtn) scanBtn.style.display = '';
        if (zoneGroup) zoneGroup.style.display = '';
        if (urlLabel) urlLabel.textContent = t('device.openrgb.url');
        if (urlHint) urlHint.textContent = t('device.openrgb.url.hint');
        urlInput.placeholder = 'openrgb://localhost:6742/0';
        if (deviceType in _discoveryCache) {
            _renderDiscoveryList();
        } else {
            scanForDevices();
        }
    } else if (isEspnowDevice(deviceType)) {
        // ESP-NOW: serial port for gateway, LED count, baud rate, + ESP-NOW fields
        urlGroup.style.display = 'none';
        urlInput.removeAttribute('required');
        serialGroup.style.display = '';
        serialSelect.setAttribute('required', '');
        ledCountGroup.style.display = '';
        baudRateGroup.style.display = '';
        if (ledTypeGroup) ledTypeGroup.style.display = 'none';
        if (sendLatencyGroup) sendLatencyGroup.style.display = 'none';
        if (discoverySection) discoverySection.style.display = 'none';
        if (scanBtn) scanBtn.style.display = 'none';
        // Show ESP-NOW specific fields
        _showEspnowFields(true);
        // Populate serial ports
        if (deviceType in _discoveryCache) {
            _populateSerialPortDropdown(_discoveryCache[deviceType]);
        } else {
            serialSelect.innerHTML = '';
            const opt = document.createElement('option');
            opt.value = '';
            opt.textContent = t('device.serial_port.hint') || 'Click to discover ports...';
            opt.disabled = true;
            opt.selected = true;
            serialSelect.appendChild(opt);
        }
    } else if (isHueDevice(deviceType)) {
        // Hue: show URL (bridge IP), LED count, + Hue auth fields
        urlGroup.style.display = '';
        urlInput.setAttribute('required', '');
        serialGroup.style.display = 'none';
        serialSelect.removeAttribute('required');
        ledCountGroup.style.display = '';
        baudRateGroup.style.display = 'none';
        if (ledTypeGroup) ledTypeGroup.style.display = 'none';
        if (sendLatencyGroup) sendLatencyGroup.style.display = 'none';
        if (scanBtn) scanBtn.style.display = '';
        _showHueFields(true);
        if (urlLabel) urlLabel.textContent = t('device.hue.url') || 'Bridge IP';
        if (urlHint) urlHint.textContent = t('device.hue.url.hint') || 'IP address of your Hue bridge';
        urlInput.placeholder = 'hue://192.168.1.2';
        if (deviceType in _discoveryCache) {
            _renderDiscoveryList();
        } else {
            scanForDevices();
        }
    } else if (isUsbhidDevice(deviceType)) {
        // USB HID: show URL (VID:PID), LED count
        urlGroup.style.display = '';
        urlInput.setAttribute('required', '');
        serialGroup.style.display = 'none';
        serialSelect.removeAttribute('required');
        ledCountGroup.style.display = '';
        baudRateGroup.style.display = 'none';
        if (ledTypeGroup) ledTypeGroup.style.display = 'none';
        if (sendLatencyGroup) sendLatencyGroup.style.display = 'none';
        if (scanBtn) scanBtn.style.display = '';
        if (urlLabel) urlLabel.textContent = t('device.usbhid.url') || 'VID:PID';
        if (urlHint) urlHint.textContent = t('device.usbhid.url.hint') || 'USB Vendor:Product ID in hex';
        urlInput.placeholder = 'hid://1532:0084';
        if (deviceType in _discoveryCache) {
            _renderDiscoveryList();
        } else {
            scanForDevices();
        }
    } else if (isSpiDevice(deviceType)) {
        // SPI Direct: show URL (gpio/spidev), LED count, + SPI fields
        urlGroup.style.display = '';
        urlInput.setAttribute('required', '');
        serialGroup.style.display = 'none';
        serialSelect.removeAttribute('required');
        ledCountGroup.style.display = '';
        baudRateGroup.style.display = 'none';
        if (ledTypeGroup) ledTypeGroup.style.display = 'none';
        if (sendLatencyGroup) sendLatencyGroup.style.display = 'none';
        if (scanBtn) scanBtn.style.display = '';
        _showSpiFields(true);
        ensureSpiLedTypeIconSelect('device-spi-led-type');
        if (urlLabel) urlLabel.textContent = t('device.spi.url') || 'GPIO/SPI Path';
        if (urlHint) urlHint.textContent = t('device.spi.url.hint') || 'GPIO pin or SPI device path';
        urlInput.placeholder = 'spi://gpio:18';
        if (deviceType in _discoveryCache) {
            _renderDiscoveryList();
        } else {
            scanForDevices();
        }
    } else if (isChromaDevice(deviceType)) {
        // Razer Chroma: auto URL, LED count, + peripheral type selector
        urlGroup.style.display = 'none';
        urlInput.removeAttribute('required');
        serialGroup.style.display = 'none';
        serialSelect.removeAttribute('required');
        ledCountGroup.style.display = '';
        baudRateGroup.style.display = 'none';
        if (ledTypeGroup) ledTypeGroup.style.display = 'none';
        if (sendLatencyGroup) sendLatencyGroup.style.display = 'none';
        if (scanBtn) scanBtn.style.display = '';
        _showChromaFields(true);
        if (deviceType in _discoveryCache) {
            _renderDiscoveryList();
        } else {
            scanForDevices();
        }
    } else if (isGameSenseDevice(deviceType)) {
        // SteelSeries GameSense: auto URL, LED count, + device type selector
        urlGroup.style.display = 'none';
        urlInput.removeAttribute('required');
        serialGroup.style.display = 'none';
        serialSelect.removeAttribute('required');
        ledCountGroup.style.display = '';
        baudRateGroup.style.display = 'none';
        if (ledTypeGroup) ledTypeGroup.style.display = 'none';
        if (sendLatencyGroup) sendLatencyGroup.style.display = 'none';
        if (scanBtn) scanBtn.style.display = '';
        _showGameSenseFields(true);
        ensureGameSenseDeviceTypeIconSelect('device-gamesense-device-type');
        if (deviceType in _discoveryCache) {
            _renderDiscoveryList();
        } else {
            scanForDevices();
        }
    } else {
        urlGroup.style.display = '';
        urlInput.setAttribute('required', '');
        serialGroup.style.display = 'none';
        serialSelect.removeAttribute('required');
        ledCountGroup.style.display = 'none';
        baudRateGroup.style.display = 'none';
        if (ledTypeGroup) ledTypeGroup.style.display = 'none';
        if (sendLatencyGroup) sendLatencyGroup.style.display = 'none';
        if (scanBtn) scanBtn.style.display = '';
        // Restore default URL label/hint/placeholder
        if (urlLabel) urlLabel.textContent = t('device.url');
        if (urlHint) urlHint.textContent = t('device.url.hint');
        urlInput.placeholder = t('device.url.placeholder') || 'http://192.168.1.100';
        // Show cached results or trigger scan for WLED
        if (deviceType in _discoveryCache) {
            _renderDiscoveryList();
        } else {
            scanForDevices();
        }
    }

    // Re-snapshot after type change so switching types alone doesn't mark as dirty
    addDeviceModal.snapshot();
}

export function updateBaudFpsHint() {
    const hintEl = document.getElementById('baud-fps-hint') as HTMLElement;
    const baudRate = parseInt((document.getElementById('device-baud-rate') as HTMLSelectElement).value, 10);
    const ledCount = parseInt((document.getElementById('device-led-count') as HTMLInputElement).value, 10);
    const deviceType = (document.getElementById('device-type') as HTMLSelectElement)?.value || 'adalight';
    _renderFpsHint(hintEl, baudRate, ledCount, deviceType);
}

function _renderDiscoveryList() {
    const selectedType = (document.getElementById('device-type') as HTMLSelectElement).value;
    const devices = _discoveryCache[selectedType];

    // Serial devices: populate serial port dropdown instead of discovery list
    if (isSerialDevice(selectedType)) {
        _populateSerialPortDropdown(devices || []);
        return;
    }

    // WLED and others: render discovery list cards
    const list = document.getElementById('discovery-list') as HTMLElement;
    const empty = document.getElementById('discovery-empty') as HTMLElement;
    const section = document.getElementById('discovery-section') as HTMLElement;
    if (!list || !section) return;

    list.innerHTML = '';

    if (!devices) {
        section.style.display = 'none';
        return;
    }

    section.style.display = 'block';

    if (devices.length === 0) {
        empty.style.display = 'block';
        return;
    }

    empty.style.display = 'none';
    devices.forEach((device: any) => {
        const card = document.createElement('div');
        card.className = 'discovery-item' + (device.already_added ? ' discovery-item--added' : '');
        const meta = [device.ip];
        if (device.led_count) meta.push(device.led_count + ' LEDs');
        if (device.version) meta.push('v' + device.version);
        card.innerHTML = `
            <div class="discovery-item-info">
                <strong>${escapeHtml(device.name)}</strong>
                <small>${escapeHtml(meta.join(' \u00b7 '))}</small>
            </div>
            ${device.already_added
                ? '<span class="discovery-badge">' + t('device.scan.already_added') + '</span>'
                : ''}
        `;
        if (!device.already_added) {
            card.onclick = () => selectDiscoveredDevice(device);
        }
        list.appendChild(card);
    });
}

function _populateSerialPortDropdown(devices: any) {
    const select = document.getElementById('device-serial-port') as HTMLSelectElement;
    select.innerHTML = '';

    if (devices.length === 0) {
        const opt = document.createElement('option');
        opt.value = '';
        opt.textContent = t('device.serial_port.none') || 'No serial ports found';
        opt.disabled = true;
        select.appendChild(opt);
        return;
    }

    // Default placeholder
    const placeholder = document.createElement('option');
    placeholder.value = '';
    placeholder.textContent = t('device.serial_port.select') || 'Select a port...';
    placeholder.disabled = true;
    placeholder.selected = true;
    select.appendChild(placeholder);

    devices.forEach((device: any) => {
        const opt = document.createElement('option');
        opt.value = device.url;
        opt.textContent = device.name;
        if (device.already_added) {
            opt.textContent += ' (' + t('device.scan.already_added') + ')';
        }
        select.appendChild(opt);
    });
}

export function onSerialPortFocus() {
    // Lazy-load: trigger discovery when user opens the serial port dropdown
    const deviceType = (document.getElementById('device-type') as HTMLSelectElement)?.value || 'adalight';
    if (!(deviceType in _discoveryCache)) {
        scanForDevices(deviceType);
    }
}

export function showAddDevice(presetType: any = null, cloneData: any = null) {
    // When no type specified: show type picker first
    if (!presetType) {
        showTypePicker({
            title: t('device.select_type'),
            items: _buildDeviceTypeItems(),
            onPick: (type: any) => showAddDevice(type),
        });
        return;
    }

    const form = document.getElementById('add-device-form') as HTMLFormElement;
    const error = document.getElementById('add-device-error') as HTMLElement;
    form.reset();
    error.style.display = 'none';
    set_discoveryCache({});
    // Reset discovery section
    const section = document.getElementById('discovery-section') as HTMLElement;
    if (section) {
        section.style.display = 'none';
        (document.getElementById('discovery-list') as HTMLElement).innerHTML = '';
        (document.getElementById('discovery-empty') as HTMLElement).style.display = 'none';
        (document.getElementById('discovery-loading') as HTMLElement).style.display = 'none';
    }
    // Reset serial port dropdown
    (document.getElementById('device-serial-port') as HTMLSelectElement).innerHTML = '';
    const scanBtn = document.getElementById('scan-network-btn') as HTMLButtonElement;
    if (scanBtn) scanBtn.disabled = false;
    _ensureDeviceTypeIconSelect();
    // Populate CSPT template selector
    csptCache.fetch().then(() => _ensureCsptEntitySelect());

    // Pre-select type and hide the type selector (already chosen)
    (document.getElementById('device-type') as HTMLSelectElement).value = presetType;
    (document.getElementById('device-type-group') as HTMLElement).style.display = 'none';
    const typeIcon = getDeviceTypeIcon(presetType);
    const typeName = t(`device.type.${presetType}`);
    (document.getElementById('add-device-modal-title') as HTMLElement).innerHTML = `${typeIcon} ${t('devices.add')}: ${typeName}`;

    addDeviceModal.open();
    onDeviceTypeChanged();

    // Prefill fields from clone data (after onDeviceTypeChanged shows/hides fields)
    if (cloneData) {
        (document.getElementById('device-name') as HTMLInputElement).value = (cloneData.name || '') + ' (Copy)';
        // Clear URL — devices must have unique addresses, user must enter a new one
        const urlInput = document.getElementById('device-url') as HTMLInputElement;
        if (urlInput) urlInput.value = '';
        // Prefill LED count
        const ledCountInput = document.getElementById('device-led-count') as HTMLInputElement;
        if (ledCountInput && cloneData.led_count) ledCountInput.value = cloneData.led_count;
        // Prefill baud rate for serial devices
        if (isSerialDevice(presetType)) {
            const baudSelect = document.getElementById('device-baud-rate') as HTMLSelectElement;
            if (baudSelect && cloneData.baud_rate) baudSelect.value = String(cloneData.baud_rate);
        }
        // Prefill mock device fields
        if (isMockDevice(presetType)) {
            const ledTypeEl = document.getElementById('device-led-type') as HTMLSelectElement;
            if (ledTypeEl) ledTypeEl.value = cloneData.rgbw ? 'rgbw' : 'rgb';
            const sendLatencyEl = document.getElementById('device-send-latency') as HTMLInputElement;
            if (sendLatencyEl) sendLatencyEl.value = cloneData.send_latency_ms ?? 0;
        }
        // Prefill DMX fields
        if (isDmxDevice(presetType)) {
            const dmxProto = document.getElementById('device-dmx-protocol') as HTMLSelectElement;
            if (dmxProto && cloneData.dmx_protocol) dmxProto.value = cloneData.dmx_protocol;
            const dmxUniverse = document.getElementById('device-dmx-start-universe') as HTMLInputElement;
            if (dmxUniverse && cloneData.dmx_start_universe != null) dmxUniverse.value = cloneData.dmx_start_universe;
            const dmxChannel = document.getElementById('device-dmx-start-channel') as HTMLInputElement;
            if (dmxChannel && cloneData.dmx_start_channel != null) dmxChannel.value = cloneData.dmx_start_channel;
        }
        // Prefill CSPT template selector (after fetch completes)
        if (cloneData.default_css_processing_template_id) {
            csptCache.fetch().then(() => {
                _ensureCsptEntitySelect();
                const csptEl = document.getElementById('device-css-processing-template') as HTMLSelectElement;
                if (csptEl) csptEl.value = cloneData.default_css_processing_template_id;
            });
        }
    }

    setTimeout(() => {
        desktopFocus(document.getElementById('device-name'));
        addDeviceModal.snapshot();
    }, 100);
}

export async function closeAddDeviceModal() {
    await addDeviceModal.close();
}

export async function scanForDevices(forceType?: any) {
    const scanType = forceType || (document.getElementById('device-type') as HTMLSelectElement)?.value || 'wled';

    // Per-type guard: prevent duplicate scans for the same type
    if (_discoveryScanRunning === scanType) return;
    set_discoveryScanRunning(scanType);

    const loading = document.getElementById('discovery-loading') as HTMLElement;
    const list = document.getElementById('discovery-list') as HTMLElement;
    const empty = document.getElementById('discovery-empty') as HTMLElement;
    const section = document.getElementById('discovery-section') as HTMLElement;
    const scanBtn = document.getElementById('scan-network-btn') as HTMLButtonElement;

    if (isSerialDevice(scanType)) {
        // Show loading in the serial port dropdown
        const select = document.getElementById('device-serial-port') as HTMLSelectElement;
        select.innerHTML = '';
        const opt = document.createElement('option');
        opt.value = '';
        opt.textContent = '\u23F3';
        opt.disabled = true;
        select.appendChild(opt);
    } else {
        // Show the discovery section with loading spinner
        section.style.display = 'block';
        loading.style.display = 'flex';
        list.innerHTML = '';
        empty.style.display = 'none';
    }
    if (scanBtn) scanBtn.disabled = true;

    try {
        const response = await fetchWithAuth(`/devices/discover?timeout=3&device_type=${encodeURIComponent(scanType)}`);

        loading.style.display = 'none';
        if (scanBtn) scanBtn.disabled = false;

        if (!response.ok) {
            if (!isSerialDevice(scanType)) {
                empty.style.display = 'block';
                (empty.querySelector('small') as HTMLElement).textContent = t('device.scan.error');
            }
            return;
        }

        const data = await response.json();
        _discoveryCache[scanType] = data.devices || [];

        // Only render if the user is still on this type
        const currentType = (document.getElementById('device-type') as HTMLSelectElement)?.value;
        if (currentType === scanType) {
            _renderDiscoveryList();
        }
    } catch (err: any) {
        if (err.isAuth) return;
        loading.style.display = 'none';
        if (scanBtn) scanBtn.disabled = false;
        if (!isSerialDevice(scanType)) {
            empty.style.display = 'block';
            (empty.querySelector('small') as HTMLElement).textContent = t('device.scan.error');
        }
        console.error('Device scan failed:', err);
    } finally {
        if (_discoveryScanRunning === scanType) {
            set_discoveryScanRunning(false);
        }
    }
}

export function selectDiscoveredDevice(device: any) {
    (document.getElementById('device-name') as HTMLInputElement).value = device.name;
    const typeSelect = document.getElementById('device-type') as HTMLSelectElement;
    if (typeSelect) typeSelect.value = device.device_type;
    onDeviceTypeChanged();
    if (isSerialDevice(device.device_type)) {
        (document.getElementById('device-serial-port') as HTMLSelectElement).value = device.url;
    } else {
        (document.getElementById('device-url') as HTMLInputElement).value = device.url;
    }
    // Fetch zones for OpenRGB devices
    if (isOpenrgbDevice(device.device_type)) {
        _fetchOpenrgbZones(device.url, 'device-zone-list');
    }
    showToast(t('device.scan.selected'), 'info');
}

export async function handleAddDevice(event: any) {
    event.preventDefault();

    const name = (document.getElementById('device-name') as HTMLInputElement).value.trim();
    const deviceType = (document.getElementById('device-type') as HTMLSelectElement)?.value || 'wled';
    const error = document.getElementById('add-device-error') as HTMLElement;

    let url;
    if (isMockDevice(deviceType)) {
        url = 'mock://';
    } else if (isWsDevice(deviceType)) {
        url = 'ws://';
    } else if (isSerialDevice(deviceType) || isEspnowDevice(deviceType)) {
        url = (document.getElementById('device-serial-port') as HTMLSelectElement).value;
    } else if (isChromaDevice(deviceType)) {
        const chromaType = (document.getElementById('device-chroma-device-type') as HTMLSelectElement)?.value || 'chromalink';
        url = `chroma://${chromaType}`;
    } else if (isGameSenseDevice(deviceType)) {
        url = 'gamesense://auto';
    } else {
        url = (document.getElementById('device-url') as HTMLInputElement).value.trim();
    }

    // MQTT: ensure mqtt:// prefix
    if (isMqttDevice(deviceType) && url && !url.startsWith('mqtt://')) {
        url = 'mqtt://' + url;
    }

    // OpenRGB: append selected zones to URL
    const checkedZones = isOpenrgbDevice(deviceType) ? _getCheckedZones('device-zone-list') : [];
    if (isOpenrgbDevice(deviceType) && checkedZones.length > 0) {
        url = _appendZonesToUrl(url, checkedZones);
    }

    if (!name || (!isMockDevice(deviceType) && !isWsDevice(deviceType) && !url)) {
        error.textContent = t('device_discovery.error.fill_all_fields');
        error.style.display = 'block';
        return;
    }

    const lastTemplateId = localStorage.getItem('lastCaptureTemplateId');

    try {
        const body: any = { name, url, device_type: deviceType };
        const ledCountInput = document.getElementById('device-led-count') as HTMLInputElement;
        if (ledCountInput && ledCountInput.value) {
            body.led_count = parseInt(ledCountInput.value, 10);
        }
        const baudRateSelect = document.getElementById('device-baud-rate') as HTMLSelectElement;
        if (isSerialDevice(deviceType) && baudRateSelect && baudRateSelect.value) {
            body.baud_rate = parseInt(baudRateSelect.value, 10);
        }
        if (isMockDevice(deviceType)) {
            const sendLatency = (document.getElementById('device-send-latency') as HTMLInputElement)?.value;
            if (sendLatency) body.send_latency_ms = parseInt(sendLatency, 10);
            const ledType = (document.getElementById('device-led-type') as HTMLSelectElement)?.value;
            body.rgbw = ledType === 'rgbw';
        }
        if (isOpenrgbDevice(deviceType) && checkedZones.length >= 2) {
            body.zone_mode = _getZoneMode();
        }
        if (isDmxDevice(deviceType)) {
            body.dmx_protocol = (document.getElementById('device-dmx-protocol') as HTMLSelectElement)?.value || 'artnet';
            body.dmx_start_universe = parseInt((document.getElementById('device-dmx-start-universe') as HTMLInputElement)?.value || '0', 10);
            body.dmx_start_channel = parseInt((document.getElementById('device-dmx-start-channel') as HTMLInputElement)?.value || '1', 10);
        }
        if (isEspnowDevice(deviceType)) {
            body.espnow_peer_mac = (document.getElementById('device-espnow-peer-mac') as HTMLInputElement)?.value || '';
            body.espnow_channel = parseInt((document.getElementById('device-espnow-channel') as HTMLInputElement)?.value || '1', 10);
            body.baud_rate = parseInt((document.getElementById('device-baud-rate') as HTMLSelectElement)?.value || '921600', 10);
        }
        if (isHueDevice(deviceType)) {
            body.hue_username = (document.getElementById('device-hue-username') as HTMLInputElement)?.value || '';
            body.hue_client_key = (document.getElementById('device-hue-client-key') as HTMLInputElement)?.value || '';
            body.hue_entertainment_group_id = (document.getElementById('device-hue-group-id') as HTMLInputElement)?.value || '';
        }
        if (isSpiDevice(deviceType)) {
            body.spi_speed_hz = parseInt((document.getElementById('device-spi-speed') as HTMLInputElement)?.value || '800000', 10);
            body.spi_led_type = (document.getElementById('device-spi-led-type') as HTMLSelectElement)?.value || 'WS2812B';
        }
        if (isChromaDevice(deviceType)) {
            body.chroma_device_type = (document.getElementById('device-chroma-device-type') as HTMLSelectElement)?.value || 'chromalink';
        }
        if (isGameSenseDevice(deviceType)) {
            body.gamesense_device_type = (document.getElementById('device-gamesense-device-type') as HTMLSelectElement)?.value || 'keyboard';
        }
        const csptId = (document.getElementById('device-css-processing-template') as HTMLSelectElement)?.value;
        if (csptId) body.default_css_processing_template_id = csptId;
        if (lastTemplateId) body.capture_template_id = lastTemplateId;

        const response = await fetchWithAuth('/devices', {
            method: 'POST',
            body: JSON.stringify(body)
        });

        if (response.ok) {
            const result = await response.json();
            console.log('Device added successfully:', result);
            showToast(t('device_discovery.added'), 'success');
            devicesCache.invalidate();
            addDeviceModal.forceClose();
            if (typeof window.loadDevices === 'function') await window.loadDevices();
            if (!localStorage.getItem('deviceTutorialSeen')) {
                localStorage.setItem('deviceTutorialSeen', '1');
                setTimeout(() => {
                    if (typeof window.startDeviceTutorial === 'function') window.startDeviceTutorial();
                }, 300);
            }
        } else {
            const errorData = await response.json();
            console.error('Failed to add device:', errorData);
            const detail = errorData.detail || errorData.message || '';
            const detailStr = Array.isArray(detail) ? detail.map((d: any) => d.msg || d).join('; ') : String(detail);
            error.textContent = detailStr || t('device_discovery.error.add_failed');
            error.style.display = 'block';
        }
    } catch (err: any) {
        if (err.isAuth) return;
        console.error('Failed to add device:', err);
        showToast(err.message || t('device_discovery.error.add_failed'), 'error');
    }
}

// ===== OpenRGB zone helpers =====

/**
 * Fetch zones for an OpenRGB device URL and render checkboxes in the given container.
 * @param {string} baseUrl - Base OpenRGB URL (e.g. openrgb://localhost:6742/0)
 * @param {string} containerId - ID of the zone checkbox list container
 * @param {string[]} [preChecked=[]] - Zone names to pre-check
 */
export async function _fetchOpenrgbZones(baseUrl: any, containerId: any, preChecked: any = []) {
    const container = document.getElementById(containerId) as HTMLElement;
    if (!container) return;

    container.innerHTML = `<span class="zone-loading">${t('device.openrgb.zone.loading')}</span>`;

    try {
        const resp = await fetchWithAuth(`/devices/openrgb-zones?url=${encodeURIComponent(baseUrl)}`);
        if (!resp.ok) {
            const err = await resp.json().catch(() => ({}));
            container.innerHTML = `<span class="zone-error">${err.detail || t('device.openrgb.zone.error')}</span>`;
            return;
        }
        const data = await resp.json();
        _renderZoneCheckboxes(container, data.zones, preChecked);
    } catch (err: any) {
        if (err.isAuth) return;
        container.innerHTML = `<span class="zone-error">${t('device.openrgb.zone.error')}</span>`;
    }
}

function _renderZoneCheckboxes(container: any, zones: any, preChecked: any = []) {
    container.innerHTML = '';
    container._zonesData = zones;
    const preSet = new Set(preChecked.map((n: any) => n.toLowerCase()));

    zones.forEach((zone: any) => {
        const label = document.createElement('label');
        label.className = 'zone-checkbox-item';

        const cb = document.createElement('input');
        cb.type = 'checkbox';
        cb.value = zone.name;
        if (preSet.has(zone.name.toLowerCase())) cb.checked = true;
        cb.addEventListener('change', () => _updateZoneModeVisibility(container.id));

        const nameSpan = document.createElement('span');
        nameSpan.textContent = zone.name;

        const countSpan = document.createElement('span');
        countSpan.className = 'zone-led-count';
        countSpan.textContent = `${zone.led_count} LEDs`;

        label.appendChild(cb);
        label.appendChild(nameSpan);
        label.appendChild(countSpan);
        container.appendChild(label);
    });

    _updateZoneModeVisibility(container.id);
}

export function _getCheckedZones(containerId: any) {
    const container = document.getElementById(containerId);
    if (!container) return [];
    return Array.from(container.querySelectorAll('input[type="checkbox"]:checked'))
        .map(cb => (cb as HTMLInputElement).value);
}

/**
 * Split an OpenRGB URL into base URL (without zones) and zone names.
 * E.g. "openrgb://localhost:6742/0/JRAINBOW1+JRAINBOW2" → { baseUrl: "openrgb://localhost:6742/0", zones: ["JRAINBOW1","JRAINBOW2"] }
 */
export function _splitOpenrgbZone(url: any) {
    if (!url || !url.startsWith('openrgb://')) return { baseUrl: url, zones: [] };
    const stripped = url.slice('openrgb://'.length);
    const parts = stripped.split('/');
    // parts: [host:port, device_index, ...zone_str]
    if (parts.length >= 3) {
        const zoneStr = parts.slice(2).join('/');
        const zones = zoneStr.split('+').map((z: any) => z.trim()).filter(Boolean);
        const baseUrl = 'openrgb://' + parts[0] + '/' + parts[1];
        return { baseUrl, zones };
    }
    return { baseUrl: url, zones: [] };
}

function _appendZonesToUrl(baseUrl: any, zones: any) {
    // Strip any existing zone suffix
    const { baseUrl: clean } = _splitOpenrgbZone(baseUrl);
    return clean + '/' + zones.join('+');
}

/** Show/hide zone mode toggle based on how many zones are checked. */
export function _updateZoneModeVisibility(containerId: any) {
    const modeGroupId = containerId === 'device-zone-list' ? 'device-zone-mode-group'
                      : containerId === 'settings-zone-list' ? 'settings-zone-mode-group'
                      : null;
    if (!modeGroupId) return;
    const modeGroup = document.getElementById(modeGroupId) as HTMLElement;
    if (!modeGroup) return;
    const checkedCount = _getCheckedZones(containerId).length;
    modeGroup.style.display = checkedCount >= 2 ? '' : 'none';
}

/** Get the selected zone mode radio value ('combined' or 'separate'). */
export function _getZoneMode(radioName = 'device-zone-mode') {
    const radio = document.querySelector(`input[name="${radioName}"]:checked`) as HTMLInputElement;
    return radio ? radio.value : 'combined';
}

/* ── New device type field visibility helpers ──────────────────── */

function _showEspnowFields(show: boolean) {
    const ids = ['device-espnow-peer-mac-group', 'device-espnow-channel-group'];
    ids.forEach(id => {
        const el = document.getElementById(id) as HTMLElement;
        if (el) el.style.display = show ? '' : 'none';
    });
}

function _showHueFields(show: boolean) {
    const ids = ['device-hue-username-group', 'device-hue-client-key-group', 'device-hue-group-id-group'];
    ids.forEach(id => {
        const el = document.getElementById(id) as HTMLElement;
        if (el) el.style.display = show ? '' : 'none';
    });
}

function _showSpiFields(show: boolean) {
    const ids = ['device-spi-speed-group', 'device-spi-led-type-group'];
    ids.forEach(id => {
        const el = document.getElementById(id) as HTMLElement;
        if (el) el.style.display = show ? '' : 'none';
    });
}

function _showChromaFields(show: boolean) {
    const el = document.getElementById('device-chroma-device-type-group') as HTMLElement;
    if (el) el.style.display = show ? '' : 'none';
}

function _showGameSenseFields(show: boolean) {
    const el = document.getElementById('device-gamesense-device-type-group') as HTMLElement;
    if (el) el.style.display = show ? '' : 'none';
}

/* ── Clone device ──────────────────────────────────────────────── */

export async function cloneDevice(deviceId: any) {
    try {
        const resp = await fetchWithAuth(`/devices/${deviceId}`);
        if (!resp.ok) throw new Error('Failed to load device');
        const device = await resp.json();
        showAddDevice(device.device_type || 'wled', device);
    } catch (error: any) {
        if (error.isAuth) return;
        console.error('Failed to clone device:', error);
        showToast(t('device.error.clone_failed'), 'error');
    }
}
