// row-ops.js — row-level operations (delete, future: duplicate,
// copy-to-table, etc.). Surfaced via a right-click context menu on
// table rows; the editor's selection state determines which row the
// action targets when the menu is invoked from the keyboard or from a
// future toolbar button.
//
// The shared context-menu primitive (window.zddc.menu) drives the
// rendering and keyboard behaviour. This module owns the menu spec
// and the action handlers.
(function (app) {
    'use strict';

    function findRowById(rowId) {
        const all = (app.state && app.state.rows) || [];
        for (let i = 0; i < all.length; i++) {
            const editor = app.modules.editor;
            const key = editor ? editor.rowKey(all[i]) : (all[i].url || '');
            if (key === rowId) return all[i];
        }
        return null;
    }

    function removeRowFromState(row) {
        const all = app.state.rows || [];
        const idx = all.indexOf(row);
        if (idx >= 0) all.splice(idx, 1);
        // Drop any drafts keyed on the row's url.
        if (app.state.drafts && row.url) {
            delete app.state.drafts[row.url];
        }
    }

    function rowDisplayName(row) {
        if (!row) return '(unknown)';
        if (row.isNew) return '(unsaved new row)';
        if (row.yamlUrl) {
            const m = row.yamlUrl.match(/[^/]+$/);
            if (m) return m[0];
        }
        return row.url || '(row)';
    }

    async function deleteRow(rowId) {
        const row = findRowById(rowId);
        if (!row) return { status: 'noop' };
        if (row.editable === false) return { status: 'readonly' };

        // Unsaved new row: just drop it. Nothing to call.
        if (row.isNew) {
            removeRowFromState(row);
            if (typeof app.repaint === 'function') app.repaint();
            return { status: 'ok-local' };
        }

        if (!row.yamlUrl) {
            // file:// or fixture context — nothing to delete server-side.
            removeRowFromState(row);
            if (typeof app.repaint === 'function') app.repaint();
            return { status: 'ok-local' };
        }

        const ok = window.confirm('Delete row "' + rowDisplayName(row) + '"?\n\nThis cannot be undone.');
        if (!ok) return { status: 'cancelled' };

        const headers = {};
        if (row.etag) headers['If-Match'] = '"' + row.etag + '"';
        let resp;
        try {
            resp = await fetch(row.yamlUrl, {
                method: 'DELETE',
                headers: headers,
                credentials: 'same-origin'
            });
        } catch (err) {
            window.alert('Delete failed: ' + (err && err.message ? err.message : err));
            return { status: 'network-error', error: err };
        }
        if (resp.status === 200 || resp.status === 204) {
            removeRowFromState(row);
            if (typeof app.repaint === 'function') app.repaint();
            return { status: 'ok' };
        }
        if (resp.status === 412) {
            window.alert('Cannot delete: this row was changed since you loaded it. Reload to see the latest version.');
            return { status: 'conflict' };
        }
        let body = '';
        try { body = await resp.text(); } catch (_) { /* ignore */ }
        window.alert('Delete failed (' + resp.status + '): ' + body);
        return { status: 'http-error', code: resp.status };
    }

    // Returns the list of visible-row indices currently included in
    // the editor's range selection. Empty when no range is active.
    function rangeRowIndices() {
        const range = app.state && app.state.range;
        if (!range) return [];
        const r0 = Math.min(range.anchor.row, range.focus.row);
        const r1 = Math.max(range.anchor.row, range.focus.row);
        const out = [];
        for (let r = r0; r <= r1; r++) out.push(r);
        return out;
    }

    // Map a visible-row index to its data-row-id (synthetic or real).
    function rowIdAtIndex(idx) {
        const trs = document.querySelectorAll('#table-root tbody > tr');
        const tr = trs[idx];
        return tr ? tr.getAttribute('data-row-id') : null;
    }

    async function deleteRows(rowIds) {
        if (!rowIds || rowIds.length === 0) return { status: 'noop' };
        if (rowIds.length === 1) return deleteRow(rowIds[0]);
        const ok = window.confirm('Delete ' + rowIds.length + ' rows?\n\nThis cannot be undone.');
        if (!ok) return { status: 'cancelled' };
        // Walk back-to-front so removing by index from state.rows
        // doesn't shift the indices of pending deletes.
        let okCount = 0, failCount = 0;
        for (let i = rowIds.length - 1; i >= 0; i--) {
            const row = findRowById(rowIds[i]);
            if (!row) continue;
            if (row.isNew || !row.yamlUrl) {
                removeRowFromState(row);
                okCount++;
                continue;
            }
            const headers = {};
            if (row.etag) headers['If-Match'] = '"' + row.etag + '"';
            try {
                const resp = await fetch(row.yamlUrl, {
                    method: 'DELETE',
                    headers: headers,
                    credentials: 'same-origin'
                });
                if (resp.status === 200 || resp.status === 204) {
                    removeRowFromState(row);
                    okCount++;
                } else {
                    failCount++;
                }
            } catch (_err) {
                failCount++;
            }
        }
        if (typeof app.repaint === 'function') app.repaint();
        if (failCount > 0) {
            window.alert('Deleted ' + okCount + ' row(s); ' + failCount + ' failed.');
        }
        return { status: 'ok', deleted: okCount, failed: failCount };
    }

    function buildRowMenu(ctx) {
        const rangeRows = ctx.rangeRowIds || [];
        const inRange = rangeRows.length > 1 && rangeRows.indexOf(ctx.rowId) !== -1;
        const targets = inRange ? rangeRows : [ctx.rowId];
        const items = [];

        // Edit row — opens the schema-driven form-mode editor for this
        // row. row.url is the real <…>/<id>.yaml.html form URL (it carries
        // the <party>/ prefix for aggregate rows, so it hits the real
        // per-party path). Disabled on multi-row range and unsaved draft
        // rows (no backing file yet).
        const singleRow = targets.length === 1 ? ctx.row : null;
        const editUrl = singleRow && !singleRow.isNew && singleRow.url ? singleRow.url : null;
        items.push({
            label: 'Edit row',
            icon: '✎',
            disabled: !editUrl,
            action: function () {
                if (editUrl) window.location.href = editUrl;
            }
        });

        items.push({ separator: true });

        const label = targets.length > 1 ? 'Delete ' + targets.length + ' rows' : 'Delete row';
        items.push({
            label: label,
            icon: '🗑',
            danger: true,
            disabled: !ctx.row || ctx.row.editable === false,
            action: function () {
                if (targets.length > 1) deleteRows(targets);
                else deleteRow(targets[0]);
            }
        });

        return items;
    }

    function onRowContext(ev) {
        const tr = ev.target.closest('tr[data-row-id]');
        if (!tr) return;
        const rowId = tr.getAttribute('data-row-id');
        const row = findRowById(rowId);
        if (!row) return;
        ev.preventDefault();
        const menu = window.zddc && window.zddc.menu;
        if (!menu || typeof menu.open !== 'function') return;
        const rangeRowIds = rangeRowIndices().map(rowIdAtIndex).filter(Boolean);
        menu.open({
            x: ev.clientX,
            y: ev.clientY,
            items: buildRowMenu({ row: row, rowId: rowId, rangeRowIds: rangeRowIds }),
            context: { row: row, rowId: rowId, rangeRowIds: rangeRowIds }
        });
    }

    function attach() {
        const tbody = document.querySelector('#table-root tbody');
        if (!tbody) return;
        tbody.addEventListener('contextmenu', onRowContext);
    }

    app.modules.rowOps = {
        attach: attach,
        deleteRow: deleteRow,
        deleteRows: deleteRows,
    };
})(window.tablesApp);
