/**
 * HA Light Targets — editor, cards, CRUD for Home Assistant light output targets.
 */

import { _cachedHASources, _cachedValueSources, haSourcesCache, colorStripSourcesCache, outputTargetsCache, valueSourcesCache } from '../core/state.ts';
import { fetchWithAuth, escapeHtml } from '../core/api.ts';
import { t } from '../core/i18n.ts';
import { Modal } from '../core/modal.ts';
import { showToast, showConfirm, formatUptime } from '../core/ui.ts';
import { ICON_CLONE, ICON_EDIT, ICON_START, ICON_STOP, ICON_TRASH, ICON_FPS, ICON_OK, ICON_WARNING, ICON_SUN, getColorStripIcon, getValueSourceIcon, getHAEntityIcon } from '../core/icons.ts';
import * as P from '../core/icon-paths.ts';
import { EntitySelect } from '../core/entity-palette.ts';
import { wrapCard } from '../core/card-colors.ts';
import { TagInput, renderTagChips } from '../core/tag-input.ts';
import { bindableSourceId, bindableValue } from '../types.ts';
import { BindableScalarWidget } from '../core/bindable-scalar.ts';

const ICON_HA = `<svg class="icon" viewBox="0 0 24 24">${P.home}</svg>`;
const _icon = (d: string) => `<svg class="icon" viewBox="0 0 24 24">${d}</svg>`;

// ── Modal ──

let _haLightTagsInput: TagInput | null = null;
let _haSourceEntitySelect: EntitySelect | null = null;
let _cssSourceEntitySelect: EntitySelect | null = null;
let _brightnessWidget: BindableScalarWidget | null = null;
let _mappingEntitySelects: EntitySelect[] = [];
let _editorCssSources: any[] = [];
let _cachedHAEntities: any[] = [];  // fetched from selected HA source
let _updateRateWidget: BindableScalarWidget | null = null;
let _transitionWidget: BindableScalarWidget | null = null;
let _colorToleranceWidget: BindableScalarWidget | null = null;
let _minBrightnessThresholdWidget: BindableScalarWidget | null = null;

class HALightEditorModal extends Modal {
    constructor() { super('ha-light-editor-modal'); }

    onForceClose() {
        if (_haLightTagsInput) { _haLightTagsInput.destroy(); _haLightTagsInput = null; }
        if (_haSourceEntitySelect) { _haSourceEntitySelect.destroy(); _haSourceEntitySelect = null; }
        if (_cssSourceEntitySelect) { _cssSourceEntitySelect.destroy(); _cssSourceEntitySelect = null; }
        if (_brightnessWidget) { _brightnessWidget.destroy(); _brightnessWidget = null; }
        if (_updateRateWidget) { _updateRateWidget.destroy(); _updateRateWidget = null; }
        if (_transitionWidget) { _transitionWidget.destroy(); _transitionWidget = null; }
        if (_colorToleranceWidget) { _colorToleranceWidget.destroy(); _colorToleranceWidget = null; }
        if (_minBrightnessThresholdWidget) { _minBrightnessThresholdWidget.destroy(); _minBrightnessThresholdWidget = null; }
        _destroyMappingEntitySelects();
    }

    snapshotValues() {
        return {
            name: (document.getElementById('ha-light-editor-name') as HTMLInputElement).value,
            ha_source: (document.getElementById('ha-light-editor-ha-source') as HTMLSelectElement).value,
            css_source: (document.getElementById('ha-light-editor-css-source') as HTMLSelectElement).value,
            brightness: _brightnessWidget ? JSON.stringify(_brightnessWidget.getValue()) : '1.0',
            update_rate: _updateRateWidget ? JSON.stringify(_updateRateWidget.getValue()) : '2.0',
            transition: _transitionWidget ? JSON.stringify(_transitionWidget.getValue()) : '0.5',
            color_tolerance: _colorToleranceWidget ? JSON.stringify(_colorToleranceWidget.getValue()) : '5',
            min_brightness_threshold: _minBrightnessThresholdWidget ? JSON.stringify(_minBrightnessThresholdWidget.getValue()) : '0',
            mappings: _getMappingsJSON(),
            tags: JSON.stringify(_haLightTagsInput ? _haLightTagsInput.getValue() : []),
        };
    }
}

