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

import {
    availableEngines, setAvailableEngines,
    currentEditingTemplateId, setCurrentEditingTemplateId,
    _templateNameManuallyEdited, set_templateNameManuallyEdited,
    currentTestingTemplate, setCurrentTestingTemplate,
    _cachedStreams, _cachedDisplays,
    captureTemplatesCache, displaysCache,
    apiKey,
} from '../core/state.ts';
import { API_BASE, getHeaders, fetchWithAuth, escapeHtml } from '../core/api.ts';
import { t } from '../core/i18n.ts';
import { Modal } from '../core/modal.ts';
import { showToast, showConfirm, openLightbox, showOverlaySpinner, hideOverlaySpinner, updateOverlayPreview, setupBackdropClose } from '../core/ui.ts';
import { openDisplayPicker, formatDisplayLabel } from './displays.ts';
import {
    getEngineIcon,
    ICON_CAPTURE_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 capture template modal ──
let _captureTemplateTagsInput: TagInput | null = null;

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

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

    onForceClose() {
        if (_captureTemplateTagsInput) { _captureTemplateTagsInput.destroy(); _captureTemplateTagsInput = null; }
        setCurrentEditingTemplateId(null);
        set_templateNameManuallyEdited(false);
    }
}

const templateModal = new CaptureTemplateModal();
const testTemplateModal = new Modal('test-template-modal');

// ===== Capture Templates =====

async function loadCaptureTemplates() {
    try {
        await captureTemplatesCache.fetch();
        await loadPictureSources();
    } catch (error) {
        if (error.isAuth) return;
        console.error('Error loading capture templates:', error);
        showToast(t('streams.error.load'), 'error');
    }
}

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

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

    await loadAvailableEngines();

    // Pre-fill from clone data after engines are loaded
    if (cloneData) {
        (document.getElementById('template-name') as HTMLInputElement).value = (cloneData.name || '') + ' (Copy)';
        (document.getElementById('template-description') as HTMLInputElement).value = cloneData.description || '';
        (document.getElementById('template-engine') as HTMLSelectElement).value = cloneData.engine_type;
        await onEngineChange();
        populateEngineConfig(cloneData.engine_config);
    }

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

    templateModal.open();
    templateModal.snapshot();
}

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

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

        await loadAvailableEngines();
        (document.getElementById('template-engine') as HTMLSelectElement).value = template.engine_type;
        await onEngineChange();
        populateEngineConfig(template.engine_config);

        await loadDisplaysForTest();

        const testResults = document.getElementById('template-test-results');
        if (testResults) testResults.style.display = 'none';
        document.getElementById('template-error')!.style.display = 'none';

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

        templateModal.open();
        templateModal.snapshot();
    } catch (error) {
        console.error('Error loading template:', error);
        showToast(t('templates.error.load') + ': ' + error.message, 'error');
    }
}

export async function closeTemplateModal() {
    await templateModal.close();
}

export function updateCaptureDuration(value: any) {
    document.getElementById('test-template-duration-value')!.textContent = value;
    localStorage.setItem('capture_duration', value);
}

function restoreCaptureDuration() {
    const savedDuration = localStorage.getItem('capture_duration');
    if (savedDuration) {
        const durationInput = document.getElementById('test-template-duration') as HTMLInputElement;
        const durationValue = document.getElementById('test-template-duration-value')!;
        durationInput.value = savedDuration;
        durationValue.textContent = savedDuration;
    }
}

export async function showTestTemplateModal(templateId: any) {
    try {
        const templates = await captureTemplatesCache.fetch();
        const template = templates.find(tp => tp.id === templateId);

        if (!template) {
            showToast(t('templates.error.load'), 'error');
            return;
        }

        setCurrentTestingTemplate(template);
        await loadDisplaysForTest();
        restoreCaptureDuration();

        testTemplateModal.open();
        setupBackdropClose((testTemplateModal as any).el, () => closeTestTemplateModal());
    } catch (error) {
        if (error.isAuth) return;
        showToast(t('templates.error.load'), 'error');
    }
}

export function closeTestTemplateModal() {
    testTemplateModal.forceClose();
    setCurrentTestingTemplate(null);
}

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

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

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

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

        // Update icon-grid selector with dynamic engine list
        const items = availableEngines
            .filter(e => e.available)
            .map(e => ({ value: e.type, icon: getEngineIcon(e.type), label: e.name, desc: t(`templates.engine.${e.type}.desc`) }));
        if (_engineIconSelect) { _engineIconSelect.updateItems(items); }
        else { _engineIconSelect = new IconSelect({ target: select, items, columns: 2 }); }
        _engineIconSelect.setValue(select.value);
    } catch (error) {
        console.error('Error loading engines:', error);
        showToast(t('templates.error.engines') + ': ' + error.message, 'error');
    }
}

