// Zone management JavaScript
let currentZoneId = null;
let brightnessSendTimeout = null;
/**
 * When true, the next `loadZoneContent` skips `sendZoneBrightness` (run/edit toggle: same zone, UI only).
 */
let suppressZoneContentDriverSideEffects = false;
/** First successful `loadZoneContent` after open: skip hardware brightness push (read-only hydration). */
let isFirstZoneContentHydration = true;

function clamp255(n) {
    const v = parseInt(n, 10);
    if (Number.isNaN(v)) return null;
    return Math.max(0, Math.min(255, v));
}

function applyBrightnessSliders(val) {
    const v = clamp255(val);
    if (v === null) return;
    const headerSlider = document.getElementById("header-brightness-slider");
    const menuSlider = document.getElementById("menu-brightness-slider");
    if (headerSlider) headerSlider.value = String(v);
    if (menuSlider) menuSlider.value = String(v);
}

async function saveZoneBrightnessToServer(zoneId, val) {
    if (!zoneId) return;
    try {
        const res = await fetch(`/zones/${zoneId}`, {
            method: "PUT",
            headers: { "Content-Type": "application/json", Accept: "application/json" },
            credentials: "same-origin",
            body: JSON.stringify({ brightness: val }),
        });
        if (!res.ok) {
            const err = await res.json().catch(() => ({}));
            console.warn("zone brightness save failed:", err.error || res.status);
        }
    } catch (e) {
        console.warn("zone brightness save failed:", e);
    }
}

function sendZoneBrightness(zoneId, value) {
    if (!zoneId) return;
    const val = Math.max(0, Math.min(255, parseInt(value, 10) || 0));
    const headerSlider = document.getElementById('header-brightness-slider');
    const menuSlider = document.getElementById('menu-brightness-slider');
    if (headerSlider && String(headerSlider.value) !== String(val)) {
        headerSlider.value = String(val);
    }
    if (menuSlider && String(menuSlider.value) !== String(val)) {
        menuSlider.value = String(val);
    }
    if (brightnessSendTimeout) {
        clearTimeout(brightnessSendTimeout);
    }
    brightnessSendTimeout = setTimeout(() => {
        (async () => {
            try {
                await saveZoneBrightnessToServer(zoneId, val);
                const section = document.querySelector('.presets-section[data-zone-id]');
                const names = typeof window.parseTabDeviceNames === 'function'
                    ? window.parseTabDeviceNames(section)
                    : [];
                const targetMacs =
                    names.length > 0 &&
                    typeof window.tabsManager !== 'undefined' &&
                    typeof window.tabsManager.resolveTabDeviceMacs === 'function'
                        ? await window.tabsManager.resolveTabDeviceMacs(names)
                        : [];
                if (typeof window.postDriverSequence === 'function') {
                    if (targetMacs.length > 0) {
                        let resolved = {};
                        try {
                            const rr = await fetch('/devices/resolve-brightness', {
                                method: 'POST',
                                credentials: 'same-origin',
                                headers: {
                                    'Content-Type': 'application/json',
                                    Accept: 'application/json',
                                },
                                body: JSON.stringify({
                                    macs: targetMacs,
                                    zone_brightness: val,
                                }),
                            });
                            if (rr.ok) {
                                const pack = await rr.json().catch(() => ({}));
                                if (pack && pack.values && typeof pack.values === 'object') {
                                    resolved = pack.values;
                                }
                            }
                        } catch (re) {
                            console.warn('resolve-brightness failed:', re);
                        }
                        for (const mac of targetMacs) {
                            const k = String(mac).toLowerCase();
                            const b =
                                resolved[k] != null && resolved[k] !== ''
                                    ? parseInt(resolved[k], 10)
                                    : val;
                            const bv = Number.isNaN(b)
                                ? val
                                : Math.max(0, Math.min(255, b));
                            await window.postDriverSequence(
                                [{ v: '1', b: bv, save: true }],
                                [mac],
                                0,
                            );
                        }
                        return;
                    }
                    await window.postDriverSequence([{ v: '1', b: val, save: true }], targetMacs, 0);
                    return;
                }
                // Fallback to raw websocket sender if presets.js helper isn't available yet.
                if (typeof window.sendEspnowRaw === 'function') {
                    window.sendEspnowRaw({ v: '1', b: val, save: true });
                }
            } catch (err) {
                console.error('Failed to send brightness via driver sequence:', err);
            }
        })();
    }, 150);
}

const isEditModeActive = () => {
    const toggle = document.querySelector('.ui-mode-toggle');
    return !!(toggle && toggle.getAttribute('aria-pressed') === 'true');
};

// Get current zone from cookie
function getCurrentZoneFromCookie() {
    const cookies = document.cookie.split(';');
    for (let cookie of cookies) {
        const [name, value] = cookie.trim().split('=');
        if (name === 'current_zone') {
            return value;
        }
    }
    return null;
}

async function fetchDevicesMap() {
    try {
        const response = await fetch("/devices", { headers: { Accept: "application/json" } });
        if (!response.ok) return {};
        const data = await response.json();
        return data && typeof data === "object" ? data : {};
    } catch (e) {
        console.error("fetchDevicesMap:", e);
        return {};
    }
}

async function fetchGroupsMap() {
    try {
        const response = await fetch("/groups", {
            headers: { Accept: "application/json" },
            credentials: "same-origin",
        });
        if (!response.ok) return {};
        const data = await response.json();
        return data && typeof data === "object" ? data : {};
    } catch (e) {
        console.error("fetchGroupsMap:", e);
        return {};
    }
}

/**
 * Resolve registry names + MACs for a zone document (``group_ids`` expands groups;
 * otherwise ``names`` only).
 */
