// ============================================================
// Scripts: CRUD, quick access, execution dialog
// ============================================================

import {
    t, showToast, escapeHtml, closeDialog, showConfirm,
    resolveMdiIcons, fetchMdiIcon,
    scripts, setScripts,
    getAuthHeaders, hasCredentials,
} from './core.js';
import { IconSelect } from './icon-select.js';
import { paramTypeIcons } from './icons.js';

export let scriptFormDirty = false;
export function setScriptFormDirty(value) { scriptFormDirty = value; }

export async function loadScripts() {
    try {
        const response = await fetch('/api/scripts/list', {
            headers: getAuthHeaders()
        });

        if (response.ok) {
            setScripts(await response.json());
            displayQuickAccess();
        }
    } catch (error) {
        console.error('Error loading scripts:', error);
    }
}

let _quickAccessGen = 0;
export async function displayQuickAccess() {
    const gen = ++_quickAccessGen;
    const grid = document.getElementById('scripts-grid');

    const fragment = document.createDocumentFragment();
    const hasScripts = scripts.length > 0;
    let hasLinks = false;

    scripts.forEach(script => {
        const button = document.createElement('button');
        button.className = 'script-btn';
        button.onclick = () => executeScript(script.name, button);

        if (script.icon) {
            const iconEl = document.createElement('div');
            iconEl.className = 'script-icon';
            iconEl.setAttribute('data-mdi-icon', script.icon);
            button.appendChild(iconEl);
        }

        const label = document.createElement('div');
        label.className = 'script-label';
        label.textContent = script.label || script.name;
        button.appendChild(label);

        if (script.description) {
            const description = document.createElement('div');
            description.className = 'script-description';
            description.textContent = script.description;
            button.appendChild(description);
        }

        fragment.appendChild(button);
    });

    try {
        if (hasCredentials()) {
            const response = await fetch('/api/links/list', {
                headers: getAuthHeaders()
            });
            if (gen !== _quickAccessGen) return;
            if (response.ok) {
                const links = await response.json();
                hasLinks = links.length > 0;
                links.forEach(link => {
                    const card = document.createElement('a');
                    card.className = 'script-btn link-card';
                    card.href = link.url;
                    card.target = '_blank';
                    card.rel = 'noopener noreferrer';
                    // Prevent the WebUI's URL (which may carry ?token=...) from
                    // leaking to third-party sites via Referer.
                    card.referrerPolicy = 'no-referrer';

                    if (link.icon) {
                        const iconEl = document.createElement('div');
                        iconEl.className = 'script-icon';
                        iconEl.setAttribute('data-mdi-icon', link.icon);
                        card.appendChild(iconEl);
                    }

                    const label = document.createElement('div');
                    label.className = 'script-label';
                    label.textContent = link.label || link.name;
                    card.appendChild(label);

                    if (link.description) {
                        const desc = document.createElement('div');
                        desc.className = 'script-description';
                        desc.textContent = link.description;
                        card.appendChild(desc);
                    }

                    fragment.appendChild(card);
                });
            }
        }
    } catch (e) {
        if (gen !== _quickAccessGen) return;
        console.warn('Failed to load links for quick access:', e);
    }

    if (!hasScripts && !hasLinks) {
        const empty = document.createElement('div');
        empty.className = 'scripts-empty empty-state-illustration';
        empty.innerHTML = `<svg viewBox="0 0 64 64"><path d="M20 8l-8 48"/><path d="M44 8l8 48"/><path d="M10 24h44"/><path d="M8 40h44"/></svg><p>${t('quick_access.no_items')}</p>`;
        fragment.prepend(empty);
    }

    grid.innerHTML = '';
    grid.appendChild(fragment);
    resolveMdiIcons(grid);
}

function _getScriptParams(scriptName) {
    const script = scripts.find(s => s.name === scriptName);
    return (script && script.parameters) ? script.parameters : {};
}

