/**
 * Mod-card renderers — structured helpers that emit the dashboard's
 * `.mod-*` markup vocabulary on entity cards.
 *
 * Background: dashboard.css already ships the `.mod-head / .mod-id /
 * .mod-badge / .mod-leds / .mod-metrics / .mod-foot / .mod-patch /
 * .mod-btn` system. This module makes that same vocabulary callable
 * from any feature's `create*Card()` builder, so device, target,
 * automation, source, etc. cards can converge on the same visual
 * language used by the Dashboard tab.
 *
 * Used by the new `wrapCard({ mod })` path in `card-colors.ts`.
 */

import { t } from './i18n.ts';
import { escapeHtml } from './api.ts';
import { ICON_TRASH, ICON_CLONE, ICON_EYE_OFF, ICON_EYE, ICON_KEBAB, ICON_PLUS } from './icons.ts';
import { cardColorDot } from './card-colors.ts';

export type LedState = 'on' | 'off' | 'blink' | 'fault';

export interface ModBadgeOpts {
    /** Mono-caps type label, e.g. "LED · CH-01" or "FFT · IN" */
    text: string;
    /** Emit a leading colour-picker dot inside the badge (default true).
     *  Set false for cards that don't carry a personal accent. */
    colorDot?: boolean;
}

export interface ModMetricOpts {
    /** Caps label */
    k: string;
    /** Display-font value (HTML allowed for <small> / <span> etc.) */
    v: string;
    /** Add 'signal' modifier so the value tints to --ch */
    accent?: boolean;
    /** Mark the cell as having errors (coral tint) */
    error?: boolean;
    /** Tooltip on the cell */
    title?: string;
    /** Inline icon next to the label */
    icon?: string;
    /** Optional id on the .v element for live updates */
    valueId?: string;
    /** Visual variant for the cell — 'text-stack' renders the value in
     *  mono font with optional `<span class="v-sub">` block for a
     *  secondary line (e.g. LED chip + RGB/RGBW). The default display
     *  font is too coarse for string identifiers. */
    variant?: 'text-stack';
    /** Extra raw HTML appended after the .v element inside the cell —
     *  used to embed a sparkline canvas (`.mod-metric-spark-canvas`)
     *  into the FPS metric, mirroring the dashboard pattern. */
    extra?: string;
    /** Extra data attributes on the .v element (e.g. data-fps-text for
     *  cached lookup during live updates). */
    valueDataAttrs?: Record<string, string>;
}

export interface ModChipOpts {
    /** Visible text */
    text: string;
    /** Inline icon HTML before the text */
    icon?: string;
    /** Click handler — triggers the link variant if set */
    onclick?: string;
    /** "tag" (filled), "err" (coral), or "default" */
    variant?: 'default' | 'tag' | 'err';
    /** Tooltip */
    title?: string;
}

export interface ModFaderOpts {
    /** Mono-caps label, e.g. "Bright" */
    label: string;
    /** Current value (0..max). Displayed as raw integer. */
    value: number;
    /** Maximum value (e.g. 255). Defaults to 100. */
    max?: number;
    /** Optional id on the slider for binding */
    sliderId?: string;
    /** oninput handler — receives `this.value` */
    oninput?: string;
    /** onchange handler — receives `this.value` */
    onchange?: string;
    /** Optional data attributes on the slider */
    dataAttrs?: Record<string, string>;
    /** Disable the fader */
    disabled?: boolean;
}

export interface ModFootOpts {
    /** Patch indicator state — controls the dot style */
    patchState?: 'live' | 'standby' | 'offline' | 'idle';
    /** Patch label — short caps text, e.g. "PATCHED · OUT-1" */
    patchLabel?: string;
    /** Primary action button (left of any secondary actions) */
    primaryAction?: ModBtnOpts;
    /** Secondary text-button(s) */
    secondaryActions?: ModBtnOpts[];
    /** Tertiary icon-only button(s) — settings, edit, etc. */
    iconActions?: ModBtnOpts[];
}

