/**
 * HTTP Endpoints — CRUD, test, cards.
 *
 * An HTTP endpoint is a connection definition only (URL + auth +
 * headers + timeout). Polling cadence is owned by HTTPValueSource —
 * one endpoint can back many value sources at different intervals.
 *
 * Structurally mirrors `home-assistant-sources.ts` (HA-source CRUD UI):
 * register icon adapter → modal subclass with dirty-check → save/edit/
 * clone/delete handlers → card builder → event delegation.
 */

import {
    _cachedHTTPEndpoints, httpEndpointsCache,
} from '../core/state.ts';
import { fetchWithAuth, escapeHtml } from '../core/api.ts';
import { t } from '../core/i18n.ts';
import { Modal } from '../core/modal.ts';
import { showToast, showConfirm } from '../core/ui.ts';
import {
    ICON_EDIT, ICON_TRASH, ICON_EYE, ICON_EYE_OFF,
    ICON_TEST, ICON_OK, ICON_WARNING,
} from '../core/icons.ts';
import * as P from '../core/icon-paths.ts';
import { wrapCard } from '../core/card-colors.ts';
import type { ModCardOpts, ModChipOpts, LedState } from '../core/mod-card.ts';
import { TagInput, renderTagChips } from '../core/tag-input.ts';
import { makeCardIconFields } from '../core/card-icon.ts';
import { registerIconEntityType, makeSimpleIconAdapter } from './icon-picker.ts';
import { IconSelect } from '../core/icon-select.ts';
import type { HTTPEndpoint, HTTPEndpointWritePayload, HTTPMethod, HTTPTestResponse } from '../types.ts';

registerIconEntityType('http_endpoint', makeSimpleIconAdapter<HTTPEndpoint>({
    cache: httpEndpointsCache,
    endpointPrefix: '/http/endpoints',
    reload: async () => {
        if (typeof window.loadIntegrations === 'function') {
            await window.loadIntegrations();
        }
    },
    typeLabelKey: 'device.icon.entity.http_endpoint',
    typeLabelFallback: 'HTTP endpoint',
    cardSelectors: (id) => [
        `[data-card-section="http-endpoints"] [data-id="${CSS.escape(id)}"]`,
    ],
}));

const ICON_HTTP = `<svg class="icon" viewBox="0 0 24 24">${P.globe}</svg>`;

// ── Modal ─────────────────────────────────────────────────────

let _httpTagsInput: TagInput | null = null;
let _httpMethodIconSelect: IconSelect | null = null;
/** In-memory headers; mirrored into hidden snapshot field. */
let _httpHeaders: Array<{ name: string; value: string }> = [];

class HTTPEndpointModal extends Modal {
    constructor() { super('http-endpoint-modal'); }

    onForceClose() {
        if (_httpTagsInput) { _httpTagsInput.destroy(); _httpTagsInput = null; }
        if (_httpMethodIconSelect) { _httpMethodIconSelect.destroy(); _httpMethodIconSelect = null; }
        _httpHeaders = [];
        const out = document.getElementById('http-endpoint-test-output');
        if (out) { out.innerHTML = ''; out.style.display = 'none'; }
    }

    snapshotValues() {
        // Headers are compared as a sorted snapshot so the dirty-check is
        // immune to a backend serialization that emits keys in a different
        // order than the user originally entered them (false-positive
        // "discard changes?" on reopen of multi-header endpoints).
        const headersSnapshot = [..._httpHeaders]
            .sort((a, b) => a.name.localeCompare(b.name));
        return {
            name: (document.getElementById('http-endpoint-name') as HTMLInputElement).value,
            url: (document.getElementById('http-endpoint-url') as HTMLInputElement).value,
            method: (document.getElementById('http-endpoint-method') as HTMLSelectElement).value,
            auth_token: (document.getElementById('http-endpoint-auth-token') as HTMLInputElement).value,
            timeout_s: (document.getElementById('http-endpoint-timeout') as HTMLInputElement).value,
            description: (document.getElementById('http-endpoint-description') as HTMLInputElement).value,
            headers: JSON.stringify(headersSnapshot),
            tags: JSON.stringify(_httpTagsInput ? _httpTagsInput.getValue() : []),
        };
    }
}

