/// <summary>
/// Shared inline-edit behaviour for quote, job, and invoice item rows.
/// Activated when the page sets window.inlineItemEdit = { patchUrl, canEdit, totals }.
/// totals: { subtotal, tax, total, finalPrice, balance } — CSS selectors, any subset.
/// Config is read inside DOMContentLoaded so script load order vs. config assignment doesn't matter.
/// </summary>
(function () {
    'use strict';

    function fmt(val) {
        return val.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
    }

    function csrfToken() {
        return document.querySelector('input[name="__RequestVerificationToken"]')?.value ?? '';
    }

    function showError(msg) {
        const el = document.createElement('div');
        el.className = 'alert alert-danger alert-permanent position-fixed bottom-0 end-0 m-3 shadow';
        el.style.zIndex = '9999';
        el.textContent = msg;
        document.body.appendChild(el);
        setTimeout(() => el.remove(), 4000);
    }

    // cfg.totals maps response-property-name → CSS selector.
    // Each key must exactly match a property returned by the server's PatchItem action.
    function updateTotals(cfg, data) {
        const t = cfg.totals || {};
        Object.entries(t).forEach(([key, selector]) => {
            const val = data[key];
            if (selector && val !== undefined && val !== null) {
                document.querySelectorAll(selector).forEach(el => { el.textContent = fmt(val); });
            }
        });
    }

    function attachListeners(cfg, span) {
        span.style.cursor = 'text';
        span.title = 'Click to edit';
        span.classList.add('inline-editable');
        span.addEventListener('click', () => makeEditable(cfg, span), { once: true });
    }

    function makeEditable(cfg, span) {
        const field = span.dataset.inlineField;
        const row   = span.closest('tr[data-item-id]');
        if (!row) return;
        const itemId = row.dataset.itemId;

        const rawVal = span.dataset.rawValue ?? span.textContent.trim();

        const input = document.createElement('input');
        input.className = 'form-control form-control-sm inline-edit-input';
        if (field === 'description') {
            input.type      = 'text';
            input.style.minWidth = '140px';
        } else {
            input.type  = 'number';
            input.step  = '0.01';
            input.min   = '0';
            input.style.width = '80px';
        }
        input.value = rawVal;

        // Stash current rendered markup so we can revert
        const savedHTML = span.innerHTML;
        span.innerHTML = '';
        span.appendChild(input);
        input.focus();
        input.select();

        let committed = false;

        function revert() {
            span.innerHTML = savedHTML;
            attachListeners(cfg, span);
        }

        async function commit() {
            if (committed) return;
            committed = true;

            const newVal = input.value.trim();
            if (newVal === '' || (field !== 'description' && isNaN(parseFloat(newVal)))) {
                revert();
                return;
            }

            // Read sibling raw values from other editable cells in the same row
            function siblingRaw(f) {
                const s = row.querySelector(`[data-inline-field="${f}"]`);
                if (!s) return null;
                // If that sibling is currently showing an input (concurrent edit, unlikely), fall back
                const inp = s.querySelector('input.inline-edit-input');
                if (inp) return inp.value;
                return s.dataset.rawValue ?? s.textContent.trim();
            }

            const description = field === 'description' ? newVal : (siblingRaw('description') ?? '');
            const quantity    = parseFloat(field === 'quantity'  ? newVal : (siblingRaw('quantity')  ?? '1'));
            const unitPrice   = parseFloat(field === 'unitPrice' ? newVal : (siblingRaw('unitPrice') ?? '0'));

            try {
                const resp = await fetch(cfg.patchUrl, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'RequestVerificationToken': csrfToken()
                    },
                    body: JSON.stringify({ itemId: parseInt(itemId, 10), description, quantity, unitPrice })
                });
                if (!resp.ok) {
                    const err = await resp.json().catch(() => ({}));
                    showError(err.error ?? 'Could not save &mdash; try again.');
                    revert();
                    return;
                }
                const data = await resp.json();

                // Update this span's display and stored raw value
                if (field === 'description') {
                    const strong = span.querySelector('strong');
                    if (strong) { strong.textContent = newVal; }
                    else        { span.innerHTML = `<strong>${newVal}</strong>`; }
                    span.dataset.rawValue = newVal;
                } else if (field === 'quantity') {
                    span.dataset.rawValue = quantity;
                    span.textContent = quantity % 1 === 0 ? quantity.toFixed(0) : quantity.toString();
                } else if (field === 'unitPrice') {
                    span.dataset.rawValue = unitPrice;
                    span.textContent = fmt(unitPrice);
                }

                // Update line total cell
                const totalCell = row.querySelector('[data-line-total]');
                if (totalCell) totalCell.textContent = fmt(data.lineTotal);

                // Update document-level totals
                updateTotals(cfg, data);

                // Optional post-save hook (e.g. refresh a dependent card)
                if (typeof cfg.afterSave === 'function') cfg.afterSave(data);

                // Re-attach click listener for next edit
                attachListeners(cfg, span);

            } catch {
                showError('Could not save &mdash; check your connection and try again.');
                revert();
            }
        }

        input.addEventListener('blur', commit);
        input.addEventListener('keydown', e => {
            if (e.key === 'Enter')  { e.preventDefault(); input.blur(); }
            if (e.key === 'Escape') { committed = true; revert(); }
        });
    }

    // Read config inside DOMContentLoaded — by then the inline <script> that sets
    // window.inlineItemEdit has already executed regardless of script load order.
    document.addEventListener('DOMContentLoaded', function () {
        const cfg = window.inlineItemEdit;
        if (!cfg || !cfg.canEdit) return;
        document.querySelectorAll('[data-inline-field]').forEach(span => attachListeners(cfg, span));
    });
})();