let _engineIconSelect: IconSelect | null = null;

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

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

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

    if (!_templateNameManuallyEdited && !(document.getElementById('template-id') as HTMLInputElement).value) {
        (document.getElementById('template-name') as HTMLInputElement).value = engine.name || engineType;
    }

    const hint = document.getElementById('engine-availability-hint')!;
    if (!engine.available) {
        hint.textContent = t('templates.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 || {};

    // Known select options for specific config keys
    const CONFIG_SELECT_OPTIONS = {
        camera_backend: ['auto', 'dshow', 'msmf', 'v4l2'],
    };

    // IconSelect definitions for specific config keys
    const CONFIG_ICON_SELECT = {
        camera_backend: {
            columns: 2,
            items: [
                { value: 'auto',  icon: _icon(P.refreshCw), label: 'Auto',  desc: t('templates.config.camera_backend.auto') },
                { value: 'dshow', icon: _icon(P.camera),    label: 'DShow', desc: t('templates.config.camera_backend.dshow') },
                { value: 'msmf',  icon: _icon(P.film),      label: 'MSMF',  desc: t('templates.config.camera_backend.msmf') },
                { value: 'v4l2',  icon: _icon(P.monitor),   label: 'V4L2',  desc: t('templates.config.camera_backend.v4l2') },
            ],
        },
    };

    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;
            const selectOptions = CONFIG_SELECT_OPTIONS[key];
            gridHtml += `
                <label class="config-grid-label" for="config-${key}">${key}</label>
                <div class="config-grid-value">
                    ${typeof value === 'boolean' ? `
                        <select id="config-${key}" data-config-key="${key}">
                            <option value="true" ${value ? 'selected' : ''}>true</option>
                            <option value="false" ${!value ? 'selected' : ''}>false</option>
                        </select>
                    ` : selectOptions ? `
                        <select id="config-${key}" data-config-key="${key}">
                            ${selectOptions.map(opt => `<option value="${opt}" ${opt === String(value) ? 'selected' : ''}>${opt}</option>`).join('')}
                        </select>
                    ` : `
                        <input type="${fieldType}" id="config-${key}" data-config-key="${key}" value="${fieldValue}">
                    `}
                </div>
            `;
        });
        gridHtml += '</div>';
        configFields.innerHTML = gridHtml;

        // Apply IconSelect to known config selects
        for (const [key, cfg] of Object.entries(CONFIG_ICON_SELECT)) {
            const sel = document.getElementById(`config-${key}`);
            if (sel) new IconSelect({ target: sel as HTMLSelectElement, items: cfg.items, columns: cfg.columns });
        }
    }

    configSection.style.display = 'block';
}

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

