/**
 * Key Colors targets — cards, test lightbox, editor, WebSocket live colors.
 */

import {
    kcTestAutoRefresh, setKcTestAutoRefresh,
    kcTestTargetId, setKcTestTargetId,
    kcTestWs, setKcTestWs,
    kcTestFps, setKcTestFps,
    _kcNameManuallyEdited, set_kcNameManuallyEdited,
    kcWebSockets,
    PATTERN_RECT_BORDERS,
    _cachedValueSources, valueSourcesCache, streamsCache,
    outputTargetsCache, patternTemplatesCache,
} from '../core/state.ts';
import { API_BASE, getHeaders, fetchWithAuth, escapeHtml } from '../core/api.ts';
import { t } from '../core/i18n.ts';
import { lockBody, showToast, showConfirm, formatUptime, formatCompact, desktopFocus } from '../core/ui.ts';
import { Modal } from '../core/modal.ts';
import {
    getValueSourceIcon, getPictureSourceIcon,
    ICON_CLONE, ICON_EDIT, ICON_TEST, ICON_START, ICON_STOP,
    ICON_LINK_SOURCE, ICON_PATTERN_TEMPLATE, ICON_FPS, ICON_PALETTE,
} from '../core/icons.ts';
import * as P from '../core/icon-paths.ts';
import { wrapCard } from '../core/card-colors.ts';
import { TagInput, renderTagChips } from '../core/tag-input.ts';
import { IconSelect } from '../core/icon-select.ts';
import { EntitySelect } from '../core/entity-palette.ts';
import type { OutputTarget } from '../types.ts';

let _kcTagsInput: any = null;

class KCEditorModal extends Modal {
    constructor() {
        super('kc-editor-modal');
    }

    snapshotValues() {
        return {
            name: (document.getElementById('kc-editor-name') as HTMLInputElement).value,
            source: (document.getElementById('kc-editor-source') as HTMLSelectElement).value,
            fps: (document.getElementById('kc-editor-fps') as HTMLInputElement).value,
            interpolation: (document.getElementById('kc-editor-interpolation') as HTMLSelectElement).value,
            smoothing: (document.getElementById('kc-editor-smoothing') as HTMLInputElement).value,
            patternTemplateId: (document.getElementById('kc-editor-pattern-template') as HTMLSelectElement).value,
            brightness_vs: (document.getElementById('kc-editor-brightness-vs') as HTMLSelectElement).value,
            tags: JSON.stringify(_kcTagsInput ? _kcTagsInput.getValue() : []),
        };
    }
}

const kcEditorModal = new KCEditorModal();

/* ── Visual selectors ─────────────────────────────────────────── */

const _icon = (d: any) => `<svg class="icon" viewBox="0 0 24 24">${d}</svg>`;

let _kcColorModeIconSelect: any = null;
let _kcSourceEntitySelect: any = null;
let _kcPatternEntitySelect: any = null;
let _kcBrightnessEntitySelect: any = null;

// Inline SVG previews for color modes
const _COLOR_MODE_SVG = {
    average:  '<svg viewBox="0 0 60 24" width="60" height="24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="4" width="52" height="16" rx="3" opacity="0.3" fill="currentColor"/><path d="M30 8v8" stroke-width="1.5"/><path d="M20 10v4" stroke-width="1.5"/><path d="M40 10v4" stroke-width="1.5"/></svg>',
    median:   '<svg viewBox="0 0 60 24" width="60" height="24" fill="none" stroke="currentColor" stroke-width="2"><rect x="6" y="14" width="8" height="6" rx="1" fill="currentColor" opacity="0.3"/><rect x="18" y="8" width="8" height="12" rx="1" fill="currentColor" opacity="0.5"/><rect x="30" y="4" width="8" height="16" rx="1" fill="currentColor" opacity="0.7"/><rect x="42" y="10" width="8" height="10" rx="1" fill="currentColor" opacity="0.4"/></svg>',
    dominant: '<svg viewBox="0 0 60 24" width="60" height="24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="20" cy="12" r="4" opacity="0.2" fill="currentColor"/><circle cx="30" cy="12" r="8" fill="currentColor" opacity="0.6"/><circle cx="42" cy="12" r="3" opacity="0.15" fill="currentColor"/></svg>',
};

function _ensureColorModeIconSelect() {
    const sel = document.getElementById('kc-editor-interpolation');
    if (!sel) return;
    const items = [
        { value: 'average',  icon: _COLOR_MODE_SVG.average,  label: t('kc.interpolation.average'),  desc: t('kc.interpolation.average.desc') },
        { value: 'median',   icon: _COLOR_MODE_SVG.median,   label: t('kc.interpolation.median'),   desc: t('kc.interpolation.median.desc') },
        { value: 'dominant', icon: _COLOR_MODE_SVG.dominant, label: t('kc.interpolation.dominant'), desc: t('kc.interpolation.dominant.desc') },
    ];
    if (_kcColorModeIconSelect) { _kcColorModeIconSelect.updateItems(items); return; }
    _kcColorModeIconSelect = new IconSelect({ target: sel, items, columns: 3 } as any);
}

