// events.js — wires up DOM listeners. Idempotent so app.js can call
// init() once on load.
(function () {
    'use strict';

    var state = window.app.state;
    var tree = window.app.modules.tree;
    var loader = window.app.modules.loader;
    // preview module is loaded later (concat order); look it up at
    // call time, not at IIFE-eval time.
    function previewMod() { return window.app.modules.preview; }

    function status(msg, kind) {
        var el = document.getElementById('statusBar');
        if (!el) return;
        el.textContent = msg || '';
        el.classList.remove('status-bar--error', 'status-bar--info');
        if (kind === 'error') el.classList.add('status-bar--error');
        if (kind === 'info')  el.classList.add('status-bar--info');
    }

    function statusError(msg) { status(msg, 'error'); }
    function statusInfo(msg)  { status(msg, 'info'); }
    function statusClear()    { status('', null); }

    async function pickLocalDir() {
        if (typeof window.showDirectoryPicker !== 'function') {
            statusError('Your browser does not support local folder selection. Use a recent Chromium-based browser, or open this page via zddc-server.');
            return;
        }
        var handle;
        try {
            handle = await window.showDirectoryPicker({ mode: 'read' });
        } catch (e) {
            // User cancelled — silent
            return;
        }
        state.source = 'fs';
        state.rootHandle = handle;
        state.currentPath = handle.name + '/';
        var raw;
        try {
            raw = await loader.fetchFsChildren(handle);
        } catch (e) {
            statusError('Failed to read directory: ' + e.message);
            return;
        }
        tree.setRoot(raw);
        showBrowseRoot();
        tree.render();
        statusInfo('Loaded ' + raw.length + ' item' + (raw.length === 1 ? '' : 's'));
    }

    function showBrowseRoot() {
        var empty = document.getElementById('emptyState');
        var root = document.getElementById('browseRoot');
        if (empty) empty.classList.add('hidden');
        if (root) root.classList.remove('hidden');
        applySourceUI();
    }

    // Visual state of the "Select Directory" button + the refresh
    // button depends on the source. In server mode the user is
    // already viewing a server-backed listing — Select Directory
    // becomes a quiet "switch to local" affordance (subtle styling),
    // and the refresh button is shown. In FS mode the button is
    // primary (it's how you got here) and refresh is hidden (the
    // listing was already a fresh enumeration).
    function applySourceUI() {
        var add = document.getElementById('addDirectoryBtn');
        var refresh = document.getElementById('refreshHeaderBtn');
        if (add) {
            if (state.source === 'server') {
                add.classList.remove('btn-primary');
                add.classList.add('btn--subtle');
            } else {
                add.classList.add('btn-primary');
                add.classList.remove('btn--subtle');
            }
        }
        if (refresh) {
            if (state.source) {
                refresh.classList.remove('hidden');
            } else {
                refresh.classList.add('hidden');
            }
        }
    }

    async function refreshListing() {
        if (state.source === 'server') {
            var raw;
            try {
                raw = await loader.fetchServerChildren(state.currentPath);
            } catch (e) {
                statusError('Refresh failed: ' + e.message);
                return;
            }
            tree.setRoot(raw);
            tree.render();
            statusInfo('Refreshed (' + raw.length + ' item'
                + (raw.length === 1 ? '' : 's') + ')');
        } else if (state.source === 'fs' && state.rootHandle) {
            var raw2;
            try {
                raw2 = await loader.fetchFsChildren(state.rootHandle);
            } catch (e) {
                statusError('Refresh failed: ' + e.message);
                return;
            }
            tree.setRoot(raw2);
            tree.render();
            statusInfo('Refreshed');
        }
    }

    function init() {
        // Header buttons
        var btn = document.getElementById('addDirectoryBtn');
        if (btn) btn.addEventListener('click', pickLocalDir);

        var refresh = document.getElementById('refreshHeaderBtn');
        if (refresh) refresh.addEventListener('click', refreshListing);

        // Sort dropdown — change → tree re-renders with the new sort.
        // Format of option value: "<key>:<asc|desc>". Defaults match
        // state.sort initial values (name:asc).
        var sortSel = document.getElementById('sortBy');
        if (sortSel) {
            sortSel.value = state.sort.key + ':' + (state.sort.dir > 0 ? 'asc' : 'desc');
            sortSel.addEventListener('change', function () {
                var parts = sortSel.value.split(':');
                var key = parts[0];
                var dir = parts[1] === 'desc' ? -1 : 1;
                tree.setSortExplicit(key, dir);
            });
        }

        // No view-mode buttons; mode is derived from the URL on every
        // scope change (resolveViewMode below). Pass-through for the
        // initial path.
        applyResolvedViewMode();

        // Pop-out preview button — opens the current preview in a separate window.
        var popout = document.getElementById('previewPopout');
        if (popout) popout.addEventListener('click', function () {
            var p = previewMod();
            if (p && state.lastPreviewedNodeId != null) {
                var n = state.nodes.get(state.lastPreviewedNodeId);
                if (n) p.showFilePreview(n, { popup: true });
            }
        });

        // Pane resizer (tree pane width). Drag horizontally; clamps to
        // [180, 60% of viewport]. State stays in-memory only — refresh
        // resets to the default 360px.
        var resizer = document.querySelector('.pane-resizer[data-resizer-for="tree-pane"]');
        var treePane = document.getElementById('treePane');
        if (resizer && treePane) {
            var dragging = false;
            var startX = 0;
            var startWidth = 0;
            resizer.addEventListener('mousedown', function (e) {
                dragging = true;
                resizer.classList.add('is-dragging');
                startX = e.clientX;
                startWidth = treePane.getBoundingClientRect().width;
                e.preventDefault();
            });
            document.addEventListener('mousemove', function (e) {
                if (!dragging) return;
                var dx = e.clientX - startX;
                var w = Math.max(180, Math.min(window.innerWidth * 0.6, startWidth + dx));
                treePane.style.width = w + 'px';
            });
            document.addEventListener('mouseup', function () {
                if (!dragging) return;
                dragging = false;
                resizer.classList.remove('is-dragging');
            });
        }

        // Tree-row clicks (event delegation on the tree body).
        // Click semantics on a folder row:
        //   - plain click  → toggle expand (deferred so dblclick wins)
        //   - shift-click  → recursive expand/collapse of the subtree
        //   - alt-click    → ALSO recursive
        //   - dblclick     → navigate into the folder
        // File rows: plain click → preview in right pane; modifier-click
        // and middle-click open in new tab.
        //
        // The plain-click toggle for folders is intentionally deferred
        // via setTimeout. Reason: toggling re-renders the tree, which
        // replaces the clicked row element. The browser detects a
        // double-click only when the second click lands on the same
        // target element as the first; replacing the row breaks that
        // continuity and the dblclick event never fires. The deferred
        // toggle lets a pending dblclick cancel it.
        var pendingFolderToggle = null;
        var treeBody = document.getElementById('treeBody');
        if (treeBody) {
            treeBody.addEventListener('click', function (e) {
                var row = e.target.closest('.tree-row');
                if (!row) return;
                var id = parseInt(row.dataset.id, 10);
                var node = state.nodes.get(id);
                if (!node) return;

                var isExpandable = row.dataset.isdir === 'true' || row.dataset.iszip === 'true';

                if (isExpandable) {
                    e.preventDefault();
                    if (e.shiftKey || e.altKey) {
                        // Modifier-click skips the dblclick race — it's
                        // an explicit recursive toggle, never followed
                        // by a dblclick.
                        if (node.expanded) tree.collapseSubtree(id);
                        else tree.expandSubtree(id);
                        return;
                    }
                    // ZIPs don't navigate-into; toggle immediately.
                    if (row.dataset.iszip === 'true') {
                        tree.toggleFolder(id);
                        return;
                    }
                    // Folder: defer the toggle so a pending dblclick
                    // can pre-empt it.
                    if (pendingFolderToggle) {
                        clearTimeout(pendingFolderToggle.timer);
                    }
                    pendingFolderToggle = {
                        id: id,
                        timer: setTimeout(function () {
                            pendingFolderToggle = null;
                            tree.toggleFolder(id);
                        }, 220)
                    };
                    return;
                }

                // File row: modifier-click → open URL in new tab if
                // available (server mode preserves the original URL,
                // useful for direct download / sharing).
                if (e.ctrlKey || e.metaKey || e.shiftKey || e.button === 1) {
                    if (node.url) window.open(node.url, '_blank', 'noopener');
                    return;
                }
                // Plain click → preview in the right pane.
                e.preventDefault();
                state.selectedId = id;
                state.lastPreviewedNodeId = id;
                tree.render(); // refresh selection highlight
                var p = previewMod();
                if (p) p.showFilePreview(node);
            });

            // Double-click on a folder → "navigate into" it. Distinct
            // from single-click (which expands inline) so users keep
            // both UX models. Server mode jumps to the folder URL —
            // zddc-server returns a fresh browse instance scoped to
            // that directory. FS-API mode swaps state.rootHandle to
            // the folder's handle and re-loads, so the user sees
            // only that subtree at the root level.
            //
            // Files: dblclick is left alone — the single-click preview
            // is already a "look at this file" action; a separate
            // navigate-into doesn't apply.
            // ZIPs: skipped too — they're inspected via inline
            // expansion (JSZip), not navigated into.
            treeBody.addEventListener('dblclick', function (e) {
                var row = e.target.closest('.tree-row');
                if (!row) return;
                if (row.dataset.isdir !== 'true') return;
                var id = parseInt(row.dataset.id, 10);
                var node = state.nodes.get(id);
                if (!node) return;
                e.preventDefault();
                // Pre-empt the deferred single-click toggle so the user
                // doesn't see a flicker of expand/collapse before nav.
                if (pendingFolderToggle) {
                    clearTimeout(pendingFolderToggle.timer);
                    pendingFolderToggle = null;
                }
                navigateIntoFolder(node);
            });
        }
    }

    // View mode is URL-driven, not UI-driven.
    //
    //   ?view=grid    → grid mode (only honored where classifier is
    //                   available; otherwise falls back to browse)
    //   ?view=browse  → browse mode (always)
    //   default       → path-based: grid when inside an incoming/
    //                   subtree, browse everywhere else
    //
    // resolveViewMode reads the current location and returns the mode
    // to render; applyResolvedViewMode toggles the panes accordingly.
    // Called on initial load and on every client-side rescope.
    function resolveViewMode() {
        var qs = new URLSearchParams(window.location.search);
        var explicit = (qs.get('view') || '').toLowerCase();
        var grid = window.app.modules.grid;
        var classifierHere = !!(grid && grid.availableHere && grid.availableHere());
        if (explicit === 'grid') return classifierHere ? 'grid' : 'browse';
        if (explicit === 'browse') return 'browse';
        return classifierHere ? 'grid' : 'browse';
    }

    function applyResolvedViewMode() {
        var mode = resolveViewMode();
        state.viewMode = mode;
        var browseView = document.getElementById('browseView');
        var gridView = document.getElementById('gridView');
        if (mode === 'grid') {
            if (browseView) browseView.classList.add('hidden');
            if (gridView) gridView.classList.remove('hidden');
            var grid = window.app.modules.grid;
            if (grid) {
                if (grid.reset) grid.reset();
                if (grid.activate) grid.activate();
            }
        } else {
            if (browseView) browseView.classList.remove('hidden');
            if (gridView) gridView.classList.add('hidden');
        }
    }

    async function navigateIntoFolder(node) {
        if (state.source === 'server') {
            // Rescope client-side rather than hard-navigating. A hard
            // nav would let zddc-server's auto-serve kick in and swap
            // us out of browse for canonical folders (e.g. /archive/
            // → archive tool, /staging/ → transmittal). Staying in
            // browse is what the user asked for; pushState keeps the
            // URL bar accurate so a reload would re-load browse at the
            // new scope.
            var url = window.app.modules.tree.pathFor(node);
            if (!url.endsWith('/')) url += '/';
            await rescopeServer(url, node.name);
            return;
        }
        if (state.source === 'fs') {
            if (!node.handle || node.handle.kind !== 'directory') return;
            state.rootHandle = node.handle;
            state.currentPath = node.handle.name + '/';
            var raw;
            try {
                raw = await loader.fetchFsChildren(node.handle);
            } catch (e) {
                statusError('Failed to enter ' + node.name + ': ' + e.message);
                return;
            }
            tree.setRoot(raw);
            tree.render();
            statusInfo('Entered ' + node.name);
        }
    }

    // Client-side rescope for server mode. Updates the URL via
    // history.pushState, fetches the new directory listing, and
    // re-renders the tree from scratch. Page DOES NOT reload.
    async function rescopeServer(url, displayName) {
        var entries;
        try {
            entries = await loader.fetchServerChildren(url);
        } catch (e) {
            statusError('Failed to enter ' + displayName + ': ' + (e.message || e));
            return;
        }
        state.currentPath = url;
        // Selection / preview belong to the old scope; clear them so
        // the new root doesn't carry stale highlight state.
        state.selectedId = null;
        state.lastPreviewedNodeId = null;
        // Virtual canonical folders are emitted by zddc-server itself
        // (so .zddc display: overrides apply uniformly); no client-side
        // merge needed.
        tree.setRoot(entries);
        tree.render();
        // Reset the preview pane so the user sees an "empty selection"
        // state at the new scope instead of the previous file.
        var previewBody = document.getElementById('previewBody');
        if (previewBody) previewBody.innerHTML = '';
        var previewTitle = document.getElementById('previewTitle');
        if (previewTitle) previewTitle.textContent = 'No file selected';
        var previewMeta = document.getElementById('previewMeta');
        if (previewMeta) previewMeta.textContent = '';
        // pushState so the URL bar reflects the new scope. A real
        // reload would re-load browse at this URL (trailing slash →
        // ServeDirectory → embedded browse SPA).
        try {
            history.pushState({ zddcBrowse: true, path: url }, '', url);
        } catch (_e) { /* private browsing edge cases */ }
        statusInfo('Entered ' + displayName);
        // The new scope may have a different default view (grid inside
        // incoming/, browse elsewhere). Re-resolve from the URL now
        // that pushState has updated it.
        applyResolvedViewMode();
    }

    // Public API
    window.app.modules.events = {
        init: init,
        statusError: statusError,
        statusInfo: statusInfo,
        statusClear: statusClear,
        showBrowseRoot: showBrowseRoot,
        applyResolvedViewMode: applyResolvedViewMode
    };
})();