const haLightEditorModal = new HALightEditorModal();

function _destroyMappingEntitySelects(): void {
    for (const es of _mappingEntitySelects) es.destroy();
    _mappingEntitySelects = [];
}

function _getMappingsJSON(): string {
    const rows = document.querySelectorAll('#ha-light-mappings-list .ha-light-mapping-row');
    const mappings: any[] = [];
    rows.forEach(row => {
        mappings.push({
            entity_id: (row.querySelector('.ha-mapping-entity') as HTMLSelectElement).value.trim(),
            led_start: parseInt((row.querySelector('.ha-mapping-led-start') as HTMLInputElement).value) || 0,
            led_end: parseInt((row.querySelector('.ha-mapping-led-end') as HTMLInputElement).value) || -1,
            brightness_scale: parseFloat((row.querySelector('.ha-mapping-brightness') as HTMLInputElement).value) || 1.0,
        });
    });
    return JSON.stringify(mappings);
}

function _getEntityItems() {
    return _cachedHAEntities
        .filter((e: any) => e.domain === 'light')
        .map((e: any) => ({
            value: e.entity_id,
            label: e.friendly_name || e.entity_id,
            icon: getHAEntityIcon(e),
            desc: e.state || '',
        }));
}

async function _fetchHAEntities(haSourceId: string): Promise<void> {
    if (!haSourceId) { _cachedHAEntities = []; return; }
    try {
        const resp = await fetchWithAuth(`/home-assistant/sources/${haSourceId}/entities`);
        if (!resp.ok) { _cachedHAEntities = []; return; }
        const data = await resp.json();
        _cachedHAEntities = data.entities || [];
    } catch {
        _cachedHAEntities = [];
    }
}

function _rebuildMappingEntityOptions(): void {
    // Update all existing mapping entity <select> elements with new options
    const lightEntities = _cachedHAEntities.filter((e: any) => e.domain === 'light');
    const rows = document.querySelectorAll('#ha-light-mappings-list .ha-light-mapping-row');
    rows.forEach(row => {
        const sel = row.querySelector('.ha-mapping-entity') as HTMLSelectElement;
        if (!sel) return;
        const currentVal = sel.value;
        sel.innerHTML = `<option value="">${t('ha_light.mapping.select_entity')}</option>` +
            lightEntities.map((e: any) =>
                `<option value="${e.entity_id}" ${e.entity_id === currentVal ? 'selected' : ''}>${escapeHtml(e.friendly_name || e.entity_id)}</option>`
            ).join('');
        // Restore value if it was set but not in list
        if (currentVal && !lightEntities.some((e: any) => e.entity_id === currentVal)) {
            sel.innerHTML += `<option value="${escapeHtml(currentVal)}" selected>${escapeHtml(currentVal)}</option>`;
        }
    });
    // Rebuild EntitySelect instances (destroy old, create new)
    _destroyMappingEntitySelects();
    rows.forEach(row => {
        const sel = row.querySelector('.ha-mapping-entity') as HTMLSelectElement;
        if (!sel) return;
        const es = new EntitySelect({
            target: sel,
            getItems: _getEntityItems,
            placeholder: t('ha_light.mapping.search_entity'),
        });
        _mappingEntitySelects.push(es);
    });
}

// ── Mapping rows ──

