/**
 * Shared helpers for "light target" editors (HA Light + Z2M Light).
 *
 * Both editors expose the same five tunable knobs (brightness, update_rate,
 * transition, color_tolerance, min_brightness_threshold), the same unified
 * Color Source picker (CSS sources + colour-returning value sources), and
 * the same IconSelect-driven stop_action picker. This module hosts the
 * boilerplate for those three concerns so the per-protocol editor files
 * stay focused on what genuinely differs (URLs, mapping row chrome,
 * binding-target picker, card chips).
 */

import { BindableScalarWidget } from './bindable-scalar.ts';
import { IconSelect, type IconSelectItem } from './icon-select.ts';
import { getColorStripIcon, getValueSourceIcon } from './icons.ts';

// ──────────────────────────────────────────────────────────────────────
// Bindable scalar widget bundle
// ──────────────────────────────────────────────────────────────────────

interface WidgetConfig {
    min: number;
    max: number;
    step: number;
    default: number;
    format: (v: number) => string;
}

const DEFAULT_WIDGET_CONFIG: Record<WidgetKey, WidgetConfig> = {
    brightness:             { min: 0,   max: 1,   step: 0.05, default: 1.0, format: (v) => v.toFixed(2) },
    updateRate:             { min: 0.5, max: 5.0, step: 0.1,  default: 2.0, format: (v) => v.toFixed(1) },
    transition:             { min: 0.0, max: 10,  step: 0.1,  default: 0.5, format: (v) => v.toFixed(1) },
    colorTolerance:         { min: 0,   max: 50,  step: 1,    default: 5,   format: (v) => String(Math.round(v)) },
    minBrightnessThreshold: { min: 0,   max: 254, step: 1,    default: 0,   format: (v) => String(Math.round(v)) },
};

export type WidgetKey =
    | 'brightness'
    | 'updateRate'
    | 'transition'
    | 'colorTolerance'
    | 'minBrightnessThreshold';

/** Maps a WidgetKey to its `id`/`container` slug. */
const WIDGET_SLUG: Record<WidgetKey, string> = {
    brightness: 'brightness',
    updateRate: 'update-rate',
    transition: 'transition',
    colorTolerance: 'color-tolerance',
    minBrightnessThreshold: 'min-brightness-threshold',
};

export interface LightTargetWidgetsConfig {
    /** Prefix for both the BindableScalarWidget `idPrefix` and the container
     *  element id (e.g. `'ha-light-editor'` → container `'ha-light-editor-brightness-container'`). */
    idPrefix: string;
    /** Value-source list provider for the binding mode of every widget. */
    valueSources: () => Array<{ id: string; name: string; source_type: string }>;
    /** Per-widget overrides (e.g. wider update_rate range for Z2M). Each
     *  field is `Partial<WidgetConfig>` and merges over the defaults. */
    overrides?: Partial<Record<WidgetKey, Partial<WidgetConfig>>>;
}

/**
 * Lazy bundle of the five BindableScalarWidgets every light-target editor
 * uses. Widgets are created on first `ensure(key)` and cached; `destroyAll()`
 * tears them down and resets the cache so the editor can be re-opened cleanly.
 */
export class LightTargetWidgets {
    private _widgets: Partial<Record<WidgetKey, BindableScalarWidget>> = {};

    constructor(private opts: LightTargetWidgetsConfig) {}

    /** Lazy-create (or return cached) widget for the given key. */
    ensure(key: WidgetKey): BindableScalarWidget {
        let w = this._widgets[key];
        if (w) return w;

        const cfg = { ...DEFAULT_WIDGET_CONFIG[key], ...(this.opts.overrides?.[key] || {}) };
        const slug = WIDGET_SLUG[key];
        const container = document.getElementById(`${this.opts.idPrefix}-${slug}-container`);
        if (!container) {
            throw new Error(`LightTargetWidgets: container not found for key '${key}' (expected #${this.opts.idPrefix}-${slug}-container)`);
        }
        w = new BindableScalarWidget({
            container,
            idPrefix: `${this.opts.idPrefix}-${slug}`,
            min: cfg.min,
            max: cfg.max,
            step: cfg.step,
            default: cfg.default,
            format: cfg.format,
            valueSources: this.opts.valueSources,
        });
        this._widgets[key] = w;
        return w;
    }

    /** Apply target-response values to all five widgets, lazy-creating each.
     *  Missing fields fall back to the per-key default. */
    applyValues(data: {
        brightness?: any;
        update_rate?: any;
        transition?: any;
        color_tolerance?: any;
        min_brightness_threshold?: any;
    }): void {
        this.ensure('brightness').setValue(data.brightness ?? this._defaultFor('brightness'));
        this.ensure('updateRate').setValue(data.update_rate ?? this._defaultFor('updateRate'));
        this.ensure('transition').setValue(data.transition ?? this._defaultFor('transition'));
        this.ensure('colorTolerance').setValue(data.color_tolerance ?? this._defaultFor('colorTolerance'));
        this.ensure('minBrightnessThreshold').setValue(data.min_brightness_threshold ?? this._defaultFor('minBrightnessThreshold'));
    }