function _ensureSourceEntitySelect(sources: any) {
    const sel = document.getElementById('kc-editor-source');
    if (!sel) return;
    if (_kcSourceEntitySelect) _kcSourceEntitySelect.destroy();
    if (sources.length > 0) {
        _kcSourceEntitySelect = new EntitySelect({
            target: sel,
            getItems: () => sources.map((s: any) => ({
                value: s.id,
                label: s.name,
                icon: getPictureSourceIcon(s.stream_type),
                desc: s.stream_type,
            })),
            placeholder: t('palette.search'),
        } as any);
    }
}

function _ensurePatternEntitySelect(patTemplates: any) {
    const sel = document.getElementById('kc-editor-pattern-template');
    if (!sel) return;
    if (_kcPatternEntitySelect) _kcPatternEntitySelect.destroy();
    if (patTemplates.length > 0) {
        _kcPatternEntitySelect = new EntitySelect({
            target: sel,
            getItems: () => patTemplates.map((pt: any) => {
                const rectCount = (pt.rectangles || []).length;
                return {
                    value: pt.id,
                    label: pt.name,
                    icon: _icon(P.fileText),
                    desc: `${rectCount} rect${rectCount !== 1 ? 's' : ''}`,
                };
            }),
            placeholder: t('palette.search'),
        } as any);
    }
}

function _ensureBrightnessEntitySelect() {
    const sel = document.getElementById('kc-editor-brightness-vs');
    if (!sel) return;
    if (_kcBrightnessEntitySelect) _kcBrightnessEntitySelect.destroy();
    if (_cachedValueSources.length > 0) {
        _kcBrightnessEntitySelect = new EntitySelect({
            target: sel,
            getItems: () => {
                const items = [{ value: '', label: t('kc.brightness_vs.none'), icon: _icon(P.sunDim), desc: '' }];
                return items.concat(_cachedValueSources.map((vs: any) => ({
                    value: vs.id,
                    label: vs.name,
                    icon: getValueSourceIcon(vs.source_type),
                    desc: vs.source_type,
                })));
            },
            placeholder: t('palette.search'),
        } as any);
    }
}

export function patchKCTargetMetrics(target: any) {
    const card = document.querySelector(`[data-kc-target-id="${CSS.escape(target.id)}"]`);
    if (!card) return;
    const state = target.state || {};
    const metrics = target.metrics || {};

    const fpsActual = card.querySelector('[data-tm="fps-actual"]');
    if (fpsActual) fpsActual.textContent = state.fps_actual?.toFixed(1) || '0.0';

    const fpsCurrent = card.querySelector('[data-tm="fps-current"]');
    if (fpsCurrent) fpsCurrent.textContent = state.fps_current ?? '-';

    const fpsTarget = card.querySelector('[data-tm="fps-target"]');
    if (fpsTarget) fpsTarget.textContent = state.fps_target || 0;

    const frames = card.querySelector('[data-tm="frames"]') as HTMLElement;
    if (frames) { frames.textContent = formatCompact(metrics.frames_processed || 0); frames.title = String(metrics.frames_processed || 0); }

    const keepalive = card.querySelector('[data-tm="keepalive"]') as HTMLElement;
    if (keepalive) { keepalive.textContent = formatCompact(state.frames_keepalive ?? 0); keepalive.title = String(state.frames_keepalive ?? 0); }

    const errors = card.querySelector('[data-tm="errors"]') as HTMLElement;
    if (errors) { errors.textContent = formatCompact(metrics.errors_count || 0); errors.title = String(metrics.errors_count || 0); }

    const uptime = card.querySelector('[data-tm="uptime"]');
    if (uptime) uptime.textContent = formatUptime(metrics.uptime_seconds);

    const timing = card.querySelector('[data-tm="timing"]');
    if (timing && state.timing_total_ms != null) {
        timing.innerHTML = `
            <div class="timing-header">
                <div class="metric-label">${t('device.metrics.timing')}</div>
                <div class="timing-total"><strong>${state.timing_total_ms}ms</strong></div>
            </div>
            <div class="timing-bar">
                <span class="timing-seg timing-extract" style="flex:${state.timing_calc_colors_ms}" title="calc ${state.timing_calc_colors_ms}ms"></span>
                <span class="timing-seg timing-smooth" style="flex:${state.timing_smooth_ms || 0.1}" title="smooth ${state.timing_smooth_ms}ms"></span>
                <span class="timing-seg timing-send" style="flex:${state.timing_broadcast_ms}" title="broadcast ${state.timing_broadcast_ms}ms"></span>
            </div>
            <div class="timing-legend">
                <span class="timing-legend-item"><span class="timing-dot timing-extract"></span>calc ${state.timing_calc_colors_ms}ms</span>
                <span class="timing-legend-item"><span class="timing-dot timing-smooth"></span>smooth ${state.timing_smooth_ms}ms</span>
                <span class="timing-legend-item"><span class="timing-dot timing-send"></span>broadcast ${state.timing_broadcast_ms}ms</span>
            </div>`;
    }
}