async function executeScript(scriptName, buttonElement) {
    const paramDefs = _getScriptParams(scriptName);
    if (Object.keys(paramDefs).length > 0) {
        _showParamsInputDialog(scriptName, paramDefs, async (params) => {
            buttonElement.classList.add('executing');
            try {
                await _doExecuteScript(scriptName, params);
            } finally {
                buttonElement.classList.remove('executing');
            }
        });
        return;
    }

    buttonElement.classList.add('executing');
    try {
        await _doExecuteScript(scriptName, {});
    } finally {
        buttonElement.classList.remove('executing');
    }
}

async function _doExecuteScript(scriptName, params) {
    try {
        const response = await fetch(`/api/scripts/execute/${encodeURIComponent(scriptName)}`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
            body: JSON.stringify({ params })
        });

        const result = await response.json();

        if (response.ok && result.success) {
            showToast(t('scripts.msg.executed', { name: scriptName }), 'success');
        } else {
            showToast(result.detail || t('scripts.msg.execute_failed', { name: scriptName }), 'error');
        }
    } catch (error) {
        console.error(`Error executing script ${scriptName}:`, error);
        showToast(t('scripts.msg.execute_error', { name: scriptName }), 'error');
    }
}

// ============================================================
// Script Parameters Input Dialog (execution-time)
// ============================================================

let _paramsCallback = null;
let _paramsScriptName = null;
let _paramsIconSelects = null;

function _showParamsInputDialog(scriptName, paramDefs, callback) {
    _paramsCallback = callback;
    _paramsScriptName = scriptName;

    const dialog = document.getElementById('scriptParamsDialog');
    const title = document.getElementById('scriptParamsDialogTitle');
    const container = document.getElementById('scriptParamsInputs');

    const script = scripts.find(s => s.name === scriptName);
    title.textContent = script ? (script.label || scriptName) : scriptName;
    container.innerHTML = '';

    // Track IconSelect instances for cleanup
    if (!_paramsIconSelects) _paramsIconSelects = [];

    for (const [pname, pdef] of Object.entries(paramDefs)) {
        const wrapper = document.createElement('label');

        const labelText = document.createElement('span');
        labelText.textContent = pname + (pdef.required ? ' *' : '');
        wrapper.appendChild(labelText);

        if (pdef.description) {
            const hint = document.createElement('small');
            hint.className = 'param-hint';
            hint.textContent = pdef.description;
            wrapper.appendChild(hint);
        }

        let input;
        if (pdef.type === 'select' && pdef.options) {
            input = document.createElement('select');
            if (!pdef.required) {
                const opt = document.createElement('option');
                opt.value = '';
                opt.textContent = '—';
                input.appendChild(opt);
            }
            for (const optVal of pdef.options) {
                const opt = document.createElement('option');
                opt.value = optVal;
                opt.textContent = optVal;
                if (pdef.default !== undefined && pdef.default !== null && String(pdef.default) === optVal) {
                    opt.selected = true;
                }
                input.appendChild(opt);
            }
            input.dataset.paramName = pname;
            input.dataset.paramType = pdef.type;
            if (pdef.required) input.required = true;
            wrapper.appendChild(input);

            // Enhance with icon grid if few options
            if (pdef.options.length <= 10) {
                const selItems = pdef.options.map(o => ({ value: o, icon: '', label: o }));
                const cols = Math.min(pdef.options.length, 4);
                const isel = new IconSelect({ target: input, items: selItems, columns: cols });
                _paramsIconSelects.push(isel);
            }
        } else if (pdef.type === 'boolean') {
            const boolSvgTrue = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>';
            const boolSvgFalse = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>';

            input = document.createElement('select');
            const optTrue = document.createElement('option');
            optTrue.value = 'true';
            optTrue.textContent = 'true';
            const optFalse = document.createElement('option');
            optFalse.value = 'false';
            optFalse.textContent = 'false';
            input.appendChild(optTrue);
            input.appendChild(optFalse);
            if (pdef.default !== undefined && pdef.default !== null) {
                input.value = String(pdef.default);
            }
            input.dataset.paramName = pname;
            input.dataset.paramType = pdef.type;
            if (pdef.required) input.required = true;
            wrapper.appendChild(input);

            // Enhance with icon grid
            const isel = new IconSelect({
                target: input,
                items: [
                    { value: 'true', icon: boolSvgTrue, label: 'True' },
                    { value: 'false', icon: boolSvgFalse, label: 'False' },
                ],
                columns: 2,
            });
            _paramsIconSelects.push(isel);
        } else if (pdef.type === 'integer' || pdef.type === 'float') {
            input = document.createElement('input');
            input.type = 'number';
            if (pdef.type === 'float') input.step = 'any';
            if (pdef.min !== undefined && pdef.min !== null) input.min = pdef.min;
            if (pdef.max !== undefined && pdef.max !== null) input.max = pdef.max;
            if (pdef.default !== undefined && pdef.default !== null) input.value = pdef.default;
            input.dataset.paramName = pname;
            input.dataset.paramType = pdef.type;
            if (pdef.required) input.required = true;
            wrapper.appendChild(input);
        } else {
            input = document.createElement('input');
            input.type = 'text';
            if (pdef.default !== undefined && pdef.default !== null) input.value = pdef.default;
            input.dataset.paramName = pname;
            input.dataset.paramType = pdef.type;
            if (pdef.required) input.required = true;
            wrapper.appendChild(input);
        }

        container.appendChild(wrapper);
    }

    document.body.classList.add('dialog-open');
    dialog.showModal();
}

