/**
 * Game Integration — CRUD, cards, modal handlers, live event monitor.
 */

import {
    gameIntegrationsCache, gameAdaptersCache,
    _cachedGameIntegrations, _cachedGameAdapters,
} 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 } from '../core/ui.ts';
import { CardSection } from '../core/card-sections.ts';
import { wrapCard } from '../core/card-colors.ts';
import { TagInput, renderTagChips } from '../core/tag-input.ts';
import { IconSelect, type IconSelectItem } from '../core/icon-select.ts';
import {
    ICON_GAMEPAD, ICON_CLONE, ICON_EDIT, ICON_TEST, ICON_TRASH,
    getGameAdapterIcon, ICON_CIRCLE_DOT,
} from '../core/icons.ts';
import * as P from '../core/icon-paths.ts';
import type {
    GameIntegration, GameAdapterInfo, GameEventMapping, GameEventRecord, GameIntegrationStatus,
    EffectPreset,
} from '../types.ts';

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

// ── Bulk actions ──

function _bulkDeleteGameIntegrations(ids: string[]) {
    return Promise.allSettled(ids.map(id =>
        fetchWithAuth(`/game-integrations/${id}`, { method: 'DELETE' })
    )).then(results => {
        const failed = results.filter(r => r.status === 'rejected' || ((r as any).value && !(r as any).value.ok)).length;
        if (failed) showToast(`${ids.length - failed}/${ids.length} deleted`, 'warning');
        else showToast(t('game_integration.deleted'), 'success');
        gameIntegrationsCache.invalidate();
        loadGameIntegrations();
    });
}

const _gameIntegrationBulkActions = [{
    key: 'delete', labelKey: 'bulk.delete', icon: ICON_TRASH, style: 'danger',
    confirm: 'bulk.confirm_delete', handler: _bulkDeleteGameIntegrations,
}];

// ── CardSection ──

export const csGameIntegrations = new CardSection('game-integrations', {
    titleKey: 'game_integration.section_title',
    gridClass: 'templates-grid',
    addCardOnclick: "showGameIntegrationEditor()",
    keyAttr: 'data-gi-id',
    emptyKey: 'section.empty.game_integrations',
    bulkActions: _gameIntegrationBulkActions,
});

// ── Modal ──

let _giTagsInput: TagInput | null = null;
let _adapterTypeIconSelect: IconSelect | null = null;
let _mappingIconSelects: IconSelect[] = [];
let _presetIconSelect: IconSelect | null = null;
let _eventMonitorTimer: ReturnType<typeof setInterval> | null = null;

class GameIntegrationModal extends Modal {
    constructor() { super('game-integration-modal'); }

    snapshotValues() {
        return {
            name: (this.$('gi-name') as HTMLInputElement)?.value || '',
            description: (this.$('gi-description') as HTMLInputElement)?.value || '',
            adapterType: (this.$('gi-adapter-type') as HTMLSelectElement)?.value || '',
            enabled: (this.$('gi-enabled') as HTMLInputElement)?.checked ? '1' : '0',
            mappings: JSON.stringify(_collectMappings()),
            tags: JSON.stringify(_giTagsInput ? _giTagsInput.getValue() : []),
            config: JSON.stringify(_collectAdapterConfig()),
        };
    }

    onForceClose() {
        if (_giTagsInput) { _giTagsInput.destroy(); _giTagsInput = null; }
        if (_adapterTypeIconSelect) { _adapterTypeIconSelect.destroy(); _adapterTypeIconSelect = null; }
        if (_presetIconSelect) { _presetIconSelect.destroy(); _presetIconSelect = null; }
        _destroyMappingIconSelects();
        _stopEventMonitor();
    }
}

const giModal = new GameIntegrationModal();

// ── Adapter config helpers ──

function _collectAdapterConfig(): Record<string, any> {
    const container = document.getElementById('gi-adapter-config-fields');
    if (!container) return {};
    const config: Record<string, any> = {};
    container.querySelectorAll('[data-config-key]').forEach(el => {
        const key = (el as HTMLElement).dataset.configKey!;
        if (el instanceof HTMLInputElement) {
            if (el.type === 'number') config[key] = parseFloat(el.value) || 0;
            else if (el.type === 'checkbox') config[key] = el.checked;
            else config[key] = el.value;
        }
    });
    return config;
}

