(function () {
    function joinPath(parent, name) {
        if (parent === '/' || parent === '') return '/' + name;
        return parent.replace(/\/+$/, '') + '/' + name;
    }

    function encodePath(p) {
        if (p === '/' || p === '') return '/';
        return '/' + p.replace(/^\/+/, '').split('/').map(encodeURIComponent).join('/') + '/';
    }

    function fetchFolder(path) {
        return fetch(encodePath(path) + '?tree=1', { credentials: 'same-origin' })
            .then(function (r) {
                if (!r.ok) throw new Error('HTTP ' + r.status);
                return r.json();
            });
    }

    function openTreePicker(opts) {
        opts = opts || {};
        var mode = opts.mode || 'folder';
        var initialPath = opts.initialPath || '/';
        var allowRoot = opts.allowRoot !== false;
        var hideFiles = !!opts.hideFiles;
        var preselect = opts.preselect || null;

        var container = document.createElement('div');

        var treeEl = document.createElement('div');
        treeEl.className = 'tree-picker panel';

        var selectedPathEl = document.createElement('div');
        selectedPathEl.className = 'tree-selected-path muted';
        selectedPathEl.textContent = '\u00a0';

        container.appendChild(treeEl);
        container.appendChild(selectedPathEl);

        var selected = null; // { path, kind, rowEl }

        var handle = openModal({
            title: opts.title || 'Pick',
            body: container,
            confirm: {
                label: opts.confirmLabel || 'SELECT',
                initiallyDisabled: true,
                onConfirm: function () {
                    if (!selected) return;
                    handle.close();
                    if (opts.onSelect) opts.onSelect(selected.path, selected.kind);
                }
            }
        });

        function setSelected(path, kind, rowEl) {
            if (selected && selected.rowEl) selected.rowEl.classList.remove('is-selected');
            selected = { path: path, kind: kind, rowEl: rowEl };
            rowEl.classList.add('is-selected');
            selectedPathEl.textContent = path;
            handle.setConfirmDisabled(false);
        }

        function isSelectable(kind) {
            if (mode === 'any') return true;
            if (mode === 'folder') return kind === 'folder';
            if (mode === 'file') return kind === 'file';
            return false;
        }

        // buildRow returns { rowEl, expand(): Promise }. `expand` is a no-op
        // for files and idempotent for folders (resolves with the already-
        // loaded children on repeat calls).
        function buildRow(parentPath, name, kind) {
            var row = document.createElement('div');
            row.className = 'tree-row';
            if (!isSelectable(kind)) row.classList.add('is-disabled');

            var chevron = document.createElement('span');
            chevron.className = 'tree-chevron';
            if (kind === 'folder') {
                chevron.textContent = '\u25b8'; // ▸
            } else {
                chevron.classList.add('is-leaf');
            }
            row.appendChild(chevron);

            var label = document.createElement('span');
            label.className = 'tree-name';
            label.textContent = name;
            row.appendChild(label);

            var fullPath = joinPath(parentPath, name);
            var childrenEl = null;
            var loadPromise = null;
            var isOpen = false;

            function expand() {
                if (kind !== 'folder') return Promise.resolve(null);
                if (isOpen) return loadPromise || Promise.resolve(childrenEl);
                if (!childrenEl) {
                    childrenEl = document.createElement('div');
                    childrenEl.className = 'tree-children';
                }
                row.parentNode.insertBefore(childrenEl, row.nextSibling);
                chevron.textContent = '\u25be'; // ▾
                isOpen = true;
                if (!loadPromise) {
                    loadPromise = loadInto(fullPath, childrenEl);
                }
                return loadPromise;
            }

            function collapse() {
                if (kind !== 'folder' || !isOpen) return;
                if (childrenEl && childrenEl.parentNode) {
                    childrenEl.parentNode.removeChild(childrenEl);
                }
                chevron.textContent = '\u25b8';
                isOpen = false;
            }

            chevron.addEventListener('click', function (e) {
                e.stopPropagation();
                if (isOpen) collapse(); else expand();
            });

            row.addEventListener('click', function () {
                if (isSelectable(kind)) {
                    setSelected(fullPath, kind, row);
                } else if (kind === 'folder') {
                    if (isOpen) collapse(); else expand();
                }
            });

            if (kind === 'folder') {
                row.addEventListener('dblclick', function (e) {
                    e.preventDefault();
                    if (isOpen) collapse(); else expand();
                });
            }

            return {
                rowEl: row,
                name: name,
                kind: kind,
                childrenEl: function () { return childrenEl; },
                expand: expand
            };
        }

        // loadInto fetches folderPath and populates `target` with rows. Returns
        // an array of the row objects on success, or [] on failure.
        function loadInto(folderPath, target) {
            target.textContent = '';
            var loading = document.createElement('div');
            loading.className = 'tree-row is-disabled';
            loading.textContent = '\u2026';
            target.appendChild(loading);

            return fetchFolder(folderPath).then(function (resp) {
                target.textContent = '';
                var rows = [];
                (resp.entries || []).forEach(function (e) {
                    if (hideFiles && e.kind !== 'folder') return;
                    var r = buildRow(resp.path, e.name, e.kind);
                    target.appendChild(r.rowEl);
                    rows.push(r);
                });
                if (rows.length === 0) {
                    var empty = document.createElement('div');
                    empty.className = 'tree-row is-disabled';
                    empty.textContent = '(empty)';
                    target.appendChild(empty);
                }
                return rows;
            }).catch(function () {
                target.textContent = '';
                var err = document.createElement('div');
                err.className = 'tree-row';
                err.textContent = '(failed — tap to retry)';
                err.addEventListener('click', function () {
                    loadInto(folderPath, target);
                });
                target.appendChild(err);
                return [];
            });
        }

        // Root selection row — visible when allowRoot and mode accepts folders.
        if (allowRoot && isSelectable('folder')) {
            var rootRow = document.createElement('div');
            rootRow.className = 'tree-row';
            var rootChev = document.createElement('span');
            rootChev.className = 'tree-chevron is-leaf';
            rootRow.appendChild(rootChev);
            var rootLabel = document.createElement('span');
            rootLabel.className = 'tree-name';
            rootLabel.textContent = '/';
            rootRow.appendChild(rootLabel);
            rootRow.addEventListener('click', function () {
                setSelected('/', 'folder', rootRow);
            });
            treeEl.appendChild(rootRow);
        }

        var rootChildren = document.createElement('div');
        treeEl.appendChild(rootChildren);

        // Expand the ancestor chain of initialPath so the user lands in
        // context. For root, just load root children.
        var segs = (initialPath || '/').split('/').filter(Boolean);
        var preselectSegs = preselect
            ? preselect.split('/').filter(Boolean)
            : null;

        loadInto('/', rootChildren).then(function (rows) {
            return expandChain(rows, segs, '/');
        }).then(function () {
            if (preselectSegs === null) return;
            if (preselectSegs.length === 0) {
                // Preselect root itself (if allowed).
                if (allowRoot && isSelectable('folder')) {
                    var root = treeEl.querySelector('.tree-row');
                    if (root) setSelected('/', 'folder', root);
                }
                return;
            }
            selectByPath(preselectSegs);
        });

        // expandChain walks `segments`, looking up each by name in the current
        // row list, expanding it, and recursing into its children.
        function expandChain(rows, segments, basePath) {
            if (segments.length === 0) return Promise.resolve();
            var seg = segments[0];
            var match = null;
            for (var i = 0; i < rows.length; i++) {
                if (rows[i].kind === 'folder' && rows[i].name === seg) {
                    match = rows[i];
                    break;
                }
            }
            if (!match) return Promise.resolve();
            return match.expand().then(function (childRows) {
                return expandChain(childRows || [], segments.slice(1), joinPath(basePath, seg));
            });
        }

        // selectByPath walks the visible tree rows to locate the row matching
        // `segments` and marks it selected. Assumes its ancestors are already
        // expanded (expandChain ran first).
        function selectByPath(segments) {
            var container = rootChildren;
            var path = '/';
            for (var i = 0; i < segments.length; i++) {
                var seg = segments[i];
                var kids = container.children;
                var found = null;
                for (var j = 0; j < kids.length; j++) {
                    var row = kids[j];
                    if (!row.classList || !row.classList.contains('tree-row')) continue;
                    var nm = row.querySelector('.tree-name');
                    if (nm && nm.textContent === seg) { found = row; break; }
                }
                if (!found) return;
                path = joinPath(path, seg);
                if (i === segments.length - 1) {
                    if (isSelectable('folder')) setSelected(path, 'folder', found);
                    try { found.scrollIntoView({ block: 'nearest' }); } catch (e) {}
                    return;
                }
                var next = found.nextSibling;
                if (!next || !next.classList || !next.classList.contains('tree-children')) return;
                container = next;
            }
        }

        return handle;
    }

    window.openTreePicker = openTreePicker;
})();