export function addHALightMapping(data: any = null): void {
    const list = document.getElementById('ha-light-mappings-list');
    if (!list) return;

    const idx = list.querySelectorAll('.ha-light-mapping-row').length + 1;
    const row = document.createElement('div');
    row.className = 'ha-light-mapping-row';

    // Build options from cached entities (light domain only)
    const lightEntities = _cachedHAEntities.filter((e: any) => e.domain === 'light');
    const selectedId = data?.entity_id || '';
    const entityOptions = `<option value="">${t('ha_light.mapping.select_entity')}</option>` +
        lightEntities.map((e: any) =>
            `<option value="${e.entity_id}" ${e.entity_id === selectedId ? 'selected' : ''}>${escapeHtml(e.friendly_name || e.entity_id)}</option>`
        ).join('');
    // If editing and the entity isn't in the list (HA disconnected), add it manually
    const hasSelected = !selectedId || lightEntities.some((e: any) => e.entity_id === selectedId);
    const extraOption = (!hasSelected && selectedId)
        ? `<option value="${escapeHtml(selectedId)}" selected>${escapeHtml(selectedId)}</option>`
        : '';

    row.innerHTML = `
        <div class="ha-mapping-header">
            <span class="ha-mapping-label">${_icon(P.lightbulb)} #${idx}</span>
            <button type="button" class="btn-remove-mapping" onclick="removeHALightMapping(this)" title="${t('common.delete')}">${ICON_TRASH}</button>
        </div>
        <div class="ha-mapping-fields">
            <div class="ha-mapping-field">
                <label>${t('ha_light.mapping.entity_id')}</label>
                <select class="ha-mapping-entity">${entityOptions}${extraOption}</select>
            </div>
            <div class="ha-mapping-range-row">
                <div>
                    <label>${t('ha_light.mapping.led_start')}</label>
                    <input type="number" class="ha-mapping-led-start" value="${data?.led_start ?? 0}" min="0" step="1">
                </div>
                <div>
                    <label>${t('ha_light.mapping.led_end')}</label>
                    <input type="number" class="ha-mapping-led-end" value="${data?.led_end ?? -1}" min="-1" step="1">
                </div>
                <div>
                    <label>${t('ha_light.mapping.brightness')}</label>
                    <input type="number" class="ha-mapping-brightness" value="${data?.brightness_scale ?? 1.0}" min="0" max="1" step="0.1">
                </div>
            </div>
        </div>
    `;
    list.appendChild(row);

    // Attach EntitySelect to the entity dropdown
    const entitySelect = row.querySelector('.ha-mapping-entity') as HTMLSelectElement;
    const es = new EntitySelect({
        target: entitySelect,
        getItems: _getEntityItems,
        placeholder: t('ha_light.mapping.search_entity'),
    });
    _mappingEntitySelects.push(es);
}

export function removeHALightMapping(btn: HTMLElement): void {
    const row = btn.closest('.ha-light-mapping-row');
    if (!row) return;
    // Find and destroy the EntitySelect for this row
    const select = row.querySelector('.ha-mapping-entity') as HTMLSelectElement;
    if (select) {
        const idx = _mappingEntitySelects.findIndex(es => (es as any)._target === select || (es as any).target === select);
        if (idx >= 0) {
            _mappingEntitySelects[idx].destroy();
            _mappingEntitySelects.splice(idx, 1);
        }
    }
    row.remove();
}

// ── Bindable scalar widgets ──

function _ensureBrightnessWidget(): BindableScalarWidget {
    if (!_brightnessWidget) {
        _brightnessWidget = new BindableScalarWidget({
            container: document.getElementById('ha-light-editor-brightness-container')!,
            min: 0, max: 1, step: 0.05, default: 1.0,
            idPrefix: 'ha-light-editor-brightness',
            valueSources: () => _cachedValueSources,
            format: (v) => v.toFixed(2),
        });
    }
    return _brightnessWidget;
}

function _ensureUpdateRateWidget(): BindableScalarWidget {
    if (!_updateRateWidget) {
        _updateRateWidget = new BindableScalarWidget({
            container: document.getElementById('ha-light-editor-update-rate-container')!,
            min: 0.5, max: 5.0, step: 0.1, default: 2.0,
            idPrefix: 'ha-light-editor-update-rate',
            valueSources: () => _cachedValueSources,
            format: (v) => v.toFixed(1),
        });
    }
    return _updateRateWidget;
}