export function createKCTargetCard(target: OutputTarget & { state?: any; metrics?: any; latestColors?: any }, sourceMap: Record<string, any>, patternTemplateMap: Record<string, any>, valueSourceMap: Record<string, any>) {
    const state = target.state || {};
    const kcSettings = target.key_colors_settings ?? {} as Partial<import('../types.ts').KeyColorsSettings>;

    const isProcessing = state.processing || false;
    const brightness = kcSettings.brightness ?? 1.0;
    const brightnessInt = Math.round(brightness * 255);

    const source = sourceMap[target.picture_source_id!];
    const sourceName = source ? source.name : (target.picture_source_id || 'No source');
    const patTmpl = patternTemplateMap[kcSettings.pattern_template_id!];
    const patternName = patTmpl ? patTmpl.name : 'No pattern';
    const rectCount = patTmpl ? (patTmpl.rectangles || []).length : 0;

    const bvsId = kcSettings.brightness_value_source_id || '';
    const bvs = bvsId && valueSourceMap ? valueSourceMap[bvsId] : null;

    // Render initial color swatches from pre-fetched REST data
    let swatchesHtml = '';
    const latestColors = target.latestColors && target.latestColors.colors;
    if (isProcessing && latestColors && Object.keys(latestColors).length > 0) {
        swatchesHtml = Object.entries(latestColors).map(([name, color]: [string, any]) => `
            <div class="kc-swatch">
                <div class="kc-swatch-color" style="background-color: ${color.hex}" title="${color.hex}"></div>
                <span class="kc-swatch-label" title="${escapeHtml(name)}">${escapeHtml(name)}</span>
            </div>
        `).join('');
    } else if (isProcessing) {
        swatchesHtml = `<span class="kc-no-colors">${t('kc.colors.none')}</span>`;
    }

    return wrapCard({
        dataAttr: 'data-kc-target-id',
        id: target.id,
        removeOnclick: `deleteKCTarget('${target.id}')`,
        removeTitle: t('common.delete'),
        content: `
            <div class="card-header">
                <div class="card-title" title="${escapeHtml(target.name)}">
                    <span class="card-title-text">${escapeHtml(target.name)}</span>
                </div>
            </div>
            <div class="stream-card-props">
                <span class="stream-card-prop${source ? ' stream-card-link' : ''}" title="${t('kc.source')}"${source ? ` onclick="event.stopPropagation(); navigateToCard('streams','${source.stream_type === 'static_image' ? 'static_image' : source.stream_type === 'processed' ? 'processed' : 'raw'}','${source.stream_type === 'static_image' ? 'static-streams' : source.stream_type === 'processed' ? 'proc-streams' : 'raw-streams'}','data-stream-id','${target.picture_source_id}')"` : ''}>${ICON_LINK_SOURCE} ${escapeHtml(sourceName)}</span>
                <span class="stream-card-prop${patTmpl ? ' stream-card-link' : ''}" title="${t('kc.pattern_template')}"${patTmpl ? ` onclick="event.stopPropagation(); navigateToCard('targets','kc-patterns','kc-patterns','data-pattern-template-id','${kcSettings.pattern_template_id}')"` : ''}>${ICON_PATTERN_TEMPLATE} ${escapeHtml(patternName)}</span>
                <span class="stream-card-prop">▭ ${rectCount} rect${rectCount !== 1 ? 's' : ''}</span>
                <span class="stream-card-prop" title="${t('kc.fps')}">${ICON_FPS} ${kcSettings.fps ?? 10}</span>
                ${bvs ? `<span class="stream-card-prop stream-card-prop-full stream-card-link" title="${t('targets.brightness_vs')}" onclick="event.stopPropagation(); navigateToCard('streams','value','value-sources','data-id','${bvsId}')">${getValueSourceIcon(bvs.source_type)} ${escapeHtml(bvs.name)}</span>` : ''}
            </div>
            ${renderTagChips(target.tags)}
            <div class="brightness-control" data-kc-brightness-wrap="${target.id}">
                <input type="range" class="brightness-slider" min="0" max="255"
                       value="${brightnessInt}" data-kc-brightness="${target.id}"
                       oninput="updateKCBrightnessLabel('${target.id}', this.value)"
                       onchange="saveKCBrightness('${target.id}', this.value)"
                       title="${Math.round(brightness * 100)}%">
            </div>
            <div id="kc-swatches-${target.id}" class="kc-color-swatches">
                ${swatchesHtml}
            </div>
            ${isProcessing ? `
            <div class="card-content">
                <div class="metrics-grid">
                    <div class="metric">
                        <div class="metric-label">${t('device.metrics.actual_fps')}</div>
                        <div class="metric-value" data-tm="fps-actual">---</div>
                    </div>
                    <div class="metric">
                        <div class="metric-label">${t('device.metrics.current_fps')}</div>
                        <div class="metric-value" data-tm="fps-current">---</div>
                    </div>
                    <div class="metric">
                        <div class="metric-label">${t('device.metrics.target_fps')}</div>
                        <div class="metric-value" data-tm="fps-target">---</div>
                    </div>
                    <div class="metric">
                        <div class="metric-label">${t('device.metrics.frames')}</div>
                        <div class="metric-value" data-tm="frames">---</div>
                    </div>
                    <div class="metric">
                        <div class="metric-label">${t('device.metrics.keepalive')}</div>
                        <div class="metric-value" data-tm="keepalive">---</div>
                    </div>
                    <div class="metric">
                        <div class="metric-label">${t('device.metrics.errors')}</div>
                        <div class="metric-value" data-tm="errors">---</div>
                    </div>
                    <div class="metric">
                        <div class="metric-label">${t('device.metrics.uptime')}</div>
                        <div class="metric-value" data-tm="uptime">---</div>
                    </div>
                </div>
                ${state.timing_total_ms != null ? `
                <div class="timing-breakdown" data-tm="timing"></div>
                ` : ''}
            </div>
            ` : ''}`,
        actions: `
                ${isProcessing ? `
                    <button class="btn btn-icon btn-danger" onclick="stopTargetProcessing('${target.id}')" title="${t('targets.button.stop')}">
                        ${ICON_STOP}
                    </button>
                ` : `
                    <button class="btn btn-icon btn-primary" onclick="startTargetProcessing('${target.id}')" title="${t('targets.button.start')}">
                        ${ICON_START}
                    </button>
                `}
                <button class="btn btn-icon btn-secondary" onclick="testKCTarget('${target.id}')" title="${t('kc.test')}">
                    ${ICON_TEST}
                </button>
                <button class="btn btn-icon btn-secondary" onclick="cloneKCTarget('${target.id}')" title="${t('common.clone')}">
                    ${ICON_CLONE}
                </button>
                <button class="btn btn-icon btn-secondary" onclick="showKCEditor('${target.id}')" title="${t('common.edit')}">
                    ${ICON_EDIT}
                </button>`,
    });
}

