/**
 * TitanCart — Sequential Options PDP gating module
 *
 * S141 (Phase 5 of Sequential Options TitanCart-native).
 *
 * Reads `data-tc-deps` JSON edges off each [data-option-id] wrapper and
 * grey-locks dependent options until their parents reach a satisfying
 * selection. Re-evaluates downstream on every parent change. Defensively
 * clears selections + strips `required` from any option that re-locks.
 *
 * Lock semantics (matches server contract from S140 Phase 1 schema):
 *   - OR within same parent_option_id (any matching variant unlocks)
 *   - AND across different parent_option_ids (every parent group must hit)
 *
 * Wrapping: this is a self-contained IIFE. No globals leak, no jQuery
 * dependency. Enqueued under the `tc_product` query var gate from
 * StorefrontModule.php so it only loads on PDP pages.
 *
 * Companion server-side enforcement (defense in depth) lives in
 * CartManager::validate_options + check_required_options — Phase 6.
 *
 * @package TitanCart
 */
( function () {
    'use strict';

    // ── Init plumbing ────────────────────────────────────────────────────────
    if ( document.readyState === 'loading' ) {
        document.addEventListener( 'DOMContentLoaded', init );
    } else {
        init();
    }

    function init() {
        // Find every wrapper that declares deps. If none, the page has no
        // sequential rules and we can no-op completely.
        var wrappers = document.querySelectorAll( '[data-option-id][data-tc-deps]' );
        if ( ! wrappers.length ) {
            return;
        }

        injectStyles();

        // Group wrappers by their nearest product-scope ancestor so multi-product
        // pages (bundles, related-products quick-add) don't cross-pollinate.
        // Falls back to <body> when no [data-product-id] ancestor exists.
        var scopeMap = new Map();
        wrappers.forEach( function ( w ) {
            var scope = w.closest( '[data-product-id], form, body' ) || document.body;
            if ( ! scopeMap.has( scope ) ) {
                scopeMap.set( scope, [] );
            }
            scopeMap.get( scope ).push( w );
        } );

        scopeMap.forEach( function ( _list, scope ) {
            setupScope( scope );
        } );
    }

    // ── Per-scope wiring ─────────────────────────────────────────────────────
    function setupScope( scope ) {
        // Build map of EVERY [data-option-id] in this scope, not just those
        // with deps — parents need to be in the map too so we can read their
        // current selections.
        var allWrappers = scope.querySelectorAll( '[data-option-id]' );
        if ( ! allWrappers.length ) {
            return;
        }

        var map = new Map();
        var anyDepsPresent = false;
        allWrappers.forEach( function ( w ) {
            var id   = String( w.dataset.optionId );
            var deps = parseDeps( w.dataset.tcDeps );
            if ( deps.length ) {
                anyDepsPresent = true;
            }
            map.set( id, {
                id: id,
                wrapper: w,
                type: detectType( w ),
                deps: deps,
                isLocked: false,
            } );
        } );

        // Defensive — if no scoped wrapper has deps, skip wiring entirely.
        if ( ! anyDepsPresent ) {
            return;
        }

        // Initial pass: lock any option whose deps aren't satisfied. The
        // PDP renders fresh on every page load so most options start
        // unselected, which means most dependent options start locked.
        map.forEach( function ( entry ) {
            evaluate( entry, map );
        } );

        // Wire change listeners. Any option's input change triggers a
        // re-evaluation of every option in scope. Cheap — option counts are
        // small (typically 1-10 per product).
        var rerun = function () {
            map.forEach( function ( entry ) {
                evaluate( entry, map );
            } );
        };
        map.forEach( function ( entry ) {
            var inputs = entry.wrapper.querySelectorAll(
                'select, input[type="radio"], input[type="checkbox"]'
            );
            inputs.forEach( function ( inp ) {
                inp.addEventListener( 'change', rerun );
            } );
        } );
    }

    // ── Lock/unlock decision ─────────────────────────────────────────────────
    function evaluate( entry, map ) {
        var shouldBeLocked = ! isUnlocked( entry, map );
        if ( shouldBeLocked === entry.isLocked ) {
            return; // no transition
        }
        if ( shouldBeLocked ) {
            var hint = computeHintText( entry, map );
            lock( entry, hint );
        } else {
            unlock( entry );
        }
        entry.isLocked = shouldBeLocked;
    }

    function isUnlocked( entry, map ) {
        if ( ! entry.deps.length ) {
            return true; // root option, always unlocked
        }

        // Group deps by parent_option_id. Within a group: OR — any matching
        // parent_variant_id unlocks. Across groups: AND — every parent group
        // must hit.
        var groups = new Map();
        for ( var i = 0; i < entry.deps.length; i++ ) {
            var d   = entry.deps[ i ];
            var pid = String( d.parent_option_id );
            if ( ! groups.has( pid ) ) {
                groups.set( pid, new Set() );
            }
            groups.get( pid ).add( parseInt( d.parent_variant_id, 10 ) );
        }

        var unlockedFlag = true;
        groups.forEach( function ( allowedVariants, pid ) {
            if ( ! unlockedFlag ) {
                return; // short-circuit
            }
            var parent = map.get( pid );
            if ( ! parent ) {
                // Parent option missing from this product (admin deleted it
                // post-rule-creation but before cascade ran, or product clone
                // with stale rule). Defensive: treat as perma-locked.
                unlockedFlag = false;
                return;
            }
            var selected = getSelectedVariantIds( parent );
            var hit = false;
            selected.forEach( function ( vid ) {
                if ( allowedVariants.has( vid ) ) {
                    hit = true;
                }
            } );
            if ( ! hit ) {
                unlockedFlag = false;
            }
        } );

        return unlockedFlag;
    }

    // ── Selection inspection ─────────────────────────────────────────────────
    function getSelectedVariantIds( entry ) {
        var ids = new Set();
        var w   = entry.wrapper;
        if ( entry.type === 'S' ) {
            var sel = w.querySelector( 'select' );
            if ( sel ) {
                var v = parseInt( sel.value, 10 );
                if ( v > 0 ) {
                    ids.add( v );
                }
            }
        } else if ( entry.type === 'R' ) {
            var r = w.querySelector( 'input[type="radio"]:checked' );
            if ( r ) {
                var rv = parseInt( r.value, 10 );
                if ( rv > 0 ) {
                    ids.add( rv );
                }
            }
        } else if ( entry.type === 'C' ) {
            w.querySelectorAll( 'input[type="checkbox"]:checked' ).forEach( function ( c ) {
                var cv = parseInt( c.value, 10 );
                if ( cv > 0 ) {
                    ids.add( cv );
                }
            } );
        }
        // I, T, F: no variant_id-based selection. Admin restricts dep parents
        // to S/R/C anyway, so this branch returns empty defensively.
        return ids;
    }

    // ── Lock UI mutations ────────────────────────────────────────────────────
    function lock( entry, hintText ) {
        var w = entry.wrapper;
        w.setAttribute( 'data-tc-locked', '1' );

        // Stash + clear `required` so HTML5 form validation doesn't block on
        // a hidden-locked input. Disable form controls so they don't submit.
        // Browsers don't submit values from disabled inputs, so the cart
        // collector at cart.js:284-308 will naturally skip them — but we
        // also empty the values defensively in case any code reads them
        // directly.
        w.querySelectorAll( 'select, input, textarea' ).forEach( function ( c ) {
            // Only stash original required state once — guard against
            // double-lock corrupting the saved value (defensive; evaluate()
            // already prevents back-to-back locks).
            if ( typeof c.dataset.tcWasRequired === 'undefined' ) {
                c.dataset.tcWasRequired = c.required ? '1' : '0';
            }
            if ( c.required ) {
                c.required = false;
            }
            c.disabled = true;
        } );

        clearSelections( w );

        // Add or update the lock hint paragraph.
        var hintEl = w.querySelector( '.tc-product-option__lock-hint' );
        if ( ! hintEl ) {
            hintEl = document.createElement( 'div' );
            hintEl.className = 'tc-product-option__lock-hint';
            w.appendChild( hintEl );
        }
        hintEl.textContent = hintText;
    }

    function unlock( entry ) {
        var w = entry.wrapper;
        w.removeAttribute( 'data-tc-locked' );
        w.querySelectorAll( 'select, input, textarea' ).forEach( function ( c ) {
            c.disabled = false;
            if ( c.dataset.tcWasRequired === '1' ) {
                c.required = true;
            }
            delete c.dataset.tcWasRequired;
        } );
        var hintEl = w.querySelector( '.tc-product-option__lock-hint' );
        if ( hintEl ) {
            hintEl.remove();
        }
    }

    function clearSelections( wrapper ) {
        wrapper.querySelectorAll( 'select' ).forEach( function ( s ) {
            s.value = '';
        } );
        wrapper.querySelectorAll( 'input[type="radio"]:checked, input[type="checkbox"]:checked' ).forEach( function ( i ) {
            i.checked = false;
        } );
        wrapper.querySelectorAll( 'input[type="text"]' ).forEach( function ( i ) {
            i.value = '';
        } );
        wrapper.querySelectorAll( 'textarea' ).forEach( function ( t ) {
            t.value = '';
        } );
    }

    // ── Hint text composition ────────────────────────────────────────────────
    function computeHintText( entry, map ) {
        if ( ! entry.deps.length ) {
            return '';
        }
        // Collect distinct parent option names.
        var seen   = new Set();
        var names  = [];
        for ( var i = 0; i < entry.deps.length; i++ ) {
            var pid = String( entry.deps[ i ].parent_option_id );
            if ( seen.has( pid ) ) {
                continue;
            }
            seen.add( pid );
            var parent = map.get( pid );
            if ( ! parent ) {
                continue;
            }
            var labelEl = parent.wrapper.querySelector( '.tc-product-option__label' );
            if ( ! labelEl ) {
                continue;
            }
            // Strip required-asterisk + whitespace for cleaner hint.
            var raw = labelEl.textContent || '';
            var clean = raw.replace( /\*/g, '' ).replace( /\s+/g, ' ' ).trim();
            if ( clean ) {
                names.push( clean );
            }
        }
        if ( ! names.length ) {
            return 'Make a selection above to enable this option.';
        }
        if ( names.length === 1 ) {
            return 'Choose "' + names[ 0 ] + '" first to enable this option.';
        }
        // Multi-parent (AND): "Choose A and B first ..."
        var joined;
        if ( names.length === 2 ) {
            joined = names[ 0 ] + '" and "' + names[ 1 ];
        } else {
            joined = names.slice( 0, -1 ).join( '", "' ) + '" and "' + names[ names.length - 1 ];
        }
        return 'Choose "' + joined + '" first to enable this option.';
    }

    // ── Helpers ──────────────────────────────────────────────────────────────
    function detectType( wrapper ) {
        if ( wrapper.querySelector( 'select' ) ) {
            return 'S';
        }
        if ( wrapper.querySelector( 'input[type="radio"]' ) ) {
            return 'R';
        }
        if ( wrapper.querySelector( 'input[type="checkbox"]' ) ) {
            return 'C';
        }
        if ( wrapper.querySelector( 'textarea' ) ) {
            return 'T';
        }
        if ( wrapper.querySelector( 'input[type="text"]' ) ) {
            return 'I';
        }
        if ( wrapper.querySelector( 'input[type="file"]' ) ) {
            return 'F';
        }
        return 'unknown';
    }

    function parseDeps( raw ) {
        if ( ! raw ) {
            return [];
        }
        try {
            var parsed = JSON.parse( raw );
            if ( ! Array.isArray( parsed ) ) {
                return [];
            }
            // Filter to well-formed rows.
            var out = [];
            for ( var i = 0; i < parsed.length; i++ ) {
                var row = parsed[ i ];
                if ( ! row ) {
                    continue;
                }
                var pOpt = parseInt( row.parent_option_id, 10 );
                var pVar = parseInt( row.parent_variant_id, 10 );
                if ( pOpt > 0 && pVar > 0 ) {
                    out.push( { parent_option_id: pOpt, parent_variant_id: pVar } );
                }
            }
            return out;
        } catch ( e ) {
            return [];
        }
    }

    // ── Visual styles ────────────────────────────────────────────────────────
    function injectStyles() {
        if ( document.getElementById( 'tc-seq-options-styles' ) ) {
            return;
        }
        var css = ''
            + '.tc-product-option[data-tc-locked="1"] {'
            + '    opacity: 0.55;'
            + '    pointer-events: none;'
            + '    transition: opacity 0.18s ease;'
            + '}'
            + '.tc-product-option[data-tc-locked="1"] select,'
            + '.tc-product-option[data-tc-locked="1"] input,'
            + '.tc-product-option[data-tc-locked="1"] textarea {'
            + '    cursor: not-allowed;'
            + '    background-color: #f5f5f5;'
            + '}'
            + '.tc-product-option__lock-hint {'
            + '    margin-top: 6px;'
            + '    font-size: 12px;'
            + '    font-style: italic;'
            + '    color: #6b7280;'
            + '    pointer-events: auto;'
            + '}';
        var s = document.createElement( 'style' );
        s.id = 'tc-seq-options-styles';
        s.textContent = css;
        document.head.appendChild( s );
    }
} )();
