/**
 * Card color assignment — localStorage-backed color labels for any card.
 *
 * Usage in card creation functions:
 *   import { wrapCard } from '../core/card-colors.ts';
 *
 *   return wrapCard({
 *       dataAttr: 'data-device-id',
 *       id: device.id,
 *       removeOnclick: `removeDevice('${device.id}')`,
 *       removeTitle: t('common.delete'),
 *       content: `<div class="card-header">...</div>`,
 *       actions: `<button class="btn btn-icon btn-secondary" ...>Edit</button>`,
 *   });
 *
 * The helper wraps content in a standard card shell:
 *   - Outer div (.card / .template-card) with border-left color
 *   - .card-top-actions with remove button + optional top buttons
 *   - Bottom actions (.card-actions / .template-card-actions) with color picker
 */

import { createColorPicker, registerColorPicker } from './color-picker.ts';
import { ICON_TRASH } from './icons.ts';
import { renderModCardInner } from './mod-card.ts';
import type { ModCardOpts } from './mod-card.ts';

const STORAGE_KEY = 'cardColors';
const DEFAULT_SWATCH = '#808080';

/** Data attributes used as the entity-id key on card elements across the
 *  app. setCardColor() walks all of these so a single picker click updates
 *  every card representing the same entity (e.g. the targets-tab card AND
 *  its dashboard mirror), not just the one that owns the picker. */
const CARD_ID_ATTRS: readonly string[] = [
    'data-target-id', 'data-device-id', 'data-automation-id',
    'data-sync-clock-id', 'data-stream-id', 'data-template-id',
    'data-pattern-template-id', 'data-pp-template-id', 'data-cspt-id',
    'data-audio-template-id', 'data-audio-source-id', 'data-gi-id',
    'data-scene-id', 'data-id',
];

function _getAll(): Record<string, string> {
    try { return JSON.parse(localStorage.getItem(STORAGE_KEY) ?? '{}') || {}; }
    catch { return {}; }
}

export function getCardColor(id: string): string {
    return _getAll()[id] || '';
}

export function setCardColor(id: string, hex: string): void {
    const m = _getAll();
    if (hex) m[id] = hex; else delete m[id];
    localStorage.setItem(STORAGE_KEY, JSON.stringify(m));

    // Live-update every card representing this entity. The card stripe is
    // the ::before pseudo-element backed by --ch (see cards.css), so we
    // override --ch inline rather than setting border-left — that avoids
    // the double-stripe (custom border + primary --ch) the old approach
    // produced, and reaches dashboard mirrors that the picker callback's
    // .closest() lookup couldn't.
    const escaped = CSS.escape(id);
    const selector = CARD_ID_ATTRS.map(a => `[${a}="${escaped}"]`).join(',');
    document.querySelectorAll(selector).forEach(el => {
        const card = el as HTMLElement;
        if (hex) card.style.setProperty('--ch', hex);
        else card.style.removeProperty('--ch');

        // Mod-card variant: also sync the leading colour-dot inside
        // .mod-badge. The picker's `_cpPick` already inlined a
        // background:hex on the swatch — clear it so the dot reverts
        // to its channel-color default when the user resets.
        const dot = card.querySelector(`#cp-swatch-cc-${escaped}`) as HTMLElement | null;
        if (dot && dot.classList.contains('mod-badge__color')) {
            if (hex) {
                dot.dataset.custom = '1';
                dot.style.setProperty('--user-color', hex);
                // Clear any inline `background:hex` set by _cpPick so the
                // CSS [data-custom] rule wins (background = var(--user-color)).
                dot.style.removeProperty('background');
            } else {
                delete dot.dataset.custom;
                dot.style.removeProperty('--user-color');
                dot.style.removeProperty('background');
            }
        }
    });
}

/**
 * Returns the inline style fragment for a card's accent override.
 * Sets the --ch CSS variable so the existing ::before channel stripe
 * picks up the user's color. Empty string when no color is set.
 */
export function cardColorStyle(entityId: string): string {
    const c = getCardColor(entityId);
    return c ? `--ch: ${c}` : '';
}