async function computeZoneTargets(zone) {
    const dm = await fetchDevicesMap();
    const gids = Array.isArray(zone && zone.group_ids)
        ? zone.group_ids.map((x) => String(x).trim()).filter((x) => x.length > 0)
        : [];
    if (gids.length > 0) {
        const gm = await fetchGroupsMap();
        const seen = new Set();
        const names = [];
        const macs = [];
        for (const gid of gids) {
            const g = gm[gid];
            if (!g || !Array.isArray(g.devices)) continue;
            for (const raw of g.devices) {
                const m = String(raw || "")
                    .trim()
                    .toLowerCase()
                    .replace(/:/g, "")
                    .replace(/-/g, "");
                if (m.length !== 12) continue;
                if (seen.has(m)) continue;
                seen.add(m);
                const d = dm[m];
                const n = d && String((d.name || "").trim()) ? String(d.name).trim() : m;
                names.push(n);
                macs.push(m);
            }
        }
        return { names, macs };
    }
    const zoneNames = Array.isArray(zone && zone.names) ? zone.names : [];
    const rows = namesToRows(zoneNames, dm);
    return {
        names: rowsToNames(rows),
        macs: [...new Set(rows.map((r) => r.mac).filter(Boolean))],
    };
}

/** Tab device list for sequences: zone ``group_ids`` first, else legacy ``names`` only. */
async function computeZoneNamesTargets(zone) {
    const gids = Array.isArray(zone && zone.group_ids)
        ? zone.group_ids.map((x) => String(x).trim()).filter((x) => x.length > 0)
        : [];
    if (gids.length > 0) {
        const t = await resolveTargetsFromGroupIds(gids);
        return {
            names: Array.isArray(t.names) ? t.names : [],
            macs: Array.isArray(t.macs) ? [...new Set(t.macs.filter(Boolean))] : [],
        };
    }
    const dm = await fetchDevicesMap();
    const zoneNames = Array.isArray(zone && zone.names) ? zone.names : [];
    const rows = namesToRows(zoneNames, dm);
    return {
        names: rowsToNames(rows),
        macs: [...new Set(rows.map((r) => r.mac).filter(Boolean))],
    };
}

function normalizeDeviceMac(raw) {
    return String(raw || "")
        .trim()
        .toLowerCase()
        .replace(/:/g, "")
        .replace(/-/g, "");
}

/** Flat preset ids on a zone document (grid or flat). */
function tabPresetIdsInZoneDoc(zoneDoc) {
    let ids = [];
    if (Array.isArray(zoneDoc && zoneDoc.presets_flat)) {
        ids = zoneDoc.presets_flat.slice();
    } else if (Array.isArray(zoneDoc && zoneDoc.presets)) {
        if (zoneDoc.presets.length && typeof zoneDoc.presets[0] === "string") {
            ids = zoneDoc.presets.slice();
        } else if (zoneDoc.presets.length && Array.isArray(zoneDoc.presets[0])) {
            ids = zoneDoc.presets.flat();
        }
    }
    return (ids || []).filter(Boolean);
}

/** Group ids used for standalone presets on this zone: zone ``group_ids`` only. */
function effectiveGroupIdsForZonePreset(zoneDoc) {
    return Array.isArray(zoneDoc && zoneDoc.group_ids)
        ? zoneDoc.group_ids.map((x) => String(x).trim()).filter((x) => x.length > 0)
        : [];
}

/** Resolve device names + MACs from a list of group ids (same rules as zone group expansion). */
async function resolveTargetsFromGroupIds(groupIds) {
    const dm = await fetchDevicesMap();
    const gids = Array.isArray(groupIds)
        ? groupIds.map((x) => String(x).trim()).filter((x) => x.length > 0)
        : [];
    if (!gids.length) {
        return { names: [], macs: [] };
    }
    const gm = await fetchGroupsMap();
    const seen = new Set();
    const names = [];
    const macs = [];
    for (const gid of gids) {
        const g = gm[gid];
        if (!g || !Array.isArray(g.devices)) continue;
        for (const raw of g.devices) {
            const m = normalizeDeviceMac(raw);
            if (m.length !== 12) continue;
            if (seen.has(m)) continue;
            seen.add(m);
            const d = dm[m];
            const n = d && String((d.name || "").trim()) ? String(d.name).trim() : m;
            names.push(n);
            macs.push(m);
        }
    }
    return { names, macs };
}

/** Device names for standalone presets: zone ``group_ids``, or all devices on the tab (``names``). */
async function resolveDeviceNamesForZonePreset(zoneDoc, presetId) {
    void presetId;
    const gids = effectiveGroupIdsForZonePreset(zoneDoc);
    if (gids.length) {
        const t = await resolveTargetsFromGroupIds(gids);
        if (t.names.length) return t.names;
    }
    const zt = await computeZoneTargets(zoneDoc);
    return Array.isArray(zt.names) ? zt.names.slice() : [];
}

/** Union of devices targeted by standalone presets on the zone (same as zone preset targeting). */
async function computeZonePresetUnionTargets(zoneDoc) {
    return await computeZoneTargets(zoneDoc);
}

/**
 * Device names for one sequence step. Only devices in checked lane groups (within the zone tab).
 */
async function resolveSequenceStepDeviceNames(zone, stepGroupIds) {
    const zoneT = await computeZoneNamesTargets(zone);
    const names = Array.isArray(zoneT.names) ? zoneT.names : [];
    const macs = Array.isArray(zoneT.macs) ? zoneT.macs : [];
    const gids = Array.isArray(stepGroupIds)
        ? stepGroupIds.map((x) => String(x).trim()).filter((x) => x.length > 0)
        : [];
    if (!gids.length) {
        return [];
    }
    const zoneMacSet = new Set(
        macs.map((m) => normalizeDeviceMac(m)).filter((m) => m.length === 12),
    );
    const zoneNameByMac = new Map();
    for (let i = 0; i < macs.length; i++) {
        const m = normalizeDeviceMac(macs[i]);
        if (m.length === 12 && !zoneNameByMac.has(m)) {
            zoneNameByMac.set(m, names[i] || m);
        }
    }
    const gm = await fetchGroupsMap();
    const stepMacs = new Set();
    for (const gid of gids) {
        const g = gm[gid];
        if (!g || !Array.isArray(g.devices)) continue;
        for (const raw of g.devices) {
            const m = normalizeDeviceMac(raw);
            if (m.length !== 12 || !zoneMacSet.has(m)) continue;
            stepMacs.add(m);
        }
    }
    const out = [];
    for (const m of stepMacs) {
        const n = zoneNameByMac.get(m);
        if (n) out.push(n);
    }
    return out;
}