function _ensureTransitionWidget(): BindableScalarWidget {
    if (!_transitionWidget) {
        _transitionWidget = new BindableScalarWidget({
            container: document.getElementById('ha-light-editor-transition-container')!,
            min: 0.0, max: 10.0, step: 0.1, default: 0.5,
            idPrefix: 'ha-light-editor-transition',
            valueSources: () => _cachedValueSources,
            format: (v) => v.toFixed(1),
        });
    }
    return _transitionWidget;
}

function _ensureColorToleranceWidget(): BindableScalarWidget {
    if (!_colorToleranceWidget) {
        _colorToleranceWidget = new BindableScalarWidget({
            container: document.getElementById('ha-light-editor-color-tolerance-container')!,
            min: 0, max: 50, step: 1, default: 5,
            idPrefix: 'ha-light-editor-color-tolerance',
            valueSources: () => _cachedValueSources,
            format: (v) => String(Math.round(v)),
        });
    }
    return _colorToleranceWidget;
}

function _ensureMinBrightnessThresholdWidget(): BindableScalarWidget {
    if (!_minBrightnessThresholdWidget) {
        _minBrightnessThresholdWidget = new BindableScalarWidget({
            container: document.getElementById('ha-light-editor-min-brightness-threshold-container')!,
            min: 0, max: 254, step: 1, default: 0,
            idPrefix: 'ha-light-editor-min-brightness-threshold',
            valueSources: () => _cachedValueSources,
            format: (v) => String(Math.round(v)),
        });
    }
    return _minBrightnessThresholdWidget;
}

// ── Show / Close ──

