/**
 * Command-palette style name picker — reusable UI for browsing a list of
 * names fetched from any API endpoint.  Mirrors the EntityPalette pattern.
 *
 * Two concrete pickers are exported:
 *
 *   - **ProcessPalette** — picks from running OS processes (`/system/processes`)
 *   - **NotificationAppPalette** — picks from OS notification history apps
 *
 * Both support single-select (returns one value) and multi-select (appends to
 * a textarea).
 *
 * Usage:
 *
 *   // Single-select
 *   const name = await ProcessPalette.pick({ current: '…', placeholder: '…' });
 *
 *   // Multi-select (appends to textarea, stays open)
 *   await ProcessPalette.pickMulti({ textarea: el, placeholder: '…' });
 *
 *   // Inline trigger (drop-in for old API)
 *   attachProcessPicker(container, textarea);
 */

import { fetchWithAuth, escapeHtml } from './api.ts';
import { t } from './i18n.ts';
import { ICON_SEARCH } from './icons.ts';

/* ─── types ────────────────────────────────────────────────── */

interface PaletteItem {
    name: string;
    added: boolean;
}

interface PickOpts {
    current?: string;
    placeholder?: string;
}

interface PickMultiOpts {
    textarea: HTMLTextAreaElement;
    placeholder?: string;
}

type FetchItemsFn = () => Promise<string[]>;

/* ─── generic NamePalette (shared logic) ───────────────────── */

class NamePalette {
    private _overlay: HTMLDivElement;
    private _input: HTMLInputElement;
    private _list: HTMLDivElement;
    private _fetchItems: FetchItemsFn;

    private _resolveSingle: ((v: string | undefined) => void) | null = null;
    private _multiTextarea: HTMLTextAreaElement | null = null;

    private _items: string[] = [];
    private _existing: Set<string> = new Set();
    private _filtered: PaletteItem[] = [];
    private _highlightIdx = 0;
    private _currentValue: string | undefined;
    private _isMulti = false;

    constructor(fetchItems: FetchItemsFn) {
        this._fetchItems = fetchItems;

        this._overlay = document.createElement('div');
        this._overlay.className = 'entity-palette-overlay process-palette-overlay';
        this._overlay.innerHTML = `
            <div class="entity-palette process-palette">
                <div class="entity-palette-search-row">
                    ${ICON_SEARCH}
                    <input type="text" class="entity-palette-input" autocomplete="off" spellcheck="false">
                </div>
                <div class="entity-palette-list"></div>
            </div>
        `;
        document.body.appendChild(this._overlay);

        this._input = this._overlay.querySelector('.entity-palette-input') as HTMLInputElement;
        this._list = this._overlay.querySelector('.entity-palette-list') as HTMLDivElement;

        this._overlay.addEventListener('click', (e) => {
            if (e.target === this._overlay) this._close();
        });
        this._input.addEventListener('input', () => this._filter());
        this._input.addEventListener('keydown', (e) => this._onKey(e));
    }

    /* ── open helpers ──────────────────────────────────────── */

    pickSingle(opts: PickOpts): Promise<string | undefined> {
        return new Promise(resolve => {
            this._isMulti = false;
            this._multiTextarea = null;
            this._resolveSingle = resolve;
            this._currentValue = opts.current;
            this._open(opts.placeholder);
        });
    }

    pickMulti(opts: PickMultiOpts): Promise<void> {
        return new Promise(resolve => {
            this._isMulti = true;
            this._multiTextarea = opts.textarea;
            this._resolveSingle = resolve as any;
            this._existing = new Set(
                opts.textarea.value.split('\n').map(s => s.trim().toLowerCase()).filter(Boolean),
            );
            this._currentValue = undefined;
            this._open(opts.placeholder);
        });
    }

    private async _open(placeholder?: string) {
        this._input.placeholder = placeholder || '';
        this._input.value = '';
        this._list.innerHTML = `<div class="entity-palette-empty">${t('common.loading')}</div>`;
        this._overlay.classList.add('open');
        requestAnimationFrame(() => this._input.focus());

        try {
            this._items = await this._fetchItems();
        } catch {
            this._items = [];
        }

        if (this._isMulti) {
            this._existing = new Set(
                this._multiTextarea!.value.split('\n').map(s => s.trim().toLowerCase()).filter(Boolean),
            );
        }

        this._filter();
    }

    /* ── filtering & rendering ─────────────────────────────── */

    private _filter() {
        const q = this._input.value.toLowerCase().trim();
        this._filtered = this._items
            .filter(p => !q || p.toLowerCase().includes(q))
            .map(p => ({
                name: p,
                added: this._existing.has(p.toLowerCase()),
            }));

        this._highlightIdx = this._filtered.findIndex(
            i => i.name.toLowerCase() === (this._currentValue || '').toLowerCase(),
        );
        if (this._highlightIdx === -1) this._highlightIdx = 0;
        this._render();
    }