/**
 * Returns color picker HTML + registers the pick callback.
 * @param {string} entityId  Unique entity ID
 * @param {string} cardAttr  Data attribute selector, e.g. 'data-device-id'
 */
export function cardColorButton(entityId: string, cardAttr: string): string {
    const color = getCardColor(entityId) || DEFAULT_SWATCH;
    const pickerId = `cc-${entityId}`;

    registerColorPicker(pickerId, (hex) => {
        // setCardColor handles the DOM update on every card representing
        // this entity (including dashboard mirrors). Nothing else to do.
        setCardColor(entityId, hex);
    });

    return createColorPicker({ id: pickerId, currentColor: color, onPick: undefined, anchor: 'left', showReset: true, resetColor: DEFAULT_SWATCH });
}

/**
 * Mod-card variant of `cardColorButton` — emits the leading
 * `.mod-badge__color` dot inside a `.mod-badge`. The dot IS the
 * picker swatch (same id as the legacy swatch so `_cpToggle()` finds
 * it). The popover is appended as a sibling of the badge so it can
 * detach to <body> with fixed positioning when needed (the existing
 * picker logic in color-picker.ts handles that automatically).
 *
 * Returns inline HTML to be injected as the first child of
 * `.mod-badge`. No surrounding span — the dot is the badge's leading
 * element directly.
 */
export function cardColorDot(entityId: string, cardAttr: string): string {
    const color = getCardColor(entityId);
    const pickerId = `cc-${entityId}`;

    registerColorPicker(pickerId, (hex) => {
        setCardColor(entityId, hex);
    });

    // The dot doubles as the picker swatch. Inline `--user-color` is
    // applied only when the user has picked a personal hue; absent it,
    // the dot inherits the channel colour from the parent badge via
    // the `.mod-badge__color` rule in cards.css.
    const customAttr = color ? ` data-custom="1" style="--user-color:${color}"` : '';
    const dataAttr = ` data-card-attr="${cardAttr}"`;

    return `<span class="color-picker-wrapper mod-badge__color-wrap" id="cp-wrap-${entityId}">` +
        `<span class="mod-badge__color color-picker-swatch" id="cp-swatch-${pickerId}" tabindex="0" role="button" aria-label="${getCardColorAriaLabel()}" onclick="event.stopPropagation(); window._cpToggle('${pickerId}')"${customAttr}${dataAttr}></span>` +
        _colorPopoverHtml(pickerId, color || DEFAULT_SWATCH) +
    `</span>`;
}

/** Build just the popover portion (the swatch is rendered separately
 *  by `cardColorDot`). Mirrors `createColorPicker`'s popover output. */
function _colorPopoverHtml(pickerId: string, currentColor: string): string {
    // Reuse createColorPicker's full output but strip the wrapping
    // .color-picker-wrapper and the legacy round swatch — we only need
    // the popover. Easier: render the full widget into a temporary
    // string and extract the popover. But that adds complexity. Since
    // the popover markup is stable, inline it here.
    const PRESETS = ['#4CAF50', '#7C4DFF', '#FF6D00', '#E91E63', '#00BCD4', '#FF5252', '#26A69A', '#2196F3', '#FFC107'];
    const dots = PRESETS.map(c => {
        const active = c.toLowerCase() === currentColor.toLowerCase() ? ' active' : '';
        return `<button class="color-picker-dot${active}" style="background:${c}" aria-label="${c}" onclick="event.stopPropagation(); window._cpPick('${pickerId}','${c}')"></button>`;
    }).join('');

    return `<div class="color-picker-popover anchor-left" id="cp-pop-${pickerId}" style="display:none" onclick="event.stopPropagation()">` +
        `<div class="color-picker-grid">${dots}</div>` +
        `<div class="color-picker-custom" onclick="this.querySelector('input').click()">` +
            `<input type="color" id="cp-native-${pickerId}" value="${currentColor}" ` +
                `oninput="event.stopPropagation(); window._cpPick('${pickerId}',this.value)" ` +
                `onchange="event.stopPropagation(); window._cpPick('${pickerId}',this.value)">` +
            `<span>Custom</span>` +
        `</div>` +
        `<div class="color-picker-reset" onclick="event.stopPropagation(); window._cpReset('${pickerId}','${DEFAULT_SWATCH}')"><span>Reset</span></div>` +
    `</div>`;
}