export async function showHALightEditor(targetId: string | null = null, cloneData: any = null): Promise<void> {
    // Load data for dropdowns
    const [haSources, cssSources] = await Promise.all([
        haSourcesCache.fetch().catch((): any[] => []),
        colorStripSourcesCache.fetch().catch((): any[] => []),
        valueSourcesCache.fetch().catch(() => {}),
    ]);
    _editorCssSources = cssSources;

    const isEdit = !!targetId;
    const isClone = !!cloneData;
    const titleKey = isEdit ? 'ha_light.edit' : 'ha_light.add';
    document.getElementById('ha-light-editor-title')!.innerHTML = `${ICON_HA} ${t(titleKey)}`;
    (document.getElementById('ha-light-editor-id') as HTMLInputElement).value = '';
    (document.getElementById('ha-light-editor-error') as HTMLElement).style.display = 'none';

    // Populate HA source dropdown
    const haSelect = document.getElementById('ha-light-editor-ha-source') as HTMLSelectElement;
    haSelect.innerHTML = haSources.map((s: any) =>
        `<option value="${s.id}">${escapeHtml(s.name)}</option>`
    ).join('');

    // Populate CSS source dropdown
    const cssSelect = document.getElementById('ha-light-editor-css-source') as HTMLSelectElement;
    cssSelect.innerHTML = `<option value="">—</option>` + cssSources.map((s: any) =>
        `<option value="${s.id}">${escapeHtml(s.name)}</option>`
    ).join('');

    // Clear mappings
    _destroyMappingEntitySelects();
    document.getElementById('ha-light-mappings-list')!.innerHTML = '';

    let editData: any = null;

    if (isEdit) {
        try {
            const resp = await fetchWithAuth(`/output-targets/${targetId}`);
            if (!resp.ok) throw new Error('Failed to load target');
            editData = await resp.json();
        } catch (e: any) {
            if (e.isAuth) return;
            showToast(e.message, 'error');
            return;
        }
    } else if (isClone) {
        editData = cloneData;
    }

    if (editData) {
        if (isEdit) (document.getElementById('ha-light-editor-id') as HTMLInputElement).value = editData.id;
        (document.getElementById('ha-light-editor-name') as HTMLInputElement).value = editData.name || '';
        haSelect.value = editData.ha_source_id || '';
        cssSelect.value = editData.color_strip_source_id || '';
        _ensureBrightnessWidget().setValue(editData.brightness ?? 1.0);
        _ensureUpdateRateWidget().setValue(editData.update_rate ?? 2.0);
        _ensureTransitionWidget().setValue(editData.transition ?? 0.5);
        _ensureColorToleranceWidget().setValue(editData.color_tolerance ?? 5);
        _ensureMinBrightnessThresholdWidget().setValue(editData.min_brightness_threshold ?? 0);
        (document.getElementById('ha-light-editor-description') as HTMLInputElement).value = editData.description || '';

        // Fetch entities from the selected HA source before loading mappings
        await _fetchHAEntities(editData.ha_source_id || '');

        // Load mappings (with entity picker populated)
        const mappings = editData.ha_light_mappings || [];
        mappings.forEach((m: any) => addHALightMapping(m));
    } else {
        (document.getElementById('ha-light-editor-name') as HTMLInputElement).value = '';
        _ensureBrightnessWidget().setValue(1.0);
        _ensureUpdateRateWidget().setValue(2.0);
        _ensureTransitionWidget().setValue(0.5);
        _ensureColorToleranceWidget().setValue(5);
        _ensureMinBrightnessThresholdWidget().setValue(0);
        (document.getElementById('ha-light-editor-description') as HTMLInputElement).value = '';

        // Fetch entities from the first HA source
        const firstSource = haSources[0]?.id || '';
        await _fetchHAEntities(firstSource);

        // Add one empty mapping by default
        addHALightMapping();
    }

    // EntitySelects
    if (_haSourceEntitySelect) { _haSourceEntitySelect.destroy(); _haSourceEntitySelect = null; }
    _haSourceEntitySelect = new EntitySelect({
        target: haSelect,
        getItems: () => haSources.map((s: any) => ({
            value: s.id, label: s.name, icon: ICON_HA,
            desc: s.connected ? t('ha_source.connected') : t('ha_source.disconnected'),
        })),
        placeholder: t('palette.search'),
        onChange: async (newSourceId: string) => {
            // Refetch entities when HA source changes, then rebuild mapping selects
            await _fetchHAEntities(newSourceId);
            _rebuildMappingEntityOptions();
        },
    });

    if (_cssSourceEntitySelect) { _cssSourceEntitySelect.destroy(); _cssSourceEntitySelect = null; }
    _cssSourceEntitySelect = new EntitySelect({
        target: cssSelect,
        getItems: () => _editorCssSources.map((s: any) => ({
            value: s.id, label: s.name, icon: getColorStripIcon(s.source_type), desc: s.source_type,
        })),
        placeholder: t('palette.search'),
    });

    // Tags
    if (_haLightTagsInput) { _haLightTagsInput.destroy(); _haLightTagsInput = null; }
    _haLightTagsInput = new TagInput(document.getElementById('ha-light-tags-container'), { placeholder: t('tags.placeholder') });
    _haLightTagsInput.setValue(editData?.tags || []);

    haLightEditorModal.open();
    haLightEditorModal.snapshot();
}

export async function closeHALightEditor(): Promise<void> {
    await haLightEditorModal.close();
}

// ── Save ──

