// Device registry: name, id (storage key), type (led), transport (wifi|espnow), address

const HEX_BOX_COUNT = 12;

/** Last TCP snapshot from WebSocket (so we can apply after async list render). */
let lastTcpSnapshotIps = null;

/** Match server-side ``normalize_tcp_peer_ip`` for WS events vs registry rows. */
function normalizeWifiAddressForMatch(addr) {
    let s = String(addr || '').trim();
    if (s.toLowerCase().startsWith('::ffff:')) {
        s = s.slice(7);
    }
    return s;
}

const DEVICES_MODAL_POLL_MS = 1000;

let devicesModalLiveTimer = null;

function stopDevicesModalLiveRefresh() {
    if (devicesModalLiveTimer != null) {
        clearInterval(devicesModalLiveTimer);
        devicesModalLiveTimer = null;
    }
}

/**
 * Refetch registry and re-render the list (no loading spinner). Keeps scroll position.
 * Used while the devices modal stays open so new TCP devices, renames, and removals appear live.
 */
async function refreshDevicesListQuiet() {
    const modal = document.getElementById('devices-modal');
    if (!modal || !modal.classList.contains('active')) return;
    const container = document.getElementById('devices-list-modal');
    if (!container) return;
    const prevTop = container.scrollTop;
    try {
        const res = await fetch('/devices', { headers: { Accept: 'application/json' } });
        if (!res.ok) return;
        const data = await res.json();
        renderDevicesList(data || {});
        container.scrollTop = prevTop;
    } catch (_) {
        /* ignore */
    }
}

function startDevicesModalLiveRefresh() {
    stopDevicesModalLiveRefresh();
    devicesModalLiveTimer = setInterval(() => {
        refreshDevicesListQuiet();
    }, DEVICES_MODAL_POLL_MS);
}

function updateWifiRowDot(row, connected) {
    const dot = row.querySelector('.device-status-dot');
    if (!dot) return;
    if ((row.dataset.deviceTransport || '') !== 'wifi') return;
    dot.classList.remove('device-status-dot--online', 'device-status-dot--offline', 'device-status-dot--unknown');
    if (connected) {
        dot.classList.add('device-status-dot--online');
        dot.title = 'Connected (Wi-Fi TCP session)';
    } else {
        dot.classList.add('device-status-dot--offline');
        dot.title = 'Not connected (no Wi-Fi TCP session)';
    }
    dot.setAttribute('aria-label', dot.title);
}

function applyTcpSnapshot(ips) {
    const set = new Set(
        (ips || []).map((x) => normalizeWifiAddressForMatch(x)).filter(Boolean),
    );
    const container = document.getElementById('devices-list-modal');
    if (!container) return;
    container.querySelectorAll('.profiles-row[data-device-transport="wifi"]').forEach((row) => {
        const addr = normalizeWifiAddressForMatch(row.dataset.deviceAddress);
        updateWifiRowDot(row, set.has(addr));
    });
}

/** Keep cached snapshot aligned with incremental WS events (connect/disconnect). */
function mergeTcpSnapshotPresence(ip, connected) {
    const n = normalizeWifiAddressForMatch(ip);
    if (!n) return;
    const prev = lastTcpSnapshotIps;
    const set = new Set(
        (Array.isArray(prev) ? prev : []).map((x) => normalizeWifiAddressForMatch(x)).filter(Boolean),
    );
    if (connected) {
        set.add(n);
    } else {
        set.delete(n);
    }
    lastTcpSnapshotIps = Array.from(set);
}