function _renderAdapterConfigFields(adapter: GameAdapterInfo, existingConfig: Record<string, any> = {}) {
    const container = document.getElementById('gi-adapter-config-fields')!;
    if (!adapter.config_schema || adapter.config_schema.length === 0) {
        container.innerHTML = `<p class="text-muted">${t('game_integration.no_config')}</p>`;
        return;
    }
    container.innerHTML = adapter.config_schema.map(field => {
        const val = existingConfig[field.name] ?? field.default ?? '';
        const inputType = field.type === 'number' ? 'number' : field.type === 'boolean' ? 'checkbox' : 'text';
        const checked = field.type === 'boolean' && val ? ' checked' : '';
        const inputVal = field.type === 'boolean' ? '' : ` value="${escapeHtml(String(val))}"`;
        return `
            <div class="form-group">
                <div class="label-row">
                    <label for="gi-config-${escapeHtml(field.name)}">${escapeHtml(field.label || field.name)}${field.required ? ' *' : ''}</label>
                    ${field.hint ? `<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>` : ''}
                </div>
                ${field.hint ? `<small class="input-hint" style="display:none">${escapeHtml(field.hint)}</small>` : ''}
                <input type="${inputType}" id="gi-config-${escapeHtml(field.name)}"
                    data-config-key="${escapeHtml(field.name)}"${inputVal}${checked}>
            </div>`;
    }).join('');
}

let _currentSetupInstructions = '';
let _currentAdapterSupportsAutoSetup = false;

function _renderSetupInstructions(adapter: GameAdapterInfo) {
    const btnWrapper = document.getElementById('gi-setup-instructions-btn-wrapper')!;
    _currentSetupInstructions = adapter.setup_instructions || '';
    _currentAdapterSupportsAutoSetup = adapter.supports_auto_setup || false;
    const visible = _currentSetupInstructions || _currentAdapterSupportsAutoSetup;
    btnWrapper.style.display = visible ? 'flex' : 'none';
    btnWrapper.style.gap = '0.5rem';

    const autoSetupBtn = document.getElementById('gi-auto-setup-btn');
    if (autoSetupBtn) {
        autoSetupBtn.style.display = _currentAdapterSupportsAutoSetup ? '' : 'none';
    }
}

export function openSetupInstructions() {
    if (!_currentSetupInstructions) return;
    const overlay = document.getElementById('gi-setup-overlay');
    const content = document.getElementById('gi-setup-overlay-content');
    if (overlay && content) {
        import('marked').then(({ marked }) => {
            content.innerHTML = marked.parse(_currentSetupInstructions) as string;
            overlay.style.display = 'flex';
        });
    }
}

export function closeSetupInstructions() {
    const overlay = document.getElementById('gi-setup-overlay');
    if (overlay) overlay.style.display = 'none';
}

export async function autoSetupGameIntegration() {
    const id = (document.getElementById('gi-id') as HTMLInputElement)?.value;
    if (!id) {
        showToast(t('game_integration.auto_setup.save_first'), 'warning');
        return;
    }

    try {
        const res = await fetchWithAuth(`/game-integrations/${id}/auto-setup`, { method: 'POST' });
        if (!res || !res.ok) {
            const err = await res!.json();
            showToast(err.detail || t('game_integration.auto_setup.failed'), 'error');
            return;
        }
        const data = await res.json();
        if (data.success) {
            let msg = t('game_integration.auto_setup.success');
            if (data.file_path) msg += `\n${data.file_path}`;
            if (data.token_generated) msg += `\n${t('game_integration.auto_setup.token_generated')}`;
            showToast(msg, 'success');

            // Reload integration data in case auth token was generated
            if (data.token_generated) {
                gameIntegrationsCache.invalidate();
                const integrations = await gameIntegrationsCache.fetch();
                const gi = integrations.find(g => g.id === id);
                if (gi) {
                    const adapters = await gameAdaptersCache.fetch();
                    const adapter = adapters.find(a => a.adapter_type === gi.adapter_type);
                    if (adapter) _renderAdapterConfigFields(adapter, gi.adapter_config || {});
                }
            }
        } else {
            showToast(data.message || t('game_integration.auto_setup.failed'), 'error');
        }
    } catch (e: any) {
        if (e.isAuth) return;
        showToast(e.message || t('game_integration.auto_setup.failed'), 'error');
    }
}