export function closeScriptParamsDialog() {
    const dialog = document.getElementById('scriptParamsDialog');
    _paramsCallback = null;
    _paramsScriptName = null;
    // Destroy icon selects from execution dialog
    if (_paramsIconSelects) {
        _paramsIconSelects.forEach(isel => isel.destroy());
        _paramsIconSelects = null;
    }
    closeDialog(dialog);
    document.body.classList.remove('dialog-open');
}

export async function submitScriptWithParams(event) {
    event.preventDefault();
    const container = document.getElementById('scriptParamsInputs');
    const inputs = container.querySelectorAll('[data-param-name]');
    const params = {};

    for (const input of inputs) {
        const name = input.dataset.paramName;
        const type = input.dataset.paramType;
        let val = input.value;

        if (val === '' && !input.required) continue;
        if (val === '') continue;

        if (type === 'integer') val = parseInt(val, 10);
        else if (type === 'float') val = parseFloat(val);
        else if (type === 'boolean') val = val === 'true';

        params[name] = val;
    }

    const callback = _paramsCallback;
    closeScriptParamsDialog();

    if (callback) {
        await callback(params);
    }
}

// ============================================================
// Script Management CRUD
// ============================================================

let _loadScriptsPromise = null;
export async function loadScriptsTable() {
    if (_loadScriptsPromise) return _loadScriptsPromise;
    _loadScriptsPromise = _loadScriptsTableImpl();
    _loadScriptsPromise.finally(() => { _loadScriptsPromise = null; });
    return _loadScriptsPromise;
}

async function _loadScriptsTableImpl() {
    const tbody = document.getElementById('scriptsTableBody');

    try {
        const response = await fetch('/api/scripts/list', {
            headers: getAuthHeaders()
        });

        if (!response.ok) {
            throw new Error('Failed to fetch scripts');
        }

        const scriptsList = await response.json();

        if (scriptsList.length === 0) {
            tbody.innerHTML = '<tr><td colspan="5" class="empty-state"><div class="empty-state-illustration"><svg viewBox="0 0 64 64"><rect x="8" y="4" width="48" height="56" rx="4"/><path d="M20 20h24M20 32h16M20 44h20"/></svg><p>' + t('scripts.empty') + '</p></div></td></tr>';
            return;
        }

        tbody.innerHTML = scriptsList.map(script => `
            <tr>
                <td><span class="name-with-icon">${script.icon ? `<span class="table-icon" data-mdi-icon="${escapeHtml(script.icon)}"></span>` : ''}<code>${escapeHtml(script.name)}</code></span></td>
                <td>${escapeHtml(script.label || script.name)}</td>
                <td style="max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
                    title="${escapeHtml(script.command || 'N/A')}">${escapeHtml(script.command || 'N/A')}</td>
                <td>${script.timeout}s</td>
                <td>
                    <div class="action-buttons">
                        <button class="action-btn execute" data-action="execute" data-script-name="${escapeHtml(script.name)}" title="Execute script">
                            <svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
                        </button>
                        <button class="action-btn" data-action="edit" data-script-name="${escapeHtml(script.name)}" title="Edit script">
                            <svg viewBox="0 0 24 24"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg>
                        </button>
                        <button class="action-btn delete" data-action="delete" data-script-name="${escapeHtml(script.name)}" title="Delete script">
                            <svg viewBox="0 0 24 24"><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>
                        </button>
                    </div>
                </td>
            </tr>
        `).join('');
        resolveMdiIcons(tbody);
    } catch (error) {
        console.error('Error loading scripts:', error);
        tbody.innerHTML = `<tr><td colspan="5" class="empty-state" style="color: var(--error);">${escapeHtml(t('scripts.msg.load_failed'))}</td></tr>`;
    }
}

