/**
 * Command Palette — global search & navigation (Ctrl+K / Cmd+K).
 */

import { fetchWithAuth, escapeHtml } from './api.ts';
import { t } from './i18n.ts';
import { navigateToCard } from './navigation.ts';
import {
    getTargetTypeIcon, getPictureSourceIcon, getColorStripIcon, getAudioSourceIcon,
    ICON_DEVICE, ICON_TARGET, ICON_AUTOMATION, ICON_VALUE_SOURCE, ICON_SCENE,
    ICON_CAPTURE_TEMPLATE, ICON_PP_TEMPLATE, ICON_PATTERN_TEMPLATE, ICON_CSPT, ICON_CLOCK,
} from './icons.ts';
import { getCardColor } from './card-colors.ts';
import { graphNavigateToNode } from '../features/graph-editor.ts';
import { showToast } from './ui.ts';

let _isOpen = false;
let _items: any[] = [];
let _filtered: any[] = [];
let _selectedIdx = 0;
let _loading = false;
let _inputDebounceTimer: ReturnType<typeof setTimeout> | null = null;
const _INPUT_DEBOUNCE_MS = 150;

// ─── Entity definitions: endpoint → palette items ───

const _streamSubTab = {
    raw: { sub: 'raw', section: 'raw-streams' },
    processed: { sub: 'processed', section: 'proc-streams' },
    static_image: { sub: 'static_image', section: 'static-streams' },
};

function _mapEntities(data: any, mapFn: (item: any) => any) {
    if (Array.isArray(data)) return data.map(mapFn).filter(Boolean);
    return [];
}