export interface ModBtnOpts {
    /** Visible label (omitted for icon-only variant) */
    label?: string;
    /** Icon HTML */
    icon?: string;
    /** onclick handler */
    onclick: string;
    /** Tooltip */
    title?: string;
    /** Variant */
    variant?: 'go' | 'stop' | 'default';
    /** Make this an icon-only button (.mod-btn-icon) */
    iconOnly?: boolean;
    /** i18n keys for runtime translation */
    i18nTitle?: string;
    /** Extra data attributes (e.g. for live-update binding) */
    dataAttrs?: Record<string, string>;
}

export interface ModMenuItemOpts {
    /** Visible text */
    label: string;
    /** Icon HTML */
    icon?: string;
    /** Inline onclick. The menu auto-closes on click. Optional when
     *  ``dataAttrs`` is provided and the caller binds via delegation. */
    onclick?: string;
    /** Extra data attributes (e.g. ``{ 'data-icon-picker-trigger': id }``)
     *  used by document-level event delegation in lieu of an inline
     *  onclick string. */
    dataAttrs?: Record<string, string>;
    /** Mark as destructive (coral colour, separator above) */
    danger?: boolean;
}

export interface ModMenuOpts {
    /** Append a Duplicate item (default true if duplicateOnclick provided) */
    duplicateOnclick?: string;
    /** Append a Hide-card item (default true). The handler should toggle
     *  the entity's hidden state via the existing CardSection helpers. */
    hideOnclick?: string;
    /** Append a destructive Delete item (with separator above). When
     *  omitted, no Delete option appears (e.g. built-in entities). */
    deleteOnclick?: string;
    /** Custom menu items inserted before the standard set */
    extraItems?: ModMenuItemOpts[];
}

export interface ModHeadOpts {
    badge: ModBadgeOpts;
    /** Card title — escaped before rendering */
    name: string;
    /** Optional secondary line under the title — plain text, escaped.
     *  Use `metaHtml` instead when you need a clickable link or other
     *  inline markup. Mutually exclusive with `metaHtml`. */
    meta?: string;
    /** Raw HTML version of `meta`. Caller is responsible for escaping
     *  any user-controlled substrings before passing in. */
    metaHtml?: string;
    /** 0–3 LEDs in the recessed bezel. Hidden when empty. */
    leds?: LedState[];
    /** Health dot to inject inline beside the name (raw HTML) */
    healthDot?: string;
    /** Overflow menu options. Pass null to suppress the menu entirely
     *  (e.g. for read-only or system cards). */
    menu?: ModMenuOpts | null;
    /** Entity id — required when colorDot is true so the picker can
     *  register against it. */
    entityId?: string;
    /** Data-attr name (e.g. 'data-device-id') used by the picker to
     *  propagate the chosen colour to every card representing this
     *  entity. */
    cardAttr?: string;
    /** Optional custom-icon plate placed before .mod-id at the leading
     *  edge of the head row. Channel-tinted by default; set ``iconColor``
     *  to override with a hex string. When ``iconHtml`` is empty, no
     *  plate is rendered and the head reverts to the legacy badge-led
     *  layout.
     *
     *  Two ways to make the plate interactive:
     *   - ``iconOnclick`` (legacy string onclick — kept for parity with
     *     other mod-card slots; will be migrated in a follow-up).
     *   - ``iconAttrs`` (preferred): emit data attributes on the plate
     *     so callers can attach event delegation at the document level
     *     instead of polluting ``window`` with global onclick targets.
     */
    iconHtml?: string;
    iconColor?: string;
    iconOnclick?: string;
    iconAttrs?: Record<string, string>;
    iconTitle?: string;
}

export interface ModBodyOpts {
    metrics?: ModMetricOpts[];
    chips?: ModChipOpts[];
    fader?: ModFaderOpts;
    /** Raw HTML for a preview surface (gradient strip, asset thumb, LED preview) */
    preview?: string;
    /** Free-form description text shown below the head */
    desc?: string;
    /** Free-form raw HTML appended at the end of the body — escape-hatch
     *  for live-updating widgets (sparkline canvases, swatch grids,
     *  collapsible detail blocks) that don't fit the predefined slots.
     *  Caller is responsible for HTML-escaping any user-controlled
     *  substrings. */
    extraHtml?: string;
}