export function showAddScriptDialog() {
    const dialog = document.getElementById('scriptDialog');
    const form = document.getElementById('scriptForm');
    const title = document.getElementById('dialogTitle');

    form.reset();
    document.getElementById('scriptOriginalName').value = '';
    document.getElementById('scriptIsEdit').value = 'false';
    document.getElementById('scriptName').disabled = false;
    document.getElementById('scriptIconPreview').innerHTML = '';
    document.getElementById('scriptParamsContainer').innerHTML = '';
    title.textContent = t('scripts.dialog.add');

    scriptFormDirty = false;

    document.body.classList.add('dialog-open');
    dialog.showModal();
}

export async function showEditScriptDialog(scriptName) {
    const dialog = document.getElementById('scriptDialog');
    const title = document.getElementById('dialogTitle');

    try {
        const response = await fetch('/api/scripts/list', {
            headers: getAuthHeaders()
        });

        if (!response.ok) {
            throw new Error('Failed to fetch script details');
        }

        const scriptsList = await response.json();
        const script = scriptsList.find(s => s.name === scriptName);

        if (!script) {
            showToast(t('scripts.msg.not_found'), 'error');
            return;
        }

        document.getElementById('scriptOriginalName').value = scriptName;
        document.getElementById('scriptIsEdit').value = 'true';
        document.getElementById('scriptName').value = scriptName;
        document.getElementById('scriptName').disabled = true;
        document.getElementById('scriptLabel').value = script.label || '';
        document.getElementById('scriptCommand').value = script.command || '';
        document.getElementById('scriptDescription').value = script.description || '';
        document.getElementById('scriptIcon').value = script.icon || '';
        document.getElementById('scriptTimeout').value = script.timeout || 30;

        const preview = document.getElementById('scriptIconPreview');
        if (script.icon) {
            fetchMdiIcon(script.icon).then(svg => { preview.innerHTML = svg; });
        } else {
            preview.innerHTML = '';
        }

        // Populate parameters
        const paramsContainer = document.getElementById('scriptParamsContainer');
        paramsContainer.innerHTML = '';
        if (script.parameters) {
            for (const [pname, pdef] of Object.entries(script.parameters)) {
                _addParameterRowWithData(pname, pdef);
            }
        }

        title.textContent = t('scripts.dialog.edit');
        scriptFormDirty = false;

        document.body.classList.add('dialog-open');
        dialog.showModal();
    } catch (error) {
        console.error('Error loading script for edit:', error);
        showToast(t('scripts.msg.load_failed'), 'error');
    }
}

export async function closeScriptDialog() {
    if (scriptFormDirty) {
        if (!await showConfirm(t('scripts.confirm.unsaved'))) {
            return;
        }
    }

    const dialog = document.getElementById('scriptDialog');
    scriptFormDirty = false;
    closeDialog(dialog);
    document.body.classList.remove('dialog-open');
}

