/**
 * Command-palette style entity selector.
 *
 * Usage:
 *   import { EntityPalette, EntitySelect } from '../core/entity-palette.ts';
 *
 *   // Direct use (promise-based):
 *   const value = await EntityPalette.pick({
 *       items: [{ value: 'abc', label: 'My Device', icon: '<svg>…</svg>', desc: '192.168.1.1' }],
 *       current: 'abc',
 *       placeholder: 'Search devices...',
 *   });
 *   // value = 'abc' (selected) or undefined (cancelled)
 *
 *   // Wrapper (replaces a <select> visually):
 *   const sel = new EntitySelect({
 *       target: document.getElementById('my-select'),
 *       getItems: () => devices.map(d => ({ value: d.id, label: d.name, icon: '…' })),
 *       placeholder: 'Search devices...',
 *       onChange: (value) => { … },
 *   });
 *
 * The original <select> is hidden but stays in the DOM (value kept in sync).
 * Call sel.refresh() after repopulating the <select>, sel.destroy() to remove.
 */

import { ICON_SEARCH } from './icons.ts';
import type { IconSelectItem } from './icon-select.ts';

// ── EntityPalette (singleton modal) ─────────────────────────

export interface EntitySelectOpts {
    target: HTMLSelectElement;
    getItems: () => IconSelectItem[];
    placeholder?: string;
    onChange?: (value: string) => void;
    allowNone?: boolean;
    noneLabel?: string;
}

interface EntityPalettePickOpts {
    items?: IconSelectItem[];
    current?: string;
    placeholder?: string;
    allowNone?: boolean;
    noneLabel?: string;
}

let _instance: EntityPalette | null = null;

export class EntityPalette {
    _overlay: HTMLDivElement;
    _input: HTMLInputElement;
    _list: HTMLDivElement;
    _resolve: ((value: string | undefined) => void) | null;
    _items: IconSelectItem[];
    _filtered: (IconSelectItem & { _isNone?: boolean })[];
    _highlightIdx: number;
    _currentValue: string | undefined;
    _allowNone: boolean;
    _noneLabel: string;

    /** Open the palette and return a promise. Resolves to selected value or undefined if cancelled. */
    static pick(opts: EntityPalettePickOpts) {
        if (!_instance) _instance = new EntityPalette();
        return _instance._pick(opts);
    }