// ===== KEY COLORS TEST =====

function _openKCTestWs(targetId: any, fps: any, previewWidth = 480) {
    // Close any existing WS
    if (kcTestWs) {
        try { kcTestWs.close(); } catch (_) {}
        setKcTestWs(null);
    }

    const key = localStorage.getItem('wled_api_key');
    if (!key) return;

    const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
    const wsUrl = `${protocol}//${window.location.host}${API_BASE}/output-targets/${targetId}/test/ws?token=${encodeURIComponent(key)}&fps=${fps}&preview_width=${previewWidth}`;

    const ws = new WebSocket(wsUrl);

    ws.onmessage = (event) => {
        try {
            const data = JSON.parse(event.data);
            if (data.type === 'frame') {
                // Hide spinner on first frame
                const spinner = document.querySelector('.lightbox-spinner') as HTMLElement;
                if (spinner) spinner.style.display = 'none';
                displayKCTestResults(data);
            }
        } catch (e) {
            console.error('KC test WS parse error:', e);
        }
    };

    ws.onclose = (ev) => {
        setKcTestWs(null);
        // Only show error if closed unexpectedly (not a normal close)
        if (ev.code !== 1000 && ev.code !== 1001 && kcTestTargetId) {
            const reason = ev.reason || t('kc.test.ws_closed');
            showToast(t('kc.test.error') + ': ' + reason, 'error');
            // Close lightbox on fatal errors (auth, bad target, etc.)
            if (ev.code === 4001 || ev.code === 4003 || ev.code === 4004) {
                if (typeof window.closeLightbox === 'function') window.closeLightbox();
            }
        }
    };

    ws.onerror = () => {
        // onclose will fire after onerror; no need to handle here
    };

    setKcTestWs(ws);
    setKcTestTargetId(targetId);
}

export async function testKCTarget(targetId: any) {
    setKcTestTargetId(targetId);

    // Show lightbox immediately with a spinner
    const lightbox = document.getElementById('image-lightbox')!;
    const lbImg = document.getElementById('lightbox-image') as HTMLImageElement;
    const statsEl = document.getElementById('lightbox-stats') as HTMLElement;
    lbImg.style.display = 'none';
    lbImg.src = '';
    statsEl.style.display = 'none';

    // Insert spinner if not already present
    let spinner = lightbox.querySelector('.lightbox-spinner') as HTMLElement;
    if (!spinner) {
        spinner = document.createElement('div');
        spinner.className = 'lightbox-spinner loading-spinner';
        lightbox.querySelector('.lightbox-content')!.prepend(spinner);
    }
    spinner.style.display = '';

    // Hide controls — KC test streams automatically
    const refreshBtn = document.getElementById('lightbox-auto-refresh') as HTMLElement;
    if (refreshBtn) refreshBtn.style.display = 'none';
    const fpsSelect = document.getElementById('lightbox-fps-select') as HTMLElement;
    if (fpsSelect) fpsSelect.style.display = 'none';

    lightbox.classList.add('active');
    lockBody();

    // Use same FPS from CSS test settings and dynamic preview resolution
    const fps = parseInt(localStorage.getItem('css_test_fps')!) || 15;
    const previewWidth = Math.round(Math.min(window.innerWidth * 0.8, 1920) * Math.min(window.devicePixelRatio || 1, 2));
    _openKCTestWs(targetId, fps, previewWidth);
}