function makeHexAddressBoxes(container) {
    if (!container || container.querySelector('.hex-addr-box')) return;
    container.innerHTML = '';
    for (let i = 0; i < HEX_BOX_COUNT; i++) {
        const input = document.createElement('input');
        input.type = 'text';
        input.className = 'hex-addr-box';
        input.maxLength = 1;
        input.autocomplete = 'off';
        input.setAttribute('data-index', i);
        input.setAttribute('inputmode', 'numeric');
        input.setAttribute('aria-label', `Hex digit ${i + 1}`);
        input.addEventListener('input', (e) => {
            const v = e.target.value.replace(/[^0-9a-fA-F]/g, '');
            e.target.value = v;
            if (v && e.target.nextElementSibling && e.target.nextElementSibling.classList.contains('hex-addr-box')) {
                e.target.nextElementSibling.focus();
            }
        });
        input.addEventListener('keydown', (e) => {
            if (e.key === 'Backspace' && !e.target.value && e.target.previousElementSibling) {
                e.target.previousElementSibling.focus();
            }
        });
        input.addEventListener('paste', (e) => {
            e.preventDefault();
            const pasted = (e.clipboardData.getData('text') || '').replace(/[^0-9a-fA-F]/g, '').slice(0, HEX_BOX_COUNT);
            const boxes = container.querySelectorAll('.hex-addr-box');
            for (let j = 0; j < pasted.length && j < boxes.length; j++) {
                boxes[j].value = pasted[j];
            }
            if (pasted.length > 0) {
                const nextIdx = Math.min(pasted.length, boxes.length - 1);
                boxes[nextIdx].focus();
            }
        });
        container.appendChild(input);
    }
}

function setAddressToBoxes(container, addrStr) {
    if (!container) return;
    const s = (addrStr || '').replace(/[^0-9a-fA-F]/g, '').slice(0, HEX_BOX_COUNT);
    const boxes = container.querySelectorAll('.hex-addr-box');
    boxes.forEach((b, i) => {
        b.value = s[i] || '';
    });
}

function applyTransportVisibility(transport) {
    const isWifi = transport === 'wifi';
    const esp = document.getElementById('edit-device-address-espnow');
    const wifiWrap = document.getElementById('edit-device-address-wifi-wrap');
    if (esp) esp.hidden = isWifi;
    if (wifiWrap) wifiWrap.hidden = !isWifi;
}

function getAddressForPayload(transport) {
    if (transport === 'wifi') {
        const el = document.getElementById('edit-device-address-wifi');
        const v = (el && el.value.trim()) || '';
        return v || null;
    }
    const boxEl = document.getElementById('edit-device-address-boxes');
    if (!boxEl) return null;
    const boxes = boxEl.querySelectorAll('.hex-addr-box');
    const hex = Array.from(boxes).map((b) => b.value).join('').toLowerCase();
    return hex || null;
}

async function loadDevicesModal() {
    const container = document.getElementById('devices-list-modal');
    if (!container) return;
    if (typeof window.getEspnowSocket === 'function') {
        window.getEspnowSocket();
    }
    container.innerHTML = '<span class="muted-text">Loading...</span>';
    try {
        const response = await fetch('/devices', { headers: { Accept: 'application/json' } });
        if (!response.ok) throw new Error('Failed to load devices');
        const devices = await response.json();
        renderDevicesList(devices || {});
    } catch (e) {
        console.error('loadDevicesModal:', e);
        container.innerHTML = '<span class="muted-text">Failed to load devices.</span>';
    }
}

