/**
 * Streams — Audio template CRUD, engine config, test modal.
 * Extracted from streams.ts to reduce file size.
 */

import {
    availableAudioEngines, setAvailableAudioEngines,
    currentEditingAudioTemplateId, setCurrentEditingAudioTemplateId,
    _audioTemplateNameManuallyEdited, set_audioTemplateNameManuallyEdited,
    _cachedAudioTemplates,
    audioTemplatesCache,
    apiKey,
} from '../core/state.ts';
import { API_BASE, fetchWithAuth, escapeHtml } from '../core/api.ts';
import { t } from '../core/i18n.ts';
import { Modal } from '../core/modal.ts';
import { showToast, showConfirm, setupBackdropClose } from '../core/ui.ts';
import {
    getAudioEngineIcon,
    ICON_AUDIO_TEMPLATE,
} from '../core/icons.ts';
import * as P from '../core/icon-paths.ts';
import { TagInput } from '../core/tag-input.ts';
import { IconSelect } from '../core/icon-select.ts';
import { loadPictureSources } from './streams.ts';

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

// ── TagInput instance for audio template modal ──
let _audioTemplateTagsInput: TagInput | null = null;

class AudioTemplateModal extends Modal {
    constructor() { super('audio-template-modal'); }

    snapshotValues() {
        const vals: any = {
            name: (document.getElementById('audio-template-name') as HTMLInputElement).value,
            description: (document.getElementById('audio-template-description') as HTMLInputElement).value,
            engine: (document.getElementById('audio-template-engine') as HTMLSelectElement).value,
            tags: JSON.stringify(_audioTemplateTagsInput ? _audioTemplateTagsInput.getValue() : []),
        };
        document.querySelectorAll('#audio-engine-config-fields [data-config-key]').forEach((field: any) => {
            vals['cfg_' + field.dataset.configKey] = field.value;
        });
        return vals;
    }

    onForceClose() {
        if (_audioTemplateTagsInput) { _audioTemplateTagsInput.destroy(); _audioTemplateTagsInput = null; }
        setCurrentEditingAudioTemplateId(null);
        set_audioTemplateNameManuallyEdited(false);
    }
}

const audioTemplateModal = new AudioTemplateModal();

// ===== Audio Templates =====

async function loadAvailableAudioEngines() {
    try {
        const response = await fetchWithAuth('/audio-engines');
        if (!response.ok) throw new Error(`Failed to load audio engines: ${response.status}`);
        const data = await response.json();
        setAvailableAudioEngines(data.engines || []);

        const select = document.getElementById('audio-template-engine') as HTMLSelectElement;
        select.innerHTML = '';

        availableAudioEngines.forEach((engine: any) => {
            const option = document.createElement('option');
            option.value = engine.type;
            option.textContent = `${engine.type.toUpperCase()}`;
            if (!engine.available) {
                option.disabled = true;
                option.textContent += ` (${t('audio_template.engine.unavailable')})`;
            }
            select.appendChild(option);
        });

        if (!select.value) {
            const firstAvailable = availableAudioEngines.find(e => e.available);
            if (firstAvailable) select.value = firstAvailable.type;
        }

        // Update icon-grid selector with dynamic engine list
        const items = availableAudioEngines
            .filter(e => e.available)
            .map(e => ({ value: e.type, icon: getAudioEngineIcon(e.type), label: e.type.toUpperCase(), desc: '' }));
        if (_audioEngineIconSelect) { _audioEngineIconSelect.updateItems(items); }
        else { _audioEngineIconSelect = new IconSelect({ target: select, items, columns: 2 }); }
        _audioEngineIconSelect.setValue(select.value);
    } catch (error) {
        console.error('Error loading audio engines:', error);
        showToast(t('audio_template.error.engines') + ': ' + error.message, 'error');
    }
}

let _audioEngineIconSelect: IconSelect | null = null;