const httpEndpointModal = new HTTPEndpointModal();

// ── Method IconSelect (GET / HEAD) ────────────────────────────

function _ensureMethodIconSelect() {
    const sel = document.getElementById('http-endpoint-method') as HTMLSelectElement | null;
    if (!sel) return;
    const items = [
        {
            value: 'GET',
            icon: `<svg class="icon" viewBox="0 0 24 24">${P.download}</svg>`,
            label: 'GET',
            desc: t('http_endpoint.method.get.desc'),
        },
        {
            value: 'HEAD',
            icon: `<svg class="icon" viewBox="0 0 24 24">${P.listChecks}</svg>`,
            label: 'HEAD',
            desc: t('http_endpoint.method.head.desc'),
        },
    ];
    if (_httpMethodIconSelect) { _httpMethodIconSelect.updateItems(items); return; }
    _httpMethodIconSelect = new IconSelect({ target: sel, items, columns: 2 });
}

// ── Headers list (key/value rows) ─────────────────────────────
// Visual model mirrors `.group-child-row` (cards.css): bordered rows on
// `--bg-color` with a subtle hover ring, inputs share the same height
// and rounded corners, trash button on the right. Empty state matches
// the `.pp-filter-empty` dashed-border pattern.

function _renderHeaderRows() {
    const list = document.getElementById('http-endpoint-headers-list');
    if (!list) return;
    if (_httpHeaders.length === 0) {
        list.innerHTML = `<div class="http-headers-empty">${escapeHtml(t('http_endpoint.headers.empty'))}</div>`;
        return;
    }
    list.innerHTML = _httpHeaders.map((h, idx) => `
        <div class="http-header-row" data-idx="${idx}">
            <span class="http-header-index" aria-hidden="true">${idx + 1}</span>
            <div class="http-header-fields">
                <input type="text" class="http-header-name" placeholder="${escapeHtml(t('http_endpoint.headers.name_placeholder'))}" value="${escapeHtml(h.name)}" spellcheck="false" autocomplete="off">
                <input type="text" class="http-header-value" placeholder="${escapeHtml(t('http_endpoint.headers.value_placeholder'))}" value="${escapeHtml(h.value)}" spellcheck="false" autocomplete="off">
            </div>
            <button type="button" class="btn btn-icon btn-secondary http-header-remove" title="${escapeHtml(t('common.delete'))}" aria-label="${escapeHtml(t('common.delete'))}">${ICON_TRASH}</button>
        </div>
    `).join('');

    list.querySelectorAll<HTMLInputElement>('.http-header-name').forEach((inp) => {
        inp.addEventListener('input', () => {
            const row = inp.closest<HTMLElement>('.http-header-row')!;
            const idx = parseInt(row.dataset.idx || '0', 10);
            if (_httpHeaders[idx]) _httpHeaders[idx].name = inp.value;
        });
    });
    list.querySelectorAll<HTMLInputElement>('.http-header-value').forEach((inp) => {
        inp.addEventListener('input', () => {
            const row = inp.closest<HTMLElement>('.http-header-row')!;
            const idx = parseInt(row.dataset.idx || '0', 10);
            if (_httpHeaders[idx]) _httpHeaders[idx].value = inp.value;
        });
    });
    list.querySelectorAll<HTMLButtonElement>('.http-header-remove').forEach((btn) => {
        btn.addEventListener('click', () => {
            const row = btn.closest<HTMLElement>('.http-header-row')!;
            const idx = parseInt(row.dataset.idx || '0', 10);
            _httpHeaders.splice(idx, 1);
            _renderHeaderRows();
        });
    });
}

