// undo.js — Phase 5 of editable-cell mode.
//
// Linear command stack, depth 50, session-local. Every successful
// per-cell edit and every bulk operation (paste, fill, delete) push
// a Command onto the stack. Ctrl/Cmd+Z pops the most recent and
// replays the inverse — sets each affected cell's draft buffer
// back to its `oldValue` (or clears the draft when oldValue was
// the row's stored value), then triggers a re-paint and the
// row-blur save flow picks the change up like any other edit.
//
// Why local-only: shared undo across multiple users is conceptually
// broken under last-writer-wins (undoing my edit might revert
// someone else's intervening edit). Every production grid keeps
// undo per-tab; we follow.
//
// Why no redo: minimum viable. Adding redo is a parallel forward
// stack cleared on any new edit. Cheap to add later if users miss
// it.
//
// Command shape:
//   { cells: [ {rowId, field, oldValue, newValue}, ... ] }
//
// One-cell edits push a single-cell Command. Bulk operations push
// one Command with N cells so a single Ctrl+Z reverts the whole
// group.
(function (app) {
    'use strict';

    const STACK_MAX = 50;
    const _stack = [];

    function push(cmd) {
        if (!cmd || !cmd.cells || cmd.cells.length === 0) return;
        _stack.push(cmd);
        if (_stack.length > STACK_MAX) {
            _stack.shift();
        }
    }

    function depth() { return _stack.length; }

    function clear() { _stack.length = 0; }

    function undo() {
        const cmd = _stack.pop();
        if (!cmd || !cmd.cells || cmd.cells.length === 0) return null;

        const editor = app.modules.editor;
        if (!editor) return null;

        for (let i = 0; i < cmd.cells.length; i++) {
            const c = cmd.cells[i];
            // Compare oldValue to the row's stored data — if they
            // match, clear the draft (the user's edit is being
            // reversed back to baseline). Otherwise set draft = old.
            const row = findRow(c.rowId);
            if (!row) continue;
            const stored = app.modules.util.resolveField(row.data, c.field);
            if (sameValue(stored, c.oldValue)) {
                editor.clearDraftField(c.rowId, c.field);
            } else {
                editor.setDraft(c.rowId, c.field, c.oldValue);
            }
        }

        if (typeof app.repaint === 'function') app.repaint();
        return cmd;
    }

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

    function sameValue(a, b) {
        if (a === b) return true;
        if (a == null && b == null) return true;
        if (a == null || b == null) return false;
        if (typeof a === 'object' || typeof b === 'object') {
            try { return JSON.stringify(a) === JSON.stringify(b); }
            catch (_) { return false; }
        }
        return String(a) === String(b);
    }

    // Hotkey: Ctrl+Z (Cmd+Z on macOS). Bound at the document level
    // so the user can undo from anywhere on the page, not just from
    // within a focused cell.
    function onKey(ev) {
        const isMod = ev.ctrlKey || ev.metaKey;
        if (!isMod) return;
        if (ev.key === 'z' || ev.key === 'Z') {
            // Skip when the active element is a text-input-like; we
            // don't want to override the browser's intra-input undo.
            const ae = document.activeElement;
            if (ae && (ae.tagName === 'INPUT' || ae.tagName === 'TEXTAREA' || ae.isContentEditable)) {
                return;
            }
            ev.preventDefault();
            undo();
        }
    }
    document.addEventListener('keydown', onKey);

    app.modules.undo = {
        push: push,
        undo: undo,
        depth: depth,
        clear: clear,
    };
})(window.tablesApp);