export function stopKCTestAutoRefresh() {
    if (kcTestAutoRefresh) {
        clearInterval(kcTestAutoRefresh);
        setKcTestAutoRefresh(null);
    }
    if (kcTestWs) {
        try { kcTestWs.close(1000, 'lightbox closed'); } catch (_) {}
        setKcTestWs(null);
    }
    setKcTestTargetId(null);
}

export function displayKCTestResults(result: any) {
    const srcImg = new window.Image();
    srcImg.onload = () => {
        const canvas = document.createElement('canvas');
        canvas.width = srcImg.width;
        canvas.height = srcImg.height;
        const ctx = canvas.getContext('2d')!;

        // Draw captured frame
        ctx.drawImage(srcImg, 0, 0);

        const w = srcImg.width;
        const h = srcImg.height;

        // Draw each rectangle with extracted color overlay
        result.rectangles.forEach((rect: any, i: number) => {
            const px = rect.x * w;
            const py = rect.y * h;
            const pw = rect.width * w;
            const ph = rect.height * h;

            const color = rect.color;
            const borderColor = PATTERN_RECT_BORDERS[i % PATTERN_RECT_BORDERS.length];

            // Semi-transparent fill with the extracted color
            ctx.fillStyle = `rgba(${color.r}, ${color.g}, ${color.b}, 0.3)`;
            ctx.fillRect(px, py, pw, ph);

            // Border using pattern colors for distinction
            ctx.strokeStyle = borderColor;
            ctx.lineWidth = 3;
            ctx.strokeRect(px, py, pw, ph);

            // Color swatch in top-left corner of rect
            const swatchSize = Math.max(16, Math.min(32, pw * 0.15));
            ctx.fillStyle = color.hex;
            ctx.fillRect(px + 4, py + 4, swatchSize, swatchSize);
            ctx.strokeStyle = '#fff';
            ctx.lineWidth = 1;
            ctx.strokeRect(px + 4, py + 4, swatchSize, swatchSize);

            // Name label with shadow for readability
            const fontSize = Math.max(12, Math.min(18, pw * 0.06));
            ctx.font = `bold ${fontSize}px sans-serif`;
            const labelX = px + swatchSize + 10;
            const labelY = py + 4 + swatchSize / 2 + fontSize / 3;
            ctx.shadowColor = 'rgba(0,0,0,0.8)';
            ctx.shadowBlur = 4;
            ctx.fillStyle = '#fff';
            ctx.fillText(rect.name, labelX, labelY);

            // Hex label below name
            ctx.font = `${fontSize - 2}px monospace`;
            ctx.fillText(color.hex, labelX, labelY + fontSize + 2);
            ctx.shadowBlur = 0;
        });

        const dataUrl = canvas.toDataURL('image/jpeg', 0.92);

        // Build stats HTML
        let statsHtml = `<div style="display:flex;flex-wrap:wrap;gap:8px;align-items:center;">`;
        statsHtml += `<span style="opacity:0.7;margin-right:8px;">${escapeHtml(result.pattern_template_name)} \u2022 ${escapeHtml(result.interpolation_mode)}</span>`;
        result.rectangles.forEach((rect: any) => {
            const c = rect.color;
            statsHtml += `<div style="display:flex;align-items:center;gap:4px;">`;
            statsHtml += `<div style="width:14px;height:14px;border-radius:3px;border:1px solid rgba(255,255,255,0.4);background:${c.hex};"></div>`;
            statsHtml += `<span style="font-size:0.85em;">${escapeHtml(rect.name)} <code>${c.hex}</code></span>`;
            statsHtml += `</div>`;
        });
        statsHtml += `</div>`;

        // Hide spinner, show result in the already-open lightbox
        const spinner = document.querySelector('.lightbox-spinner') as HTMLElement;
        if (spinner) spinner.style.display = 'none';

        const lbImg = document.getElementById('lightbox-image') as HTMLImageElement;
        const statsEl = document.getElementById('lightbox-stats') as HTMLElement;
        lbImg.src = dataUrl;
        lbImg.style.display = '';
        statsEl.innerHTML = statsHtml;
        statsEl.style.display = '';
    };
    srcImg.src = result.image;
}

// ===== KEY COLORS EDITOR =====