export function addHTTPEndpointHeader() {
    _httpHeaders.push({ name: '', value: '' });
    _renderHeaderRows();
    // Focus the newest row so the user can immediately start typing.
    const list = document.getElementById('http-endpoint-headers-list');
    if (list) {
        const names = list.querySelectorAll<HTMLInputElement>('.http-header-name');
        names[names.length - 1]?.focus();
    }
}

// ── Show / Close ──────────────────────────────────────────────

export async function showHTTPEndpointModal(editData: HTTPEndpoint | null = null): Promise<void> {
    const isEdit = !!editData;
    const titleKey = isEdit ? 'http_endpoint.edit' : 'http_endpoint.add';
    document.getElementById('http-endpoint-modal-title')!.innerHTML = `${ICON_HTTP} ${t(titleKey)}`;
    (document.getElementById('http-endpoint-id') as HTMLInputElement).value = editData?.id || '';
    (document.getElementById('http-endpoint-error') as HTMLElement).style.display = 'none';

    const nameInput = document.getElementById('http-endpoint-name') as HTMLInputElement;
    const urlInput = document.getElementById('http-endpoint-url') as HTMLInputElement;
    const methodSel = document.getElementById('http-endpoint-method') as HTMLSelectElement;
    const tokenInput = document.getElementById('http-endpoint-auth-token') as HTMLInputElement;
    const timeoutInput = document.getElementById('http-endpoint-timeout') as HTMLInputElement;
    const descInput = document.getElementById('http-endpoint-description') as HTMLInputElement;

    nameInput.value = editData?.name || '';
    urlInput.value = editData?.url || '';
    methodSel.value = editData?.method || 'GET';
    tokenInput.value = '';  // never expose stored token
    tokenInput.type = 'password';
    timeoutInput.value = String(editData?.timeout_s ?? 10);
    descInput.value = editData?.description || '';

    // Headers: snapshot from data into our editable buffer
    _httpHeaders = editData?.headers
        ? Object.entries(editData.headers).map(([name, value]) => ({ name, value }))
        : [];
    _renderHeaderRows();

    _ensureMethodIconSelect();
    if (_httpMethodIconSelect) _httpMethodIconSelect.setValue(editData?.method || 'GET');

    // Show "leave blank to keep" hint only when editing an endpoint
    // that already has a token configured.
    const tokenHint = document.getElementById('http-endpoint-token-hint');
    if (tokenHint) tokenHint.style.display = (isEdit && editData?.auth_token_set) ? '' : 'none';

    // Reveal password toggle
    const revealBtn = document.getElementById('http-endpoint-token-reveal');
    if (revealBtn) revealBtn.innerHTML = ICON_EYE;

    // Inject icon into the inline Test button
    const testBtnIcon = document.querySelector('#http-endpoint-test-btn .http-endpoint-test-btn-icon');
    if (testBtnIcon) testBtnIcon.innerHTML = ICON_TEST;

    // Reset test output
    const out = document.getElementById('http-endpoint-test-output');
    if (out) { out.innerHTML = ''; out.style.display = 'none'; }

    // Tags
    if (_httpTagsInput) { _httpTagsInput.destroy(); _httpTagsInput = null; }
    _httpTagsInput = new TagInput(
        document.getElementById('http-endpoint-tags-container'),
        { placeholder: t('tags.placeholder') }
    );
    _httpTagsInput.setValue(isEdit ? (editData?.tags || []) : []);

    httpEndpointModal.open();
    httpEndpointModal.snapshot();
}

export async function closeHTTPEndpointModal(): Promise<void> {
    await httpEndpointModal.close();
}

export function toggleHTTPEndpointTokenVisibility() {
    const inp = document.getElementById('http-endpoint-auth-token') as HTMLInputElement;
    const btn = document.getElementById('http-endpoint-token-reveal');
    if (!inp || !btn) return;
    if (inp.type === 'password') {
        inp.type = 'text';
        btn.innerHTML = ICON_EYE_OFF;
    } else {
        inp.type = 'password';
        btn.innerHTML = ICON_EYE;
    }
}