    private _render() {
        if (this._filtered.length === 0) {
            this._list.innerHTML = `<div class="entity-palette-empty">${
                this._items.length === 0
                    ? t('automations.condition.application.no_processes')
                    : '—'
            }</div>`;
            return;
        }

        this._list.innerHTML = this._filtered.map((item, i) => {
            const cls = [
                'entity-palette-item',
                i === this._highlightIdx ? 'ep-highlight' : '',
                item.added ? 'ep-current' : '',
                item.name.toLowerCase() === (this._currentValue || '').toLowerCase() ? 'ep-current' : '',
            ].filter(Boolean).join(' ');

            return `<div class="${cls}" data-idx="${i}">
                <span class="ep-item-label">${escapeHtml(item.name)}</span>
                ${item.added ? '<span class="ep-item-desc">\u2713</span>' : ''}
            </div>`;
        }).join('');

        this._list.querySelectorAll('.entity-palette-item').forEach(el => {
            el.addEventListener('click', () => {
                const idx = parseInt((el as HTMLElement).dataset.idx!);
                this._selectItem(this._filtered[idx]);
            });
        });

        const hl = this._list.querySelector('.ep-highlight');
        if (hl) hl.scrollIntoView({ block: 'nearest' });
    }

    /* ── selection ──────────────────────────────────────────── */

    private _selectItem(item: PaletteItem) {
        if (this._isMulti) {
            if (!item.added) {
                const ta = this._multiTextarea!;
                const cur = ta.value.trim();
                ta.value = cur ? cur + '\n' + item.name : item.name;
                this._existing.add(item.name.toLowerCase());
                item.added = true;
                this._render();
            }
        } else {
            this._overlay.classList.remove('open');
            if (this._resolveSingle) this._resolveSingle(item.name);
            this._resolveSingle = null;
        }
    }

    private _close() {
        this._overlay.classList.remove('open');
        if (this._resolveSingle) this._resolveSingle(this._isMulti ? undefined as any : undefined);
        this._resolveSingle = null;
    }

    /* ── keyboard ──────────────────────────────────────────── */

    private _onKey(e: KeyboardEvent) {
        if (e.key === 'ArrowDown') {
            e.preventDefault();
            this._highlightIdx = Math.min(this._highlightIdx + 1, this._filtered.length - 1);
            this._render();
        } else if (e.key === 'ArrowUp') {
            e.preventDefault();
            this._highlightIdx = Math.max(this._highlightIdx - 1, 0);
            this._render();
        } else if (e.key === 'Enter') {
            e.preventDefault();
            if (this._filtered[this._highlightIdx]) {
                this._selectItem(this._filtered[this._highlightIdx]);
            }
        } else if (e.key === 'Escape') {
            this._close();
        } else if (e.key === 'Tab') {
            e.preventDefault();
        }
    }
}

/* ─── fetch helpers ────────────────────────────────────────── */

async function _fetchProcesses(): Promise<string[]> {
    const resp = await fetchWithAuth('/system/processes');
    if (!resp || !resp.ok) return [];
    const data = await resp.json();
    return data.processes || [];
}

async function _fetchNotificationApps(): Promise<string[]> {
    const resp = await fetchWithAuth('/color-strip-sources/os-notifications/history');
    if (!resp || !resp.ok) return [];
    const data = await resp.json();
    const history: any[] = data.history || [];
    // Deduplicate app names, preserving original case of first occurrence
    const seen = new Map<string, string>();
    for (const entry of history) {
        const app = entry.app;
        if (app && !seen.has(app.toLowerCase())) {
            seen.set(app.toLowerCase(), app);
        }
    }
    return Array.from(seen.values()).sort((a, b) => a.localeCompare(b));
}

/* ─── ProcessPalette (running processes) ───────────────────── */

let _processInst: NamePalette | null = null;

export class ProcessPalette {
    static pick(opts: PickOpts): Promise<string | undefined> {
        if (!_processInst) _processInst = new NamePalette(_fetchProcesses);
        return _processInst.pickSingle(opts);
    }

    static pickMulti(opts: PickMultiOpts): Promise<void> {
        if (!_processInst) _processInst = new NamePalette(_fetchProcesses);
        return _processInst.pickMulti(opts);
    }
}

/* ─── NotificationAppPalette (OS notification history) ─────── */

let _notifInst: NamePalette | null = null;

export class NotificationAppPalette {
    static pick(opts: PickOpts): Promise<string | undefined> {
        if (!_notifInst) _notifInst = new NamePalette(_fetchNotificationApps);
        return _notifInst.pickSingle(opts);
    }

    static pickMulti(opts: PickMultiOpts): Promise<void> {
        if (!_notifInst) _notifInst = new NamePalette(_fetchNotificationApps);
        return _notifInst.pickMulti(opts);
    }
}

/* ─── drop-in replacement for the old attachProcessPicker ─── */

/**
 * Wire up a `.btn-browse-apps` button inside `containerEl` to open the
 * multi-select process palette feeding into `textareaEl`.
 */
export function attachProcessPicker(containerEl: HTMLElement, textareaEl: HTMLTextAreaElement): void {
    const browseBtn = containerEl.querySelector('.btn-browse-apps');
    if (!browseBtn) return;

    browseBtn.addEventListener('click', () => {
        ProcessPalette.pickMulti({
            textarea: textareaEl,
            placeholder: t('automations.condition.application.search') || 'Search processes…',
        });
    });
}

/**
 * Wire up a `.btn-browse-apps` button to open the notification app palette
 * (multi-select, feeding into a textarea).
 */
export function attachNotificationAppPicker(containerEl: HTMLElement, textareaEl: HTMLTextAreaElement): void {
    const browseBtn = containerEl.querySelector('.btn-browse-apps');
    if (!browseBtn) return;

    browseBtn.addEventListener('click', () => {
        NotificationAppPalette.pickMulti({
            textarea: textareaEl,
            placeholder: t('color_strip.notification.search_apps') || 'Search notification apps…',
        });
    });
}