// ── Event mapping helpers ──

let _currentMappings: GameEventMapping[] = [];

function _collectMappings(): GameEventMapping[] {
    const rows = document.querySelectorAll('#gi-mappings-list .gi-mapping-row');
    return Array.from(rows).map(row => {
        const eventType = (row.querySelector('[data-field="event_type"]') as HTMLSelectElement)?.value || 'kill';
        const effectType = (row.querySelector('[data-field="effect_type"]') as HTMLSelectElement)?.value || 'flash';
        const colorInput = (row.querySelector('input[data-field="color"]') as HTMLInputElement)?.value || '#ff0000';
        const duration = parseFloat((row.querySelector('[data-field="duration_ms"]') as HTMLInputElement)?.value) || 500;
        const intensity = parseFloat((row.querySelector('[data-field="intensity"]') as HTMLInputElement)?.value) || 1.0;
        const priority = parseInt((row.querySelector('[data-field="priority"]') as HTMLInputElement)?.value) || 5;
        const rgb = _hexToRgb(colorInput);
        return { event_type: eventType, effect_type: effectType, color: rgb, duration_ms: duration, intensity, priority };
    });
}

function _hexToRgb(hex: string): number[] {
    const m = hex.replace('#', '').match(/.{2}/g);
    if (!m) return [255, 0, 0];
    return m.map(c => parseInt(c, 16));
}

function _rgbToHex(rgb: number[]): string {
    return '#' + rgb.map(c => c.toString(16).padStart(2, '0')).join('');
}

function _destroyMappingIconSelects() {
    _mappingIconSelects.forEach(is => is.destroy());
    _mappingIconSelects = [];
}

const EFFECT_TYPES: IconSelectItem[] = [
    { value: 'flash', label: 'Flash', icon: _icon(P.zap) },
    { value: 'pulse', label: 'Pulse', icon: _icon(P.activity) },
    { value: 'sweep', label: 'Sweep', icon: _icon(P.fastForward) },
    { value: 'color_shift', label: 'Color Shift', icon: _icon(P.rainbow) },
    { value: 'breathing', label: 'Breathing', icon: _icon(P.heart) },
];

/** Map well-known game event types to icons. Falls back to a generic icon. */
const _EVENT_TYPE_ICONS: Record<string, string> = {
    kill: P.crosshair, death: P.xIcon, health: P.heart, armor: P.shield,
    round_start: P.play, round_end: P.square, bomb_planted: P.flame, bomb_defused: P.circleCheck,
    assist: P.swords, headshot: P.target, damage: P.zap, gold: P.star,
    level_up: P.trendingUp, respawn: P.refreshCw, item_pickup: P.packageIcon,
};

function _buildEventTypeItems(): IconSelectItem[] {
    return _getAvailableEventTypes().map(et => ({
        value: et,
        label: et,
        icon: _icon(_EVENT_TYPE_ICONS[et] || P.circleDot),
    }));
}

function _getAvailableEventTypes(): string[] {
    const adapterType = (document.getElementById('gi-adapter-type') as HTMLSelectElement)?.value;
    const adapter = _cachedGameAdapters.find(a => a.adapter_type === adapterType);
    if (adapter && adapter.supported_events.length > 0) return adapter.supported_events;
    return ['kill', 'death', 'health', 'armor', 'round_start', 'round_end', 'bomb_planted', 'bomb_defused', 'assist', 'headshot'];
}