// ── Header collection (commits live <input> values to buffer) ─

function _collectHeaders(): Record<string, string> {
    // Pull current input values to be safe (in case of paste / IME).
    const list = document.getElementById('http-endpoint-headers-list');
    if (list) {
        list.querySelectorAll<HTMLElement>('.http-header-row').forEach((row) => {
            const idx = parseInt(row.dataset.idx || '0', 10);
            const name = (row.querySelector('.http-header-name') as HTMLInputElement)?.value || '';
            const value = (row.querySelector('.http-header-value') as HTMLInputElement)?.value || '';
            if (_httpHeaders[idx]) { _httpHeaders[idx].name = name; _httpHeaders[idx].value = value; }
        });
    }
    const out: Record<string, string> = {};
    for (const h of _httpHeaders) {
        const name = h.name.trim();
        if (!name) continue;
        out[name] = h.value;
    }
    return out;
}

// ── Save ──────────────────────────────────────────────────────

export async function saveHTTPEndpoint(): Promise<void> {
    const id = (document.getElementById('http-endpoint-id') as HTMLInputElement).value;
    if (httpEndpointModal.closeIfPristine(id)) return;

    const name = (document.getElementById('http-endpoint-name') as HTMLInputElement).value.trim();
    const url = (document.getElementById('http-endpoint-url') as HTMLInputElement).value.trim();
    const method = (document.getElementById('http-endpoint-method') as HTMLSelectElement).value as HTTPMethod;
    const token = (document.getElementById('http-endpoint-auth-token') as HTMLInputElement).value;
    const timeoutRaw = (document.getElementById('http-endpoint-timeout') as HTMLInputElement).value;
    const description = (document.getElementById('http-endpoint-description') as HTMLInputElement).value.trim() || undefined;
    const headers = _collectHeaders();
    const timeout_s = parseFloat(timeoutRaw);

    if (!name) {
        httpEndpointModal.showError(t('http_endpoint.error.name_required'));
        return;
    }
    if (!url) {
        httpEndpointModal.showError(t('http_endpoint.error.url_required'));
        return;
    }
    if (isNaN(timeout_s) || timeout_s <= 0) {
        httpEndpointModal.showError(t('http_endpoint.error.timeout_invalid'));
        return;
    }

    const payload: HTTPEndpointWritePayload = {
        name, url, method, headers, timeout_s, description,
        tags: _httpTagsInput ? _httpTagsInput.getValue() : [],
    };

    // Auth token semantics (per backend schema):
    //   POST    — empty string means "no token"
    //   PUT new — non-empty replaces; empty string CLEARS; omit to KEEP existing.
    // For PUT we omit the field unless the user typed something so we don't
    // accidentally clear a previously-configured token.
    if (!id) {
        payload.auth_token = token;
    } else if (token) {
        payload.auth_token = token;
    }

    try {
        const method = id ? 'PUT' : 'POST';
        const url = id ? `/http/endpoints/${id}` : '/http/endpoints';
        const resp = await fetchWithAuth(url, {
            method,
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload),
        });
        if (!resp.ok) {
            const err = await resp.json().catch(() => ({}));
            throw new Error(err.detail || `HTTP ${resp.status}`);
        }
        showToast(t(id ? 'http_endpoint.updated' : 'http_endpoint.created'), 'success');
        httpEndpointModal.forceClose();
        httpEndpointsCache.invalidate();
        if (typeof window.loadIntegrations === 'function') await window.loadIntegrations();
    } catch (e: any) {
        if (e.isAuth) return;
        httpEndpointModal.showError(e.message);
    }
}

// ── Edit / Clone / Delete ─────────────────────────────────────