async function resolveZoneDeviceMacsFromZoneData(zone) {
    const t = await computeZoneTargets(zone);
    return t.macs;
}

/** Registry MACs for zone device names (order matches zone names; skips unknown names). */
async function resolveZoneDeviceMacs(zoneNames) {
    const section = document.querySelector(".presets-section[data-zone-id]");
    if (section) {
        const enc = section.getAttribute("data-zone-target-macs-json");
        if (enc) {
            try {
                const macs = JSON.parse(decodeURIComponent(enc));
                if (Array.isArray(macs) && macs.length) {
                    return [...new Set(macs.map((m) => String(m).toLowerCase()))];
                }
            } catch (e) {
                /* fall through */
            }
        }
    }
    const dm = await fetchDevicesMap();
    const rows = namesToRows(Array.isArray(zoneNames) ? zoneNames : [], dm);
    const macs = rows.map((r) => r.mac).filter(Boolean);
    return [...new Set(macs)];
}

function namesToRows(zoneNames, devicesMap) {
    const usedMacs = new Set();
    const list = Array.isArray(zoneNames) ? zoneNames : [];
    return list.map((name) => {
        const n = String(name || "").trim();
        const matches = Object.entries(devicesMap || {}).filter(
            ([mac, d]) => d && String((d.name || "").trim()) === n && !usedMacs.has(mac),
        );
        if (matches.length === 0) {
            return { mac: null, name: n || "unknown" };
        }
        const [mac] = matches[0];
        usedMacs.add(mac);
        return { mac, name: n };
    });
}

function rowsToNames(rows) {
    return (rows || []).map((r) => String(r.name || "").trim()).filter((n) => n.length > 0);
}

function renderZoneGroupsEditor(containerEl, rows, groupsMap) {
    if (!containerEl) return;
    containerEl.innerHTML = "";
    const entries = Object.entries(groupsMap || {}).sort(([a], [b]) => a.localeCompare(b));

    rows.forEach((row, idx) => {
        const div = document.createElement("div");
        div.className = "zone-device-row profiles-row";
        const label = document.createElement("span");
        label.className = "zone-device-row-label";
        const strong = document.createElement("strong");
        strong.textContent = row.name || row.id || "—";
        label.appendChild(strong);
        label.appendChild(document.createTextNode(" "));
        const sub = document.createElement("span");
        sub.className = "muted-text";
        sub.textContent = `group ${row.id}`;
        label.appendChild(sub);

        const rm = document.createElement("button");
        rm.type = "button";
        rm.className = "btn btn-danger btn-small";
        rm.textContent = "Remove";
        rm.addEventListener("click", () => {
            rows.splice(idx, 1);
            renderZoneGroupsEditor(containerEl, rows, groupsMap);
        });
        div.appendChild(label);
        div.appendChild(rm);
        containerEl.appendChild(div);
    });

    const idsInRows = new Set(rows.map((r) => String(r.id)));
    const addWrap = document.createElement("div");
    addWrap.className = "zone-devices-add profiles-actions";
    const sel = document.createElement("select");
    sel.className = "zone-device-add-select";
    sel.appendChild(new Option("Add group…", ""));
    entries.forEach(([gid, g]) => {
        if (idsInRows.has(gid)) return;
        const gn = g && g.name ? String(g.name).trim() : "";
        const optLabel = gn ? `${gn} (${gid})` : `Group ${gid}`;
        sel.appendChild(new Option(optLabel, gid));
    });
    const addBtn = document.createElement("button");
    addBtn.type = "button";
    addBtn.className = "btn btn-primary btn-small";
    addBtn.textContent = "Add";
    addBtn.addEventListener("click", () => {
        const gid = sel.value;
        if (!gid || !groupsMap[gid]) return;
        const gn = groupsMap[gid].name ? String(groupsMap[gid].name).trim() : gid;
        rows.push({ id: gid, name: gn });
        sel.value = "";
        renderZoneGroupsEditor(containerEl, rows, groupsMap);
    });
    addWrap.appendChild(sel);
    addWrap.appendChild(addBtn);
    containerEl.appendChild(addWrap);
}

/** Read zone device names from the presets section (JSON attr preferred; legacy comma list fallback). */
function parseTabDeviceNames(section) {
    if (!section) return [];
    const enc = section.getAttribute("data-device-names-json");
    if (enc) {
        try {
            const arr = JSON.parse(decodeURIComponent(enc));
            return Array.isArray(arr) ? arr.map((n) => String(n).trim()).filter((n) => n.length > 0) : [];
        } catch (e) {
            /* ignore */
        }
    }
    const legacy = section.getAttribute("data-device-names");
    if (legacy) {
        return legacy.split(",").map((n) => n.trim()).filter((n) => n.length > 0);
    }
    return [];
}

window.parseTabDeviceNames = parseTabDeviceNames;
window.parseZoneDeviceNames = parseTabDeviceNames;