    constructor() {
        this._overlay = document.createElement('div');
        this._overlay.className = 'entity-palette-overlay';
        this._overlay.innerHTML = `
            <div class="entity-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._resolve = null;
        this._items = [];
        this._filtered = [];
        this._highlightIdx = 0;
        this._currentValue = undefined;
        this._allowNone = false;
        this._noneLabel = '';

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

    _pick({ items, current, placeholder, allowNone, noneLabel }: EntityPalettePickOpts) {
        return new Promise(resolve => {
            this._resolve = resolve;
            this._items = items || [];
            this._currentValue = current;
            this._allowNone = allowNone ?? false;
            this._noneLabel = noneLabel ?? '';

            this._input.placeholder = placeholder || '';
            this._input.value = '';

            this._filter();
            this._overlay.classList.add('open');
            // Focus after paint so the overlay is visible
            requestAnimationFrame(() => this._input.focus());
        });
    }

    _buildFullList(): (IconSelectItem & { _isNone?: boolean })[] {
        const all: (IconSelectItem & { _isNone?: boolean })[] = [];
        if (this._allowNone) {
            all.push({ value: '', label: this._noneLabel || '—', icon: '', desc: '', _isNone: true });
        }
        all.push(...this._items);
        return all;
    }

    _filter() {
        const query = this._input.value.toLowerCase().trim();
        const all = this._buildFullList();
        this._filtered = query
            ? all.filter(i => i.label.toLowerCase().includes(query) || (i.desc && i.desc.toLowerCase().includes(query)))
            : all;

        // Highlight current value, or first item
        this._highlightIdx = this._filtered.findIndex(i => i.value === this._currentValue);
        if (this._highlightIdx === -1) this._highlightIdx = 0;
        this._render();
    }

    _render() {
        if (this._filtered.length === 0) {
            this._list.innerHTML = '<div class="entity-palette-empty">—</div>';
            return;
        }

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

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

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

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

    _onKeyDown(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._select(this._filtered[this._highlightIdx]);
            }
        } else if (e.key === 'Escape') {
            this._cancel();
        } else if (e.key === 'Tab') {
            e.preventDefault();
        }
    }

    _select(item: any) {
        this._overlay.classList.remove('open');
        if (this._resolve) this._resolve(item.value);
        this._resolve = null;
    }

    _cancel() {
        this._overlay.classList.remove('open');
        if (this._resolve) this._resolve(undefined);
        this._resolve = null;
    }
}

// ── EntitySelect (wrapper around a <select>) ────────────────

export class EntitySelect {
    _select: HTMLSelectElement;
    _getItems: () => IconSelectItem[];
    _placeholder: string;
    _onChange: ((value: string) => void) | null;
    _allowNone: boolean;
    _noneLabel: string;
    _items: IconSelectItem[];
    _trigger: HTMLButtonElement;

    constructor({ target, getItems, placeholder, onChange, allowNone, noneLabel }: EntitySelectOpts) {
        this._select = target;
        this._getItems = getItems;
        this._placeholder = placeholder || '';
        this._onChange = onChange ?? null;
        this._allowNone = allowNone || false;
        this._noneLabel = noneLabel || '—';
        this._items = getItems();

        // Hide native select
        this._select.style.display = 'none';

        // Build trigger button
        this._trigger = document.createElement('button');
        this._trigger.type = 'button';
        this._trigger.className = 'entity-select-trigger';
        this._trigger.addEventListener('click', () => this._open());
        this._select.parentNode!.insertBefore(this._trigger, this._select.nextSibling);

        this._syncTrigger();
    }

    async _open() {
        this._items = this._getItems();
        const value = await EntityPalette.pick({
            items: this._items,
            current: this._select.value,
            placeholder: this._placeholder,
            allowNone: this._allowNone,
            noneLabel: this._noneLabel,
        }) as string | undefined;
        if (value !== undefined) {
            this._select.value = value;
            this._syncTrigger();
            this._select.dispatchEvent(new Event('change', { bubbles: true }));
            if (this._onChange) this._onChange(value);
        }
    }

    _syncTrigger() {
        const val = this._select.value;
        const item = this._items.find(i => i.value === val);
        if (item) {
            this._trigger.innerHTML =
                `${item.icon ? `<span class="es-trigger-icon">${item.icon}</span>` : ''}` +
                `<span class="es-trigger-label">${item.label}</span>` +
                `<span class="es-trigger-arrow">&#x25BE;</span>`;
        } else if (this._allowNone && !val) {
            this._trigger.innerHTML =
                `<span class="es-trigger-label es-trigger-none">${this._noneLabel}</span>` +
                `<span class="es-trigger-arrow">&#x25BE;</span>`;
        } else {
            // Fallback: read from selected option text
            const opt = this._select.selectedOptions[0];
            const text = opt ? opt.textContent : val || '—';
            this._trigger.innerHTML =
                `<span class="es-trigger-label">${text}</span>` +
                `<span class="es-trigger-arrow">&#x25BE;</span>`;
        }
    }

    /** Update the value programmatically (no change event). */
    setValue(value: string) {
        this._select.value = value;
        this._syncTrigger();
    }

    /** Refresh items and trigger display (call after repopulating the <select>). */
    refresh() {
        this._items = this._getItems();
        this._syncTrigger();
    }

    /** Remove enhancement, restore native <select>. */
    destroy() {
        this._trigger.remove();
        this._select.style.display = '';
    }
}
