/**
 * ZDDC Classifier — workspace persistence (IndexedDB).
 *
 * A "workspace" is one classification project: the picked source directory
 * HANDLE, a SNAPSHOT of its completed scan (folder/file structure — names and
 * paths only, no contents), and the Classify & Copy map (assignments + target
 * trees). Scan once, resume instantly across sessions without re-walking the
 * (often cloud-backed, high-latency) source.
 *
 * Two object stores so the welcome list stays cheap:
 *   - 'index' (tiny):  { id, name, rootName, createdAt, updatedAt, summary }
 *   - 'data'  (large): { id, rootHandle, tree, classify }
 *
 * A FileSystemDirectoryHandle is structured-cloneable, so IndexedDB can hold
 * it; on reuse we re-request permission (one click). It's only needed at COPY
 * time — opening a workspace runs entirely from the snapshot.
 */
(function () {
    'use strict';

    var DB_NAME = 'zddc-classifier';
    var DB_VERSION = 2;
    var IDX = 'index';
    var DATA = 'data';

    var available = typeof indexedDB !== 'undefined';

    function openDB() {
        return new Promise(function (resolve, reject) {
            if (!available) { reject(new Error('IndexedDB unavailable')); return; }
            var req = indexedDB.open(DB_NAME, DB_VERSION);
            req.onupgradeneeded = function () {
                var db = req.result;
                // 'kv' (v1, single implicit map) is intentionally left behind.
                if (!db.objectStoreNames.contains(IDX)) db.createObjectStore(IDX, { keyPath: 'id' });
                if (!db.objectStoreNames.contains(DATA)) db.createObjectStore(DATA, { keyPath: 'id' });
            };
            req.onsuccess = function () { resolve(req.result); };
            req.onerror = function () { reject(req.error); };
        });
    }

    function reqP(req) {
        return new Promise(function (resolve, reject) {
            req.onsuccess = function () { resolve(req.result); };
            req.onerror = function () { reject(req.error); };
        });
    }

    // ── public API ─────────────────────────────────────────────────────────

    // Light metadata for every workspace (for the welcome list). Sorted newest
    // first. Never loads the big snapshot.
    function listWorkspaces() {
        return openDB().then(function (db) {
            return reqP(db.transaction(IDX, 'readonly').objectStore(IDX).getAll());
        }).then(function (rows) {
            (rows || []).sort(function (a, b) { return (b.updatedAt || 0) - (a.updatedAt || 0); });
            return rows || [];
        }).catch(function (e) { console.warn('persist.list', e); return []; });
    }

    // Full data record for one workspace: { id, rootHandle, tree, classify }.
    function getWorkspace(id) {
        return openDB().then(function (db) {
            return reqP(db.transaction(DATA, 'readonly').objectStore(DATA).get(id));
        }).catch(function (e) { console.warn('persist.get', e); return null; });
    }

    // Save (create or update). meta = {id,name,rootName,createdAt,updatedAt,summary};
    // data = {id, rootHandle, tree, classify}. tree may be omitted on a classify-
    // only autosave (the snapshot rarely changes) — then we preserve the stored one.
    function putWorkspace(meta, data) {
        return openDB().then(function (db) {
            return new Promise(function (resolve, reject) {
                var t = db.transaction([IDX, DATA], 'readwrite');
                t.oncomplete = function () { resolve(); };
                t.onerror = function () { reject(t.error); };
                t.objectStore(IDX).put(meta);
                var ds = t.objectStore(DATA);
                if (data && typeof data.tree !== 'undefined') {
                    ds.put(data);
                } else if (data) {
                    // Merge classify/rootHandle without clobbering the snapshot.
                    var g = ds.get(meta.id);
                    g.onsuccess = function () {
                        var existing = g.result || { id: meta.id };
                        if (typeof data.rootHandle !== 'undefined') existing.rootHandle = data.rootHandle;
                        if (typeof data.classify !== 'undefined') existing.classify = data.classify;
                        existing.id = meta.id;
                        ds.put(existing);
                    };
                }
            });
        }).catch(function (e) { console.warn('persist.put', e); });
    }

    function deleteWorkspace(id) {
        return openDB().then(function (db) {
            return new Promise(function (resolve, reject) {
                var t = db.transaction([IDX, DATA], 'readwrite');
                t.oncomplete = function () { resolve(); };
                t.onerror = function () { reject(t.error); };
                t.objectStore(IDX).delete(id);
                t.objectStore(DATA).delete(id);
            });
        }).catch(function (e) { console.warn('persist.delete', e); });
    }

    // Re-acquire read permission on a stored handle (one click). true if usable.
    function verifyPermission(handle, write) {
        if (!handle || typeof handle.queryPermission !== 'function') return Promise.resolve(false);
        var opts = { mode: write ? 'readwrite' : 'read' };
        return handle.queryPermission(opts).then(function (p) {
            if (p === 'granted') return true;
            return handle.requestPermission(opts).then(function (p2) { return p2 === 'granted'; });
        }).catch(function () { return false; });
    }

    window.app.modules.persist = {
        available: available,
        listWorkspaces: listWorkspaces,
        getWorkspace: getWorkspace,
        putWorkspace: putWorkspace,
        deleteWorkspace: deleteWorkspace,
        verifyPermission: verifyPermission,
    };
})();