function getCardColorAriaLabel(): string {
    // i18n is not always loaded when this module evaluates (renders
    // happen during early page boot). Fall back to English.
    return window.__t?.('common.card_color') || 'Card color';
}

/**
 * Build a standard card shell with color support.
 *
 * Two rendering paths:
 *
 *   1. Legacy (content/actions) — emits `.card-top-actions` with the
 *      remove button and a bottom `.card-actions` row with action
 *      buttons + colour picker. Used by all cards that haven't been
 *      migrated to the modular system yet.
 *
 *   2. Modular (`mod`) — emits the dashboard's `.mod-head / .mod-body /
 *      .mod-foot` markup with a kebab overflow menu housing duplicate /
 *      hide / delete. Card-color picker is integrated into the
 *      `.mod-badge` as a leading dot. Use this for any new card and
 *      when migrating existing card builders.
 *
 * Old callers keep working unchanged. New callers pass `mod`.
 */
export function wrapCard(opts: WrapCardLegacyOpts | WrapCardModOpts): string {
    if ('mod' in opts && opts.mod) {
        return _renderModCard(opts as WrapCardModOpts);
    }
    return _renderLegacyCard(opts as WrapCardLegacyOpts);
}

export interface WrapCardLegacyOpts {
    type?: 'card' | 'template-card';
    dataAttr: string;
    id: string;
    classes?: string;
    topButtons?: string;
    removeOnclick: string;
    removeTitle: string;
    content: string;
    actions: string;
    mod?: undefined;
}

export interface WrapCardModOpts {
    type?: 'card' | 'template-card';
    dataAttr: string;
    id: string;
    classes?: string;
    mod: ModCardOpts;
}

function _renderLegacyCard({
    type = 'card',
    dataAttr,
    id,
    classes = '',
    topButtons = '',
    removeOnclick,
    removeTitle,
    content,
    actions,
}: WrapCardLegacyOpts): string {
    const actionsClass = type === 'template-card' ? 'template-card-actions' : 'card-actions';
    const colorStyle = cardColorStyle(id);
    return `
        <div class="${type}${classes ? ' ' + classes : ''}" ${dataAttr}="${id}"${colorStyle ? ` style="${colorStyle}" data-has-color="1"` : ''}>
            <div class="card-top-actions">
                ${topButtons}
                ${removeOnclick ? `<button class="card-remove-btn" onclick="${removeOnclick}" title="${removeTitle}">${ICON_TRASH}</button>` : ''}
            </div>
            ${content}
            <div class="${actionsClass}">
                ${actions}
                ${cardColorButton(id, dataAttr)}
            </div>
        </div>`;
}

function _renderModCard({
    type = 'card',
    dataAttr,
    id,
    classes = '',
    mod,
}: WrapCardModOpts): string {
    // mod-card.ts and card-colors.ts have a cyclic import (mod-card uses
    // cardColorDot, card-colors uses renderModCardInner). The cycle is
    // safe because both modules only call each other inside function
    // bodies — never during module initialization. ESM live bindings
    // resolve correctly by the time these functions actually run.
    const inner = renderModCardInner(_withColorBindings(mod, id, dataAttr));
    const colorStyle = cardColorStyle(id);
    const runningCls = mod.running ? ' card-running is-running' : '';
    const colorAttr = colorStyle ? ` style="${colorStyle}" data-has-color="1"` : '';
    return `
        <div class="${type} mod-card${classes ? ' ' + classes : ''}${runningCls}" ${dataAttr}="${id}"${colorAttr}>
            ${inner}
        </div>`;
}

/** Inject entityId/cardAttr into the head opts so the badge dot can
 *  register its picker without callers needing to pass the same id
 *  twice. */
function _withColorBindings(mod: ModCardOpts, id: string, dataAttr: string): ModCardOpts {
    return {
        ...mod,
        head: {
            ...mod.head,
            entityId: mod.head.entityId ?? id,
            cardAttr: mod.head.cardAttr ?? dataAttr,
        },
    };
}
