/* ============================================================
   PPphp Designer — Store (single source of truth)
   Holds the full designer model + UI/selection state.
   Pub/sub + undo/redo over model mutations.
   ============================================================ */
(function () {
    'use strict';
    var PPD = window.PPD = window.PPD || {};

    var boot = window.PpDesignerBootstrap || {};

    var state = {
        // server-loaded reference data
        tables: boot.tables || [],
        fieldsByTable: boot.fieldsByTable || {},
        availablePageTypes: boot.availablePageTypes || {},
        pagesConfig: boot.pagesConfig || {},
        // the persisted designer document (single source of truth)
        designer: boot.designer && boot.designer.version ? boot.designer : { version: '1.0', pages: {}, common: {}, active: {} },
        // Misc-module data mirrored here so the Designer reads/writes the SAME
        // store as the Label Editor + Table Settings (single source of truth).
        labels: boot.miscLabels || {},          // { placeholders:{'table.field':v}, tooltips:{…}, … } for labelLang
        labelLang: boot.defaultLang || 'en',
        tableSettings: boot.tableSettings || {}, // { table: { recordsPerPage, defaultSortOrder, … } }
        whereTabs: boot.whereTabs || {},         // { table: [ { name, clause, showCount, hideEmpty } ] } from Query
        // ephemeral UI state (never persisted)
        ui: {
            scope: 'table',          // 'table' | 'common'
            table: '',
            pageType: 'list',
            commonPage: '',
            pageLabel: 'Select a page',
            device: 'desktop',
            leftTab: 'pages',
            selectedId: '',
            selectedPart: '',
            dirty: false,
            zoom: 100
        }
    };

    var undoStack = [];
    var redoStack = [];
    var LIMIT = 60;
    var listeners = {};

    function clone(v) { try { return JSON.parse(JSON.stringify(v)); } catch (e) { return v; } }

    function on(evt, fn) { (listeners[evt] = listeners[evt] || []).push(fn); return function () { off(evt, fn); }; }
    function off(evt, fn) { listeners[evt] = (listeners[evt] || []).filter(function (f) { return f !== fn; }); }
    function emit(evt, payload) { (listeners[evt] || []).slice().forEach(function (fn) { try { fn(payload); } catch (e) { console.error('[PPD] listener', evt, e); } }); }

    /* ---- reference data ---- */
    function tables() { return state.tables; }
    function fieldsFor(table) { return state.fieldsByTable[table] || []; }
    function pageTypesFor(table) { return state.availablePageTypes[table] || ['list', 'add', 'edit', 'view']; }
    function pagesConfigFor(table) { return state.pagesConfig[table] || null; }
    function whereTabsFor(table) { var t = state.whereTabs[table]; return Array.isArray(t) ? t : []; }
    function setPagesConfig(map) { if (map) state.pagesConfig = map; }
    // Register a newly-created page-type (e.g. a custom_<slug> page) so the ribbon
    // surfaces it immediately without a reload; it persists via the saved state.
    function addPageType(table, pt) {
        if (!table || !pt) return;
        var list = state.availablePageTypes[table] = state.availablePageTypes[table] || [];
        if (list.indexOf(pt) < 0) list.push(pt);
    }
    // Drop a page-type (a deleted custom page) from the ribbon + the model.
    function removePageType(table, pt) {
        if (!table || !pt) return;
        var list = state.availablePageTypes[table];
        if (list) { var i = list.indexOf(pt); if (i >= 0) list.splice(i, 1); }
        if (state.designer && state.designer.pages && state.designer.pages[table]) {
            delete state.designer.pages[table][pt];
        }
    }
    function designer() { return state.designer; }

    /* ---- UI ---- */
    function ui(key) { return key ? state.ui[key] : clone(state.ui); }
    function setUi(patch) {
        var changed = {};
        Object.keys(patch).forEach(function (k) {
            if (state.ui[k] !== patch[k]) { state.ui[k] = patch[k]; changed[k] = patch[k]; }
        });
        if (Object.keys(changed).length) emit('ui', changed);
        return clone(state.ui);
    }

    /* ---- dirty / persistence flag ---- */
    function markDirty(on) {
        var v = on !== false;
        if (state.ui.dirty !== v) { state.ui.dirty = v; emit('ui', { dirty: v }); emit('dirty', v); }
    }
    function isDirty() { return state.ui.dirty; }

    /* ---- undo/redo (snapshots of designer.pages + designer.common) ---- */
    function snapshot() {
        return { pages: clone(state.designer.pages), common: clone(state.designer.common) };
    }
    function pushHistory() {
        undoStack.push(snapshot());
        if (undoStack.length > LIMIT) undoStack.shift();
        redoStack.length = 0;
        emit('history', { undo: undoStack.length, redo: redoStack.length });
    }
    function applySnap(snap) {
        state.designer.pages = clone(snap.pages);
        state.designer.common = clone(snap.common);
    }
    function undo() {
        if (!undoStack.length) return;
        redoStack.push(snapshot());
        applySnap(undoStack.pop());
        markDirty(true);
        emit('history', { undo: undoStack.length, redo: redoStack.length });
        emit('model', { fromHistory: true });
    }
    function redo() {
        if (!redoStack.length) return;
        undoStack.push(snapshot());
        applySnap(redoStack.pop());
        markDirty(true);
        emit('history', { undo: undoStack.length, redo: redoStack.length });
        emit('model', { fromHistory: true });
    }
    function canUndo() { return undoStack.length > 0; }
    function canRedo() { return redoStack.length > 0; }

    /* ---- Misc labels (placeholders / tooltips) — synced to misc.json via the
           same endpoints the Label Editor uses. Keyed by 'table.field'. ---- */
    var labelTimers = {};
    function labelGet(section, key) { return (state.labels[section] || {})[key] || ''; }
    function labelSet(section, key, value) {
        if (!state.labels[section]) state.labels[section] = {};
        if (state.labels[section][key] === value) return;
        state.labels[section][key] = value;
        var tk = section + '::' + key;
        clearTimeout(labelTimers[tk]);
        labelTimers[tk] = setTimeout(function () {
            if (PPD.api && PPD.api.miscLabelSave) {
                PPD.api.miscLabelSave(state.labelLang, section, key, value)
                    .catch(function (e) { console.error('[PPD] label save', e); });
            }
        }, 500);
        emit('labels', { section: section, key: key, value: value });
    }

    /* ---- Per-table list settings (records per page, sort order, toggles) —
           synced to misc.json byTable via the same endpoint Table Settings uses. ---- */
    var TABLE_SETTING_DEFAULTS = {
        recordsPerPage: '20', recordsPerPageOptions: '10,20,50,100', defaultSortOrder: 'DESC',
        rowHighlight: true, nextPrevButtons: true, warnLeave: true, loadingBox: false, hideUntilSearch: false
    };
    var tableTimers = {};
    function tableSettings(table) {
        if (!table) return clone(TABLE_SETTING_DEFAULTS);
        return Object.assign({}, TABLE_SETTING_DEFAULTS, state.tableSettings[table] || {});
    }
    function setTableSetting(table, patch) {
        if (!table) return;
        var cur = Object.assign({}, TABLE_SETTING_DEFAULTS, state.tableSettings[table] || {});
        state.tableSettings[table] = Object.assign(cur, patch);
        var data = state.tableSettings[table];
        clearTimeout(tableTimers[table]);
        tableTimers[table] = setTimeout(function () {
            if (PPD.api && PPD.api.miscTableSave) {
                PPD.api.miscTableSave(table, data)
                    .catch(function (e) { console.error('[PPD] table settings save', e); });
            }
        }, 500);
        emit('tableSettings', { table: table, patch: patch });
    }

    /* ---- Per-table LIST behaviour settings (pages.json → byTable[t].settings.list):
           freeze/pin toggle, saved views, group-by, kanban field. Single source of
           truth shared with the Pages module — the Designer edits the same file via
           the existing /pages/save-state endpoint (sends the full merged table state). ---- */
    var listTimers = {};
    function listSettings(table) {
        var cfg = state.pagesConfig[table] || {};
        var ls = (cfg.settings && cfg.settings.list) || {};
        return Object.assign({}, ls);
    }
    function setListSetting(table, patch) {
        if (!table) return;
        var cfg = state.pagesConfig[table];
        if (!cfg || typeof cfg !== 'object') {
            cfg = { pageTypes: ['list'], functionality: {}, keyColumns: [], settings: {} };
        }
        if (!cfg.settings || typeof cfg.settings !== 'object' || Array.isArray(cfg.settings)) cfg.settings = {};
        if (!cfg.settings.list || typeof cfg.settings.list !== 'object' || Array.isArray(cfg.settings.list)) cfg.settings.list = {};
        Object.assign(cfg.settings.list, patch);
        state.pagesConfig[table] = cfg;
        clearTimeout(listTimers[table]);
        listTimers[table] = setTimeout(function () {
            if (PPD.api && PPD.api.pagesSaveState) {
                PPD.api.pagesSaveState(table, cfg).catch(function (e) { console.error('[PPD] list settings save', e); });
            }
        }, 500);
        emit('listSettings', { table: table, patch: patch });
    }

    /* ---- mutate: any model change goes through here (records history + dirty + re-render) ---- */
    function mutate(fn, opts) {
        opts = opts || {};
        if (!opts.noHistory) pushHistory();
        fn(state.designer);
        markDirty(true);
        if (!opts.silent) emit('model');
    }

    PPD.store = {
        clone: clone, on: on, off: off, emit: emit,
        tables: tables, fieldsFor: fieldsFor, pageTypesFor: pageTypesFor, addPageType: addPageType, removePageType: removePageType, pagesConfigFor: pagesConfigFor, whereTabsFor: whereTabsFor, setPagesConfig: setPagesConfig, designer: designer,
        ui: ui, setUi: setUi,
        labelGet: labelGet, labelSet: labelSet, labelLang: function () { return state.labelLang; },
        tableSettings: tableSettings, setTableSetting: setTableSetting,
        listSettings: listSettings, setListSetting: setListSetting,
        markDirty: markDirty, isDirty: isDirty,
        mutate: mutate, undo: undo, redo: redo, canUndo: canUndo, canRedo: canRedo
    };
})();