function _renderMappingRow(mapping: GameEventMapping, index: number): string {
    const eventTypes = _getAvailableEventTypes();
    const eventOptions = eventTypes.map(et =>
        `<option value="${et}"${et === mapping.event_type ? ' selected' : ''}>${et}</option>`
    ).join('');
    const effectOptions = EFFECT_TYPES.map(ef =>
        `<option value="${ef.value}"${ef.value === mapping.effect_type ? ' selected' : ''}>${ef.label}</option>`
    ).join('');
    const effectLabel = EFFECT_TYPES.find(ef => ef.value === mapping.effect_type)?.label || mapping.effect_type;
    const hexColor = _rgbToHex(mapping.color);

    return `
        <div class="gi-mapping-row" data-mapping-index="${index}">
            <div class="gi-mapping-header">
                <span class="gi-mapping-expand-btn">&#x25B6;</span>
                <span class="gi-mapping-summary">
                    <span class="gi-mapping-summary-event">${escapeHtml(mapping.event_type)}</span>
                    <span class="gi-mapping-summary-effect">${escapeHtml(effectLabel)}</span>
                    <span class="gi-mapping-summary-color" style="background:${hexColor}"></span>
                </span>
                <button type="button" class="btn-remove-rule" onclick="event.stopPropagation(); removeGameMapping(${index})" title="${t('common.delete')}">${ICON_TRASH}</button>
            </div>
            <div class="gi-mapping-body-wrapper">
            <div class="gi-mapping-body">
                <div class="gi-mapping-field-row">
                    <label>${t('game_integration.mapping.event_type')}</label>
                    <select data-field="event_type">${eventOptions}</select>
                </div>
                <div class="gi-mapping-field-row">
                    <label>${t('game_integration.mapping.effect_type')}</label>
                    <select data-field="effect_type" id="gi-effect-type-${index}">${effectOptions}</select>
                </div>
                <div class="gi-mapping-field-row">
                    <label>${t('game_integration.mapping.color')}</label>
                    <input type="color" data-field="color" value="${hexColor}">
                </div>
                <div class="gi-mapping-field-row">
                    <label>${t('game_integration.mapping.duration')}</label>
                    <input type="number" data-field="duration_ms" value="${mapping.duration_ms}" min="50" max="10000" step="50">
                </div>
                <div class="gi-mapping-field-row">
                    <label>${t('game_integration.mapping.intensity')}</label>
                    <input type="range" data-field="intensity" value="${mapping.intensity}" min="0" max="1" step="0.05"
                        oninput="this.title = this.value">
                </div>
                <div class="gi-mapping-field-row">
                    <label>${t('game_integration.mapping.priority')}</label>
                    <input type="number" data-field="priority" value="${mapping.priority}" min="1" max="10">
                </div>
            </div>
            </div>
        </div>`;
}

function _renderMappings(mappings: GameEventMapping[]) {
    _currentMappings = [...mappings];
    const container = document.getElementById('gi-mappings-list')!;
    _destroyMappingIconSelects();
    container.innerHTML = mappings.map((m, i) => _renderMappingRow(m, i)).join('');
    _wireMappingRows(container);
}

function _wireMappingRows(container: HTMLElement) {
    // Expand/collapse on header click
    container.querySelectorAll('.gi-mapping-header').forEach(header => {
        const item = header.closest('.gi-mapping-row') as HTMLElement;
        header.addEventListener('click', (e: Event) => {
            const target = e.target as HTMLElement;
            if (target.closest('.btn-remove-rule')) return;
            item.classList.toggle('gi-mapping-expanded');
        });
    });

    // Wire IconSelect + summary sync on each row
    container.querySelectorAll('.gi-mapping-row').forEach(row => {
        const eventSel = row.querySelector('[data-field="event_type"]') as HTMLSelectElement | null;
        const effectSel = row.querySelector('[data-field="effect_type"]') as HTMLSelectElement | null;
        const colorInput = row.querySelector('input[data-field="color"]') as HTMLInputElement | null;
        const summaryEvent = row.querySelector('.gi-mapping-summary-event') as HTMLElement | null;
        const summaryEffect = row.querySelector('.gi-mapping-summary-effect') as HTMLElement | null;
        const summaryColor = row.querySelector('.gi-mapping-summary-color') as HTMLElement | null;

        // Event type IconSelect
        if (eventSel) {
            const is = new IconSelect({ target: eventSel, items: _buildEventTypeItems(), columns: 4 });
            _mappingIconSelects.push(is);
            if (summaryEvent) {
                eventSel.addEventListener('change', () => { summaryEvent.textContent = eventSel.value; });
            }
        }

        // Effect type IconSelect
        if (effectSel) {
            const is = new IconSelect({ target: effectSel, items: EFFECT_TYPES, columns: 3 });
            _mappingIconSelects.push(is);
            if (summaryEffect) {
                effectSel.addEventListener('change', () => {
                    const label = EFFECT_TYPES.find(ef => ef.value === effectSel.value)?.label || effectSel.value;
                    summaryEffect.textContent = label;
                });
            }
        }

        // Color swatch sync
        if (colorInput && summaryColor) {
            colorInput.addEventListener('input', () => { summaryColor.style.background = colorInput.value; });
        }
    });
}