function renderDevicesList(devices) {
    const container = document.getElementById('devices-list-modal');
    if (!container) return;
    container.innerHTML = '';
    const ids = Object.keys(devices).filter((k) => devices[k] && typeof devices[k] === 'object');
    if (ids.length === 0) {
        const p = document.createElement('p');
        p.className = 'muted-text';
        p.textContent = 'No devices yet. Wi-Fi drivers will appear here when they connect over TCP.';
        container.appendChild(p);
        return;
    }
    ids.forEach((devId) => {
        const dev = devices[devId];
        const t = (dev && dev.type) || 'led';
        const tr = (dev && dev.transport) || 'espnow';
        const addrRaw = (dev && dev.address) != null ? String(dev.address).trim() : '';
        const addrDisplay = addrRaw || '—';

        const row = document.createElement('div');
        row.className = 'profiles-row';
        row.style.display = 'flex';
        row.style.alignItems = 'center';
        row.style.gap = '0.5rem';
        row.style.flexWrap = 'wrap';
        row.dataset.deviceId = devId;
        row.dataset.deviceTransport = tr;
        row.dataset.deviceAddress = addrRaw;

        const dot = document.createElement('span');
        dot.className = 'device-status-dot';
        dot.setAttribute('role', 'img');
        const live = dev && Object.prototype.hasOwnProperty.call(dev, 'connected') ? dev.connected : null;
        if (live === true) {
            dot.classList.add('device-status-dot--online');
            dot.title = 'Connected (Wi-Fi TCP session)';
            dot.setAttribute('aria-label', dot.title);
        } else if (live === false) {
            dot.classList.add('device-status-dot--offline');
            dot.title = 'Not connected (no Wi-Fi TCP session)';
            dot.setAttribute('aria-label', dot.title);
        } else {
            dot.classList.add('device-status-dot--unknown');
            dot.title = 'ESP-NOW — TCP status does not apply';
            dot.setAttribute('aria-label', dot.title);
        }

        const label = document.createElement('span');
        label.textContent = (dev && dev.name) || devId;
        label.style.flex = '1';
        label.style.minWidth = '100px';

        const macEl = document.createElement('code');
        macEl.className = 'device-row-mac';
        macEl.textContent = devId;
        macEl.title = 'MAC (registry id)';

        const meta = document.createElement('span');
        meta.className = 'muted-text';
        meta.style.fontSize = '0.85em';
        meta.textContent = `${t} · ${tr} · ${addrDisplay}`;

        const editBtn = document.createElement('button');
        editBtn.className = 'btn btn-secondary btn-small';
        editBtn.textContent = 'Edit';
        editBtn.addEventListener('click', () => openEditDeviceModal(devId, dev));

        const identifyBtn = document.createElement('button');
        identifyBtn.className = 'btn btn-primary btn-small';
        identifyBtn.type = 'button';
        identifyBtn.textContent = 'Identify';
        identifyBtn.title = 'Red blink at 10 Hz (~50% brightness) for 2 s, then off (not saved as a preset)';
        identifyBtn.addEventListener('click', async () => {
            try {
                const res = await fetch(`/devices/${encodeURIComponent(devId)}/identify`, {
                    method: 'POST',
                    credentials: 'same-origin',
                    headers: { Accept: 'application/json' },
                });
                const data = await res.json().catch(() => ({}));
                if (!res.ok) {
                    alert(data.error || 'Identify failed');
                    return;
                }
            } catch (err) {
                console.error(err);
                alert('Identify failed');
            }
        });

        const deleteBtn = document.createElement('button');
        deleteBtn.className = 'btn btn-secondary btn-small';
        deleteBtn.textContent = 'Delete';
        deleteBtn.addEventListener('click', async () => {
            if (!confirm(`Delete device "${(dev && dev.name) || devId}"?`)) return;
            try {
                const res = await fetch(`/devices/${encodeURIComponent(devId)}`, { method: 'DELETE' });
                if (res.ok) await loadDevicesModal();
                else {
                    const data = await res.json().catch(() => ({}));
                    alert(data.error || 'Delete failed');
                }
            } catch (err) {
                console.error(err);
                alert('Delete failed');
            }
        });

        row.appendChild(dot);
        row.appendChild(label);
        row.appendChild(macEl);
        row.appendChild(meta);
        row.appendChild(editBtn);
        row.appendChild(identifyBtn);
        row.appendChild(deleteBtn);
        container.appendChild(row);
    });
    // Do not re-apply lastTcpSnapshotIps here: it is only updated on WS open and
    // device_tcp events; re-applying after each /devices poll overwrites correct
    // API "connected" with a stale list and leaves Wi-Fi rows stuck online.
}

function openEditDeviceModal(devId, dev) {
    const modal = document.getElementById('edit-device-modal');
    const idInput = document.getElementById('edit-device-id');
    const storageLabel = document.getElementById('edit-device-storage-id');
    const nameInput = document.getElementById('edit-device-name');
    const typeSel = document.getElementById('edit-device-type');
    const transportSel = document.getElementById('edit-device-transport');
    const addressBoxes = document.getElementById('edit-device-address-boxes');
    const wifiInput = document.getElementById('edit-device-address-wifi');
    if (!modal || !idInput) return;
    idInput.value = devId;
    if (storageLabel) storageLabel.textContent = devId;
    if (nameInput) nameInput.value = (dev && dev.name) || '';
    if (typeSel) typeSel.value = (dev && dev.type) || 'led';
    const tr = (dev && dev.transport) || 'espnow';
    if (transportSel) transportSel.value = tr;
    applyTransportVisibility(tr);
    setAddressToBoxes(addressBoxes, tr === 'espnow' ? ((dev && dev.address) || '') : '');
    if (wifiInput) wifiInput.value = tr === 'wifi' ? ((dev && dev.address) || '') : '';
    modal.classList.add('active');
}