function escapeHtmlAttr(s) {
    return String(s)
        .replace(/&/g, "&amp;")
        .replace(/"/g, "&quot;")
        .replace(/</g, "&lt;");
}

/** @returns {null | 'presets' | 'sequences'} */
function normalizeZoneContentKind(zoneDoc) {
    const k = zoneDoc && zoneDoc.content_kind;
    if (k === 'presets' || k === 'sequences') return k;
    return null;
}

/** Display/save kind when ``content_kind`` is missing (legacy rows). */
function effectiveZoneContentKind(zoneDoc) {
    const explicit = normalizeZoneContentKind(zoneDoc);
    if (explicit) return explicit;
    const seqIds = Array.isArray(zoneDoc && zoneDoc.sequence_ids)
        ? zoneDoc.sequence_ids.filter(Boolean)
        : [];
    const presetIds = tabPresetIdsInZoneDoc(zoneDoc || {});
    if (seqIds.length > 0 && presetIds.length === 0) return 'sequences';
    return 'presets';
}

/** @returns {boolean} */
function zoneAllowsPresets(zoneDoc, zoneId) {
    void zoneId;
    return effectiveZoneContentKind(zoneDoc) === 'presets';
}

/** @returns {boolean} */
function zoneAllowsSequences(zoneDoc, zoneId) {
    void zoneId;
    return effectiveZoneContentKind(zoneDoc) === 'sequences';
}

function applyZoneContentKindEditModal(kind) {
    const presetsBlock = document.getElementById('edit-zone-block-presets');
    const groupsBlock = document.getElementById('edit-zone-block-groups');
    const seqBlock = document.getElementById('edit-zone-block-sequences');
    const vis = (el, show) => {
        if (el) el.style.display = show ? '' : 'none';
    };
    const k = kind === 'sequences' ? 'sequences' : 'presets';
    vis(groupsBlock, true);
    vis(presetsBlock, k === 'presets');
    vis(seqBlock, k === 'sequences');
}

window.normalizeZoneContentKind = normalizeZoneContentKind;
window.effectiveZoneContentKind = effectiveZoneContentKind;
window.zoneAllowsPresets = zoneAllowsPresets;
window.zoneAllowsSequences = zoneAllowsSequences;

// Load tabs list
async function loadZones() {
    try {
        const response = await fetch('/zones');
        const data = await response.json();
        
        // Get current zone from cookie first, then from server response
        const cookieTabId = getCurrentZoneFromCookie();
        const serverCurrent = data.current_zone_id;
        const tabs = data.zones || {};
        const zoneIds = Object.keys(tabs);

        let candidateId = cookieTabId || serverCurrent || null;
        // If the candidate doesn't exist anymore (e.g. after DB reset), fall back to first zone.
        if (candidateId && !zoneIds.includes(String(candidateId))) {
            candidateId = zoneIds.length > 0 ? zoneIds[0] : null;
            // Clear stale cookie
            document.cookie = 'current_zone=; path=/; max-age=0';
        }

        currentZoneId = candidateId;
        renderZonesList(data.zones, data.zone_order, currentZoneId);
        
        // Load current zone content if available
        if (currentZoneId) {
            await loadZoneContent(currentZoneId);
        } else if (data.zone_order && data.zone_order.length > 0) {
            // Set first zone as current if none is set
            const firstTabId = data.zone_order[0];
            await setCurrentZone(firstTabId);
            await loadZoneContent(firstTabId);
        }
    } catch (error) {
        console.error('Failed to load zones:', error);
        const container = document.getElementById('zones-list');
        if (container) {
            container.innerHTML = '<div class="error">Failed to load zones</div>';
        }
    }
}

// Render tabs list in the main UI
function renderZonesList(tabs, tabOrder, currentZoneId) {
    const container = document.getElementById('zones-list');
    if (!container) return;
    
    if (!tabOrder || tabOrder.length === 0) {
        container.innerHTML = '<div class="muted-text">No zones available</div>';
        return;
    }
    
    const editMode = isEditModeActive();
    let html = '<div class="zones-list">';
    for (const zoneId of tabOrder) {
        const zone = tabs[zoneId];
        if (zone) {
            const activeClass = String(zoneId) === String(currentZoneId) ? 'active' : '';
            const disp = zone.name || `Zone ${zoneId}`;
            html += `
                <button class="zone-button ${activeClass}" 
                        data-zone-id="${zoneId}"
                        title="${editMode ? 'Click to select, right-click to edit' : 'Click to select'}"
                        onclick="selectZone('${zoneId}')">
                    ${escapeHtmlAttr(disp)}
                </button>
            `;
        }
    }
    html += '</div>';
    container.innerHTML = html;
}

// Render tabs list in modal (like profiles)
function renderZonesListModal(tabs, tabOrder, currentZoneId) {
    const container = document.getElementById('zones-list-modal');
    if (!container) return;
    
    container.innerHTML = "";
    let entries = [];

    if (Array.isArray(tabOrder)) {
        entries = tabOrder.map((zoneId) => [zoneId, tabs[zoneId] || {}]);
    } else if (tabs && typeof tabs === "object") {
        entries = Object.entries(tabs).filter(([key]) => {
            return key !== 'current_zone_id' && key !== 'zones' && key !== 'zone_order';
        });
    }

    if (entries.length === 0) {
        const empty = document.createElement("p");
        empty.className = "muted-text";
        empty.textContent = "No zones found.";
        container.appendChild(empty);
        return;
    }

    const editMode = isEditModeActive();
    entries.forEach(([zoneId, zone]) => {
        const row = document.createElement("div");
        row.className = "profiles-row";
        row.dataset.zoneId = String(zoneId);

        const label = document.createElement("span");
        const disp = zone.name || `Zone ${zoneId}`;
        label.textContent = disp;
        if (String(zoneId) === String(currentZoneId)) {
            label.textContent = `✓ ${disp}`;
            label.style.fontWeight = "bold";
            label.style.color = "#FFD700";
        }

        const applyButton = document.createElement("button");
        applyButton.className = "btn btn-secondary btn-small";
        applyButton.textContent = "Select";
        applyButton.addEventListener("click", async () => {
            await selectZone(zoneId);
            document.getElementById('zones-modal').classList.remove('active');
        });

        const editButton = document.createElement("button");
        editButton.className = "btn btn-secondary btn-small";
        editButton.textContent = "Edit";
        editButton.addEventListener("click", async () => {
            await openEditZoneModal(zoneId, zone);
        });

        const cloneButton = document.createElement("button");
        cloneButton.className = "btn btn-secondary btn-small";
        cloneButton.textContent = "Clone";
        cloneButton.addEventListener("click", async () => {
            const baseName = (zone && zone.name) || zoneId;
            const suggested = `${baseName} Copy`;
            const name = prompt("New zone name:", suggested);
            if (name === null) {
                return;
            }
            const trimmed = String(name).trim();
            if (!trimmed) {
                alert("Zone name cannot be empty.");
                return;
            }
            try {
                const response = await fetch(`/zones/${zoneId}/clone`, {
                    method: "POST",
                    headers: {
                        "Content-Type": "application/json",
                        "Accept": "application/json",
                    },
                    body: JSON.stringify({ name: trimmed }),
                });
                if (!response.ok) {
                    const errorData = await response.json().catch(() => ({ error: "Failed to clone zone" }));
                    throw new Error(errorData.error || "Failed to clone zone");
                }
                const data = await response.json().catch(() => null);
                let newTabId = null;
                if (data && typeof data === "object") {
                    if (data.id) {
                        newTabId = String(data.id);
                    } else {
                        const ids = Object.keys(data);
                        if (ids.length > 0) {
                            newTabId = String(ids[0]);
                        }
                    }
                }
                await loadZonesModal();
                if (newTabId) {
                    await selectZone(newTabId);
                } else {
                    await loadZones();
                }
            } catch (error) {
                console.error("Clone zone failed:", error);
                alert("Failed to clone zone: " + error.message);
            }
        });

        const deleteButton = document.createElement("button");
        deleteButton.className = "btn btn-danger btn-small";
        deleteButton.textContent = "Delete";
        deleteButton.addEventListener("click", async () => {
            const confirmed = confirm(`Delete zone "${label.textContent}"?`);
            if (!confirmed) {
                return;
            }
            try {
                const response = await fetch(`/zones/${zoneId}`, {
                    method: "DELETE",
                    headers: { Accept: "application/json" },
                });
                if (!response.ok) {
                    const errorData = await response.json().catch(() => ({ error: "Failed to delete zone" }));
                    throw new Error(errorData.error || "Failed to delete zone");
                }
                // Clear cookie if deleted zone was current
                if (zoneId === currentZoneId) {
                    document.cookie = 'current_zone=; path=/; max-age=0';
                    currentZoneId = null;
                }
                await loadZonesModal();
                await loadZones(); // Reload main tabs list
            } catch (error) {
                console.error("Delete zone failed:", error);
                alert("Failed to delete zone: " + error.message);
            }
        });

        row.appendChild(label);
        row.appendChild(applyButton);
        if (editMode) {
            row.appendChild(editButton);
            row.appendChild(cloneButton);
            row.appendChild(deleteButton);
        }
        container.appendChild(row);
    });
}

// Load tabs in modal
async function loadZonesModal() {
    const container = document.getElementById('zones-list-modal');
    if (!container) return;
    
    container.innerHTML = "";
    const loading = document.createElement("p");
    loading.className = "muted-text";
    loading.textContent = "Loading zones...";
    container.appendChild(loading);

    try {
        const response = await fetch("/zones", {
            headers: { Accept: "application/json" },
        });
        if (!response.ok) {
            throw new Error("Failed to load zones");
        }
        const data = await response.json();
        const tabs = data.zones || data;
        const currentZoneId = getCurrentZoneFromCookie() || data.current_zone_id || null;
        renderZonesListModal(tabs, data.zone_order || [], currentZoneId);
    } catch (error) {
        console.error("Load tabs failed:", error);
        container.innerHTML = "";
        const errorMessage = document.createElement("p");
        errorMessage.className = "muted-text";
        errorMessage.textContent = "Failed to load zones.";
        container.appendChild(errorMessage);
    }
}

// Select a zone
async function selectZone(zoneId) {
    // Update active state
    document.querySelectorAll('.zone-button').forEach(btn => {
        btn.classList.remove('active');
    });
    const btn = document.querySelector(`[data-zone-id="${zoneId}"]`);
    if (btn) {
        btn.classList.add('active');
    }
    
    // Set as current zone
    await setCurrentZone(zoneId);
    // Load zone content
    loadZoneContent(zoneId);
}

// Set current zone in cookie
async function setCurrentZone(zoneId) {
    try {
        const response = await fetch(`/zones/${zoneId}/set-current`, {
            method: 'POST'
        });
        const data = await response.json();
        if (response.ok) {
            currentZoneId = zoneId;
            // Also set cookie on client side
            document.cookie = `current_zone=${zoneId}; path=/; max-age=31536000`;
        } else {
            console.error('Failed to set current zone:', data.error);
        }
    } catch (error) {
        console.error('Error setting current zone:', error);
    }
}

// Load zone content
async function loadZoneContent(zoneId) {
    const container = document.getElementById('zone-content');
    if (!container) return;
    
    try {
        const response = await fetch(`/zones/${zoneId}`);
        const zone = await response.json();
        
        if (zone.error) {
            container.innerHTML = `<div class="error">${zone.error}</div>`;
            return;
        }
        
        // Render zone content (presets section)
        const tabName = zone.name || `Zone ${zoneId}`;
        const targets = await computeZoneTargets(zone);
        const namesJsonAttr = encodeURIComponent(JSON.stringify(targets.names));
        const macsJsonAttr = encodeURIComponent(JSON.stringify(targets.macs));
        const legacyOk =
            targets.names.length > 0 && !targets.names.some((n) => /[",]/.test(String(n)));
        const legacyAttr = legacyOk
            ? ` data-device-names="${escapeHtmlAttr(targets.names.join(","))}"`
            : "";
        container.innerHTML = `
            <div class="presets-section" data-zone-id="${zoneId}" data-device-names-json="${namesJsonAttr}" data-zone-target-macs-json="${macsJsonAttr}"${legacyAttr}>
                <div id="presets-list-zone" class="presets-list">
                    <!-- Presets will be loaded here by presets.js -->
                </div>
            </div>
        `;

        // Keep header and menu brightness controls in sync.
        const zoneBrightness =
            typeof zone.brightness === 'number'
                ? zone.brightness
                : parseInt(String(zone.brightness ?? ''), 10);
        const normalizedBrightness = Number.isFinite(zoneBrightness)
            ? Math.max(0, Math.min(255, Math.round(zoneBrightness)))
            : 255;
        applyBrightnessSliders(normalizedBrightness);
        const initialHydration = isFirstZoneContentHydration;
        if (isFirstZoneContentHydration) {
            isFirstZoneContentHydration = false;
        }
        if (!suppressZoneContentDriverSideEffects && !initialHydration) {
            // Apply this zone's saved brightness when switching zones (not initial page load or UI-only strip refresh).
            sendZoneBrightness(zoneId, normalizedBrightness);
        }
        
        // Trigger presets loading if the function exists
        if (typeof renderTabPresets === 'function') {
            renderTabPresets(zoneId);
        }
    } catch (error) {
        console.error('Failed to load zone content:', error);
        container.innerHTML = '<div class="error">Failed to load zone content</div>';
    }
}

// Send all presets used by all tabs in the current profile via /presets/send.
async function sendProfilePresets() {
    try {
        // Load current profile to get its tabs
        const profileRes = await fetch('/profiles/current', {
            headers: { Accept: 'application/json' },
        });
        if (!profileRes.ok) {
            alert('Failed to load current profile.');
            return;
        }
        const profileData = await profileRes.json();
        const profile = profileData.profile || {};
        let zoneList = null;
        if (Array.isArray(profile.zones)) {
            zoneList = profile.zones;
        } else if (profile.zones) {
            zoneList = [profile.zones];
        }
        if (!zoneList || zoneList.length === 0) {
            if (Array.isArray(profile.zones)) {
                zoneList = profile.zones;
            } else if (profile.zones) {
                zoneList = [profile.zones];
            }
        }
        if (!zoneList || zoneList.length === 0) {
            console.warn('sendProfilePresets: no zones found', {
                profileData,
                profile,
            });
        }

        if (!zoneList.length) {
            alert('Current profile has no zones to send presets for.');
            return;
        }

        let totalSent = 0;
        let totalMessages = 0;
        let zonesWithPresets = 0;

        for (const zoneId of zoneList) {
            try {
                const tabResp = await fetch(`/zones/${zoneId}`, {
                    headers: { Accept: 'application/json' },
                });
                if (!tabResp.ok) {
                    continue;
                }
                const tabData = await tabResp.json();
                let presetIds = [];
                if (Array.isArray(tabData.presets_flat)) {
                    presetIds = tabData.presets_flat;
                } else if (Array.isArray(tabData.presets)) {
                    if (tabData.presets.length && typeof tabData.presets[0] === 'string') {
                        presetIds = tabData.presets;
                    } else if (tabData.presets.length && Array.isArray(tabData.presets[0])) {
                        presetIds = tabData.presets.flat();
                    }
                }
                presetIds = (presetIds || []).filter(Boolean);
                if (!presetIds.length) {
                    continue;
                }
                zonesWithPresets += 1;
                const targets = await resolveZoneDeviceMacsFromZoneData(tabData);
                const payload = { preset_ids: presetIds };
                if (tabData.default_preset) {
                    payload.default = tabData.default_preset;
                }
                if (targets.length > 0) {
                    payload.targets = targets;
                }
                const response = await fetch('/presets/send', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'Accept': 'application/json',
                    },
                    body: JSON.stringify(payload),
                });
                const data = await response.json().catch(() => ({}));
                if (!response.ok) {
                    const msg = (data && data.error) || `Failed to send presets for zone ${zoneId}.`;
                    console.warn(msg);
                    continue;
                }
                totalSent += typeof data.presets_sent === 'number' ? data.presets_sent : presetIds.length;
                totalMessages += typeof data.messages_sent === 'number' ? data.messages_sent : 0;
            } catch (e) {
                console.error('Failed to send profile presets for zone:', zoneId, e);
            }
        }

        if (!zonesWithPresets) {
            alert('No presets to send for the current profile.');
            return;
        }

        const messagesLabel = totalMessages ? totalMessages : '?';
        alert(`Sent ${totalSent} preset(s) across ${zonesWithPresets} zone(s) (${messagesLabel} driver send(s)).`);
    } catch (error) {
        console.error('Failed to send profile presets:', error);
        alert('Failed to send profile presets.');
    }
}

function tabPresetIdsInOrder(tabData) {
    return tabPresetIdsInZoneDoc(tabData);
}

// Presets already on the zone (remove) and presets available to add (select).
async function refreshEditTabPresetsUi(zoneId) {
    const currentEl = document.getElementById("edit-zone-presets-current");
    const addEl = document.getElementById("edit-zone-presets-list");
    if (!zoneId || !currentEl || !addEl) return;

    currentEl.innerHTML = '<span class="muted-text">Loading…</span>';
    addEl.innerHTML = '<span class="muted-text">Loading…</span>';

    try {
        const tabRes = await fetch(`/zones/${zoneId}`, { headers: { Accept: "application/json" } });
        if (!tabRes.ok) {
            const msg = '<span class="muted-text">Failed to load zone presets.</span>';
            currentEl.innerHTML = msg;
            addEl.innerHTML = msg;
            return;
        }
        const tabData = await tabRes.json();
        if (!zoneAllowsPresets(tabData, zoneId)) {
            currentEl.innerHTML =
                '<span class="muted-text">This zone is for sequences only. Presets are hidden.</span>';
            addEl.innerHTML = '<span class="muted-text">—</span>';
            return;
        }
        const inTabIds = tabPresetIdsInOrder(tabData);
        const inTabSet = new Set(inTabIds.map((id) => String(id)));

        const presetsRes = await fetch("/presets", { headers: { Accept: "application/json" } });
        const allPresets = presetsRes.ok ? await presetsRes.json() : {};

        const makeRow = () => {
            const row = document.createElement("div");
            row.className = "profiles-row";
            row.style.display = "flex";
            row.style.alignItems = "center";
            row.style.justifyContent = "space-between";
            row.style.gap = "0.5rem";
            return row;
        };

        currentEl.innerHTML = "";
        if (inTabIds.length === 0) {
            currentEl.innerHTML = '<span class="muted-text">No presets on this zone yet.</span>';
        } else {
            for (const presetId of inTabIds) {
                const preset = allPresets[presetId] || {};
                const name = preset.name || presetId;
                const block = document.createElement("div");
                block.style.cssText =
                    "border:1px solid rgba(255,255,255,0.12);border-radius:6px;padding:0.5rem 0.65rem;margin-bottom:0.65rem;";
                const top = makeRow();
                const label = document.createElement("span");
                label.style.fontWeight = "600";
                label.textContent = name;
                const removeBtn = document.createElement("button");
                removeBtn.type = "button";
                removeBtn.className = "btn btn-danger btn-small";
                removeBtn.textContent = "Remove";
                removeBtn.addEventListener("click", async () => {
                    if (typeof window.removePresetFromTab !== "function") return;
                    if (!window.confirm(`Remove this preset from the zone?\n\n${name}`)) return;
                    await window.removePresetFromTab(zoneId, presetId);
                    await refreshEditTabPresetsUi(zoneId);
                });
                top.appendChild(label);
                top.appendChild(removeBtn);
                block.appendChild(top);

                currentEl.appendChild(block);
            }
        }

        const allIds = Object.keys(allPresets);
        const availableToAdd = allIds.filter((id) => !inTabSet.has(String(id)));
        addEl.innerHTML = "";
        if (availableToAdd.length === 0) {
            addEl.innerHTML =
                '<span class="muted-text">No presets to add. All presets are already on this zone.</span>';
        } else {
            const addWrap = document.createElement("div");
            addWrap.className = "zone-devices-add profiles-actions";
            const sel = document.createElement("select");
            sel.className = "zone-device-add-select";
            sel.setAttribute("aria-label", "Preset to add to this zone");
            sel.appendChild(new Option("Add preset…", ""));
            const sorted = availableToAdd.slice().sort((a, b) => {
                const na = (allPresets[a] && allPresets[a].name) || a;
                const nb = (allPresets[b] && allPresets[b].name) || b;
                return String(na).localeCompare(String(nb), undefined, { sensitivity: "base" });
            });
            sorted.forEach((presetId) => {
                const preset = allPresets[presetId] || {};
                const name = preset.name || presetId;
                sel.appendChild(new Option(`${name} — ${presetId}`, presetId));
            });
            const addBtn = document.createElement("button");
            addBtn.type = "button";
            addBtn.className = "btn btn-primary btn-small";
            addBtn.textContent = "Add";
            addBtn.addEventListener("click", async () => {
                const presetId = sel.value;
                if (!presetId) return;
                if (typeof window.addPresetToTab === "function") {
                    await window.addPresetToTab(presetId, zoneId);
                    sel.value = "";
                    await refreshEditTabPresetsUi(zoneId);
                }
            });
            addWrap.appendChild(sel);
            addWrap.appendChild(addBtn);
            addEl.appendChild(addWrap);
        }
    } catch (e) {
        console.error("refreshEditTabPresetsUi:", e);
        const msg = '<span class="muted-text">Failed to load presets.</span>';
        currentEl.innerHTML = msg;
        addEl.innerHTML = msg;
    }
}

async function populateEditTabPresetsList(zoneId) {
    await refreshEditTabPresetsUi(zoneId);
}

// Open edit zone modal
async function openEditZoneModal(zoneId, zone) {
    const modal = document.getElementById("edit-zone-modal");
    const idInput = document.getElementById("edit-zone-id");
    const nameInput = document.getElementById("edit-zone-name");

    let tabData = zone;
    if (!tabData || typeof tabData !== "object" || tabData.error) {
        try {
            const response = await fetch(`/zones/${zoneId}`);
            if (response.ok) {
                tabData = await response.json();
            }
        } catch (e) {
            console.error("openEditZoneModal fetch zone:", e);
        }
    }
    tabData = tabData || {};

    if (idInput) idInput.value = zoneId;
    if (nameInput) nameInput.value = tabData.name || "";

    const groupsEditor = document.getElementById("edit-zone-groups-editor");
    const groupsMap = await fetchGroupsMap();
    const rawGids = Array.isArray(tabData.group_ids) ? tabData.group_ids : [];
    window.__editTabGroupRows = rawGids.map((gid) => {
        const id = String(gid);
        const g = groupsMap[id];
        return { id, name: g && g.name ? String(g.name).trim() : id };
    });
    renderZoneGroupsEditor(groupsEditor, window.__editTabGroupRows, groupsMap);

    const kind = effectiveZoneContentKind(tabData);
    const typeLabel = document.getElementById('edit-zone-type-label');
    if (typeLabel) {
        typeLabel.textContent =
            kind === 'sequences'
                ? 'Zone type: Sequences (set when the zone was created)'
                : 'Zone type: Presets (set when the zone was created)';
    }

    if (modal) modal.classList.add("active");
    applyZoneContentKindEditModal(kind);
    await refreshEditTabPresetsUi(zoneId);
    if (typeof window.refreshEditTabSequencesUi === "function") {
        await window.refreshEditTabSequencesUi(zoneId);
    }
}

// Update an existing zone (name, group list; devices come from groups only).
async function updateZone(zoneId, name, groupRows) {
    try {
        const gids = Array.isArray(groupRows)
            ? groupRows.map((r) => String(r.id || "").trim()).filter((x) => x.length > 0)
            : [];
        let existing = {};
        try {
            const cur = await fetch(`/zones/${encodeURIComponent(zoneId)}`, {
                headers: { Accept: 'application/json' },
            });
            if (cur.ok) {
                const j = await cur.json();
                if (j && typeof j === 'object') existing = j;
            }
        } catch (_) {
            /* use empty existing */
        }
        const lockedKind = effectiveZoneContentKind(existing);
        const response = await fetch(`/zones/${zoneId}`, {
            method: 'PUT',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                ...existing,
                name: name,
                names: [],
                group_ids: gids,
                preset_group_ids:
                    existing.preset_group_ids && typeof existing.preset_group_ids === 'object'
                        ? existing.preset_group_ids
                        : {},
                content_kind: lockedKind,
            })
        });
        
        const data = await response.json();
        if (response.ok) {
            // Reload tabs list
            await loadZonesModal();
            await loadZones();
            if (String(currentZoneId) === String(zoneId)) {
                await loadZoneContent(zoneId);
            }
            // Close modal
            document.getElementById('edit-zone-modal').classList.remove('active');
            return true;
        } else {
            alert(`Error: ${data.error || 'Failed to update zone'}`);
            return false;
        }
    } catch (error) {
        console.error('Failed to update zone:', error);
        alert('Failed to update zone');
        return false;
    }
}