export function addGameMapping() {
    const newMapping: GameEventMapping = {
        event_type: _getAvailableEventTypes()[0] || 'kill',
        effect_type: 'flash',
        color: [255, 0, 0],
        duration_ms: 500,
        intensity: 1.0,
        priority: 5,
    };
    const collected = _collectMappings();
    collected.push(newMapping);
    _renderMappings(collected);
}

export function removeGameMapping(index: number) {
    const collected = _collectMappings();
    collected.splice(index, 1);
    _renderMappings(collected);
}

let _cachedPresets: EffectPreset[] = [];

async function _loadPresets(): Promise<EffectPreset[]> {
    if (_cachedPresets.length > 0) return _cachedPresets;
    try {
        const res = await fetchWithAuth('/game-integrations/presets');
        if (res && res.ok) {
            const data = await res.json();
            _cachedPresets = data.presets || [];
        }
    } catch { /* ignore */ }
    return _cachedPresets;
}

function _applyMappingPreset(presetKey: string) {
    const preset = _cachedPresets.find(p => p.key === presetKey);
    if (!preset) return;
    // Map API effect field to frontend effect_type field
    const mappings: GameEventMapping[] = preset.event_mappings.map(m => ({
        event_type: m.event_type,
        effect_type: (m as any).effect || (m as any).effect_type || 'flash',
        color: m.color,
        duration_ms: m.duration_ms,
        intensity: m.intensity,
        priority: m.priority,
    }));
    _renderMappings(mappings);
}

export function onMappingPresetChange() {
    const sel = document.getElementById('gi-mapping-preset') as HTMLSelectElement;
    if (sel.value) {
        _applyMappingPreset(sel.value);
        sel.value = '';
    }
}

async function _populatePresetSelector() {
    const sel = document.getElementById('gi-mapping-preset') as HTMLSelectElement;
    if (!sel) return;
    if (_presetIconSelect) { _presetIconSelect.destroy(); _presetIconSelect = null; }
    const presets = await _loadPresets();
    sel.innerHTML = `<option value="">${t('game_integration.mapping.select_preset')}</option>` +
        presets.map(p => `<option value="${p.key}">${escapeHtml(p.name)}</option>`).join('');
    if (presets.length > 0) {
        const items: IconSelectItem[] = [
            { value: '', label: t('game_integration.mapping.select_preset'), icon: '' },
            ...presets.map(p => ({
                value: p.key,
                label: p.name,
                icon: _icon(P.sparkles),
                desc: p.description,
            })),
        ];
        _presetIconSelect = new IconSelect({ target: sel, items, columns: 2 });
    }
}

// ── Live event monitor ──

function _stopEventMonitor() {
    if (_eventMonitorTimer) {
        clearInterval(_eventMonitorTimer);
        _eventMonitorTimer = null;
    }
}