export async function editHTTPEndpoint(endpointId: string): Promise<void> {
    try {
        const resp = await fetchWithAuth(`/http/endpoints/${endpointId}`);
        if (!resp.ok) throw new Error(t('http_endpoint.error.load'));
        const data: HTTPEndpoint = await resp.json();
        await showHTTPEndpointModal(data);
    } catch (e: any) {
        if (e.isAuth) return;
        showToast(e.message, 'error');
    }
}

export async function cloneHTTPEndpoint(endpointId: string): Promise<void> {
    try {
        const resp = await fetchWithAuth(`/http/endpoints/${endpointId}`);
        if (!resp.ok) throw new Error(t('http_endpoint.error.load'));
        const data = await resp.json();
        delete data.id;
        data.name = data.name + ' (copy)';
        // Cloning never reveals the token — user must re-enter if needed.
        data.auth_token_set = false;
        await showHTTPEndpointModal(data);
    } catch (e: any) {
        if (e.isAuth) return;
        showToast(e.message, 'error');
    }
}

export async function deleteHTTPEndpoint(endpointId: string): Promise<void> {
    const confirmed = await showConfirm(t('http_endpoint.delete.confirm'));
    if (!confirmed) return;
    try {
        const resp = await fetchWithAuth(`/http/endpoints/${endpointId}`, { method: 'DELETE' });
        if (!resp.ok) {
            const err = await resp.json().catch(() => ({}));
            throw new Error(err.detail || `HTTP ${resp.status}`);
        }
        showToast(t('http_endpoint.deleted'), 'success');
        httpEndpointsCache.invalidate();
        if (typeof window.loadIntegrations === 'function') await window.loadIntegrations();
    } catch (e: any) {
        if (e.isAuth) return;
        showToast(e.message, 'error');
    }
}

// ── Test (in-form, pre-save) ──────────────────────────────────

/** Builds a `HTTPTestRequest` from the live form values and renders
 *  the response inline. Works for both new and edit modes. */
export async function testHTTPEndpoint(): Promise<void> {
    const url = (document.getElementById('http-endpoint-url') as HTMLInputElement).value.trim();
    const method = (document.getElementById('http-endpoint-method') as HTMLSelectElement).value as HTTPMethod;
    const token = (document.getElementById('http-endpoint-auth-token') as HTMLInputElement).value;
    const timeoutRaw = (document.getElementById('http-endpoint-timeout') as HTMLInputElement).value;
    const headers = _collectHeaders();
    const timeout_s = parseFloat(timeoutRaw);

    const out = document.getElementById('http-endpoint-test-output');
    if (!out) return;

    // Render validation errors inline next to the Test button — the
    // modal-level banner at the top of the form is invisible from here.
    const renderValidationFail = (msg: string): void => {
        out.style.display = '';
        out.innerHTML = `<div class="http-test-result http-test-fail">
            <span class="http-test-badge http-test-badge-fail">${escapeHtml(t('http_endpoint.test.failed'))}</span>
            <code class="http-test-error">${escapeHtml(msg)}</code>
        </div>`;
    };

    if (!url) {
        renderValidationFail(t('http_endpoint.error.url_required'));
        return;
    }
    if (isNaN(timeout_s) || timeout_s <= 0) {
        renderValidationFail(t('http_endpoint.error.timeout_invalid'));
        return;
    }

    const testBtn = document.getElementById('http-endpoint-test-btn');
    if (testBtn) testBtn.classList.add('loading');
    out.style.display = '';
    out.innerHTML = `<div class="http-test-pending">
        <span class="http-test-pending-spinner" aria-hidden="true"></span>
        <span>${escapeHtml(t('http_endpoint.test.pending'))}</span>
    </div>`;

    try {
        const resp = await fetchWithAuth('/http/endpoints/test', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ url, method, auth_token: token, headers, timeout_s }),
        });
        if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
        const data: HTTPTestResponse = await resp.json();
        _renderTestResult(out, data);
    } catch (e: any) {
        if (e.isAuth) return;
        out.innerHTML = _renderTestErrorHtml(e.message);
    } finally {
        if (testBtn) testBtn.classList.remove('loading');
    }
}