export interface ModCardOpts {
    head: ModHeadOpts;
    body?: ModBodyOpts;
    foot?: ModFootOpts;
    /** Mark the card as `is-running` */
    running?: boolean;
}

// ─────────────────────────────────────────────────────────────────
// Renderers
// ─────────────────────────────────────────────────────────────────

function _ledsHtml(leds?: LedState[]): string {
    if (!leds || leds.length === 0) return '';
    const dots = leds.map(s => {
        if (s === 'off') return '<span class="led"></span>';
        if (s === 'fault') return '<span class="led fault"></span>';
        if (s === 'blink') return '<span class="led on blink"></span>';
        return '<span class="led on"></span>';
    }).join('');
    return `<div class="mod-leds" aria-hidden="true">${dots}</div>`;
}

function _menuHtml(menu: ModMenuOpts | null | undefined): string {
    if (menu === null) return '';
    const m = menu || {};

    const items: string[] = [];

    if (m.extraItems) {
        for (const it of m.extraItems) {
            const cls = it.danger ? 'mod-menu__item mod-menu__item--danger' : 'mod-menu__item';
            const onclickAttr = it.onclick ? ` onclick="${it.onclick}"` : '';
            const dataAttrs = it.dataAttrs
                ? ' ' + Object.entries(it.dataAttrs).map(([k, v]) => `${k}="${escapeHtml(v)}"`).join(' ')
                : '';
            items.push(`<button type="button" class="${cls}" role="menuitem"${onclickAttr}${dataAttrs}>${it.icon || ''} <span>${escapeHtml(it.label)}</span></button>`);
        }
    }

    if (m.duplicateOnclick) {
        items.push(`<button type="button" class="mod-menu__item" role="menuitem" onclick="${m.duplicateOnclick}">${ICON_CLONE} <span>${t('common.clone')}</span></button>`);
    }

    if (m.hideOnclick) {
        items.push(`<button type="button" class="mod-menu__item" role="menuitem" onclick="${m.hideOnclick}">${ICON_EYE_OFF} <span>${t('common.hide') || 'Hide'}</span></button>`);
    }

    if (m.deleteOnclick) {
        if (items.length > 0) items.push('<div class="mod-menu__sep"></div>');
        items.push(`<button type="button" class="mod-menu__item mod-menu__item--danger" role="menuitem" onclick="${m.deleteOnclick}">${ICON_TRASH} <span>${t('common.delete')}</span></button>`);
    }

    if (items.length === 0) return '';

    return `<div class="mod-menu-wrap">
        <button type="button" class="mod-menu-btn" aria-haspopup="true" aria-expanded="false" aria-label="${t('common.more_actions') || 'More actions'}" title="${t('common.more_actions') || 'More actions'}">${ICON_KEBAB}</button>
        <div class="mod-menu" role="menu">${items.join('')}</div>
    </div>`;
}

function _badgeHtml(badge: ModBadgeOpts, entityId?: string, cardAttr?: string): string {
    const showDot = badge.colorDot !== false && entityId && cardAttr;
    const dot = showDot ? cardColorDot(entityId!, cardAttr!) : '';
    return `<span class="mod-badge">${dot}${escapeHtml(badge.text)}</span>`;
}

function _iconPlateHtml(head: ModHeadOpts): string {
    const interactive = !!(head.iconOnclick || head.iconAttrs);
    const isEmpty = !head.iconHtml;

    // No icon set AND no picker hook → render nothing, head reverts to
    // the legacy badge-only layout. With a picker hook we still render
    // the slot as a dashed placeholder so the action stays discoverable.
    if (isEmpty && !interactive) return '';

    const styleAttr = head.iconColor && !isEmpty
        ? ` style="--ch:${escapeHtml(head.iconColor)};color:${escapeHtml(head.iconColor)}"`
        : '';
    const onclickAttr = head.iconOnclick ? ` onclick="${head.iconOnclick}"` : '';
    const dataAttrs = head.iconAttrs
        ? ' ' + Object.entries(head.iconAttrs).map(([k, v]) => `${k}="${escapeHtml(v)}"`).join(' ')
        : '';
    const titleAttr = head.iconTitle ? ` title="${escapeHtml(head.iconTitle)}" aria-label="${escapeHtml(head.iconTitle)}"` : '';
    const tag = interactive ? 'button' : 'div';
    const typeAttr = interactive ? ' type="button"' : '';
    const classAttr = isEmpty ? 'mod-icon is-empty' : 'mod-icon';
    const inner = isEmpty ? ICON_PLUS : head.iconHtml;
    return `<${tag} class="${classAttr}"${typeAttr}${styleAttr}${onclickAttr}${dataAttrs}${titleAttr}>${inner}</${tag}>`;
}

