// Audit results grid column UX (#23 follow-ups Task 10).
//
// A tiny, dependency-free helper for the AuditResultsGrid component:
//   - drag-to-resize: a pointer-driven handle on each <th>'s right edge,
//   - drag-to-reorder: native HTML5 drag-and-drop on the header row,
//   - save/load: a sessionStorage round-trip, mirroring treeview-storage.js.
//
// The Blazor component owns the column model; this file is purely the
// browser-side drag plumbing. After a resize or reorder it calls back into
// .NET via a DotNetObjectReference so the component can persist + re-render.
//
// No drag-drop libraries — hand-rolled pointer + native-DnD handlers only.
window.auditGrid = {
    // --- sessionStorage wrapper (mirrors window.treeviewStorage) -----------
    // Keys are namespaced under "auditGrid:" so they never collide with the
    // treeview's "treeview:" namespace.
    save: function (key, json) {
        try {
            sessionStorage.setItem("auditGrid:" + key, json);
        } catch {
            // Quota / privacy-mode failures are non-fatal — the grid simply
            // falls back to defaults on the next load.
        }
    },

    load: function (key) {
        try {
            return sessionStorage.getItem("auditGrid:" + key);
        } catch {
            return null;
        }
    },

    // Minimum column width in pixels. A column can never be dragged narrower
    // than this so a header can't collapse to an unclickable sliver.
    minWidth: 64,

    // --- wire-up ----------------------------------------------------------
    // `table` is the <table> element, `dotNet` is a DotNetObjectReference
    // exposing OnColumnResized / OnColumnReordered. Safe to call on every
    // render: it re-scans the header and binds only cells not already bound,
    // and always refreshes the live .NET reference. Handlers read the column
    // key live from data-col-key at event time, so Blazor reusing a <th> DOM
    // node for a different column (after a reorder re-render) is harmless.
    init: function (table, dotNet) {
        if (!table) {
            return;
        }
        table.__auditGridDotNet = dotNet;

        var headerRow = table.tHead && table.tHead.rows[0];
        if (!headerRow) {
            return;
        }

        for (var i = 0; i < headerRow.cells.length; i++) {
            this._bindHeaderCell(table, headerRow.cells[i]);
        }
    },

    // Bind resize + reorder handlers to a single <th>. Idempotent — a cell
    // already carrying handlers is skipped. The handlers resolve the column
    // key live (th.getAttribute) so they stay correct if the renderer reuses
    // the element for another column.
    _bindHeaderCell: function (table, th) {
        var self = this;
        if (th.__auditGridCellBound) {
            return;
        }
        th.__auditGridCellBound = true;

        // --- resize: pointer drag on the handle ---------------------------
        var handle = th.querySelector(".audit-grid-resize-handle");
        if (handle) {
            handle.addEventListener("pointerdown", function (ev) {
                ev.preventDefault();
                // Stop the pointerdown from also starting a header drag.
                ev.stopPropagation();

                var startX = ev.clientX;
                var startWidth = th.getBoundingClientRect().width;
                handle.setPointerCapture(ev.pointerId);
                th.classList.add("resizing");

                function onMove(moveEv) {
                    var next = Math.max(self.minWidth, startWidth + (moveEv.clientX - startX));
                    self._applyWidth(th, next);
                }

                function onUp() {
                    handle.releasePointerCapture(ev.pointerId);
                    handle.removeEventListener("pointermove", onMove);
                    handle.removeEventListener("pointerup", onUp);
                    handle.removeEventListener("pointercancel", onUp);
                    th.classList.remove("resizing");

                    var key = th.getAttribute("data-col-key");
                    var finalWidth = Math.round(th.getBoundingClientRect().width);
                    var dn = table.__auditGridDotNet;
                    if (key && dn) {
                        dn.invokeMethodAsync("OnColumnResized", key, finalWidth);
                    }
                }

                handle.addEventListener("pointermove", onMove);
                handle.addEventListener("pointerup", onUp);
                handle.addEventListener("pointercancel", onUp);
            });
        }

        // --- reorder: native HTML5 drag-and-drop on the header ------------
        // The whole <th> is draggable; dropping it onto another header swaps
        // the dragged column into the drop target's position.
        th.setAttribute("draggable", "true");

        th.addEventListener("dragstart", function (ev) {
            // A resize in progress sets .resizing; never start a reorder then.
            if (th.classList.contains("resizing")) {
                ev.preventDefault();
                return;
            }
            var key = th.getAttribute("data-col-key");
            if (!key) {
                ev.preventDefault();
                return;
            }
            table.__auditGridDragKey = key;
            ev.dataTransfer.effectAllowed = "move";
            // Some browsers require data to be set for the drag to begin.
            try { ev.dataTransfer.setData("text/plain", key); } catch { /* ignore */ }
            th.classList.add("dragging");
        });

        th.addEventListener("dragend", function () {
            th.classList.remove("dragging");
            table.__auditGridDragKey = null;
            self._clearDropTargets(table);
        });

        th.addEventListener("dragover", function (ev) {
            // Allowing the drop is what lets dragover/drop fire at all.
            var key = th.getAttribute("data-col-key");
            if (key && table.__auditGridDragKey && table.__auditGridDragKey !== key) {
                ev.preventDefault();
                ev.dataTransfer.dropEffect = "move";
                th.classList.add("drop-target");
            }
        });

        th.addEventListener("dragleave", function () {
            th.classList.remove("drop-target");
        });

        th.addEventListener("drop", function (ev) {
            ev.preventDefault();
            th.classList.remove("drop-target");
            var key = th.getAttribute("data-col-key");
            var fromKey = table.__auditGridDragKey;
            table.__auditGridDragKey = null;
            if (!key || !fromKey || fromKey === key) {
                return;
            }
            var dn = table.__auditGridDotNet;
            if (dn) {
                // fromKey moves to occupy toKey's slot; the component computes
                // the resulting order and re-renders + persists.
                dn.invokeMethodAsync("OnColumnReordered", fromKey, key);
            }
        });
    },

    // Apply a width to a <th> via a CSS custom property. The scoped stylesheet
    // reads --audit-col-width; absent it, the column falls back to auto.
    //
    // Known, intentional behaviour: during a live resize drag this updates the
    // <th> width immediately, but the <td> body cells only catch up on the next
    // .NET re-render (driven by OnColumnResized at pointer-up). The brief
    // header/body width mismatch mid-drag is an accepted trade-off for an
    // internal tool — not a bug.
    _applyWidth: function (th, widthPx) {
        th.style.setProperty("--audit-col-width", widthPx + "px");
    },

    _clearDropTargets: function (table) {
        var hits = table.querySelectorAll(".drop-target, .dragging");
        for (var i = 0; i < hits.length; i++) {
            hits[i].classList.remove("drop-target", "dragging");
        }
    }
};
