
(function($) {
    'use strict';

    if (!window.jQuery) return;

    var ImpC = window.ImpC || {
        surface: '#1a1d21', surface2: '#1d2327', panel: '#2a2e33',
        border: '#3a3f44', dim: '#555', muted: '#999',
        text: '#e0e0e0', light: '#f5f5f5', white: '#fff'
    };

    
    var _playhead     = 0;          
    var _selectedKf   = null;       
    var _ctxKf        = null;       
    var _dragging     = false;

    window._impulseTimelineRecord = window._impulseTimelineRecord || false;

    

    function _iframeDoc() {
        var f = document.getElementById('impulse-iframe');
        return (f && f.contentDocument) || null;
    }
    function _iframeWin() {
        var f = document.getElementById('impulse-iframe');
        return (f && f.contentWindow) || null;
    }

    function _isSceneMode() {
        var s = (window.Impulse && window.Impulse.pageSettings) ? window.Impulse.pageSettings : {};
        return true;
    }


    
    function _getCurrentScreenEl() {
        var doc = _iframeDoc();
        if (!doc) return null;
        var wrapper = doc.querySelector('.impulse-content-wrapper') || doc.body;
        var selId = window.selectedElementId || null;
        if (selId) {
            var node = doc.querySelector('.impulse-el[data-id="' + selId + '"]');
            if (node) {
                
                var cur = node;
                while (cur && cur !== wrapper) {
                    if (cur.classList && cur.classList.contains('impulse-el') &&
                        cur.getAttribute('data-type') === 'section' &&
                        cur.parentElement === wrapper) {
                        return cur;
                    }
                    cur = cur.parentElement;
                }
            }
        }
        
        var win = _iframeWin();
        if (win && win.impulseScreenMode && typeof win.impulseScreenMode.getCurrentScreen === 'function') {
            var idx = win.impulseScreenMode.getCurrentScreen();
            var screens = wrapper.querySelectorAll(':scope > .impulse-el[data-type="section"]');
            if (screens[idx]) return screens[idx];
            if (screens[0])   return screens[0];
        }
        return null;
    }

    function _elementLabel(el) {
        
        var name = el.getAttribute('data-struct-label');
        if (name) return name;
        var type = el.getAttribute('data-type') || 'element';
        var id   = el.getAttribute('data-id') || '?';
        
        var pretty = type.charAt(0).toUpperCase() + type.slice(1).replace(/_/g, ' ');
        return pretty + ' #' + id;
    }

    function _getTrackedElementsInScreen(screenEl) {
        if (!screenEl) return [];
        var nodes = screenEl.querySelectorAll('[data-keyframes]');
        var out = [];
        for (var i = 0; i < nodes.length; i++) {
            var raw = nodes[i].getAttribute('data-keyframes');
            if (!raw) continue;
            var parsed = null;
            try { parsed = JSON.parse(raw); } catch (_) {}
            if (Array.isArray(parsed) && parsed.length >= 1) {
                out.push({ el: nodes[i], id: nodes[i].getAttribute('data-id'), kf: parsed });
            }
        }
        return out;
    }


    function _captureProps(el) {
        var cs = (_iframeWin() || window).getComputedStyle(el);
        var props = {};

        
        var op = parseFloat(cs.opacity);
        if (!isNaN(op)) props.opacity = op;


        
        
        var tf = cs.transform;
        if (tf && tf !== 'none') {
            var m2 = tf.match(/^matrix\(([^)]+)\)$/);
            var m3 = tf.match(/^matrix3d\(([^)]+)\)$/);
            if (m2) {
                var v = m2[1].split(',').map(function(x){ return parseFloat(x.trim()); });
                
                props.tx    = v[4] || 0;
                props.ty    = v[5] || 0;
                var sx = Math.sqrt(v[0]*v[0] + v[1]*v[1]);
                props.scale = +(sx.toFixed(4));
                var rz = Math.atan2(v[1], v[0]) * (180 / Math.PI);
                if (Math.abs(rz) > 0.01) props.rz = +(rz.toFixed(2));
            } else if (m3) {
                var w = m3[1].split(',').map(function(x){ return parseFloat(x.trim()); });
                
                props.tx = w[12] || 0;
                props.ty = w[13] || 0;
                props.tz = w[14] || 0;
                var sx3 = Math.sqrt(w[0]*w[0] + w[1]*w[1] + w[2]*w[2]);
                props.scale = +(sx3.toFixed(4));
            }
        }

        
        var filt = cs.filter;
        if (filt && filt !== 'none') {
            var mb = filt.match(/blur\(([\d.]+)px\)/);
            if (mb) props.blur = parseFloat(mb[1]) || 0;
        }


        

        
        
        var col = _rgbToHex(_computedStyleOfFirstInlineMatch(el, 'color')) || _rgbToHex(cs.color);
        if (col) props.color = col;
        var bg  = _rgbToHex(_computedStyleOfFirstInlineMatch(el, 'backgroundColor')) || _rgbToHex(cs.backgroundColor);
        if (bg) props.bgColor = bg;

        return props;
    }


    
    
    function _computedStyleOfFirstInlineMatch(root, cssProp) {
        if (root.style && root.style[cssProp]) {
            return (_iframeWin() || window).getComputedStyle(root)[cssProp];
        }
        var inner = root.querySelectorAll('[style]');
        for (var i = 0; i < inner.length; i++) {
            if (inner[i].style[cssProp]) {
                return (_iframeWin() || window).getComputedStyle(inner[i])[cssProp];
            }
        }
        return null;
    }

    function _rgbToHex(str) {
        if (!str || typeof str !== 'string') return null;
        if (str.charAt(0) === '#') return str;
        var m = str.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\d.]+))?/);
        if (!m) return null;

        

        

        
        var alpha = m[4] !== undefined ? parseFloat(m[4]) : 1;
        if (alpha < 1) {
            return 'rgba(' + parseInt(m[1], 10) + ',' + parseInt(m[2], 10) + ',' + parseInt(m[3], 10) + ',' + alpha + ')';
        }
        function h(x){ x = parseInt(x, 10); return (x < 16 ? '0' : '') + x.toString(16); }
        return '#' + h(m[1]) + h(m[2]) + h(m[3]);
    }

    


    

    

    
    var _mutationChain = {};  

    function _mutateKeyframes(elementId, mutator, onDone) {
        var Impulse = window.Impulse || {};
        var elKey = String(elementId);
        var prevChain = _mutationChain[elKey] || Promise.resolve();
        var chain = prevChain.catch(function() {  })
                             .then(function() { return _mutateKeyframesOnce(elementId, mutator, onDone); });
        _mutationChain[elKey] = chain;
        return chain;
    }

    function _mutateKeyframesOnce(elementId, mutator, onDone) {
        var Impulse = window.Impulse || {};
        return new Promise(function(resolve, reject) {
          $.ajax({
            url: Impulse.api + 'elements/' + elementId,
            method: 'GET',
            headers: { 'X-WP-Nonce': Impulse.nonce }
          }).done(function(resp) {
            var settings = (resp && resp.settings) || {};
            var prevKf   = Array.isArray(settings.keyframes) ? JSON.parse(JSON.stringify(settings.keyframes)) : [];
            var kf       = Array.isArray(settings.keyframes) ? settings.keyframes.slice() : [];
            var nextKf   = mutator(kf) || kf;
            
            nextKf.sort(function(a, b){ return (a.t || 0) - (b.t || 0); });

            

            
            if (window.saveToHistory) {
                window.saveToHistory('update', {
                    elementId:   parseInt(elementId, 10),
                    oldSettings: { keyframes: prevKf },
                    newSettings: { keyframes: nextKf }
                });
            }
            $.ajax({
                url: Impulse.api + 'elements/' + elementId,
                method: 'PUT',
                headers: { 'X-WP-Nonce': Impulse.nonce },
                contentType: 'application/json',
                data: JSON.stringify({ settings: { keyframes: nextKf }, page_id: Impulse.pageId })
            }).done(function() {

                var doc = _iframeDoc();
                var node = doc && doc.querySelector('.impulse-el[data-id="' + elementId + '"]');
                if (node) {
                    if (nextKf.length) node.setAttribute('data-keyframes', JSON.stringify(nextKf));
                    else               node.removeAttribute('data-keyframes');
                    try {
                        var win = _iframeWin();
                        if (win && win.ImpulseTimeline && typeof win.ImpulseTimeline.invalidate === 'function') {
                            win.ImpulseTimeline.invalidate(node);
                        }
                    } catch(e) {}
                }

                

                try {
                    var $panel = $('.impulse-panel-right');
                    if ($panel.length && String($panel.data('element-id')) === String(elementId)) {
                        var cached = $panel.data('existing-settings') || {};
                        cached.keyframes = nextKf.slice();
                        $panel.data('existing-settings', cached);
                    }
                } catch(e) {}
                if (typeof onDone === 'function') onDone(null, nextKf);
                render();

                

                

                
                
                resolve(nextKf);
            }).fail(function(xhr) {
                if (typeof onDone === 'function') onDone(xhr);
                if (window.showToast) window.showToast('Keyframe save failed', 'error');
                reject(xhr);
            });
          }).fail(function(xhr) {
            if (typeof onDone === 'function') onDone(xhr);
            if (window.showToast) window.showToast('Element fetch failed', 'error');
            reject(xhr);
          });
        });
    }

    


    
    function _sample(track, p) {
        var win = _iframeWin();
        if (win && win.ImpulseTimeline && typeof win.ImpulseTimeline._sampleTrack === 'function') {
            
            return win.ImpulseTimeline._sampleTrack(track, p);
        }
        
        for (var i = 0; i < track.length - 1; i++) {
            if (track[i].t <= p && track[i+1].t >= p) {
                return p < (track[i].t + track[i+1].t) / 2 ? (track[i].props || {}) : (track[i+1].props || {});
            }
        }
        return track[track.length - 1].props || {};
    }


    

    
    
    function _applyInlineDeep(el, cssProp, value) {
        el.style[cssProp] = value;
        var inner = el.querySelectorAll('[style]');
        for (var i = 0; i < inner.length; i++) {
            if (inner[i].style[cssProp] !== undefined && inner[i].style[cssProp] !== '') {
                inner[i].style[cssProp] = value;
            }
        }
    }

    function _applyPropsToEl(el, props) {
        var TK = ['tx','ty','tz','rx','ry','rz','scale'];
        var hasTf = false;
        for (var i = 0; i < TK.length; i++) { if (props[TK[i]] !== undefined) { hasTf = true; break; } }
        if (hasTf) {
            var tx = props.tx || 0, ty = props.ty || 0, tz = props.tz || 0;
            var rx = props.rx || 0, ry = props.ry || 0, rz = props.rz || 0;
            var sc = (props.scale === undefined ? 1 : props.scale);
            var parts = ['translate3d(' + tx + 'px,' + ty + 'px,' + tz + 'px)'];
            if (rx) parts.push('rotateX(' + rx + 'deg)');
            if (ry) parts.push('rotateY(' + ry + 'deg)');
            if (rz) parts.push('rotateZ(' + rz + 'deg)');
            if (sc !== 1) parts.push('scale(' + sc + ')');
            el.style.transform = parts.join(' ');
        }
        if (props.opacity !== undefined) el.style.opacity = props.opacity;
        if (props.blur    !== undefined) el.style.filter  = 'blur(' + props.blur + 'px)';
        if (props.color   !== undefined) _applyInlineDeep(el, 'color', props.color);
        var bg = props.bgColor !== undefined ? props.bgColor : props.background_color;
        if (bg !== undefined) _applyInlineDeep(el, 'backgroundColor', bg);
    }

    function _applyPlayhead() {
        var screenEl = _getCurrentScreenEl();
        if (!screenEl) return;
        var tracks = _getTrackedElementsInScreen(screenEl);
        for (var i = 0; i < tracks.length; i++) {
            var t = tracks[i];
            if (t.kf.length < 1) continue;
            var props = _sample(t.kf.slice().sort(function(a,b){return (a.t||0)-(b.t||0);}), _playhead);
            _applyPropsToEl(t.el, props);
        }
        
        $('#impulse-tl-playhead').css('left', (_playhead * 100) + '%');
    }

    

    
    var ICON_PLUS   = '<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg>';
    var ICON_REC    = '<svg viewBox="0 0 24 24" width="10" height="10" fill="currentColor" stroke="currentColor" stroke-width="0"><circle cx="12" cy="12" r="6"/></svg>';


    

    
    
    function render() {

        

        

        

        var $mount = $('.impulse-screen-bar-panel[data-tab="timeline"][data-mount="section-panel"]').first();
        if (!$mount.length) $mount = $('#impulse-screen-bar .impulse-screen-bar-panel[data-tab="timeline"]').first();
        if (!$mount.length) return;


        

        

        

        

        

        

        
        
        var screenEl = _getCurrentScreenEl();
        var hasStart = false, hasEnd = false, anyKf = false;
        function _scanKf(arr) {
            if (!Array.isArray(arr)) return;
            for (var j = 0; j < arr.length; j++) {
                var ct = +arr[j].t || 0;
                if (ct <= 0.05) hasStart = true;
                if (ct >= 0.95) hasEnd   = true;
                anyKf = true;
            }
        }
        if (screenEl && screenEl.hasAttribute('data-keyframes')) {
            try { _scanKf(JSON.parse(screenEl.getAttribute('data-keyframes'))); } catch (_e) {}
        }
        var tracks = _getTrackedElementsInScreen(screenEl);
        for (var i = 0; i < tracks.length; i++) { _scanKf(tracks[i].kf); }

        var html = '<div id="impulse-tl-strip" class="impulse-tl-strip impulse-tl-simple">';
        html += '<div class="impulse-tl-globals">';
        html += '<button type="button" class="impulse-tl-snap-all' + (hasStart ? ' on' : '') + '" data-pos="start">Set Start</button>';
        html += '<button type="button" class="impulse-tl-snap-all' + (hasEnd ? ' on' : '') + '" data-pos="end">Set End</button>';

        
        html += '<button type="button" class="impulse-tl-clear-all"' + (anyKf ? '' : ' disabled') + ' title="Remove every keyframe on this screen">Clear</button>';
        html += '</div>';
        html += '</div>';
        $mount.html(html);
    }

    function _escape(s) {
        return String(s == null ? '' : s)
            .replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
    }

    


    

    

    

    

    function addKeyframeForElement(elId) {
        if (!elId) return false;
        var doc = _iframeDoc();
        var node = doc && doc.querySelector('.impulse-el[data-id="' + elId + '"]');
        if (!node) return false;
        var props = _captureProps(node);
        var propCount = Object.keys(props).length;
        var atT = _playhead;   
        _mutateKeyframes(elId, function(kf) {
            var found = -1;
            for (var i = 0; i < kf.length; i++) {
                if (Math.abs((kf[i].t || 0) - atT) < 0.002) { found = i; break; }
            }
            if (found >= 0) {
                kf[found] = { t: atT, ease: kf[found].ease || 'linear', props: props };
            } else {
                kf.push({ t: atT, ease: 'linear', props: props });
            }
            return kf;
        });
        if (window.showToast) {
            window.showToast(
                'KF @ ' + Math.round(atT * 100) + '% · ' +
                propCount + ' prop' + (propCount === 1 ? '' : 's') + ' captured'
            );
        }
        return true;
    }

    
    $(document).on('click', '.impulse-tl-add-kf', function(e) {
        e.preventDefault();
        var elId = window.selectedElementId;
        if (!elId) { if (window.showToast) window.showToast('Select an element first', 'info'); return; }
        addKeyframeForElement(elId);
    });


    

    $(document).on('click', '.impulse-tl-step', function(e) {
        e.preventDefault();
        var $btn = $(this);
        var elId = $btn.attr('data-elid');
        var pos  = $btn.attr('data-pos');
        if (!elId || !pos) return;
        var targetT = (pos === 'end') ? 1 : 0;
        var doc = _iframeDoc();
        var node = doc && doc.querySelector('.impulse-el[data-id="' + elId + '"]');
        if (!node) return;
        var props = _captureProps(node);
        _mutateKeyframes(elId, function(kf) {
            
            var found = -1;
            for (var i = 0; i < kf.length; i++) {
                var ct = +kf[i].t || 0;
                if (pos === 'start' && ct <= 0.05) { found = i; break; }
                if (pos === 'end'   && ct >= 0.95) { found = i; break; }
            }
            if ($btn.hasClass('on')) {
                
                if (found >= 0) kf.splice(found, 1);
            } else {
                
                if (found >= 0) {
                    kf[found] = { t: targetT, ease: kf[found].ease || 'linear', props: props };
                } else {
                    kf.push({ t: targetT, ease: 'linear', props: props });
                }
            }
            return kf;
        });
    });


    
    $(document).on('click', '.impulse-tl-rec', function(e) {
        e.preventDefault();
        window._impulseTimelineRecord = !window._impulseTimelineRecord;
        $(this).toggleClass('impulse-timeline-record-on', !!window._impulseTimelineRecord);
    });


    

    $(document).on('click', '.impulse-tl-snap-all', function(e) {
        e.preventDefault();
        var $btn = $(this);
        var pos = $btn.attr('data-pos');
        var targetT = (pos === 'end') ? 1 : 0;

        

        
        
        $btn.removeClass('fired');
        void $btn[0].offsetWidth;  
        $btn.addClass('fired');
        setTimeout(function() { $btn.removeClass('fired'); }, 350);
        var screenEl = _getCurrentScreenEl();
        if (!screenEl) {
            if (window.showToast) window.showToast('No active screen', 'info');
            return;
        }

        

        

        var kidNodes = Array.prototype.slice.call(screenEl.querySelectorAll('.impulse-el[data-id]'));
        var kids = [screenEl].concat(kidNodes);
        if (!kids.length) return;

        

        

        var touched = 0;
        for (var k = 0; k < kids.length; k++) {
            var kid = kids[k];
            var elId = kid.getAttribute('data-id');
            if (!elId) continue;
            var props = _captureProps(kid);
            (function(elIdLocal, propsLocal) {
                _mutateKeyframes(elIdLocal, function(kf) {
                    var found = -1;
                    for (var i = 0; i < kf.length; i++) {
                        var ct = +kf[i].t || 0;
                        if (pos === 'start' && ct <= 0.05) { found = i; break; }
                        if (pos === 'end'   && ct >= 0.95) { found = i; break; }
                    }
                    if (found >= 0) {
                        kf[found] = { t: targetT, ease: kf[found].ease || 'linear', props: propsLocal };
                    } else {
                        kf.push({ t: targetT, ease: 'linear', props: propsLocal });
                    }
                    return kf;
                });
            })(elId, props);
            touched++;
        }
        if (window.showToast) {

            
            
            window.showToast(
                (pos === 'start' ? 'Saved start state' : 'Saved end state') +
                ' (' + touched + ' element' + (touched === 1 ? '' : 's') + ')'
            );
        }
        setTimeout(render, 60); 
    });


    

    

    
    $(document).on('click', '.impulse-tl-clear-all', function(e) {
        e.preventDefault();
        var screenEl = _getCurrentScreenEl();
        if (!screenEl) return;
        var _tlWin = (typeof _iframeWin === 'function') ? _iframeWin() : null;

        
        
        if (_tlWin && _tlWin.ImpulseTimeline && typeof _tlWin.ImpulseTimeline.reset === 'function') {
            try { _tlWin.ImpulseTimeline.reset(screenEl); } catch (_e) {}
        }

        

        
        
        var kfNodes = [];
        if (screenEl.hasAttribute('data-keyframes')) kfNodes.push(screenEl);
        Array.prototype.forEach.call(screenEl.querySelectorAll('[data-keyframes]'), function(n){ kfNodes.push(n); });
        for (var k = 0; k < kfNodes.length; k++) {
            var n = kfNodes[k];
            var elId = n.getAttribute('data-id');
            if (elId) _mutateKeyframes(elId, function() { return []; });
            try { n.removeAttribute('data-keyframes'); } catch (_e) {}
            try {
                if (_tlWin && _tlWin.ImpulseTimeline && typeof _tlWin.ImpulseTimeline.invalidate === 'function') {
                    _tlWin.ImpulseTimeline.invalidate(n);
                }
            } catch (_e) {}
        }
        render();
    });

    
    var _multiSel = [];
    function _multiSelHas(elId, idx) {
        for (var i = 0; i < _multiSel.length; i++) {
            if (String(_multiSel[i].elementId) === String(elId) && _multiSel[i].index === idx) return i;
        }
        return -1;
    }
    function _repaintSelection() {
        $('.impulse-tl-kf').removeClass('selected');
        _multiSel.forEach(function(s) {
            $('.impulse-tl-kf[data-elid="' + s.elementId + '"][data-idx="' + s.index + '"]').addClass('selected');
        });
    }


    

    
    var _kfDrag = null;
    var _kfDragSuppressClick = false;
    $(document).on('pointerdown', '.impulse-tl-kf', function(e) {
        if (e.button !== 0) return; 
        var $kf = $(this);
        var $track = $kf.closest('.impulse-tl-track');
        if (!$track.length) return;
        var rect = $track[0].getBoundingClientRect();
        _kfDrag = {
            elId:     $kf.attr('data-elid'),
            idx:      parseInt($kf.attr('data-idx'), 10),
            trackL:   rect.left,
            trackW:   Math.max(1, rect.width),
            startX:   e.clientX,
            startT:   parseFloat(($kf[0].style.left || '0').replace('%','')) / 100,
            moved:    false,
            $kf:      $kf
        };
        
        try { this.setPointerCapture(e.pointerId); } catch(_) {}
        e.stopPropagation();
    });
    $(document).on('pointermove', function(e) {
        if (!_kfDrag) return;
        var dx = e.clientX - _kfDrag.startX;
        if (!_kfDrag.moved && Math.abs(dx) < 3) return;
        _kfDrag.moved = true;
        var t = (e.clientX - _kfDrag.trackL) / _kfDrag.trackW;
        t = Math.max(0, Math.min(1, t));
        _kfDrag.curT = t;
        _kfDrag.$kf.css('left', (t * 100) + '%');
    });
    $(document).on('pointerup pointercancel', function(e) {
        if (!_kfDrag) return;
        var d = _kfDrag;
        _kfDrag = null;
        if (!d.moved) return;
        _kfDragSuppressClick = true;
        setTimeout(function(){ _kfDragSuppressClick = false; }, 0);

        
        
        var newT = d.curT;
        _mutateKeyframes(d.elId, function(kf) {
            if (d.idx >= 0 && d.idx < kf.length) {
                kf[d.idx] = $.extend({}, kf[d.idx], { t: newT });
            }
            return kf;
        });
    });


    $(document).on('click', '.impulse-tl-kf', function(e) {
        if (_kfDragSuppressClick) { e.stopPropagation(); return; }
        e.stopPropagation();
        var elId = $(this).attr('data-elid');
        var idx  = parseInt($(this).attr('data-idx'), 10);
        if (e.shiftKey) {
            var exists = _multiSelHas(elId, idx);
            if (exists >= 0) _multiSel.splice(exists, 1);
            else             _multiSel.push({ elementId: elId, index: idx });
            _selectedKf = _multiSel.length ? _multiSel[_multiSel.length - 1] : null;
        } else {
            _multiSel = [{ elementId: elId, index: idx }];
            _selectedKf = _multiSel[0];
        }
        _repaintSelection();
        
        if (_multiSel.length === 1) _showKfPopover(this, elId, idx);
        else                        $('#impulse-tl-kf-pop').remove();
    });


    
    $(document).on('keydown.tlkbd', function(e) {
        if (!$('#impulse-tl-toggle').hasClass('on')) return;
        var active = document.activeElement;
        var tag = active && active.tagName;
        if (tag === 'INPUT' || tag === 'TEXTAREA' || (active && active.isContentEditable)) return;
        var ctrl = e.ctrlKey || e.metaKey;
        
        if ((e.key === 'Delete' || e.key === 'Backspace') && _multiSel.length) {
            e.preventDefault();
            _deleteSelectedKeyframes();
            return;
        }
        
        if (ctrl && (e.key === 'c' || e.key === 'C') && _multiSel.length) {
            e.preventDefault();
            _copySelectedKeyframes();
            return;
        }
        
        if (ctrl && (e.key === 'v' || e.key === 'V') && _kfClipboard && _kfClipboard.length) {
            e.preventDefault();
            _pasteKeyframes();
            return;
        }
    });

    
    function _deleteSelectedKeyframes() {
        if (!_multiSel.length) return;
        var byEl = {};
        _multiSel.forEach(function(s) {
            byEl[s.elementId] = byEl[s.elementId] || [];
            byEl[s.elementId].push(s.index);
        });
        Object.keys(byEl).forEach(function(elId) {
            var idxs = byEl[elId].slice().sort(function(a,b){ return b - a; }); 
            _mutateKeyframes(elId, function(kf) {
                idxs.forEach(function(i) { if (i >= 0 && i < kf.length) kf.splice(i, 1); });
                return kf;
            });
        });
        _multiSel = [];
        _selectedKf = null;
        $('#impulse-tl-kf-pop').remove();
    }


    var _kfClipboard = null; 
    function _copySelectedKeyframes() {
        var items = [];
        _multiSel.forEach(function(s) {
            var track = _getElementKeyframes(s.elementId);
            if (!track || !track[s.index]) return;
            items.push({
                relT:  track[s.index].t || 0,
                ease:  track[s.index].ease || 'linear',
                props: JSON.parse(JSON.stringify(track[s.index].props || {}))
            });
        });
        if (!items.length) return;
        var anchor = items.reduce(function(m, it) { return Math.min(m, it.relT); }, Infinity);
        items.forEach(function(it) { it.relT = it.relT - anchor; });
        _kfClipboard = { items: items };
        if (window.showToast) window.showToast('Copied ' + items.length + ' keyframe' + (items.length > 1 ? 's' : ''));
    }


    
    function _pasteKeyframes() {
        if (!_kfClipboard || !_kfClipboard.items.length) return;
        var targetId = window.selectedElementId;
        if (!targetId) {
            
            if (_selectedKf) targetId = _selectedKf.elementId;
        }
        if (!targetId) {
            if (window.showToast) window.showToast('Select an element to paste onto', 'info');
            return;
        }
        var ph = _playhead;
        _mutateKeyframes(targetId, function(kf) {
            _kfClipboard.items.forEach(function(it) {
                var t = Math.max(0, Math.min(1, ph + it.relT));
                
                var found = -1;
                for (var i = 0; i < kf.length; i++) {
                    if (Math.abs((kf[i].t || 0) - t) < 0.01) { found = i; break; }
                }
                if (found >= 0) {
                    kf[found] = { t: t, ease: it.ease, props: it.props };
                } else {
                    kf.push({ t: t, ease: it.ease, props: it.props });
                }
            });
            return kf;
        });
    }

    
    $(document).on('click.tlkfpop', function(e) {
        if ($(e.target).closest('#impulse-tl-kf-pop, .impulse-tl-kf').length) return;
        $('#impulse-tl-kf-pop').remove();
    });

    function _showKfPopover(anchorEl, elId, idx) {
        var track = _getElementKeyframes(elId);
        if (!track || !track[idx]) return;
        var currentEase = track[idx].ease || 'linear';
        var options = [
            ['linear',    'Linear'],
            ['easeIn',    'Ease In'],
            ['easeOut',   'Ease Out'],
            ['easeInOut', 'Ease In-Out'],
            ['backIn',    'Back In'],
            ['backOut',   'Back Out'],
            ['backInOut', 'Back In-Out'],
            ['bounceOut', 'Bounce Out'],
            ['spring',    'Spring'] ];
        var optHtml = options.map(function(o) {
            var sel = (o[0] === currentEase) ? ' selected' : '';
            return '<option value="' + o[0] + '"' + sel + '>' + o[1] + '</option>';
        }).join('');
        $('#impulse-tl-kf-pop').remove();

        

        
        var $pop = $(
            '<div id="impulse-tl-kf-pop" class="impulse-tl-kf-pop">'
            + '<span class="impulse-tl-pop-caption">Ease</span>'
            + '<select class="impulse-tl-ease-sel" data-elid="' + elId + '" data-idx="' + idx + '">' + optHtml + '</select>'
            + '</div>'
        );
        $('body').append($pop);
        var r = anchorEl.getBoundingClientRect();
        $pop.css({ top: Math.round(r.top - $pop.outerHeight() - 6) + 'px', left: Math.round(r.left + r.width / 2 - $pop.outerWidth() / 2) + 'px' });
        
        setTimeout(function() {
            $pop.css({ top: Math.round(r.top - $pop.outerHeight() - 6) + 'px', left: Math.round(r.left + r.width / 2 - $pop.outerWidth() / 2) + 'px' });
        }, 0);
    }

    function _getElementKeyframes(elId) {
        var doc = _iframeDoc();
        var node = doc && doc.querySelector('.impulse-el[data-id="' + elId + '"]');
        if (!node) return null;
        var raw = node.getAttribute('data-keyframes');
        if (!raw) return null;
        try { return JSON.parse(raw); } catch (_) { return null; }
    }


    

    $(document).on('change', '.impulse-tl-ease-sel', function() {
        var elId = $(this).attr('data-elid');
        var idxAttr = $(this).attr('data-idx');
        var ease = $(this).val();
        if (idxAttr !== undefined && idxAttr !== '') {
            var idx = parseInt(idxAttr, 10);
            if (window.ImpulseTimelineEditor && window.ImpulseTimelineEditor.setKeyframeEase) {
                window.ImpulseTimelineEditor.setKeyframeEase(elId, idx, ease);
            }
            return;
        }
        _mutateKeyframes(elId, function(kf) {

            var endIdx = -1;
            for (var i = 0; i < kf.length; i++) {
                var ct = +kf[i].t || 0;
                if (ct >= 0.95 && (endIdx < 0 || ct > (kf[endIdx].t || 0))) endIdx = i;
            }
            if (endIdx < 0 && kf.length) endIdx = kf.length - 1;
            if (endIdx >= 0) kf[endIdx].ease = ease || 'linear';
            return kf;
        });
    });


    
    $(document).on('contextmenu', '.impulse-tl-kf', function(e) {
        e.preventDefault(); e.stopPropagation();
        _ctxKf = { elementId: $(this).attr('data-elid'), index: parseInt($(this).attr('data-idx'), 10) };
        var h = (window.ctxItem ? window.ctxItem('tl-kf-delete', 'Delete', '', 'danger') : '');
        var $menu = $('#impulse-context-menu');
        $menu.addClass('impulse-tl-compact-ctx');
        if (window.ctxShow) window.ctxShow($menu, e.clientX, e.clientY, h);
    });


    $(document).on('mousedown.tlctx', function(e) {
        if (!$(e.target).closest('#impulse-context-menu').length) {
            $('#impulse-context-menu').removeClass('impulse-tl-compact-ctx');
        }
    });

    
    $(document).on('click', '#impulse-context-menu .impulse-context-item[data-action="tl-kf-delete"]', function() {
        $('#impulse-context-menu').removeClass('impulse-tl-compact-ctx');
        if (window.ctxHide) window.ctxHide();
        if (!_ctxKf) return;
        var elId = _ctxKf.elementId, idx = _ctxKf.index;
        _ctxKf = null;
        _mutateKeyframes(elId, function(kf) {
            if (idx >= 0 && idx < kf.length) kf.splice(idx, 1);
            return kf;
        });
    });

    
    function _beginDrag(e) {
        _dragging = true;
        $(document).on('pointermove.tldrag', _onDrag).on('pointerup.tldrag pointercancel.tldrag', _endDrag);
        _onDrag(e);
    }
    function _onDrag(e) {
        if (!_dragging) return;
        var $track = $('#impulse-tl-strip .impulse-tl-ruler-track');
        if (!$track.length) return;
        var rect = $track[0].getBoundingClientRect();
        var x = (e.clientX != null ? e.clientX : (e.originalEvent && e.originalEvent.clientX)) - rect.left;
        var p = Math.max(0, Math.min(1, x / Math.max(1, rect.width)));
        _playhead = p;
        _applyPlayhead();
    }
    function _endDrag() {
        _dragging = false;
        $(document).off('.tldrag');
    }

    $(document).on('pointerdown', '#impulse-tl-playhead', function(e) {
        e.preventDefault(); e.stopPropagation();
        _beginDrag(e);
    });
    $(document).on('pointerdown', '#impulse-tl-strip .impulse-tl-track', function(e) {
        
        if ($(e.target).hasClass('impulse-tl-kf')) return;
        _beginDrag(e);
    });

    

    function attachStrip() {

        render();
    }

    
    $(document).on('impulse:preview:loaded', function() {
        
        setTimeout(function() { render(); _applyPlayhead(); }, 50);
    });


    $(document).on('impulse:selection:changed', function() {
        render();
    });

    

    
    $(document).on('impulse:settings-panel-loaded', function() {
        render();
    });

    
    window.ImpulseTimelineEditor = {
        render:      render,
        attachStrip: attachStrip,
        setPlayhead: function(p) { _playhead = Math.max(0, Math.min(1, +p || 0)); _applyPlayhead(); },
        getPlayhead: function()  { return _playhead; },
        addKeyframeForElement: addKeyframeForElement,
        
        setKeyframeEase: function(elId, idx, ease) {
            _mutateKeyframes(elId, function(kf) {
                if (idx >= 0 && idx < kf.length) kf[idx].ease = ease || 'linear';
                return kf;
            });
        }
    };

})(window.jQuery);