export function renderModHead(head: ModHeadOpts): string {
    const iconHtml = _iconPlateHtml(head);
    const badgeHtml = _badgeHtml(head.badge, head.entityId, head.cardAttr);
    const nameHtml = `<div class="mod-name"><span>${escapeHtml(head.name)}</span>${head.healthDot || ''}</div>`;
    const metaHtml = head.metaHtml ? `<div class="mod-meta">${head.metaHtml}</div>`
                   : head.meta ? `<div class="mod-meta">${escapeHtml(head.meta)}</div>`
                   : '';
    const ledsHtml = _ledsHtml(head.leds);
    const menuHtml = _menuHtml(head.menu);
    const headCls = iconHtml ? 'mod-head mod-head--with-icon' : 'mod-head';

    // Order: optional icon plate → id (flex:1) → kebab → LED bezel.
    // LED status is the running/idle indicator and lives at the far-right
    // corner where it doubles as the visual anchor of the head row.
    return `<div class="${headCls}">
        ${iconHtml}
        <div class="mod-id">
            ${badgeHtml}
            ${nameHtml}
            ${metaHtml}
        </div>
        ${menuHtml}
        ${ledsHtml}
    </div>`;
}

export function renderModMetrics(metrics: ModMetricOpts[]): string {
    if (!metrics.length) return '';
    const cellsClass = metrics.length === 1 ? 'mod-metrics mod-metrics--1'
                     : metrics.length === 2 ? 'mod-metrics mod-metrics--2'
                     : 'mod-metrics';
    const cells = metrics.map(m => {
        const cellCls = [
            'mod-metric',
            m.error ? 'has-errors' : '',
            m.variant ? `mod-metric--${m.variant}` : '',
        ].filter(Boolean).join(' ');
        const titleAttr = m.title ? ` title="${escapeHtml(m.title)}"` : '';
        const vCls = ['v', m.accent ? 'signal' : '', m.error ? 'has-errors' : ''].filter(Boolean).join(' ');
        const vIdAttr = m.valueId ? ` id="${m.valueId}"` : '';
        const vDataAttrs = m.valueDataAttrs
            ? ' ' + Object.entries(m.valueDataAttrs).map(([k, v]) => `${k}="${escapeHtml(v)}"`).join(' ')
            : '';
        const labelHtml = `<span class="k">${m.icon || ''} <span>${escapeHtml(m.k)}</span></span>`;
        const extraHtml = m.extra || '';
        return `<div class="${cellCls}"${titleAttr}>${labelHtml}<span class="${vCls}"${vIdAttr}${vDataAttrs}>${m.v}</span>${extraHtml}</div>`;
    }).join('');
    return `<div class="${cellsClass}">${cells}</div>`;
}

export function renderModChips(chips: ModChipOpts[]): string {
    if (!chips.length) return '';
    const items = chips.map(c => {
        const variant = c.variant === 'tag' ? ' chip--tag'
                      : c.variant === 'err' ? ' chip--err'
                      : '';
        const link = c.onclick ? ' chip--link' : '';
        const titleAttr = c.title ? ` title="${escapeHtml(c.title)}"` : '';
        const onclickAttr = c.onclick ? ` onclick="${c.onclick}"` : '';
        return `<span class="chip${variant}${link}"${titleAttr}${onclickAttr}>${c.icon || ''} ${escapeHtml(c.text)}</span>`;
    }).join('');
    return `<div class="mod-chips">${items}</div>`;
}