function _buildItems(results: any[], states: any = {}) {
    const [devices, targets, css, automations, capTempl, ppTempl, patTempl, audioSrc, valSrc, streams, scenePresets, csptTemplates, syncClocks] = results;
    const items: any[] = [];

    _mapEntities(devices, d => items.push({
        name: d.name, detail: d.device_type, group: 'devices', icon: ICON_DEVICE,
        nav: ['targets', 'led-devices', 'led-devices', 'data-device-id', d.id],
    }));

    _mapEntities(targets, tgt => {
        const running = !!states[tgt.id]?.processing;
        if (tgt.target_type === 'key_colors') {
            items.push({
                name: tgt.name, detail: 'key_colors', group: 'kc_targets', icon: getTargetTypeIcon('key_colors'),
                nav: ['targets', 'kc-targets', 'kc-targets', 'data-kc-target-id', tgt.id], running,
            });
        } else {
            items.push({
                name: tgt.name, detail: tgt.target_type, group: 'targets', icon: ICON_TARGET,
                nav: ['targets', 'led-targets', 'led-targets', 'data-target-id', tgt.id], running,
            });
        }
        // Action item: toggle start/stop
        const actionItem: any = {
            name: tgt.name, group: 'actions',
            detail: running ? t('search.action.stop') : t('search.action.start'),
            icon: running ? '■' : '▶',
            _running: running, _targetId: tgt.id,
            action: async () => {
                const isRunning = actionItem._running;
                const endpoint = isRunning ? 'stop' : 'start';
                const resp = await fetchWithAuth(`/output-targets/${tgt.id}/${endpoint}`, { method: 'POST' });
                if (resp.ok) {
                    showToast(t(isRunning ? 'device.stopped' : 'device.started'), 'success');
                    actionItem._running = !isRunning;
                    actionItem.detail = !isRunning ? t('search.action.stop') : t('search.action.start');
                    actionItem.icon = !isRunning ? '■' : '▶';
                    _render();
                } else {
                    const err = await resp.json().catch(() => ({}));
                    const d = err.detail || err.message || '';
                    const ds = Array.isArray(d) ? d.map(x => x.msg || x).join('; ') : String(d);
                    showToast(ds || t(`target.error.${endpoint}_failed`), 'error');
                }
            },
        };
        items.push(actionItem);
    });

    _mapEntities(css, c => items.push({
        name: c.name, detail: c.css_type || c.source_type, group: 'css', icon: getColorStripIcon(c.css_type || c.source_type),
        nav: ['streams', 'color_strip', 'color-strips', 'data-css-id', c.id],
    }));

    _mapEntities(automations, a => {
        items.push({
            name: a.name, detail: a.enabled ? 'enabled' : '', group: 'automations', icon: ICON_AUTOMATION,
            nav: ['automations', null, 'automations', 'data-automation-id', a.id],
        });
        const autoItem: any = {
            name: a.name, group: 'actions', icon: ICON_AUTOMATION,
            detail: a.enabled ? t('search.action.disable') : t('search.action.enable'),
            _enabled: a.enabled,
            action: async () => {
                const isEnabled = autoItem._enabled;
                const endpoint = isEnabled ? 'disable' : 'enable';
                const resp = await fetchWithAuth(`/automations/${a.id}/${endpoint}`, { method: 'POST' });
                if (resp.ok) {
                    showToast(t('search.action.' + endpoint) + ': ' + a.name, 'success');
                    autoItem._enabled = !isEnabled;
                    autoItem.detail = !isEnabled ? t('search.action.disable') : t('search.action.enable');
                    _render();
                } else {
                    const err = await resp.json().catch(() => ({}));
                    const d = err.detail || err.message || '';
                    const ds = Array.isArray(d) ? d.map(x => x.msg || x).join('; ') : String(d);
                    showToast(ds || (t('search.action.' + endpoint) + ' failed'), 'error');
                }
            },
        };
        items.push(autoItem);
    });

    _mapEntities(capTempl, ct => items.push({
        name: ct.name, detail: ct.engine_type, group: 'capture_templates', icon: ICON_CAPTURE_TEMPLATE,
        nav: ['streams', 'raw_templates', 'raw-templates', 'data-template-id', ct.id],
    }));

    _mapEntities(ppTempl, pp => items.push({
        name: pp.name, detail: '', group: 'pp_templates', icon: ICON_PP_TEMPLATE,
        nav: ['streams', 'proc_templates', 'proc-templates', 'data-pp-template-id', pp.id],
    }));

    _mapEntities(patTempl, pt => items.push({
        name: pt.name, detail: '', group: 'pattern_templates', icon: ICON_PATTERN_TEMPLATE,
        nav: ['targets', 'kc-patterns', 'kc-patterns', 'data-pattern-template-id', pt.id],
    }));

    _mapEntities(audioSrc, a => {
        const section = a.source_type === 'mono' ? 'audio-mono' : a.source_type === 'band_extract' ? 'audio-band-extract' : 'audio-multi';
        const tab = a.source_type === 'mono' ? 'audio_mono' : a.source_type === 'band_extract' ? 'audio_band_extract' : 'audio_multi';
        items.push({
            name: a.name, detail: a.source_type, group: 'audio', icon: getAudioSourceIcon(a.source_type),
            nav: ['streams', tab, section, 'data-id', a.id],
        });
    });

    _mapEntities(valSrc, v => items.push({
        name: v.name, detail: v.source_type, group: 'value', icon: ICON_VALUE_SOURCE,
        nav: ['streams', 'value', 'value-sources', 'data-id', v.id],
    }));

    _mapEntities(streams, s => {
        const mapping = _streamSubTab[s.stream_type] || _streamSubTab.raw;
        items.push({
            name: s.name, detail: s.stream_type, group: 'streams', icon: getPictureSourceIcon(s.stream_type),
            nav: ['streams', mapping.sub, mapping.section, 'data-stream-id', s.id],
        });
    });

    _mapEntities(scenePresets, sp => {
        items.push({
            name: sp.name, detail: sp.description || '', group: 'scenes', icon: ICON_SCENE,
            nav: ['automations', null, 'scenes', 'data-scene-id', sp.id],
        });
        items.push({
            name: sp.name, detail: t('search.action.activate'), group: 'actions', icon: '⚡',
            action: async () => {
                const resp = await fetchWithAuth(`/scene-presets/${sp.id}/activate`, { method: 'POST' });
                if (resp.ok) { showToast(t('scenes.activated'), 'success'); }
                else { const err = await resp.json().catch(() => ({})); const d = err.detail || err.message || ''; const ds = Array.isArray(d) ? d.map(x => x.msg || x).join('; ') : String(d); showToast(ds || t('scenes.error.activate_failed'), 'error'); }
            },
        });
    });

    _mapEntities(csptTemplates, ct => items.push({
        name: ct.name, detail: '', group: 'cspt', icon: ICON_CSPT,
        nav: ['streams', 'css_processing', 'cspt-templates', 'data-cspt-id', ct.id],
    }));

    _mapEntities(syncClocks, sc => items.push({
        name: sc.name, detail: sc.is_running ? 'running' : '', group: 'sync_clocks', icon: ICON_CLOCK,
        nav: ['streams', 'sync', 'sync-clocks', 'data-id', sc.id],
    }));

    return items;
}