export async function saveScript(event) {
    event.preventDefault();

    const submitBtn = event.target.querySelector('button[type="submit"]');
    if (submitBtn) submitBtn.disabled = true;

    const isEdit = document.getElementById('scriptIsEdit').value === 'true';
    const scriptName = isEdit ?
        document.getElementById('scriptOriginalName').value :
        document.getElementById('scriptName').value;

    const data = {
        command: document.getElementById('scriptCommand').value,
        label: document.getElementById('scriptLabel').value || null,
        description: document.getElementById('scriptDescription').value || '',
        icon: document.getElementById('scriptIcon').value || null,
        timeout: parseInt(document.getElementById('scriptTimeout').value) || 30,
        shell: true,
        parameters: _collectParameterDefinitions(),
    };

    const encodedName = encodeURIComponent(scriptName);
    const endpoint = isEdit ?
        `/api/scripts/update/${encodedName}` :
        `/api/scripts/create/${encodedName}`;

    const method = isEdit ? 'PUT' : 'POST';

    try {
        const response = await fetch(endpoint, {
            method,
            headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
            body: JSON.stringify(data)
        });

        const result = await response.json();

        if (response.ok && result.success) {
            showToast(t(isEdit ? 'scripts.msg.updated' : 'scripts.msg.created'), 'success');
            scriptFormDirty = false;
            closeScriptDialog();
        } else {
            showToast(result.detail || t(isEdit ? 'scripts.msg.update_failed' : 'scripts.msg.create_failed'), 'error');
        }
    } catch (error) {
        console.error('Error saving script:', error);
        showToast(t(isEdit ? 'scripts.msg.update_failed' : 'scripts.msg.create_failed'), 'error');
    } finally {
        if (submitBtn) submitBtn.disabled = false;
    }
}

export async function deleteScriptConfirm(scriptName) {
    if (!await showConfirm(t('scripts.confirm.delete').replace('{name}', scriptName))) {
        return;
    }

    try {
        const response = await fetch(`/api/scripts/delete/${encodeURIComponent(scriptName)}`, {
            method: 'DELETE',
            headers: getAuthHeaders()
        });

        const result = await response.json();

        if (response.ok && result.success) {
            showToast(t('scripts.msg.deleted'), 'success');
        } else {
            showToast(result.detail || t('scripts.msg.delete_failed'), 'error');
        }
    } catch (error) {
        console.error('Error deleting script:', error);
        showToast(t('scripts.msg.delete_failed'), 'error');
    }
}

// ============================================================
// Execution Result Dialog (shared by scripts and callbacks)
// ============================================================

export function closeExecutionDialog() {
    const dialog = document.getElementById('executionDialog');
    closeDialog(dialog);
    document.body.classList.remove('dialog-open');
}

function showExecutionResult(name, result, type = 'script') {
    const dialog = document.getElementById('executionDialog');
    const title = document.getElementById('executionDialogTitle');
    const statusDiv = document.getElementById('executionStatus');
    const outputSection = document.getElementById('outputSection');
    const errorSection = document.getElementById('errorSection');
    const outputPre = document.getElementById('executionOutput');
    const errorPre = document.getElementById('executionError');

    title.textContent = `${t('execution.result')}: ${name}`;

    const success = result.success && result.exit_code === 0;
    const statusClass = success ? 'success' : 'error';
    const statusText = t(success ? 'execution.success' : 'execution.failed');

    statusDiv.innerHTML = `
        <div class="status-item ${statusClass}">
            <label>${escapeHtml(t('execution.status'))}</label>
            <value>${escapeHtml(statusText)}</value>
        </div>
        <div class="status-item">
            <label>${escapeHtml(t('execution.exit_code'))}</label>
            <value>${result.exit_code !== undefined ? result.exit_code : 'N/A'}</value>
        </div>
        <div class="status-item">
            <label>${escapeHtml(t('execution.duration'))}</label>
            <value>${result.execution_time !== undefined && result.execution_time !== null ? result.execution_time.toFixed(3) + 's' : 'N/A'}</value>
        </div>
    `;

    outputSection.style.display = 'block';
    if (result.stdout && result.stdout.trim()) {
        outputPre.textContent = result.stdout;
    } else {
        outputPre.textContent = t('execution.no_output');
        outputPre.style.fontStyle = 'italic';
        outputPre.style.color = 'var(--text-secondary)';
    }

    if (result.stderr && result.stderr.trim()) {
        errorSection.style.display = 'block';
        errorPre.textContent = result.stderr;
        errorPre.style.fontStyle = 'normal';
        errorPre.style.color = 'var(--error)';
    } else if (!success && result.error) {
        errorSection.style.display = 'block';
        errorPre.textContent = result.error;
        errorPre.style.fontStyle = 'normal';
        errorPre.style.color = 'var(--error)';
    } else {
        errorSection.style.display = 'none';
    }

    dialog.showModal();
}