function _renderTestErrorHtml(message: string): string {
    return `<div class="http-test-result http-test-fail">
        <div class="http-test-line">
            <span class="http-test-badge http-test-badge-fail">${ICON_WARNING}<span>${escapeHtml(t('http_endpoint.test.failed'))}</span></span>
        </div>
        <code class="http-test-error">${escapeHtml(message)}</code>
    </div>`;
}

function _renderTestResult(out: HTMLElement, data: HTTPTestResponse) {
    const statusClass = data.success ? 'http-test-ok' : 'http-test-fail';
    const badgeClass = data.success ? 'http-test-badge-ok' : 'http-test-badge-fail';
    const badgeIcon = data.success ? ICON_OK : ICON_WARNING;
    const badgeText = data.success
        ? t('http_endpoint.test.success')
        : t('http_endpoint.test.failed');
    const statusLine = data.status_code != null
        ? `<span class="http-test-status" title="HTTP ${data.status_code}">${data.status_code}</span>`
        : '';
    const errLine = data.error && !data.success
        ? `<code class="http-test-error">${escapeHtml(data.error)}</code>`
        : '';
    // Show JSON body when available — easier to copy a json_path from than raw text.
    let bodyHtml = '';
    let bodyLabel = '';
    if (data.body_json != null) {
        let pretty: string;
        try { pretty = JSON.stringify(data.body_json, null, 2); }
        catch { pretty = String(data.body_json); }
        bodyLabel = `<div class="http-test-body-label">${escapeHtml(t('http_endpoint.test.body.json'))}</div>`;
        bodyHtml = `<pre class="http-test-body">${escapeHtml(pretty)}</pre>`;
    } else if (data.body_preview) {
        bodyLabel = `<div class="http-test-body-label">${escapeHtml(t('http_endpoint.test.body.text'))}</div>`;
        bodyHtml = `<pre class="http-test-body">${escapeHtml(data.body_preview)}</pre>`;
    }
    out.innerHTML = `<div class="http-test-result ${statusClass}">
        <div class="http-test-line">
            <span class="http-test-badge ${badgeClass}">${badgeIcon}<span>${escapeHtml(badgeText)}</span></span>
            ${statusLine}
        </div>
        ${errLine}
        ${bodyLabel}
        ${bodyHtml}
    </div>`;
}

// ── Card-level test (uses stored config, no form needed) ──────

async function _testHTTPEndpointFromCard(endpointId: string): Promise<void> {
    try {
        const resp = await fetchWithAuth(`/http/endpoints/${endpointId}/test`, { method: 'POST' });
        if (!resp.ok) {
            const err = await resp.json().catch(() => ({}));
            throw new Error(err.detail || `HTTP ${resp.status}`);
        }
        const data: HTTPTestResponse = await resp.json();
        if (data.success) {
            const status = data.status_code != null ? ` (${data.status_code})` : '';
            showToast(`${t('http_endpoint.test.success')}${status}`, 'success');
        } else {
            const detail = data.error || `HTTP ${data.status_code ?? '?'}`;
            showToast(`${t('http_endpoint.test.failed')}: ${detail}`, 'error');
        }
    } catch (e: any) {
        if (e.isAuth) return;
        showToast(`${t('http_endpoint.test.failed')}: ${e.message}`, 'error');
    }
}

// ── Card rendering ────────────────────────────────────────────