// Create a new zone (add devices in Edit zone). ``contentKind`` is ``'presets'`` | ``'sequences'``.
async function createZone(name, contentKind) {
    try {
        const ck =
            contentKind === 'sequences' || contentKind === 'presets' ? contentKind : 'presets';
        const response = await fetch('/zones', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                name: name,
                names: [],
                group_ids: [],
                content_kind: ck,
            })
        });
        
        const data = await response.json();
        if (response.ok) {
            // Reload tabs list
            await loadZonesModal();
            await loadZones();
            // Select the new zone
            if (data && Object.keys(data).length > 0) {
                const newTabId = Object.keys(data)[0];
                await selectZone(newTabId);
            }
            return true;
        } else {
            alert(`Error: ${data.error || 'Failed to create zone'}`);
            return false;
        }
    } catch (error) {
        console.error('Failed to create zone:', error);
        alert('Failed to create zone');
        return false;
    }
}

// Initialize on page load
document.addEventListener('DOMContentLoaded', () => {
    loadZones();
    
    // Set up tabs modal
    const tabsButton = document.getElementById('zones-btn');
    const zonesModal = document.getElementById('zones-modal');
    const tabsCloseButton = document.getElementById('zones-close-btn');
    const newTabNameInput = document.getElementById("new-zone-name");
    const createZoneButton = document.getElementById("create-zone-btn");

    if (tabsButton && zonesModal) {
        tabsButton.addEventListener("click", async () => {
            zonesModal.classList.add("active");
            await loadZonesModal();
        });
    }
    
    if (tabsCloseButton) {
        tabsCloseButton.addEventListener('click', () => {
            zonesModal.classList.remove('active');
        });
    }
    
    // Right-click on a zone button in the main header bar to edit that zone
    document.addEventListener('contextmenu', async (event) => {
        if (!isEditModeActive()) {
            return;
        }
        const btn = event.target.closest('.zone-button');
        if (!btn || !btn.dataset.zoneId) {
            return;
        }
        event.preventDefault();
        const zoneId = btn.dataset.zoneId;
        try {
            const response = await fetch(`/zones/${zoneId}`);
            if (response.ok) {
                const zone = await response.json();
                await openEditZoneModal(zoneId, zone);
            } else {
                alert('Failed to load zone for editing');
            }
        } catch (error) {
            console.error('Failed to load zone:', error);
            alert('Failed to load zone for editing');
        }
    });
    
    // Set up create zone
    const createZoneHandler = async () => {
        if (!newTabNameInput) return;
        const name = newTabNameInput.value.trim();

        if (name) {
            const kindRadio = document.querySelector(
                'input[name="new-zone-content-kind"]:checked',
            );
            const contentKind =
                kindRadio && kindRadio.value === 'sequences' ? 'sequences' : 'presets';
            await createZone(name, contentKind);
            if (newTabNameInput) newTabNameInput.value = "";
        }
    };
    
    if (createZoneButton) {
        createZoneButton.addEventListener('click', createZoneHandler);
    }
    
    if (newTabNameInput) {
        newTabNameInput.addEventListener('keypress', (event) => {
            if (event.key === 'Enter') {
                createZoneHandler();
            }
        });
    }
    
    // Set up edit zone form
    const editZoneForm = document.getElementById('edit-zone-form');
    if (editZoneForm) {
        editZoneForm.addEventListener("submit", async (e) => {
            e.preventDefault();
            const idInput = document.getElementById("edit-zone-id");
            const nameInput = document.getElementById("edit-zone-name");

            const zoneId = idInput ? idInput.value : null;
            const name = nameInput ? nameInput.value.trim() : "";
            const groupRows = window.__editTabGroupRows || [];

            if (zoneId && name) {
                await updateZone(zoneId, name, groupRows);
                editZoneForm.reset();
            }
        });
    }
    
    // Profile-wide "Send Presets" button in header
    const sendProfilePresetsBtn = document.getElementById('send-profile-presets-btn');
    if (sendProfilePresetsBtn) {
        sendProfilePresetsBtn.addEventListener('click', async () => {
            await sendProfilePresets();
        });
    }

    const menuBrightnessSlider = document.getElementById('menu-brightness-slider');
    const headerBrightnessSlider = document.getElementById('header-brightness-slider');
    (async () => {
        if (menuBrightnessSlider) {
            menuBrightnessSlider.addEventListener('input', (e) => {
                if (!currentZoneId) return;
                sendZoneBrightness(currentZoneId, e.target.value);
            });
        }
        if (headerBrightnessSlider) {
            headerBrightnessSlider.addEventListener('input', (e) => {
                if (!currentZoneId) return;
                sendZoneBrightness(currentZoneId, e.target.value);
            });
        }
    })();

    // When run/edit mode toggles, refresh tabs UI so edit actions show/hide immediately.
    document.querySelectorAll('.ui-mode-toggle').forEach((btn) => {
        btn.addEventListener('click', async () => {
            suppressZoneContentDriverSideEffects = true;
            try {
                await loadZones();
                if (zonesModal && zonesModal.classList.contains("active")) {
                    await loadZonesModal();
                }
            } finally {
                suppressZoneContentDriverSideEffects = false;
            }
        });
    });
});

window.selectZone = selectZone;

// Export for use in other scripts
window.zonesManager = {
    loadZones,
    loadZonesModal,
    selectZone,
    createZone,
    updateZone,
    openEditZoneModal,
    resolveZoneDeviceMacs,
    resolveZoneDeviceMacsFromZoneData,
    resolveTabDeviceMacs: resolveZoneDeviceMacs,
    getCurrentZoneId: () => currentZoneId,
    computeZoneTargets,
    computeZoneNamesTargets,
    computeZonePresetUnionTargets,
    effectiveGroupIdsForZonePreset,
    resolveDeviceNamesForZonePreset,
    resolveSequenceStepDeviceNames,
    fetchGroupsMap,
    renderZoneGroupsEditor,
};
window.tabsManager = window.zonesManager;
window.tabsManager.getCurrentTabId = () => currentZoneId;
window.tabsManager.loadTabs = loadZones;
window.tabsManager.loadTabsModal = loadZonesModal;
window.tabsManager.openEditTabModal = openEditZoneModal;