function _startEventMonitor(integrationId: string) {
    _stopEventMonitor();
    const feed = document.getElementById('gi-event-feed');
    if (!feed) return;
    feed.innerHTML = `<div class="gi-event-waiting">${t('game_integration.events.waiting')}</div>`;

    const poll = async () => {
        try {
            const res = await fetchWithAuth(`/game-integrations/${integrationId}/events`);
            if (!res || !res.ok) return;
            const data = await res.json();
            const events: GameEventRecord[] = data.events || [];
            if (events.length === 0) return;
            feed.innerHTML = events.slice(0, 20).map(ev => {
                const ts = new Date(ev.timestamp).toLocaleTimeString();
                const valStr = ev.value !== undefined ? ` = ${ev.value}` : '';
                return `<div class="gi-event-item">
                    <span class="gi-event-time">${ts}</span>
                    <span class="gi-event-type">${escapeHtml(ev.event_type)}</span>
                    <span class="gi-event-value">${valStr}</span>
                </div>`;
            }).join('');
        } catch { /* ignore polling errors */ }
    };

    poll();
    _eventMonitorTimer = setInterval(poll, 2000);
}

// ── Connection test ──

let _connectionTestTimer: ReturnType<typeof setInterval> | null = null;

export function testGameConnection() {
    const id = (document.getElementById('gi-id') as HTMLInputElement)?.value;
    if (!id) {
        showToast(t('game_integration.error.save_first'), 'warning');
        return;
    }
    const panel = document.getElementById('gi-test-panel')!;
    panel.style.display = '';
    panel.innerHTML = `<div class="gi-test-waiting">${ICON_CIRCLE_DOT} ${t('game_integration.test.waiting')}</div>`;

    if (_connectionTestTimer) clearInterval(_connectionTestTimer);

    let attempts = 0;
    _connectionTestTimer = setInterval(async () => {
        attempts++;
        try {
            const res = await fetchWithAuth(`/game-integrations/${id}/status`);
            if (!res || !res.ok) return;
            const status: GameIntegrationStatus = await res.json();
            if (status.event_count > 0) {
                clearInterval(_connectionTestTimer!);
                _connectionTestTimer = null;
                panel.innerHTML = `<div class="gi-test-success">${t('game_integration.test.success')} (${status.event_count})</div>`;
            } else if (status.error) {
                clearInterval(_connectionTestTimer!);
                _connectionTestTimer = null;
                panel.innerHTML = `<div class="gi-test-error">${t('game_integration.test.error')}: ${escapeHtml(status.error)}</div>`;
            }
        } catch { /* ignore */ }
        if (attempts >= 30) {
            clearInterval(_connectionTestTimer!);
            _connectionTestTimer = null;
            panel.innerHTML = `<div class="gi-test-timeout">${t('game_integration.test.timeout')}</div>`;
        }
    }, 2000);
}

// ── Card renderer ──

export function createGameIntegrationCard(gi: GameIntegration): string {
    const adapterIcon = getGameAdapterIcon(gi.adapter_type);
    const adapterName = _cachedGameAdapters.find(a => a.adapter_type === gi.adapter_type)?.display_name || gi.adapter_type;
    const enabledClass = gi.enabled ? 'gi-status-active' : 'gi-status-inactive';
    const enabledLabel = gi.enabled ? t('game_integration.status.active') : t('game_integration.status.inactive');
    const mappingCount = gi.event_mappings?.length || 0;

    return wrapCard({
        type: 'template-card',
        dataAttr: 'data-gi-id',
        id: gi.id,
        removeOnclick: `deleteGameIntegration('${gi.id}')`,
        removeTitle: t('common.delete'),
        content: `
            <div class="template-card-header">
                <div class="template-name" title="${escapeHtml(gi.name)}">${adapterIcon} ${escapeHtml(gi.name)}</div>
            </div>
            ${gi.description ? `<div class="template-config" style="opacity:0.7;">${escapeHtml(gi.description)}</div>` : ''}
            <div class="stream-card-props">
                <span class="stream-card-prop" title="${t('game_integration.adapter')}">${ICON_GAMEPAD} ${escapeHtml(adapterName)}</span>
                <span class="stream-card-prop ${enabledClass}" title="${t('game_integration.status')}">${ICON_CIRCLE_DOT} ${enabledLabel}</span>
                ${mappingCount > 0 ? `<span class="stream-card-prop" title="${t('game_integration.mappings')}">${_icon(P.listChecks)} ${mappingCount}</span>` : ''}
            </div>
            ${renderTagChips(gi.tags)}`,
        actions: `
            <button class="btn btn-icon btn-secondary" onclick="event.stopPropagation(); showGameEventMonitor('${gi.id}')" title="${t('game_integration.events.monitor')}">${ICON_TEST}</button>
            <button class="btn btn-icon btn-secondary" onclick="cloneGameIntegration('${gi.id}')" title="${t('common.clone')}">${ICON_CLONE}</button>
            <button class="btn btn-icon btn-secondary" onclick="showGameIntegrationEditor('${gi.id}')" title="${t('common.edit')}">${ICON_EDIT}</button>`,
    });
}