export async function onAudioEngineChange() {
    const engineType = (document.getElementById('audio-template-engine') as HTMLSelectElement).value;
    if (_audioEngineIconSelect) _audioEngineIconSelect.setValue(engineType);
    const configSection = document.getElementById('audio-engine-config-section')!;
    const configFields = document.getElementById('audio-engine-config-fields')!;

    if (!engineType) { configSection.style.display = 'none'; return; }

    const engine = availableAudioEngines.find((e: any) => e.type === engineType);
    if (!engine) { configSection.style.display = 'none'; return; }

    if (!_audioTemplateNameManuallyEdited && !(document.getElementById('audio-template-id') as HTMLInputElement).value) {
        (document.getElementById('audio-template-name') as HTMLInputElement).value = engine.type.toUpperCase();
    }

    const hint = document.getElementById('audio-engine-availability-hint')!;
    if (!engine.available) {
        hint.textContent = t('audio_template.engine.unavailable.hint');
        hint.style.display = 'block';
        hint.style.color = 'var(--error-color)';
    } else {
        hint.style.display = 'none';
    }

    configFields.innerHTML = '';
    const defaultConfig = engine.default_config || {};

    if (Object.keys(defaultConfig).length === 0) {
        configSection.style.display = 'none';
        return;
    } else {
        let gridHtml = '<div class="config-grid">';
        Object.entries(defaultConfig).forEach(([key, value]) => {
            const fieldType = typeof value === 'number' ? 'number' : 'text';
            const fieldValue = typeof value === 'boolean' ? (value ? 'true' : 'false') : value;
            gridHtml += `
                <label class="config-grid-label" for="audio-config-${key}">${key}</label>
                <div class="config-grid-value">
                    ${typeof value === 'boolean' ? `
                        <select id="audio-config-${key}" data-config-key="${key}">
                            <option value="true" ${value ? 'selected' : ''}>true</option>
                            <option value="false" ${!value ? 'selected' : ''}>false</option>
                        </select>
                    ` : `
                        <input type="${fieldType}" id="audio-config-${key}" data-config-key="${key}" value="${fieldValue}">
                    `}
                </div>
            `;
        });
        gridHtml += '</div>';
        configFields.innerHTML = gridHtml;
    }

    configSection.style.display = 'block';
}

function populateAudioEngineConfig(config: any) {
    Object.entries(config).forEach(([key, value]: [string, any]) => {
        const field = document.getElementById(`audio-config-${key}`) as HTMLInputElement | HTMLSelectElement | null;
        if (field) {
            if (field.tagName === 'SELECT') {
                field.value = value.toString();
            } else {
                field.value = value;
            }
        }
    });
}

function collectAudioEngineConfig() {
    const config: any = {};
    document.querySelectorAll('#audio-engine-config-fields [data-config-key]').forEach((field: any) => {
        const key = field.dataset.configKey;
        let value: any = field.value;
        if (field.type === 'number') {
            value = parseFloat(value);
        } else if (field.tagName === 'SELECT' && (value === 'true' || value === 'false')) {
            value = value === 'true';
        }
        config[key] = value;
    });
    return config;
}

async function loadAudioTemplates() {
    try {
        await audioTemplatesCache.fetch();
        await loadPictureSources();
    } catch (error) {
        if (error.isAuth) return;
        console.error('Error loading audio templates:', error);
        showToast(t('audio_template.error.load'), 'error');
    }
}

export async function showAddAudioTemplateModal(cloneData: any = null) {
    setCurrentEditingAudioTemplateId(null);
    document.getElementById('audio-template-modal-title')!.innerHTML = `${ICON_AUDIO_TEMPLATE} ${t('audio_template.add')}`;
    (document.getElementById('audio-template-form') as HTMLFormElement).reset();
    (document.getElementById('audio-template-id') as HTMLInputElement).value = '';
    document.getElementById('audio-engine-config-section')!.style.display = 'none';
    document.getElementById('audio-template-error')!.style.display = 'none';

    set_audioTemplateNameManuallyEdited(!!cloneData);
    (document.getElementById('audio-template-name') as HTMLInputElement).oninput = () => { set_audioTemplateNameManuallyEdited(true); };

    await loadAvailableAudioEngines();

    if (cloneData) {
        (document.getElementById('audio-template-name') as HTMLInputElement).value = (cloneData.name || '') + ' (Copy)';
        (document.getElementById('audio-template-description') as HTMLInputElement).value = cloneData.description || '';
        (document.getElementById('audio-template-engine') as HTMLSelectElement).value = cloneData.engine_type;
        await onAudioEngineChange();
        populateAudioEngineConfig(cloneData.engine_config);
    }

    // Tags
    if (_audioTemplateTagsInput) { _audioTemplateTagsInput.destroy(); _audioTemplateTagsInput = null; }
    _audioTemplateTagsInput = new TagInput(document.getElementById('audio-template-tags-container'), { placeholder: t('tags.placeholder') });
    _audioTemplateTagsInput.setValue(cloneData ? (cloneData.tags || []) : []);

    audioTemplateModal.open();
    audioTemplateModal.snapshot();
}