export async function executeScriptDebug(scriptName) {
    const paramDefs = _getScriptParams(scriptName);
    if (Object.keys(paramDefs).length > 0) {
        _showParamsInputDialog(scriptName, paramDefs, (params) => _executeScriptDebugWithParams(scriptName, params));
        return;
    }
    await _executeScriptDebugWithParams(scriptName, {});
}

async function _executeScriptDebugWithParams(scriptName, params) {
    const dialog = document.getElementById('executionDialog');
    const title = document.getElementById('executionDialogTitle');
    const statusDiv = document.getElementById('executionStatus');

    title.textContent = `${t('execution.executing')}: ${scriptName}`;
    statusDiv.innerHTML = `
        <div class="status-item">
            <label>${escapeHtml(t('execution.status'))}</label>
            <value><span class="loading-spinner"></span> ${escapeHtml(t('execution.running'))}</value>
        </div>
    `;
    document.getElementById('outputSection').style.display = 'none';
    document.getElementById('errorSection').style.display = 'none';
    document.body.classList.add('dialog-open');
    dialog.showModal();

    try {
        const response = await fetch(`/api/scripts/execute/${scriptName}`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
            body: JSON.stringify({ params })
        });

        const result = await response.json();

        if (response.ok) {
            showExecutionResult(scriptName, result, 'script');
        } else {
            showExecutionResult(scriptName, {
                success: false,
                exit_code: -1,
                error: result.detail || 'Execution failed',
                stderr: result.detail || 'Unknown error'
            }, 'script');
        }
    } catch (error) {
        console.error(`Error executing script ${scriptName}:`, error);
        showExecutionResult(scriptName, {
            success: false,
            exit_code: -1,
            error: error.message,
            stderr: `Network error: ${error.message}`
        }, 'script');
    }
}

// ============================================================
// Parameter Definition Editor (CRUD dialog)
// ============================================================

const PARAM_TYPES = ['string', 'integer', 'float', 'boolean', 'select'];

export function addParameterRow() {
    _addParameterRowWithData('', {});
    scriptFormDirty = true;
}

const _paramTypeItems = PARAM_TYPES.map(pt => ({
    value: pt,
    icon: paramTypeIcons[pt] || '',
    label: pt.charAt(0).toUpperCase() + pt.slice(1),
}));

function _addParameterRowWithData(name, def) {
    const container = document.getElementById('scriptParamsContainer');
    const row = document.createElement('div');
    row.className = 'param-row';

    row.innerHTML = `
        <div class="param-row-header">
            <input type="text" class="param-name" value="${escapeHtml(name)}"
                   placeholder="${t('scripts.params.name_placeholder')}" pattern="[a-zA-Z][a-zA-Z0-9_]*">
            <select class="param-type">
                ${PARAM_TYPES.map(pt => `<option value="${pt}" ${def.type === pt ? 'selected' : ''}>${pt}</option>`).join('')}
            </select>
            <label class="param-required-label" title="${t('scripts.params.required')}">
                <input type="checkbox" class="param-required" ${def.required ? 'checked' : ''}>
                <span>*</span>
            </label>
            <button type="button" class="param-remove-btn" title="${t('scripts.params.remove')}">&times;</button>
        </div>
        <div class="param-row-details">
            <input type="text" class="param-description" value="${escapeHtml(def.description || '')}"
                   placeholder="${t('scripts.params.description_placeholder')}">
            <div class="param-row-extra">
                <input type="text" class="param-default" value="${def.default !== undefined && def.default !== null ? escapeHtml(String(def.default)) : ''}"
                       placeholder="${t('scripts.params.default_placeholder')}">
                <input type="text" class="param-min" value="${def.min !== undefined && def.min !== null ? def.min : ''}"
                       placeholder="Min" style="display:${def.type === 'integer' || def.type === 'float' ? '' : 'none'}">
                <input type="text" class="param-max" value="${def.max !== undefined && def.max !== null ? def.max : ''}"
                       placeholder="Max" style="display:${def.type === 'integer' || def.type === 'float' ? '' : 'none'}">
                <input type="text" class="param-options" value="${def.options ? def.options.join(', ') : ''}"
                       placeholder="${t('scripts.params.options_placeholder')}" style="display:${def.type === 'select' ? '' : 'none'}">
            </div>
        </div>
    `;

    // Enhance the type <select> with icon grid
    const typeSelect = row.querySelector('.param-type');
    const iconSelect = new IconSelect({
        target: typeSelect,
        items: _paramTypeItems,
        columns: 5,
        onChange: () => {
            const isNumeric = typeSelect.value === 'integer' || typeSelect.value === 'float';
            const isSelect = typeSelect.value === 'select';
            row.querySelector('.param-min').style.display = isNumeric ? '' : 'none';
            row.querySelector('.param-max').style.display = isNumeric ? '' : 'none';
            row.querySelector('.param-options').style.display = isSelect ? '' : 'none';
            scriptFormDirty = true;
        },
    });

    row.querySelector('.param-remove-btn').addEventListener('click', () => {
        iconSelect.destroy();
        row.remove();
        scriptFormDirty = true;
    });

    // Mark dirty on any input change
    row.querySelectorAll('input').forEach(el => {
        el.addEventListener('input', () => { scriptFormDirty = true; });
    });

    container.appendChild(row);
}