function _autoGenerateKCName() {
    if (_kcNameManuallyEdited) return;
    if ((document.getElementById('kc-editor-id') as HTMLInputElement).value) return;
    const sourceSelect = document.getElementById('kc-editor-source') as HTMLSelectElement;
    const sourceName = sourceSelect.selectedOptions[0]?.dataset?.name || '';
    if (!sourceName) return;
    const mode = (document.getElementById('kc-editor-interpolation') as HTMLSelectElement).value || 'average';
    const modeName = t(`kc.interpolation.${mode}`);
    const patSelect = document.getElementById('kc-editor-pattern-template') as HTMLSelectElement;
    const patName = patSelect.selectedOptions[0]?.dataset?.name || '';
    (document.getElementById('kc-editor-name') as HTMLInputElement).value = `${sourceName} \u00b7 ${patName} (${modeName})`;
}

function _populateKCBrightnessVsDropdown(selectedId = '') {
    const sel = document.getElementById('kc-editor-brightness-vs') as HTMLSelectElement;
    // Keep the first "None" option, remove the rest
    while (sel.options.length > 1) sel.remove(1);
    _cachedValueSources.forEach((vs: any) => {
        const opt = document.createElement('option');
        opt.value = vs.id;
        opt.textContent = vs.name;
        sel.appendChild(opt);
    });
    sel.value = selectedId || '';
    _ensureBrightnessEntitySelect();
}

export async function showKCEditor(targetId: any = null, cloneData: any = null) {
    try {
        // Load sources, pattern templates, and value sources in parallel
        const [sources, patTemplates, valueSources] = await Promise.all([
            streamsCache.fetch().catch((): any[] => []),
            patternTemplatesCache.fetch().catch((): any[] => []),
            valueSourcesCache.fetch(),
        ]);

        // Populate source select
        const sourceSelect = document.getElementById('kc-editor-source') as HTMLSelectElement;
        sourceSelect.innerHTML = '';
        sources.forEach((s: any) => {
            const opt = document.createElement('option');
            opt.value = s.id;
            opt.dataset.name = s.name;
            opt.textContent = s.name;
            sourceSelect.appendChild(opt);
        });

        // Populate pattern template select
        const patSelect = document.getElementById('kc-editor-pattern-template') as HTMLSelectElement;
        patSelect.innerHTML = '';
        patTemplates.forEach((pt: any) => {
            const opt = document.createElement('option');
            opt.value = pt.id;
            opt.dataset.name = pt.name;
            const rectCount = (pt.rectangles || []).length;
            opt.textContent = `${pt.name} (${rectCount} rect${rectCount !== 1 ? 's' : ''})`;
            patSelect.appendChild(opt);
        });

        // Set up visual selectors
        _ensureColorModeIconSelect();
        _ensureSourceEntitySelect(sources);
        _ensurePatternEntitySelect(patTemplates);

        let _editorTags: any[] = [];
        if (targetId) {
            const resp = await fetch(`${API_BASE}/output-targets/${targetId}`, { headers: getHeaders() });
            if (!resp.ok) throw new Error('Failed to load target');
            const target = await resp.json();
            _editorTags = target.tags || [];
            const kcSettings = target.key_colors_settings || {};

            (document.getElementById('kc-editor-id') as HTMLInputElement).value = target.id;
            (document.getElementById('kc-editor-name') as HTMLInputElement).value = target.name;
            sourceSelect.value = target.picture_source_id || '';
            (document.getElementById('kc-editor-fps') as HTMLInputElement).value = kcSettings.fps ?? 10;
            (document.getElementById('kc-editor-fps-value') as HTMLElement).textContent = kcSettings.fps ?? 10;
            (document.getElementById('kc-editor-interpolation') as HTMLSelectElement).value = kcSettings.interpolation_mode ?? 'average';
            if (_kcColorModeIconSelect) _kcColorModeIconSelect.setValue(kcSettings.interpolation_mode ?? 'average');
            (document.getElementById('kc-editor-smoothing') as HTMLInputElement).value = kcSettings.smoothing ?? 0.3;
            (document.getElementById('kc-editor-smoothing-value') as HTMLElement).textContent = kcSettings.smoothing ?? 0.3;
            patSelect.value = kcSettings.pattern_template_id || '';
            _populateKCBrightnessVsDropdown(kcSettings.brightness_value_source_id || '');
            (document.getElementById('kc-editor-title') as HTMLElement).innerHTML = `${ICON_PALETTE} ${t('kc.edit')}`;
        } else if (cloneData) {
            _editorTags = cloneData.tags || [];
            const kcSettings = cloneData.key_colors_settings || {};
            (document.getElementById('kc-editor-id') as HTMLInputElement).value = '';
            (document.getElementById('kc-editor-name') as HTMLInputElement).value = (cloneData.name || '') + ' (Copy)';
            sourceSelect.value = cloneData.picture_source_id || '';
            (document.getElementById('kc-editor-fps') as HTMLInputElement).value = kcSettings.fps ?? 10;
            (document.getElementById('kc-editor-fps-value') as HTMLElement).textContent = kcSettings.fps ?? 10;
            (document.getElementById('kc-editor-interpolation') as HTMLSelectElement).value = kcSettings.interpolation_mode ?? 'average';
            if (_kcColorModeIconSelect) _kcColorModeIconSelect.setValue(kcSettings.interpolation_mode ?? 'average');
            (document.getElementById('kc-editor-smoothing') as HTMLInputElement).value = kcSettings.smoothing ?? 0.3;
            (document.getElementById('kc-editor-smoothing-value') as HTMLElement).textContent = kcSettings.smoothing ?? 0.3;
            patSelect.value = kcSettings.pattern_template_id || '';
            _populateKCBrightnessVsDropdown(kcSettings.brightness_value_source_id || '');
            (document.getElementById('kc-editor-title') as HTMLElement).innerHTML = `${ICON_PALETTE} ${t('kc.add')}`;
        } else {
            (document.getElementById('kc-editor-id') as HTMLInputElement).value = '';
            (document.getElementById('kc-editor-name') as HTMLInputElement).value = '';
            if (sourceSelect.options.length > 0) sourceSelect.selectedIndex = 0;
            (document.getElementById('kc-editor-fps') as HTMLInputElement).value = '10' as any;
            (document.getElementById('kc-editor-fps-value') as HTMLElement).textContent = '10';
            (document.getElementById('kc-editor-interpolation') as HTMLSelectElement).value = 'average';
            if (_kcColorModeIconSelect) _kcColorModeIconSelect.setValue('average');
            (document.getElementById('kc-editor-smoothing') as HTMLInputElement).value = '0.3' as any;
            (document.getElementById('kc-editor-smoothing-value') as HTMLElement).textContent = '0.3';
            if (patTemplates.length > 0) patSelect.value = patTemplates[0].id;
            _populateKCBrightnessVsDropdown('');
            (document.getElementById('kc-editor-title') as HTMLElement).innerHTML = `${ICON_PALETTE} ${t('kc.add')}`;
        }

        // Auto-name
        set_kcNameManuallyEdited(!!(targetId || cloneData));
        (document.getElementById('kc-editor-name') as HTMLInputElement).oninput = () => { set_kcNameManuallyEdited(true); };
        sourceSelect.onchange = () => _autoGenerateKCName();
        (document.getElementById('kc-editor-interpolation') as HTMLSelectElement).onchange = () => _autoGenerateKCName();
        patSelect.onchange = () => _autoGenerateKCName();
        if (!targetId && !cloneData) _autoGenerateKCName();

        // Tags
        if (_kcTagsInput) _kcTagsInput.destroy();
        _kcTagsInput = new TagInput(document.getElementById('kc-tags-container'), {
            placeholder: t('tags.placeholder'),
        });
        _kcTagsInput.setValue(_editorTags);

        kcEditorModal.snapshot();
        kcEditorModal.open();

        (document.getElementById('kc-editor-error') as HTMLElement).style.display = 'none';
        setTimeout(() => desktopFocus(document.getElementById('kc-editor-name')), 100);
    } catch (error) {
        console.error('Failed to open KC editor:', error);
        showToast(t('kc_target.error.editor_open_failed'), 'error');
    }
}