export async function editAudioTemplate(templateId: any) {
    try {
        const response = await fetchWithAuth(`/audio-templates/${templateId}`);
        if (!response.ok) throw new Error(`Failed to load audio template: ${response.status}`);
        const template = await response.json();

        setCurrentEditingAudioTemplateId(templateId);
        document.getElementById('audio-template-modal-title')!.innerHTML = `${ICON_AUDIO_TEMPLATE} ${t('audio_template.edit')}`;
        (document.getElementById('audio-template-id') as HTMLInputElement).value = templateId;
        (document.getElementById('audio-template-name') as HTMLInputElement).value = template.name;
        (document.getElementById('audio-template-description') as HTMLInputElement).value = template.description || '';

        await loadAvailableAudioEngines();
        (document.getElementById('audio-template-engine') as HTMLSelectElement).value = template.engine_type;
        await onAudioEngineChange();
        populateAudioEngineConfig(template.engine_config);

        document.getElementById('audio-template-error')!.style.display = 'none';

        // Tags
        if (_audioTemplateTagsInput) { _audioTemplateTagsInput.destroy(); _audioTemplateTagsInput = null; }
        _audioTemplateTagsInput = new TagInput(document.getElementById('audio-template-tags-container'), { placeholder: t('tags.placeholder') });
        _audioTemplateTagsInput.setValue(template.tags || []);

        audioTemplateModal.open();
        audioTemplateModal.snapshot();
    } catch (error: any) {
        console.error('Error loading audio template:', error);
        showToast(t('audio_template.error.load') + ': ' + error.message, 'error');
    }
}

export async function closeAudioTemplateModal() {
    await audioTemplateModal.close();
}

export async function saveAudioTemplate() {
    const templateId = currentEditingAudioTemplateId;
    const name = (document.getElementById('audio-template-name') as HTMLInputElement).value.trim();
    const engineType = (document.getElementById('audio-template-engine') as HTMLSelectElement).value;

    if (!name || !engineType) {
        showToast(t('audio_template.error.required'), 'error');
        return;
    }

    const description = (document.getElementById('audio-template-description') as HTMLInputElement).value.trim();
    const engineConfig = collectAudioEngineConfig();

    const payload = { name, engine_type: engineType, engine_config: engineConfig, description: description || null, tags: _audioTemplateTagsInput ? _audioTemplateTagsInput.getValue() : [] };

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

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

        showToast(templateId ? t('audio_template.updated') : t('audio_template.created'), 'success');
        audioTemplateModal.forceClose();
        audioTemplatesCache.invalidate();
        await loadAudioTemplates();
    } catch (error) {
        console.error('Error saving audio template:', error);
        document.getElementById('audio-template-error')!.textContent = (error as any).message;
        document.getElementById('audio-template-error')!.style.display = 'block';
    }
}

export async function deleteAudioTemplate(templateId: any) {
    const confirmed = await showConfirm(t('audio_template.delete.confirm'));
    if (!confirmed) return;

    try {
        const response = await fetchWithAuth(`/audio-templates/${templateId}`, { method: 'DELETE' });
        if (!response.ok) {
            const error = await response.json();
            throw new Error(error.detail || error.message || 'Failed to delete audio template');
        }
        showToast(t('audio_template.deleted'), 'success');
        audioTemplatesCache.invalidate();
        await loadAudioTemplates();
    } catch (error) {
        console.error('Error deleting audio template:', error);
        showToast(t('audio_template.error.delete') + ': ' + error.message, 'error');
    }
}

export async function cloneAudioTemplate(templateId: any) {
    try {
        const resp = await fetchWithAuth(`/audio-templates/${templateId}`);
        if (!resp.ok) throw new Error('Failed to load audio template');
        const tmpl = await resp.json();
        showAddAudioTemplateModal(tmpl);
    } catch (error) {
        if (error.isAuth) return;
        console.error('Failed to clone audio template:', error);
        showToast(t('audio_template.error.clone_failed'), 'error');
    }
}

// ===== Audio Template Test =====