export async function saveHALightEditor(): Promise<void> {
    const targetId = (document.getElementById('ha-light-editor-id') as HTMLInputElement).value;
    const name = (document.getElementById('ha-light-editor-name') as HTMLInputElement).value.trim();
    const haSourceId = (document.getElementById('ha-light-editor-ha-source') as HTMLSelectElement).value;
    const cssSourceId = (document.getElementById('ha-light-editor-css-source') as HTMLSelectElement).value;
    const updateRate = _updateRateWidget ? _updateRateWidget.getValue() : 2.0;
    const transition = _transitionWidget ? _transitionWidget.getValue() : 0.5;
    const colorTolerance = _colorToleranceWidget ? _colorToleranceWidget.getValue() : 5;
    const minBrightnessThreshold = _minBrightnessThresholdWidget ? _minBrightnessThresholdWidget.getValue() : 0;
    const description = (document.getElementById('ha-light-editor-description') as HTMLInputElement).value.trim() || null;

    if (!name) {
        haLightEditorModal.showError(t('ha_light.error.name_required'));
        return;
    }
    if (!haSourceId) {
        haLightEditorModal.showError(t('ha_light.error.ha_source_required'));
        return;
    }

    // Collect mappings
    const mappings = JSON.parse(_getMappingsJSON()).filter((m: any) => m.entity_id);

    const brightness = _brightnessWidget ? _brightnessWidget.getValue() : 1.0;

    const payload: any = {
        name,
        ha_source_id: haSourceId,
        color_strip_source_id: cssSourceId,
        brightness,
        ha_light_mappings: mappings,
        update_rate: updateRate,
        transition,
        color_tolerance: colorTolerance,
        min_brightness_threshold: minBrightnessThreshold,
        description,
        tags: _haLightTagsInput ? _haLightTagsInput.getValue() : [],
    };

    payload.target_type = 'ha_light';

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

        if (!response.ok) {
            const err = await response.json().catch(() => ({}));
            throw new Error(err.detail || `HTTP ${response.status}`);
        }

        showToast(targetId ? t('ha_light.updated') : t('ha_light.created'), 'success');
        outputTargetsCache.invalidate();
        haLightEditorModal.forceClose();
        // Reload targets tab
        if (window.loadTargetsTab) await window.loadTargetsTab();
    } catch (e: any) {
        if (e.isAuth) return;
        haLightEditorModal.showError(e.message);
    }
}

// ── Edit / Clone / Delete ──

export async function editHALightTarget(targetId: string): Promise<void> {
    await showHALightEditor(targetId);
}

export async function cloneHALightTarget(targetId: string): Promise<void> {
    try {
        const resp = await fetchWithAuth(`/output-targets/${targetId}`);
        if (!resp.ok) throw new Error('Failed to load target');
        const data = await resp.json();
        delete data.id;
        data.name = data.name + ' (copy)';
        await showHALightEditor(null, data);
    } catch (e: any) {
        if (e.isAuth) return;
        showToast(e.message, 'error');
    }
}

// ── Card rendering ──