// ── CRUD ──

export async function showGameIntegrationEditor(editId: string | null = null) {
    const titleEl = document.getElementById('gi-title')!;
    const idInput = document.getElementById('gi-id') as HTMLInputElement;
    const nameInput = document.getElementById('gi-name') as HTMLInputElement;
    const descInput = document.getElementById('gi-description') as HTMLInputElement;
    const adapterSel = document.getElementById('gi-adapter-type') as HTMLSelectElement;
    const enabledCheck = document.getElementById('gi-enabled') as HTMLInputElement;
    const testPanel = document.getElementById('gi-test-panel')!;

    // Reset form
    idInput.value = '';
    nameInput.value = '';
    descInput.value = '';
    enabledCheck.checked = true;
    testPanel.style.display = 'none';
    document.getElementById('gi-error')!.style.display = 'none';

    // Ensure adapters are loaded
    const adapters = await gameAdaptersCache.fetch();
    adapterSel.innerHTML = adapters.map(a =>
        `<option value="${a.adapter_type}">${escapeHtml(a.display_name)}</option>`
    ).join('');

    // Setup adapter type IconSelect
    if (_adapterTypeIconSelect) { _adapterTypeIconSelect.destroy(); _adapterTypeIconSelect = null; }
    const adapterItems: IconSelectItem[] = adapters.map(a => ({
        value: a.adapter_type,
        label: a.display_name,
        icon: getGameAdapterIcon(a.adapter_type),
        desc: a.game_name,
    }));
    _adapterTypeIconSelect = new IconSelect({
        target: adapterSel,
        items: adapterItems,
        columns: 3,
    });

    // Tags
    if (_giTagsInput) { _giTagsInput.destroy(); _giTagsInput = null; }
    _giTagsInput = new TagInput(document.getElementById('gi-tags-container')!);

    if (editId) {
        const integrations = await gameIntegrationsCache.fetch();
        const gi = integrations.find(g => g.id === editId);
        if (!gi) return;
        idInput.value = gi.id;
        nameInput.value = gi.name;
        descInput.value = gi.description || '';
        adapterSel.value = gi.adapter_type;
        if (_adapterTypeIconSelect) _adapterTypeIconSelect.setValue(gi.adapter_type);
        enabledCheck.checked = gi.enabled;
        _giTagsInput.setValue(gi.tags || []);

        const adapter = adapters.find(a => a.adapter_type === gi.adapter_type);
        if (adapter) {
            _renderAdapterConfigFields(adapter, gi.adapter_config || {});
            _renderSetupInstructions(adapter);
        }
        _renderMappings(gi.event_mappings || []);

        titleEl.innerHTML = `${ICON_GAMEPAD} ${t('game_integration.edit')}`;

        // Start event monitor for existing integration
        _startEventMonitor(gi.id);
    } else {
        titleEl.innerHTML = `${ICON_GAMEPAD} ${t('game_integration.add')}`;
        _renderMappings([]);

        // Show config for first adapter
        if (adapters.length > 0) {
            _renderAdapterConfigFields(adapters[0]);
            _renderSetupInstructions(adapters[0]);
        }
    }

    // Listen for adapter type changes
    adapterSel.onchange = () => {
        const adapter = adapters.find(a => a.adapter_type === adapterSel.value);
        if (adapter) {
            _renderAdapterConfigFields(adapter);
            _renderSetupInstructions(adapter);
            // Re-render mappings to update available event types
            _renderMappings(_collectMappings());
        }
    };

    // Populate preset selector from API
    await _populatePresetSelector();

    giModal.open();
    giModal.snapshot();
}