const NUM_BANDS_TPL = 64;
const TPL_PEAK_DECAY = 0.02;
const TPL_BEAT_FLASH_DECAY = 0.06;

let _tplTestWs: WebSocket | null = null;
let _tplTestAnimFrame: number | null = null;
let _tplTestLatest: any = null;
let _tplTestPeaks = new Float32Array(NUM_BANDS_TPL);
let _tplTestBeatFlash = 0;
let _currentTestAudioTemplateId: string | null = null;

const testAudioTemplateModal = new Modal('test-audio-template-modal', { backdrop: true, lock: true });

export async function showTestAudioTemplateModal(templateId: any) {
    _currentTestAudioTemplateId = templateId;

    // Find template's engine type so we show the correct device list
    const template = _cachedAudioTemplates.find((t: any) => t.id === templateId);
    const engineType = template ? template.engine_type : null;

    // Load audio devices for picker — filter by engine type
    const deviceSelect = document.getElementById('test-audio-template-device') as HTMLSelectElement;
    try {
        const resp = await fetchWithAuth('/audio-devices');
        if (resp.ok) {
            const data = await resp.json();
            // Use engine-specific device list if available, fall back to flat list
            const devices = (engineType && data.by_engine && data.by_engine[engineType])
                ? data.by_engine[engineType]
                : (data.devices || []);
            deviceSelect.innerHTML = devices.map(d => {
                const label = d.name;
                const val = `${d.index}:${d.is_loopback ? '1' : '0'}`;
                return `<option value="${val}">${escapeHtml(label)}</option>`;
            }).join('');
            if (devices.length === 0) {
                deviceSelect.innerHTML = '<option value="-1:1">Default</option>';
            }
        }
    } catch {
        deviceSelect.innerHTML = '<option value="-1:1">Default</option>';
    }

    // Restore last used device
    const lastDevice = localStorage.getItem('lastAudioTestDevice');
    if (lastDevice) {
        const opt = Array.from(deviceSelect.options).find((o: any) => o.value === lastDevice);
        if (opt) deviceSelect.value = lastDevice;
    }

    // Reset visual state
    document.getElementById('audio-template-test-canvas')!.style.display = 'none';
    document.getElementById('audio-template-test-stats')!.style.display = 'none';
    document.getElementById('audio-template-test-status')!.style.display = 'none';
    document.getElementById('test-audio-template-start-btn')!.style.display = '';

    _tplCleanupTest();

    testAudioTemplateModal.open();
}

export function closeTestAudioTemplateModal() {
    _tplCleanupTest();
    testAudioTemplateModal.forceClose();
    _currentTestAudioTemplateId = null;
}

export function startAudioTemplateTest() {
    if (!_currentTestAudioTemplateId) return;

    const deviceVal = (document.getElementById('test-audio-template-device') as HTMLSelectElement).value || '-1:1';
    const [devIdx, devLoop] = deviceVal.split(':');
    localStorage.setItem('lastAudioTestDevice', deviceVal);

    // Show canvas + stats, hide run button, disable device picker
    document.getElementById('audio-template-test-canvas')!.style.display = '';
    document.getElementById('audio-template-test-stats')!.style.display = '';
    document.getElementById('test-audio-template-start-btn')!.style.display = 'none';
    (document.getElementById('test-audio-template-device') as HTMLSelectElement).disabled = true;

    const statusEl = document.getElementById('audio-template-test-status')!;
    statusEl.textContent = t('audio_source.test.connecting');
    statusEl.style.display = '';

    // Reset state
    _tplTestLatest = null;
    _tplTestPeaks.fill(0);
    _tplTestBeatFlash = 0;
    document.getElementById('audio-template-test-rms')!.textContent = '---';
    document.getElementById('audio-template-test-peak')!.textContent = '---';
    document.getElementById('audio-template-test-beat-dot')!.classList.remove('active');

    // Size canvas
    const canvas = document.getElementById('audio-template-test-canvas') as HTMLCanvasElement;
    _tplSizeCanvas(canvas);

    // Connect WebSocket
    const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
    const wsUrl = `${protocol}//${window.location.host}${API_BASE}/audio-templates/${_currentTestAudioTemplateId}/test/ws?token=${encodeURIComponent(apiKey ?? '')}&device_index=${devIdx}&is_loopback=${devLoop === '1' ? '1' : '0'}`;

    try {
        _tplTestWs = new WebSocket(wsUrl);

        _tplTestWs.onopen = () => {
            statusEl.style.display = 'none';
        };

        _tplTestWs.onmessage = (event) => {
            try { _tplTestLatest = JSON.parse(event.data); } catch {}
        };

        _tplTestWs.onclose = () => { _tplTestWs = null; };

        _tplTestWs.onerror = () => {
            showToast(t('audio_source.test.error'), 'error');
            _tplCleanupTest();
        };
    } catch {
        showToast(t('audio_source.test.error'), 'error');
        _tplCleanupTest();
        return;
    }

    _tplTestAnimFrame = requestAnimationFrame(_tplRenderLoop);
}