export function isKCEditorDirty() {
    return kcEditorModal.isDirty();
}

export async function closeKCEditorModal() {
    await kcEditorModal.close();
    set_kcNameManuallyEdited(false);
}

export function forceCloseKCEditorModal() {
    if (_kcTagsInput) { _kcTagsInput.destroy(); _kcTagsInput = null; }
    kcEditorModal.forceClose();
    set_kcNameManuallyEdited(false);
}

export async function saveKCEditor() {
    const targetId = (document.getElementById('kc-editor-id') as HTMLInputElement).value;
    const name = (document.getElementById('kc-editor-name') as HTMLInputElement).value.trim();
    const sourceId = (document.getElementById('kc-editor-source') as HTMLSelectElement).value;
    const fps = parseInt((document.getElementById('kc-editor-fps') as HTMLInputElement).value) || 10;
    const interpolation = (document.getElementById('kc-editor-interpolation') as HTMLSelectElement).value;
    const smoothing = parseFloat((document.getElementById('kc-editor-smoothing') as HTMLInputElement).value);
    const patternTemplateId = (document.getElementById('kc-editor-pattern-template') as HTMLSelectElement).value;
    const brightnessVsId = (document.getElementById('kc-editor-brightness-vs') as HTMLSelectElement).value;

    if (!name) {
        kcEditorModal.showError(t('kc.error.required'));
        return;
    }

    if (!patternTemplateId) {
        kcEditorModal.showError(t('kc.error.no_pattern'));
        return;
    }

    const payload: any = {
        name,
        picture_source_id: sourceId,
        tags: _kcTagsInput ? _kcTagsInput.getValue() : [],
        key_colors_settings: {
            fps,
            interpolation_mode: interpolation,
            smoothing,
            pattern_template_id: patternTemplateId,
            brightness_value_source_id: brightnessVsId,
        },
    };

    try {
        let response;
        if (targetId) {
            response = await fetchWithAuth(`/output-targets/${targetId}`, {
                method: 'PUT',
                body: JSON.stringify(payload),
            });
        } else {
            payload.target_type = 'key_colors';
            response = await fetchWithAuth('/output-targets', {
                method: 'POST',
                body: JSON.stringify(payload),
            });
        }

        if (!response.ok) {
            const err = await response.json();
            throw new Error(err.detail || 'Failed to save');
        }

        showToast(targetId ? t('kc.updated') : t('kc.created'), 'success');
        outputTargetsCache.invalidate();
        kcEditorModal.forceClose();
        // Use window.* to avoid circular import with targets.js
        if (typeof window.loadTargetsTab === 'function') window.loadTargetsTab();
    } catch (error: any) {
        if (error.isAuth) return;
        console.error('Error saving KC target:', error);
        kcEditorModal.showError(error.message);
    }
}