function _collectParameterDefinitions() {
    const container = document.getElementById('scriptParamsContainer');
    const rows = container.querySelectorAll('.param-row');
    const params = {};

    for (const row of rows) {
        const name = row.querySelector('.param-name').value.trim();
        if (!name) continue;

        const type = row.querySelector('.param-type').value;
        const def = { type };

        const description = row.querySelector('.param-description').value.trim();
        if (description) def.description = description;

        if (row.querySelector('.param-required').checked) def.required = true;

        const defaultVal = row.querySelector('.param-default').value.trim();
        if (defaultVal !== '') {
            if (type === 'integer') def.default = parseInt(defaultVal, 10);
            else if (type === 'float') def.default = parseFloat(defaultVal);
            else if (type === 'boolean') def.default = defaultVal.toLowerCase() === 'true';
            else def.default = defaultVal;
        }

        if (type === 'integer' || type === 'float') {
            const minVal = row.querySelector('.param-min').value.trim();
            const maxVal = row.querySelector('.param-max').value.trim();
            if (minVal !== '') def.min = parseFloat(minVal);
            if (maxVal !== '') def.max = parseFloat(maxVal);
        }

        if (type === 'select') {
            const optStr = row.querySelector('.param-options').value.trim();
            if (optStr) def.options = optStr.split(',').map(s => s.trim()).filter(Boolean);
        }

        params[name] = def;
    }

    return params;
}

export async function executeCallbackDebug(callbackName) {
    const dialog = document.getElementById('executionDialog');
    const title = document.getElementById('executionDialogTitle');
    const statusDiv = document.getElementById('executionStatus');

    title.textContent = `${t('execution.executing')}: ${callbackName}`;
    statusDiv.innerHTML = `
        <div class="status-item">
            <label>${escapeHtml(t('execution.status'))}</label>
            <value><span class="loading-spinner"></span> ${escapeHtml(t('execution.running'))}</value>
        </div>
    `;
    document.getElementById('outputSection').style.display = 'none';
    document.getElementById('errorSection').style.display = 'none';
    document.body.classList.add('dialog-open');
    dialog.showModal();

    try {
        const response = await fetch(`/api/callbacks/execute/${encodeURIComponent(callbackName)}`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json', ...getAuthHeaders() }
        });

        const result = await response.json();

        if (response.ok) {
            showExecutionResult(callbackName, result, 'callback');
        } else {
            showExecutionResult(callbackName, {
                success: false,
                exit_code: -1,
                error: result.detail || 'Execution failed',
                stderr: result.detail || 'Unknown error'
            }, 'callback');
        }
    } catch (error) {
        console.error(`Error executing callback ${callbackName}:`, error);
        showExecutionResult(callbackName, {
            success: false,
            exit_code: -1,
            error: error.message,
            stderr: `Network error: ${error.message}`
        }, 'callback');
    }
}