// Maps endpoint → response key that holds the array
const _responseKeys = [
    ['/devices',                 'devices'],
    ['/output-targets',         'targets'],
    ['/color-strip-sources',     'sources'],
    ['/automations',             'automations'],
    ['/capture-templates',       'templates'],
    ['/postprocessing-templates','templates'],
    ['/pattern-templates',       'templates'],
    ['/audio-sources',           'sources'],
    ['/value-sources',           'sources'],
    ['/picture-sources',         'streams'],
    ['/scene-presets',           'presets'],
    ['/color-strip-processing-templates', 'templates'],
    ['/sync-clocks',                      'clocks'],
];

async function _fetchAllEntities() {
    const [statesData, ...results] = await Promise.all([
        fetchWithAuth('/output-targets/batch/states', { retry: false, timeout: 5000 })
            .then(r => r.ok ? r.json() : {})
            .then((data: any) => data.states || {})
            .catch(() => ({})),
        ..._responseKeys.map(([ep, key]) =>
            fetchWithAuth(ep as string, { retry: false, timeout: 5000 })
                .then((r: any) => r.ok ? r.json() : {})
                .then((data: any) => data[key as string] || [])
                .catch((): any[] => [])),
    ]);
    return _buildItems(results, statesData);
}

// ─── Group ordering ───

const _groupOrder = [
    'actions',
    'devices', 'targets', 'kc_targets', 'css', 'cspt', 'automations',
    'streams', 'capture_templates', 'pp_templates', 'pattern_templates',
    'audio', 'value', 'scenes', 'sync_clocks',
];

const _groupRank = new Map(_groupOrder.map((g, i) => [g, i]));

// ─── Filtering ───

function _filterItems(query: string) {
    let result = _items;
    if (query) {
        const lower = query.toLowerCase();
        const terms = lower.split(/\s+/).filter(Boolean);
        result = _items.filter(item => {
            const text = `${item.name} ${item.detail} ${t('search.group.' + item.group)}`.toLowerCase();
            return terms.every(term => text.includes(term));
        });
    }
    // Sort by _groupOrder so indices match the grouped render order
    result.sort((a, b) => (_groupRank.get(a.group) ?? 99) - (_groupRank.get(b.group) ?? 99));
    return result;
}

// ─── Rendering ───

function _render() {
    const results = document.getElementById('cp-results');
    if (!results) return;

    if (_loading) {
        results.innerHTML = `<div class="cp-loading">${t('search.loading')}</div>`;
        return;
    }

    if (_filtered.length === 0) {
        results.innerHTML = `<div class="cp-empty">${t('search.no_results')}</div>`;
        return;
    }

    // Group items preserving order
    const grouped = new Map();
    for (const g of _groupOrder) grouped.set(g, []);
    for (const item of _filtered) {
        if (!grouped.has(item.group)) grouped.set(item.group, []);
        grouped.get(item.group).push(item);
    }

    let html = '';
    let idx = 0;
    for (const [group, items] of grouped) {
        if (items.length === 0) continue;
        html += `<div class="cp-group-header">${t('search.group.' + group)}</div>`;
        for (const item of items) {
            const active = idx === _selectedIdx ? ' cp-active' : '';
            const entityId = item.nav ? item.nav[4] : null;
            const color = entityId ? getCardColor(entityId) : null;
            const colorStyle = color ? ` style="border-left:3px solid ${color}"` : '';
            const actionClass = item.action ? ' cp-action-item' : '';
            html += `<div class="cp-result${active}${actionClass}" data-cp-idx="${idx}"${colorStyle}>` +
                `<span class="cp-icon">${item.icon}</span>` +
                `<span class="cp-name">${escapeHtml(item.name)}</span>` +
                (item.running ? '<span class="cp-running"></span>' : '') +
                (item.detail ? `<span class="cp-detail">${escapeHtml(item.detail)}</span>` : '') +
                `</div>`;
            idx++;
        }
    }
    results.innerHTML = html;
    _scrollActive(results);
}