export async function cloneKCTarget(targetId: any) {
    try {
        const targets = await outputTargetsCache.fetch();
        const target = targets.find((t: any) => t.id === targetId);
        if (!target) throw new Error('Target not found');
        showKCEditor(null, target);
    } catch (error: any) {
        if (error.isAuth) return;
        showToast(t('kc_target.error.clone_failed'), 'error');
    }
}

export async function deleteKCTarget(targetId: any) {
    const confirmed = await showConfirm(t('kc.delete.confirm'));
    if (!confirmed) return;

    try {
        disconnectKCWebSocket(targetId);
        const response = await fetchWithAuth(`/output-targets/${targetId}`, {
            method: 'DELETE',
        });
        if (response.ok) {
            showToast(t('kc.deleted'), 'success');
            outputTargetsCache.invalidate();
            // Use window.* to avoid circular import with targets.js
            if (typeof window.loadTargetsTab === 'function') window.loadTargetsTab();
        } else {
            const error = await response.json();
            showToast(error.detail || t('kc_target.error.delete_failed'), 'error');
        }
    } catch (error: any) {
        if (error.isAuth) return;
        showToast(t('kc_target.error.delete_failed'), 'error');
    }
}

// ===== KC BRIGHTNESS =====

export function updateKCBrightnessLabel(targetId: any, value: any) {
    const slider = document.querySelector(`[data-kc-brightness="${CSS.escape(targetId)}"]`) as HTMLElement;
    if (slider) slider.title = Math.round(parseInt(value) / 255 * 100) + '%';
}

export async function saveKCBrightness(targetId: any, value: any) {
    const brightness = parseInt(value) / 255;
    try {
        await fetch(`${API_BASE}/output-targets/${targetId}`, {
            method: 'PUT',
            headers: getHeaders(),
            body: JSON.stringify({ key_colors_settings: { brightness } }),
        });
    } catch (err) {
        console.error('Failed to save KC brightness:', err);
        showToast(t('kc.error.brightness') || 'Failed to save brightness', 'error');
    }
}

// ===== KEY COLORS WEBSOCKET =====

export function connectKCWebSocket(targetId: any) {
    // Disconnect existing connection if any
    disconnectKCWebSocket(targetId);

    const key = localStorage.getItem('wled_api_key');
    if (!key) return;

    const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
    const wsUrl = `${protocol}//${window.location.host}${API_BASE}/output-targets/${targetId}/ws?token=${encodeURIComponent(key)}`;

    try {
        const ws = new WebSocket(wsUrl);

        ws.onmessage = (event) => {
            try {
                const data = JSON.parse(event.data);
                updateKCColorSwatches(targetId, data.colors || {});
            } catch (e) {
                console.error('Failed to parse KC WebSocket message:', e);
            }
        };

        ws.onclose = () => {
            delete kcWebSockets[targetId];
        };

        ws.onerror = (error) => {
            console.error(`KC WebSocket error for ${targetId}:`, error);
        };

        kcWebSockets[targetId] = ws;
    } catch (error) {
        console.error(`Failed to connect KC WebSocket for ${targetId}:`, error);
    }
}

export function disconnectKCWebSocket(targetId: any) {
    const ws = kcWebSockets[targetId];
    if (ws) {
        ws.close();
        delete kcWebSockets[targetId];
    }
}

export function disconnectAllKCWebSockets() {
    Object.keys(kcWebSockets).forEach(targetId => disconnectKCWebSocket(targetId));
}

export function updateKCColorSwatches(targetId: any, colors: any) {
    const container = document.getElementById(`kc-swatches-${targetId}`);
    if (!container) return;

    const entries = Object.entries(colors);
    if (entries.length === 0) {
        container.innerHTML = `<span class="kc-no-colors">${t('kc.colors.none')}</span>`;
        return;
    }

    container.innerHTML = entries.map(([name, color]: [string, any]) => {
        const hex = color.hex || `#${(color.r || 0).toString(16).padStart(2, '0')}${(color.g || 0).toString(16).padStart(2, '0')}${(color.b || 0).toString(16).padStart(2, '0')}`;
        return `
            <div class="kc-swatch">
                <div class="kc-swatch-color" style="background-color: ${hex}" title="${hex}"></div>
                <span class="kc-swatch-label" title="${escapeHtml(name)}">${escapeHtml(name)}</span>
            </div>
        `;
    }).join('');
}