    /** Reset all widgets to their (override-aware) default values. */
    applyDefaults(): void {
        this.ensure('brightness').setValue(this._defaultFor('brightness'));
        this.ensure('updateRate').setValue(this._defaultFor('updateRate'));
        this.ensure('transition').setValue(this._defaultFor('transition'));
        this.ensure('colorTolerance').setValue(this._defaultFor('colorTolerance'));
        this.ensure('minBrightnessThreshold').setValue(this._defaultFor('minBrightnessThreshold'));
    }

    /** Read the current value (BindableFloat-shaped) for a widget. Auto-creates if not yet ensured. */
    getValue(key: WidgetKey) {
        return this.ensure(key).getValue();
    }

    destroyAll(): void {
        for (const key of Object.keys(this._widgets) as WidgetKey[]) {
            this._widgets[key]?.destroy();
        }
        this._widgets = {};
    }

    private _defaultFor(key: WidgetKey): number {
        return this.opts.overrides?.[key]?.default ?? DEFAULT_WIDGET_CONFIG[key].default;
    }
}

// ──────────────────────────────────────────────────────────────────────
// Unified Color Source picker (CSS + colour value sources)
// ──────────────────────────────────────────────────────────────────────

export interface ColorSourcePickerLabels {
    /** Header for the CSS sources section (e.g. "Color strip"). */
    cssHeader: string;
    /** Header for the colour value sources section (e.g. "Color value source"). */
    colorVsHeader: string;
}

/**
 * Build the dual-section item list for the unified Color Source picker.
 * Items use the `css:<id>` / `cvs:<id>` value encoding that both editors
 * decode in their `onChange` handlers.
 */
export function buildColorSourcePickerItems(
    cssSources: Array<{ id: string; name: string; source_type: string }>,
    colorValueSources: Array<{ id: string; name: string; source_type: string }>,
    labels: ColorSourcePickerLabels,
): Array<{ value: string; label: string; icon: string; desc?: string; header?: boolean }> {
    const items: Array<{ value: string; label: string; icon: string; desc?: string; header?: boolean }> = [];

    if (cssSources.length > 0) {
        items.push({ value: '', label: labels.cssHeader, icon: '', header: true });
        for (const s of cssSources) {
            items.push({
                value: `css:${s.id}`,
                label: s.name,
                icon: getColorStripIcon(s.source_type),
                desc: s.source_type,
            });
        }
    }

    if (colorValueSources.length > 0) {
        items.push({ value: '', label: labels.colorVsHeader, icon: '', header: true });
        for (const s of colorValueSources) {
            items.push({
                value: `cvs:${s.id}`,
                label: s.name,
                icon: getValueSourceIcon(s.source_type),
                desc: s.source_type,
            });
        }
    }

    return items;
}

/** Decode a unified picker value (`css:abc` / `cvs:abc` / `''`) into kind+id. */
export function decodeColorSourceValue(raw: string): { kind: 'css' | 'color_vs'; id: string } {
    const kind: 'css' | 'color_vs' = raw.startsWith('cvs:') ? 'color_vs' : 'css';
    const id = raw.includes(':') ? raw.slice(raw.indexOf(':') + 1) : '';
    return { kind, id };
}

/** Encode a kind+id pair into the unified picker's option-value string. */
export function encodeColorSourceValue(kind: 'css' | 'color_vs', id: string): string {
    if (!id) return '';
    return `${kind === 'color_vs' ? 'cvs' : 'css'}:${id}`;
}

// ──────────────────────────────────────────────────────────────────────
// Stop-action IconSelect
// ──────────────────────────────────────────────────────────────────────

/**
 * Wire (or refresh) an IconSelect for a stop-action `<select>`.
 *
 * Pass the existing `IconSelect | null` ref; the helper either calls
 * `updateItems()` on it or constructs a new one. The (new or existing)
 * instance is returned so the caller can keep the singleton ref alive.
 */
export function ensureStopActionIconSelect(
    selectId: string,
    items: IconSelectItem[],
    columns: number,
    existing: IconSelect | null,
): IconSelect | null {
    const sel = document.getElementById(selectId) as HTMLSelectElement | null;
    if (!sel) return existing;
    if (existing) {
        existing.updateItems(items);
        return existing;
    }
    return new IconSelect({ target: sel, items, columns });
}