export function createHTTPEndpointCard(endpoint: HTTPEndpoint) {
    const hasAuth = !!endpoint.auth_token_set;
    const headerCount = Object.keys(endpoint.headers || {}).length;
    const leds: LedState[] = ['on'];

    const chips: ModChipOpts[] = [
        {
            icon: `<svg class="icon" viewBox="0 0 24 24">${P.globe}</svg>`,
            text: endpoint.url,
            title: endpoint.url,
        },
        {
            icon: `<svg class="icon" viewBox="0 0 24 24">${P.refreshCw}</svg>`,
            text: endpoint.method,
        },
    ];
    if (hasAuth) {
        chips.push({
            icon: `<svg class="icon" viewBox="0 0 24 24">${P.lock}</svg>`,
            text: t('http_endpoint.auth.set'),
        });
    }
    if (headerCount > 0) {
        chips.push({
            icon: `<svg class="icon" viewBox="0 0 24 24">${P.listChecks}</svg>`,
            text: t('http_endpoint.headers.count').replace('{n}', String(headerCount)),
        });
    }

    const mod: ModCardOpts = {
        head: {
            badge: { text: 'HTTP · ENDPOINT' },
            name: endpoint.name,
            metaHtml: escapeHtml(endpoint.url),
            leds,
            ...makeCardIconFields('http_endpoint', endpoint.id, endpoint),
            menu: {
                duplicateOnclick: `cloneHTTPEndpoint('${endpoint.id}')`,
                hideOnclick: `toggleCardHidden('http-endpoints','${endpoint.id}')`,
                deleteOnclick: `deleteHTTPEndpoint('${endpoint.id}')`,
            },
        },
        body: {
            desc: endpoint.description || undefined,
            chips,
        },
        foot: {
            patchState: 'idle',
            patchLabel: `${endpoint.method} · ${Math.round(endpoint.timeout_s)}s`,
            iconActions: [
                { icon: ICON_TEST, onclick: '', title: t('http_endpoint.test'), dataAttrs: { 'data-action': 'test' } },
                { icon: ICON_EDIT, onclick: '', title: t('common.edit'), dataAttrs: { 'data-action': 'edit' } },
            ],
        },
        running: false,
    };
    const cardHtml = wrapCard({ type: 'template-card', dataAttr: 'data-id', id: endpoint.id, mod });
    const tagsHtml = renderTagChips(endpoint.tags);
    return tagsHtml ? cardHtml.replace(/<\/div>\s*$/, `${tagsHtml}</div>`) : cardHtml;
}

// ── Event delegation ──────────────────────────────────────────

const _httpEndpointActions: Record<string, (id: string) => void> = {
    clone: cloneHTTPEndpoint,
    edit: editHTTPEndpoint,
    test: _testHTTPEndpointFromCard,
};

export function initHTTPEndpointDelegation(container: HTMLElement): void {
    container.addEventListener('click', (e: MouseEvent) => {
        const btn = (e.target as HTMLElement).closest<HTMLElement>('[data-action]');
        if (!btn) return;

        const section = btn.closest<HTMLElement>('[data-card-section="http-endpoints"]');
        if (!section) return;
        const card = btn.closest<HTMLElement>('[data-id]');
        if (!card) return;

        const action = btn.dataset.action;
        const id = card.getAttribute('data-id');
        if (!action || !id) return;

        const handler = _httpEndpointActions[action];
        if (handler) {
            e.stopPropagation();
            handler(id);
        }
    });
}

// ── Expose to global scope for HTML onclick handlers ──────────

window.showHTTPEndpointModal = showHTTPEndpointModal;
window.closeHTTPEndpointModal = closeHTTPEndpointModal;
window.saveHTTPEndpoint = saveHTTPEndpoint;
window.editHTTPEndpoint = editHTTPEndpoint;
window.cloneHTTPEndpoint = cloneHTTPEndpoint;
window.deleteHTTPEndpoint = deleteHTTPEndpoint;
window.testHTTPEndpoint = testHTTPEndpoint;
window.addHTTPEndpointHeader = addHTTPEndpointHeader;
window.toggleHTTPEndpointTokenVisibility = toggleHTTPEndpointTokenVisibility;