function _tplCleanupTest() {
    if (_tplTestAnimFrame) {
        cancelAnimationFrame(_tplTestAnimFrame);
        _tplTestAnimFrame = null;
    }
    if (_tplTestWs) {
        _tplTestWs.onclose = null;
        _tplTestWs.close();
        _tplTestWs = null;
    }
    _tplTestLatest = null;
    // Re-enable device picker
    const devSel = document.getElementById('test-audio-template-device') as HTMLSelectElement | null;
    if (devSel) devSel.disabled = false;
}

function _tplSizeCanvas(canvas: HTMLCanvasElement) {
    const rect = canvas.parentElement!.getBoundingClientRect();
    const dpr = window.devicePixelRatio || 1;
    canvas.width = rect.width * dpr;
    canvas.height = 200 * dpr;
    canvas.style.height = '200px';
    canvas.getContext('2d')!.scale(dpr, dpr);
}

function _tplRenderLoop() {
    _tplRenderSpectrum();
    if (testAudioTemplateModal.isOpen && _tplTestWs) {
        _tplTestAnimFrame = requestAnimationFrame(_tplRenderLoop);
    }
}

function _tplRenderSpectrum() {
    const canvas = document.getElementById('audio-template-test-canvas') as HTMLCanvasElement | null;
    if (!canvas) return;

    const ctx = canvas.getContext('2d')!;
    const dpr = window.devicePixelRatio || 1;
    const w = canvas.width / dpr;
    const h = canvas.height / dpr;

    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    ctx.clearRect(0, 0, w, h);

    const data = _tplTestLatest;
    if (!data || !data.spectrum) return;

    const spectrum = data.spectrum;
    const gap = 1;
    const barWidth = (w - gap * (NUM_BANDS_TPL - 1)) / NUM_BANDS_TPL;

    // Beat flash
    if (data.beat) _tplTestBeatFlash = Math.min(1.0, data.beat_intensity + 0.3);
    if (_tplTestBeatFlash > 0) {
        ctx.fillStyle = `rgba(255, 255, 255, ${_tplTestBeatFlash * 0.08})`;
        ctx.fillRect(0, 0, w, h);
        _tplTestBeatFlash = Math.max(0, _tplTestBeatFlash - TPL_BEAT_FLASH_DECAY);
    }

    for (let i = 0; i < NUM_BANDS_TPL; i++) {
        const val = Math.min(1, spectrum[i]);
        const barHeight = val * h;
        const x = i * (barWidth + gap);
        const y = h - barHeight;

        const hue = (1 - val) * 120;
        ctx.fillStyle = `hsl(${hue}, 85%, 50%)`;
        ctx.fillRect(x, y, barWidth, barHeight);

        if (val > _tplTestPeaks[i]) {
            _tplTestPeaks[i] = val;
        } else {
            _tplTestPeaks[i] = Math.max(0, _tplTestPeaks[i] - TPL_PEAK_DECAY);
        }
        const peakY = h - _tplTestPeaks[i] * h;
        const peakHue = (1 - _tplTestPeaks[i]) * 120;
        ctx.fillStyle = `hsl(${peakHue}, 90%, 70%)`;
        ctx.fillRect(x, peakY, barWidth, 2);
    }

    document.getElementById('audio-template-test-rms')!.textContent = (data.rms * 100).toFixed(1) + '%';
    document.getElementById('audio-template-test-peak')!.textContent = (data.peak * 100).toFixed(1) + '%';
    const beatDot = document.getElementById('audio-template-test-beat-dot')!;
    if (data.beat) {
        beatDot.classList.add('active');
    } else {
        beatDot.classList.remove('active');
    }
}