export function createHALightTargetCard(target: any, haSourceMap: Record<string, any> = {}, cssSourceMap: Record<string, any> = {}, valueSourceMap: Record<string, any> = {}): string {
    const haSource = haSourceMap[target.ha_source_id];
    const cssSource = cssSourceMap[target.color_strip_source_id];
    const haName = haSource ? escapeHtml(haSource.name) : target.ha_source_id || '—';
    const cssId = target.color_strip_source_id;
    const cssName = cssSource ? escapeHtml(cssSource.name) : cssId || '—';
    const mappingCount = target.ha_light_mappings?.length || 0;
    const isRunning = target.state?.processing;
    const state = target.state || {};
    const metrics = target.metrics || {};

    // Crosslinks
    const haLink = haSource
        ? ` stream-card-link" onclick="event.stopPropagation(); navigateToCard('streams','home_assistant','ha-sources','data-id','${target.ha_source_id}')`
        : '';
    const cssLink = cssSource
        ? ` stream-card-link" onclick="event.stopPropagation(); navigateToCard('streams','color_strip','color-strips','data-css-id','${cssId}')`
        : '';

    // Brightness value source
    const bvsId = bindableSourceId(target.brightness);
    const bvs = bvsId && valueSourceMap[bvsId] ? valueSourceMap[bvsId] : null;

    return wrapCard({
        type: 'card',
        dataAttr: 'data-ha-target-id',
        id: target.id,
        removeOnclick: `deleteTarget('${target.id}')`,
        removeTitle: t('common.delete'),
        content: `
            <div class="card-header">
                <span class="card-title-text">${ICON_HA} ${escapeHtml(target.name)}</span>
            </div>
            <div class="stream-card-props">
                <span class="stream-card-prop${haLink}" title="HA Connection">${ICON_HA} ${haName}</span>
                ${cssName !== '—' ? `<span class="stream-card-prop${cssLink}" title="${t('targets.color_strip_source')}">${cssSource ? getColorStripIcon(cssSource.source_type) : _icon(P.palette)} ${cssName}</span>` : ''}
                <span class="stream-card-prop">${_icon(P.listChecks)} ${mappingCount} light${mappingCount !== 1 ? 's' : ''}</span>
                <span class="stream-card-prop">${_icon(P.clock)} ${target.update_rate ?? 2.0} Hz</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>` : (bindableValue(target.brightness, 1.0) < 1.0 ? `<span class="stream-card-prop" title="${t('targets.brightness')}">${ICON_SUN} ${Math.round(bindableValue(target.brightness, 1.0) * 100)}%</span>` : '')}
            </div>
            ${renderTagChips(target.tags || [])}
            ${target.description ? `<div class="template-config" style="opacity:0.7;">${escapeHtml(target.description)}</div>` : ''}
            <div class="card-content">
                ${isRunning ? `
                <div class="metrics-grid target-metrics-expanded">
                    <div class="metric">
                        <div class="metric-label">${t('targets.fps')}</div>
                        <div class="metric-value" data-tm="fps">${(state.fps_actual ?? target.update_rate ?? 2).toFixed(1)} Hz</div>
                    </div>
                    <div class="metric">
                        <div class="metric-label">${t('device.metrics.uptime')}</div>
                        <div class="metric-value" data-tm="uptime">${metrics.uptime_seconds ? formatUptime(metrics.uptime_seconds) : '---'}</div>
                    </div>
                    <div class="metric">
                        <div class="metric-label">HA</div>
                        <div class="metric-value" data-tm="ha-status">${state.ha_connected ? ICON_OK : ICON_WARNING}</div>
                    </div>
                </div>
                <div class="ha-light-swatches" data-ha-swatches="${target.id}">
                    ${_renderEntitySwatches(state.entity_colors || {}, target.ha_light_mappings || [])}
                </div>
                ` : ''}
            </div>`,
        actions: `
                <button class="btn btn-icon ${isRunning ? 'btn-danger' : 'btn-primary'}" data-action="${isRunning ? 'stop' : 'start'}" title="${isRunning ? t('targets.stop') : t('targets.start')}">
                    ${isRunning ? ICON_STOP : ICON_START}
                </button>
                <button class="btn btn-icon btn-secondary" data-action="clone" title="${t('common.clone')}">${ICON_CLONE}</button>
                <button class="btn btn-icon btn-secondary" data-action="edit" title="${t('common.edit')}">${ICON_EDIT}</button>`,
    });
}

// ── Metrics patching ──

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

    const fpsEl = card.querySelector('[data-tm="fps"]') as HTMLElement | null;
    if (fpsEl) fpsEl.textContent = `${(state.fps_actual ?? 0).toFixed(1)} Hz`;

    const uptimeEl = card.querySelector('[data-tm="uptime"]') as HTMLElement | null;
    if (uptimeEl) uptimeEl.textContent = metrics.uptime_seconds ? formatUptime(metrics.uptime_seconds) : '---';

    const haEl = card.querySelector('[data-tm="ha-status"]') as HTMLElement | null;
    if (haEl) haEl.innerHTML = state.ha_connected ? ICON_OK : ICON_WARNING;
}

// ── Event delegation ──

const _haLightActions: Record<string, (id: string) => void> = {
    start: (id) => _startStop(id, 'start'),
    stop: (id) => _startStop(id, 'stop'),
    clone: cloneHALightTarget,
    edit: editHALightTarget,
};

async function _startStop(targetId: string, action: 'start' | 'stop'): Promise<void> {
    try {
        const resp = await fetchWithAuth(`/output-targets/${targetId}/${action}`, { method: 'POST' });
        if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
        outputTargetsCache.invalidate();
        if (window.loadTargetsTab) await window.loadTargetsTab();
    } catch (e: any) {
        if (e.isAuth) return;
        showToast(e.message, 'error');
    }
}

export function initHALightTargetDelegation(container: HTMLElement): void {
    container.addEventListener('click', (e: MouseEvent) => {
        const btn = (e.target as HTMLElement).closest<HTMLElement>('[data-action]');
        if (!btn) return;

        const section = btn.closest<HTMLElement>('[data-card-section="ha-light-targets"]');
        if (!section) return;
        const card = btn.closest<HTMLElement>('[data-ha-target-id]');
        if (!card) return;

        const action = btn.dataset.action;
        const id = card.getAttribute('data-ha-target-id');
        if (!action || !id) return;

        const handler = _haLightActions[action];
        if (handler) {
            e.stopPropagation();
            handler(id);
        }
    });
}

// ── Entity color swatches ──

function _renderEntitySwatches(entityColors: Record<string, any>, mappings: any[]): string {
    if (!mappings.length) return '';
    return mappings.map(m => {
        const c = entityColors[m.entity_id];
        const bg = c ? c.hex : '#333';
        const label = m.entity_id.replace('light.', '');
        return `<div class="ha-light-swatch" data-entity="${escapeHtml(m.entity_id)}">
            <span class="swatch-color" style="background:${bg}"></span>
            <span class="swatch-label">${escapeHtml(label)}</span>
        </div>`;
    }).join('');
}

// ── WebSocket color preview ──

const _haLightWS: Record<string, WebSocket> = {};

export function connectHALightWS(targetId: string): void {
    if (_haLightWS[targetId]) return;
    const loc = window.location;
    const wsProto = loc.protocol === 'https:' ? 'wss:' : 'ws:';
    const apiKey = (window as any).apiKey || localStorage.getItem('wled_api_key') || '';
    const url = `${wsProto}//${loc.host}/api/v1/output-targets/${targetId}/ha-light/ws?token=${encodeURIComponent(apiKey)}`;

    const ws = new WebSocket(url);
    _haLightWS[targetId] = ws;

    ws.onmessage = (ev) => {
        try {
            const data = JSON.parse(ev.data);
            if (data.type === 'colors_update') {
                _updateSwatchColors(targetId, data.colors);
            }
        } catch {}
    };

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

    ws.onerror = () => {
        delete _haLightWS[targetId];
    };
}

export function disconnectHALightWS(targetId: string): void {
    const ws = _haLightWS[targetId];
    if (ws) {
        ws.close();
        delete _haLightWS[targetId];
    }
}

export function disconnectAllHALightWS(): void {
    for (const id of Object.keys(_haLightWS)) {
        disconnectHALightWS(id);
    }
}

function _updateSwatchColors(targetId: string, colors: Record<string, any>): void {
    const container = document.querySelector(`[data-ha-swatches="${targetId}"]`);
    if (!container) return;
    for (const [entityId, c] of Object.entries(colors)) {
        const swatch = container.querySelector(`[data-entity="${entityId}"] .swatch-color`) as HTMLElement | null;
        if (swatch) {
            swatch.style.background = (c as any).hex;
        }
    }
}

// ── Expose to global scope ──

window.showHALightEditor = showHALightEditor;
window.closeHALightEditor = closeHALightEditor;
window.saveHALightEditor = saveHALightEditor;
window.editHALightTarget = editHALightTarget;
window.cloneHALightTarget = cloneHALightTarget;
window.addHALightMapping = addHALightMapping;
window.removeHALightMapping = removeHALightMapping;
