/**
 * ZipWP MCP — Vibe Editing v2: shared editor utilities.
 *
 * The ONE source of truth for helpers the editor handlers (get-context,
 * apply-change, get/set-scripts, get/set-styles) need identically — so they can
 * never drift:
 *   - currentPostId: the live post id from the editor store, with the same
 *     null-guard. apply-change uses it for the two-tab post_id guard;
 *     get-context returns it in the response.
 *   - editorSelect / editorDispatch: the null-guarded `core/editor` store
 *     select/dispatch accessors. styles read the editing session through these
 *     (getEditedPostAttribute / editPost).
 *   - currentMeta: the live post meta object from the session (or {}), the
 *     read side of the session-scoped styles write.
 *   - blockEditorSelect / blockEditorDispatch / rootClientId: the null-guarded
 *     `core/block-editor` store accessors + the page root container's clientId.
 *     get/set-scripts read/patch a block's `spectraCustomJS` attribute through
 *     these (getBlockAttributes / updateBlockAttributes).
 *   - bannedVisualAttrs / isBannedVisualAttr: the 8 GBS-banned per-block visual
 *     styling props (style, *Color(Hover), boxShadow(Hover), styleAttributes) —
 *     styling lives in `className` ONLY (the Spectra GBS JIT grammar). apply-change
 *     strips them from an incoming attrs write; get-context omits them from the
 *     authorable attr-keys it advertises. ONE list so the two can never drift
 *     (mirrors Laravel's StrictAttrValidator). The 8 props are a sanctioned
 *     constant, not an allowlist — every OTHER attr is decided by the registry.
 *
 * Dual-mode: attaches to window.zipwpEditorShared in the browser; CommonJS
 * export for jest.
 *
 * Load order: enqueued before the editor handlers in source mode
 * (react-manager.php) and concatenated ahead of handler.js in the production
 * grunt bundle (matches the `tools/**\/*-utils.js` glob). The handlers also
 * resolve it lazily at call time, so a missing global degrades consistently
 * (post id -> null = the two-tab guard no-ops, exactly as a null
 * getCurrentPostId() would).
 *
 * @package zip-ai
 */
(function () {
    'use strict';

    function editorSelect() {
        return (typeof window !== 'undefined' && window.wp && window.wp.data && window.wp.data.select)
            ? window.wp.data.select('core/editor')
            : null;
    }

    function editorDispatch() {
        return (typeof window !== 'undefined' && window.wp && window.wp.data && window.wp.data.dispatch)
            ? window.wp.data.dispatch('core/editor')
            : null;
    }

    function currentPostId() {
        var editor = editorSelect();
        return editor && typeof editor.getCurrentPostId === 'function'
            ? editor.getCurrentPostId()
            : null;
    }

    // The live post meta object from the editing session (or {} when absent) —
    // the read side of the session-scoped scripts/styles write.
    function currentMeta(sel) {
        var meta = sel && sel.getEditedPostAttribute ? sel.getEditedPostAttribute('meta') : null;
        return meta && typeof meta === 'object' ? meta : {};
    }

    // The null-guarded `core/block-editor` store — the read/write side of a
    // block's attributes (get/set-scripts operate on `spectraCustomJS`, an attr).
    // Edits through it are inherently session-scoped: in-memory now, persisted on
    // Save, discarded with the session (same model apply-change uses).
    function blockEditorSelect() {
        return (typeof window !== 'undefined' && window.wp && window.wp.data && window.wp.data.select)
            ? window.wp.data.select('core/block-editor')
            : null;
    }
    function blockEditorDispatch() {
        return (typeof window !== 'undefined' && window.wp && window.wp.data && window.wp.data.dispatch)
            ? window.wp.data.dispatch('core/block-editor')
            : null;
    }

    // The page root container's clientId — the first top-level block — the
    // default owner for page-wide JS when no clientId is passed. null when the
    // tree is empty or the store is absent.
    function rootClientId(sel) {
        var order = sel && sel.getBlockOrder ? sel.getBlockOrder() : null;
        return Array.isArray(order) && order.length > 0 ? order[0] : null;
    }

    // The 8 GBS-banned per-block visual styling props. Per-block styling lives in
    // `className` ONLY (the Spectra GBS JIT grammar); these are never authorable
    // attrs even when a block's registry declares them. A sanctioned constant —
    // every OTHER attribute's validity is decided by the registry (getBlockType).
    var bannedVisualAttrs = [
        'style', 'styleAttributes',
        'backgroundColor', 'backgroundColorHover',
        'boxShadow', 'boxShadowHover',
        'textColor', 'textColorHover',
    ];
    function isBannedVisualAttr(key) {
        return bannedVisualAttrs.indexOf(key) !== -1;
    }

    if (typeof window !== 'undefined') {
        window.zipwpEditorShared = window.zipwpEditorShared || {};
        window.zipwpEditorShared.currentPostId = currentPostId;
        window.zipwpEditorShared.editorSelect = editorSelect;
        window.zipwpEditorShared.editorDispatch = editorDispatch;
        window.zipwpEditorShared.currentMeta = currentMeta;
        window.zipwpEditorShared.blockEditorSelect = blockEditorSelect;
        window.zipwpEditorShared.blockEditorDispatch = blockEditorDispatch;
        window.zipwpEditorShared.rootClientId = rootClientId;
        window.zipwpEditorShared.bannedVisualAttrs = bannedVisualAttrs;
        window.zipwpEditorShared.isBannedVisualAttr = isBannedVisualAttr;
    }
    if (typeof module !== 'undefined' && module.exports) {
        module.exports = {
            currentPostId: currentPostId,
            editorSelect: editorSelect,
            editorDispatch: editorDispatch,
            currentMeta: currentMeta,
            blockEditorSelect: blockEditorSelect,
            blockEditorDispatch: blockEditorDispatch,
            rootClientId: rootClientId,
            bannedVisualAttrs: bannedVisualAttrs,
            isBannedVisualAttr: isBannedVisualAttr,
        };
    }
})();