function collectEngineConfig() {
    const config: any = {};
    const fields = document.querySelectorAll('[data-config-key]');
    fields.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 loadDisplaysForTest() {
    try {
        // Use engine-specific display list for engines with own devices (camera, scrcpy)
        const engineType = currentTestingTemplate?.engine_type;
        const engineHasOwnDisplays = availableEngines.find(e => e.type === engineType)?.has_own_displays || false;
        const url = engineHasOwnDisplays
            ? `/config/displays?engine_type=${engineType}`
            : '/config/displays';

        // Always refetch for engines with own displays (devices may change); use cache for desktop
        if (!_cachedDisplays || engineHasOwnDisplays) {
            const response = await fetchWithAuth(url);
            if (!response.ok) throw new Error(`Failed to load displays: ${response.status}`);
            const displaysData = await response.json();
            displaysCache.update(displaysData.displays || []);
        }

        let selectedIndex: number | null = null;
        const lastDisplay = localStorage.getItem('lastTestDisplayIndex');

        if (lastDisplay !== null && _cachedDisplays) {
            const found = _cachedDisplays.find(d => d.index === parseInt(lastDisplay));
            if (found) selectedIndex = found.index;
        }

        if (selectedIndex === null && _cachedDisplays) {
            const primary = _cachedDisplays.find(d => d.is_primary);
            if (primary) selectedIndex = primary.index;
            else if (_cachedDisplays.length > 0) selectedIndex = _cachedDisplays[0].index;
        }

        if (selectedIndex !== null && _cachedDisplays) {
            const display = _cachedDisplays.find(d => d.index === selectedIndex);
            (window as any).onTestDisplaySelected(selectedIndex, display);
        }
    } catch (error) {
        console.error('Error loading displays:', error);
    }
}

export function runTemplateTest() {
    if (!currentTestingTemplate) {
        showToast(t('templates.test.error.no_engine'), 'error');
        return;
    }

    const displayIndex = (document.getElementById('test-template-display') as HTMLSelectElement).value;
    const captureDuration = parseFloat((document.getElementById('test-template-duration') as HTMLInputElement).value);

    if (displayIndex === '') {
        showToast(t('templates.test.error.no_display'), 'error');
        return;
    }

    const template = currentTestingTemplate;
    localStorage.setItem('lastTestDisplayIndex', displayIndex);

    const previewWidth = Math.round(Math.min(window.innerWidth * 0.8, 1920) * Math.min(window.devicePixelRatio || 1, 2));
    _runTestViaWS(
        '/capture-templates/test/ws',
        {},
        {
            engine_type: template.engine_type,
            engine_config: template.engine_config,
            display_index: parseInt(displayIndex),
            capture_duration: captureDuration,
            preview_width: previewWidth,
        },
        captureDuration,
    );
}

function buildTestStatsHtml(result: any) {
    // Support both REST format (nested) and WS format (flat)
    const p = result.performance || result;
    const duration = p.capture_duration_s ?? p.elapsed_s ?? 0;
    const frameCount = p.frame_count ?? 0;
    const fps = p.actual_fps ?? p.fps ?? 0;
    const avgMs = p.avg_capture_time_ms ?? p.avg_capture_ms ?? 0;
    const w = result.full_capture?.width ?? result.width ?? 0;
    const h = result.full_capture?.height ?? result.height ?? 0;
    const res = `${w}x${h}`;

    let html = `
        <div class="stat-item"><span>${t('templates.test.results.duration')}:</span> <strong>${Number(duration).toFixed(2)}s</strong></div>
        <div class="stat-item"><span>${t('templates.test.results.frame_count')}:</span> <strong>${frameCount}</strong></div>`;
    if (frameCount > 1) {
        html += `
        <div class="stat-item"><span>${t('templates.test.results.actual_fps')}:</span> <strong>${Number(fps).toFixed(1)}</strong></div>
        <div class="stat-item"><span>${t('templates.test.results.avg_capture_time')}:</span> <strong>${Number(avgMs).toFixed(1)}ms</strong></div>`;
    }
    html += `
        <div class="stat-item"><span>${t('templates.test.results.resolution')}</span> <strong>${res}</strong></div>`;
    return html;
}

// ===== Shared WebSocket test helper =====

/**
 * Run a capture test via WebSocket, streaming intermediate previews into
 * the overlay spinner and opening the lightbox with the final result.
 *
 * @param {string} wsPath  Relative WS path (e.g. '/picture-sources/{id}/test/ws')
 * @param {Object} queryParams  Extra query params (duration, source_stream_id, etc.)
 * @param {Object|null} firstMessage  If non-null, sent as JSON after WS opens (for template test)
 * @param {number} duration  Test duration for overlay progress ring
 */
export function _runTestViaWS(wsPath: string, queryParams: any = {}, firstMessage: any = null, duration = 5) {
    const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
    // Dynamic preview resolution: 80% of viewport width, scaled by DPR, capped at 1920px
    const previewWidth = Math.round(Math.min(window.innerWidth * 0.8, 1920) * Math.min(window.devicePixelRatio || 1, 2));
    const params = new URLSearchParams({ token: apiKey, preview_width: String(previewWidth), ...queryParams });
    const wsUrl = `${protocol}//${window.location.host}${API_BASE}${wsPath}?${params}`;

    showOverlaySpinner(t('streams.test.running'), duration);

    let gotResult = false;
    let ws;

    try {
        ws = new WebSocket(wsUrl);
    } catch (e) {
        hideOverlaySpinner();
        showToast(t('streams.test.error.failed') + ': ' + e.message, 'error');
        return;
    }

    // Close WS when user cancels overlay
    const patchCloseBtn = () => {
        const closeBtn = document.querySelector('.overlay-spinner-close') as HTMLElement | null;
        if (closeBtn) {
            const origHandler = closeBtn.onclick;
            closeBtn.onclick = () => {
                if (ws.readyState <= WebSocket.OPEN) ws.close();
                if (origHandler) (origHandler as any)();
            };
        }
    };
    patchCloseBtn();

    // Also close on ESC (overlay ESC handler calls hideOverlaySpinner which aborts)
    const origAbort = window._overlayAbortController;
    if (origAbort) {
        origAbort.signal.addEventListener('abort', () => {
            if (ws.readyState <= WebSocket.OPEN) ws.close();
        }, { once: true });
    }

    ws.onopen = () => {
        if (firstMessage) {
            ws.send(JSON.stringify(firstMessage));
        }
    };

    ws.onmessage = (event) => {
        try {
            const msg = JSON.parse(event.data);
            if (msg.type === 'frame') {
                updateOverlayPreview(msg.thumbnail, msg);
            } else if (msg.type === 'result') {
                gotResult = true;
                hideOverlaySpinner();
                openLightbox(msg.full_image, buildTestStatsHtml(msg));
                ws.close();
            } else if (msg.type === 'error') {
                hideOverlaySpinner();
                showToast(msg.detail || 'Test failed', 'error');
                ws.close();
            }
        } catch (e) {
            console.error('Error parsing test WS message:', e);
        }
    };

    ws.onerror = () => {
        if (!gotResult) {
            hideOverlaySpinner();
            showToast(t('streams.test.error.failed'), 'error');
        }
    };

    ws.onclose = () => {
        if (!gotResult) {
            hideOverlaySpinner();
        }
    };
}

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

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

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

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

    try {
        let response;
        if (templateId) {
            response = await fetchWithAuth(`/capture-templates/${templateId}`, { method: 'PUT', body: JSON.stringify(payload) });
        } else {
            response = await fetchWithAuth('/capture-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 template');
        }

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

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

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