export function renderModFader(f: ModFaderOpts): string {
    const max = f.max || 100;
    const pct = Math.round(Math.max(0, Math.min(1, f.value / max)) * 100);
    const sliderId = f.sliderId ? ` id="${f.sliderId}"` : '';
    const dataAttrs = f.dataAttrs
        ? Object.entries(f.dataAttrs).map(([k, v]) => `${k}="${escapeHtml(v)}"`).join(' ')
        : '';
    const oninputAttr = f.oninput ? ` oninput="${f.oninput}"` : '';
    const onchangeAttr = f.onchange ? ` onchange="${f.onchange}"` : '';
    const disabledAttr = f.disabled ? ' disabled' : '';
    return `<div class="mod-fader">
        <span class="mod-fader__k">${escapeHtml(f.label)}</span>
        <div class="mod-fader__lane">
            <div class="mod-fader__track"><div class="mod-fader__fill" style="width:${pct}%"></div></div>
            <input type="range" class="mod-fader__slider" min="0" max="${max}" value="${f.value}"${sliderId} ${dataAttrs}${oninputAttr}${onchangeAttr}${disabledAttr}>
        </div>
        <span class="mod-fader__v">${f.value}</span>
    </div>`;
}

function _btnHtml(b: ModBtnOpts): string {
    const variant = b.variant === 'go' ? ' mod-btn-go'
                  : b.variant === 'stop' ? ' mod-btn-stop'
                  : '';
    const icon = b.iconOnly ? ' mod-btn-icon' : '';
    const titleAttr = b.title ? ` title="${escapeHtml(b.title)}"` : '';
    const i18nAttr = b.i18nTitle ? ` data-i18n-title="${b.i18nTitle}"` : '';
    const labelHtml = b.label ? ` <span>${escapeHtml(b.label)}</span>` : '';
    const dataAttrs = b.dataAttrs
        ? ' ' + Object.entries(b.dataAttrs).map(([k, v]) => `${k}="${escapeHtml(v)}"`).join(' ')
        : '';
    return `<button type="button" class="mod-btn${variant}${icon}" onclick="${b.onclick}"${titleAttr}${i18nAttr}${dataAttrs}>${b.icon || ''}${labelHtml}</button>`;
}

export function renderModFoot(foot: ModFootOpts): string {
    const liveCls = foot.patchState === 'live' ? ' is-live' : '';
    const dimCls = foot.patchState === 'offline' ? ' is-offline' : '';
    const patchHtml = foot.patchLabel
        ? `<div class="mod-patch"><span class="patch-dot${liveCls}${dimCls}"></span><span>${escapeHtml(foot.patchLabel)}</span></div>`
        : '';

    const buttons: string[] = [];
    if (foot.primaryAction) buttons.push(_btnHtml(foot.primaryAction));
    if (foot.secondaryActions) {
        for (const b of foot.secondaryActions) buttons.push(_btnHtml(b));
    }
    if (foot.iconActions) {
        for (const b of foot.iconActions) buttons.push(_btnHtml({ ...b, iconOnly: true }));
    }

    return `<div class="mod-foot">${patchHtml}${buttons.join('')}</div>`;
}

export function renderModBody(body: ModBodyOpts | undefined): string {
    if (!body) return '';
    const parts: string[] = [];
    if (body.desc) parts.push(`<div class="mod-desc">${escapeHtml(body.desc)}</div>`);
    if (body.metrics) parts.push(renderModMetrics(body.metrics));
    if (body.preview) parts.push(`<div class="mod-preview">${body.preview}</div>`);
    if (body.chips) parts.push(renderModChips(body.chips));
    if (body.fader) parts.push(renderModFader(body.fader));
    if (body.extraHtml) parts.push(body.extraHtml);
    return parts.join('');
}

/**
 * Compose a full mod-card body (head + optional body + foot).
 * Used by `wrapCard({ mod })` — callers normally don't invoke this
 * directly.
 */
export function renderModCardInner(opts: ModCardOpts): string {
    return [
        renderModHead(opts.head),
        renderModBody(opts.body),
        opts.foot ? renderModFoot(opts.foot) : '',
    ].filter(Boolean).join('');
}