async function updateDevice(devId, name, type, transport, address) {
    try {
        const res = await fetch(`/devices/${encodeURIComponent(devId)}`, {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                name,
                type: type || 'led',
                transport: transport || 'espnow',
                address,
            }),
        });
        const data = await res.json().catch(() => ({}));
        if (res.ok) {
            await loadDevicesModal();
            return true;
        }
        alert(data.error || 'Failed to update device');
        return false;
    } catch (e) {
        console.error('updateDevice:', e);
        alert('Failed to update device');
        return false;
    }
}

document.addEventListener('DOMContentLoaded', () => {
    window.addEventListener('deviceTcpStatus', (ev) => {
        const { ip, connected } = ev.detail || {};
        if (ip == null || typeof connected !== 'boolean') return;
        mergeTcpSnapshotPresence(ip, connected);
        const norm = normalizeWifiAddressForMatch(ip);
        const container = document.getElementById('devices-list-modal');
        if (!container) return;
        container.querySelectorAll('.profiles-row[data-device-transport="wifi"]').forEach((row) => {
            if (normalizeWifiAddressForMatch(row.dataset.deviceAddress) === norm) {
                updateWifiRowDot(row, connected);
            }
        });
    });
    window.addEventListener('deviceTcpSnapshot', (ev) => {
        const ips = ev.detail && ev.detail.connectedIps;
        lastTcpSnapshotIps = ips;
        applyTcpSnapshot(ips);
    });

    window.addEventListener('deviceTcpWsOpen', () => {
        refreshDevicesListQuiet();
    });

    makeHexAddressBoxes(document.getElementById('edit-device-address-boxes'));

    const transportEdit = document.getElementById('edit-device-transport');
    if (transportEdit) {
        transportEdit.addEventListener('change', () => {
            applyTransportVisibility(transportEdit.value);
        });
    }

    const devicesBtn = document.getElementById('devices-btn');
    const devicesModal = document.getElementById('devices-modal');
    const devicesCloseBtn = document.getElementById('devices-close-btn');
    const editForm = document.getElementById('edit-device-form');
    const editCloseBtn = document.getElementById('edit-device-close-btn');
    const editDeviceModal = document.getElementById('edit-device-modal');

    if (devicesBtn && devicesModal) {
        devicesBtn.addEventListener('click', () => {
            devicesModal.classList.add('active');
            if (typeof window.getEspnowSocket === 'function') {
                window.getEspnowSocket();
            }
            loadDevicesModal();
            startDevicesModalLiveRefresh();
        });
    }
    if (devicesCloseBtn) {
        devicesCloseBtn.addEventListener('click', () => {
            if (devicesModal) devicesModal.classList.remove('active');
        });
    }

    const devicesModalEl = document.getElementById('devices-modal');
    if (devicesModalEl) {
        new MutationObserver(() => {
            if (!devicesModalEl.classList.contains('active')) {
                stopDevicesModalLiveRefresh();
            }
        }).observe(devicesModalEl, { attributes: true, attributeFilter: ['class'] });
    }

    if (editForm) {
        editForm.addEventListener('submit', async (e) => {
            e.preventDefault();
            const idInput = document.getElementById('edit-device-id');
            const nameInput = document.getElementById('edit-device-name');
            const typeSel = document.getElementById('edit-device-type');
            const transportSel = document.getElementById('edit-device-transport');
            const devId = idInput && idInput.value;
            if (!devId) return;
            const transport = (transportSel && transportSel.value) || 'espnow';
            const address = getAddressForPayload(transport);
            const ok = await updateDevice(
                devId,
                nameInput ? nameInput.value.trim() : '',
                (typeSel && typeSel.value) || 'led',
                transport,
                address
            );
            if (ok) editDeviceModal.classList.remove('active');
        });
    }
    if (editCloseBtn) {
        editCloseBtn.addEventListener('click', () => editDeviceModal && editDeviceModal.classList.remove('active'));
    }
});