function _scrollActive(container: HTMLElement) {
    const active = container.querySelector('.cp-active');
    if (active) active.scrollIntoView({ block: 'nearest' });
}


// ─── Open / Close ───

export async function openCommandPalette() {
    if (_isOpen) return;
    _isOpen = true;
    _selectedIdx = 0;

    const overlay = document.getElementById('command-palette')!;
    const input = document.getElementById('cp-input') as HTMLInputElement;
    overlay.style.display = '';
    document.documentElement.classList.add('modal-open');
    input.value = '';
    input.placeholder = t('search.placeholder');
    _loading = true;
    _render();
    input.focus();

    try {
        _items = await _fetchAllEntities();
    } catch {
        _items = [];
    }
    _loading = false;
    _filtered = _filterItems(input.value.trim());
    _render();
}

export function closeCommandPalette() {
    if (!_isOpen) return;
    _isOpen = false;
    if (_inputDebounceTimer !== null) {
        clearTimeout(_inputDebounceTimer);
        _inputDebounceTimer = null;
    }
    const overlay = document.getElementById('command-palette')!;
    overlay.style.display = 'none';
    document.documentElement.classList.remove('modal-open');
    _items = [];
    _filtered = [];
}

// ─── Event handlers ───

function _onInput() {
    if (_inputDebounceTimer !== null) clearTimeout(_inputDebounceTimer);
    _inputDebounceTimer = setTimeout(() => {
        _inputDebounceTimer = null;
        const input = document.getElementById('cp-input') as HTMLInputElement;
        _filtered = _filterItems(input.value.trim());
        _selectedIdx = 0;
        _render();
    }, _INPUT_DEBOUNCE_MS);
}

function _onKeydown(e: KeyboardEvent) {
    if (!_isOpen) return;
    if (e.key === 'ArrowDown') {
        e.preventDefault();
        _selectedIdx = Math.min(_selectedIdx + 1, _filtered.length - 1);
        _render();
    } else if (e.key === 'ArrowUp') {
        e.preventDefault();
        _selectedIdx = Math.max(_selectedIdx - 1, 0);
        _render();
    } else if (e.key === 'Enter') {
        e.preventDefault();
        _selectCurrent();
    } else if (e.key === 'Escape') {
        e.preventDefault();
        e.stopPropagation();
        closeCommandPalette();
    }
}

function _onClick(e: Event) {
    const row = (e.target as HTMLElement).closest('.cp-result') as HTMLElement | null;
    if (row) {
        _selectedIdx = parseInt(row.dataset.cpIdx ?? '0', 10);
        _selectCurrent();
        return;
    }
    if ((e.target as HTMLElement).classList.contains('cp-backdrop')) {
        closeCommandPalette();
    }
}

function _selectCurrent() {
    if (_selectedIdx < 0 || _selectedIdx >= _filtered.length) return;
    const item = _filtered[_selectedIdx];
    if (item.action) {
        item.action().catch(err => {
            if (!err.isAuth) showToast(err.message || 'Action failed', 'error');
        });
        return;
    }
    closeCommandPalette();
    // If graph tab is active, navigate to graph node instead of card
    const graphTabActive = document.querySelector('.tab-btn[data-tab="graph"].active');
    if (graphTabActive) {
        const entityId = item.nav[4]; // last element is always entity ID
        if (graphNavigateToNode(entityId)) return;
    }
    navigateToCard(...(item.nav as [string, string | null, string | null, string, string]));
}

// ─── Initialization ───

export function initCommandPalette() {
    const overlay = document.getElementById('command-palette');
    if (!overlay) return;

    const input = document.getElementById('cp-input')!;
    input.addEventListener('input', _onInput);
    input.addEventListener('keydown', _onKeydown as EventListener);
    overlay.addEventListener('click', _onClick);
}