export async function saveGameIntegration() {
    const id = (document.getElementById('gi-id') as HTMLInputElement).value;
    const name = (document.getElementById('gi-name') as HTMLInputElement).value.trim();
    if (!name) { giModal.showError(t('game_integration.error.name_required')); return; }

    const adapterType = (document.getElementById('gi-adapter-type') as HTMLSelectElement).value;
    const description = (document.getElementById('gi-description') as HTMLInputElement).value.trim();
    const enabled = (document.getElementById('gi-enabled') as HTMLInputElement).checked;
    const adapterConfig = _collectAdapterConfig();
    const eventMappings = _collectMappings();
    const tags = _giTagsInput ? _giTagsInput.getValue() : [];

    const payload = {
        name, adapter_type: adapterType, adapter_config: adapterConfig,
        event_mappings: eventMappings, enabled, description, tags,
    };

    try {
        const url = id ? `/game-integrations/${id}` : '/game-integrations';
        const method = id ? 'PUT' : 'POST';
        const res = await fetchWithAuth(url, { method, body: JSON.stringify(payload) });
        if (!res || !res.ok) {
            const err = await res!.json();
            throw new Error(err.detail || t('game_integration.error.save_failed'));
        }
        showToast(id ? t('game_integration.updated') : t('game_integration.created'), 'success');
        gameIntegrationsCache.invalidate();
        giModal.forceClose();
        loadGameIntegrations();
    } catch (e: any) {
        if (e.isAuth) return;
        giModal.showError(e.message);
    }
}

export async function deleteGameIntegration(entityId: string) {
    const ok = await showConfirm(t('game_integration.confirm_delete'));
    if (!ok) return;
    try {
        await fetchWithAuth(`/game-integrations/${entityId}`, { method: 'DELETE' });
        showToast(t('game_integration.deleted'), 'success');
        gameIntegrationsCache.invalidate();
        loadGameIntegrations();
    } catch (e: any) {
        if (e.isAuth) return;
        showToast(e.message || t('game_integration.error.delete_failed'), 'error');
    }
}

export async function cloneGameIntegration(entityId: string) {
    const integrations = await gameIntegrationsCache.fetch();
    const source = integrations.find(g => g.id === entityId);
    if (!source) return;
    await showGameIntegrationEditor(null);
    (document.getElementById('gi-name') as HTMLInputElement).value = source.name + ' (Copy)';
    (document.getElementById('gi-description') as HTMLInputElement).value = source.description || '';
    const adapterSel = document.getElementById('gi-adapter-type') as HTMLSelectElement;
    adapterSel.value = source.adapter_type;
    if (_adapterTypeIconSelect) _adapterTypeIconSelect.setValue(source.adapter_type);
    (document.getElementById('gi-enabled') as HTMLInputElement).checked = source.enabled;
    if (_giTagsInput) _giTagsInput.setValue(source.tags || []);

    const adapter = _cachedGameAdapters.find(a => a.adapter_type === source.adapter_type);
    if (adapter) {
        _renderAdapterConfigFields(adapter, source.adapter_config || {});
        _renderSetupInstructions(adapter);
    }
    _renderMappings(source.event_mappings || []);
    giModal.snapshot();
}

export function closeGameIntegrationModal() {
    giModal.close();
}

// ── Event monitor (standalone, triggered from card) ──

export function showGameEventMonitor(integrationId: string) {
    const gi = _cachedGameIntegrations.find(g => g.id === integrationId);
    if (!gi) return;
    // Open editor and start monitoring
    showGameIntegrationEditor(integrationId);
}

// ── Load function (called from streams.ts) ──

export async function loadGameIntegrations() {
    await Promise.all([
        gameIntegrationsCache.fetch(),
        gameAdaptersCache.fetch(),
    ]);
    // Streams.ts handles rendering via its own renderPictureSourcesList
    if (window.loadPictureSources) window.loadPictureSources();
}
