// noinspection JSUnusedGlobalSymbols

const debug = new URLSearchParams(window.location.search).get('debug');

// console.log("debug mode:", debug);
const debugDrag = debug === 'drag';

const screenXtoDesctopX = x => Math.round(x - (window.innerWidth - workspaceWidth()) / 2)

function sendMouseEventTo(e, name, data = undefined/*, overrideTarget = undefined*/) {
    let t = /*overrideTarget ?? */e.target
    // console.log("sendMouseEventTo:", name, t, e)
    let e1 = new MouseEvent(name, {
        bubbles: true,
        clientX: e.clientX, clientY: e.clientY, button: e.button,
        shiftKey: e.shiftKey, altKey: e.altKey, ctrlKey: e.ctrlKey
    })
    e1.htmxData = data
    t.dispatchEvent(e1);
}

const API = 'session'
const SESSION_ID_HEADER = 'X-sessionId';

let activeWindow = null;

function maybeBringToTop(e) {
    const l = document.querySelectorAll('.positionable')
    let maxZ = 0
    let eZ = 0
    l.forEach((e1) => {
        const z = parseInt(e1.style.zIndex)
        if(z > maxZ) maxZ = z
        if(e1 === e) eZ = z
    })
    // console.log("maxZ:", maxZ, " eZ:", eZ)
    if(eZ < maxZ) {
        void htmx.ajax('post', `${ API }/activate?w=${ e.id }`, {
            // swap: 'none'
            source: '#desktop > .progress-circle'
        })
    }
}

const minDesktopWidth = 400;
const maxDesktopWidth = 2000;

let widthAdjustment = {
    active: false,
    // startX: 0,
    startWidth: 0
};

function saveDesktopWidth(w) {
    document.documentElement.style.setProperty('--max-page-width', w + 'px');
}

let desktopWidth = 0
let desktopHeight = 0

function workspaceWidth() {
    let cs = getComputedStyle(document.documentElement)
    let p = cs.getPropertyValue('--max-page-width')
    const maxWidth = parseInt(p);
    let ww = window.innerWidth
    if(isNaN(maxWidth)) {
        return ww;
    }
    return Math.min(ww, maxWidth);
}

function workspaceHeight() {
    return window.innerHeight;
}

function onDesktopWidthChanged() {
    const winW = workspaceWidth()
    const winH = workspaceHeight()
    if(winW !== desktopWidth || winH !== desktopHeight) {
        desktopWidth = winW;
        desktopHeight = winH;
        void htmx.ajax('post', `${ API }/onPageResize?x=${ winW }&y=${ winH }`, {
            // swap: 'none'
            source: '#desktop > .progress-circle'
        });
    }
}

let longClickTimer = null;

function cancelLongClickTimer() {
    if(longClickTimer) {
        clearTimeout(longClickTimer);
        longClickTimer = null;
    }
}

/**
 * @typedef {Object} DragInfo
 * @property {MouseEvent} [startEvent] - The mousedown event that initiated the drag
 * set later when drag operation starts:
 * @property {HTMLElement} [dragClone] - Cloned element(s) container for visual feedback during drag
 * @property {Element[]} [draggedElements] - Array of elements being dragged
 * @property {number} [offsetX] - Horizontal offset from mouse position to drag clone container
 * @property {number} [offsetY] - Vertical offset from mouse position to drag clone container
 * @property {string} [dropSel] - Selector for matching drop targets
 * set dynamically during drag operation:
 * @property {HTMLElement} [lastDropTarget] - Current drop target element being hovered over
 */

/**
 * @typedef {Object} MouseDownInfoState
 * @property {number} startX - Initial mouse X position (clientX)
 * @property {number} startY - Initial mouse Y position (clientY)
 * @property {boolean} isValidForClick - Whether this mousedown can still result in a click
 * @property {boolean} moved - Whether mouse has moved enough to invalidate click
 * @property {DragInfo} [drag] - Drag operation state (present only if initiated on draggable element)
 * @property {Number} [scrollTimer] - Timer ID for auto-scrolling during drag
 */

/**
 * Global state tracking the current mouse down operation
 * @type {MouseDownInfoState|undefined}
 */
let mouseDownInfo = undefined;
let dblCkickValid = false;

// window dragging and width adjustment
document.addEventListener('mousedown', (e) => {
    if(debugDrag && mouseDownInfo) return
    resetDrag()
    cancelLongClickTimer()
    dblCkickValid = true
    const draggable = e.target.closest('.draggable')
    mouseDownInfo = {
        startX: e.clientX, startY: e.clientY,
        isValidForClick: !(e.button === 2),
        moved: false
    }
    if(draggable) {
        mouseDownInfo.drag = {
            startEvent: e
        }
    }

    // Check for width handle first (priority over window dragging)
    const handle = e.target.closest('.width-handle');
    if(handle) {
        widthAdjustment = {
            active: true,
            startX: e.clientX,
            startWidth: workspaceWidth(),
            isLeft: handle.classList.contains('width-handle-left')
        };
        // e.preventDefault();
        e.stopPropagation();
        return;
    }

    const pe = e.target.closest('.positionable')
    pe && maybeBringToTop(pe)
    const win = e.target.closest('.window');
    if(win) {
        if(e.target.closest('.window-header')) {
            if(e.target.closest('.window-button')) return;
            activeWindow = {
                element: win,
                type: 'move',
                // startX: e.clientX,
                // startY: e.clientY,
                initialLeft: win.offsetLeft,
                initialTop: win.offsetTop,
                winX: win.style.left,
                winY: win.style.top
            };
            e.preventDefault();
            return
        }
        if(e.target.closest('.resize-handle')) {
            activeWindow = {
                element: win,
                type: 'resize',
                // startX: e.clientX,
                // startY: e.clientY,
                initialWidth: win.offsetWidth,
                initialHeight: win.offsetHeight
            };
            e.preventDefault();
            return
        }
    }
    longClickTimer = setTimeout(() => {
        mouseDownInfo.isValidForClick = false;
        // console.log(`Long click on ${e.target.id || e.target.className || e.target.tagName}`);
        if(!mouseDownInfo.drag) {
            sendMouseEventTo(e, 'x-longclick')
        }
    }, 300)
})

const EVENT_DRAG = 'x-drag';

function beginDrag(e) {
    let drag = mouseDownInfo.drag
    const dse = drag?.startEvent
    if(dse) {
        sendMouseEventTo(dse, EVENT_DRAG, {op: "begin"})
        // d.startEvent = undefined
    }
}

/**
 * Initializes drag operation for selected elements
 * @typedef {Object} InitDragParams
 * @property {string} srcSel - CSS selector for elements to drag
 * @property {string} dropSel - CSS selector for matching drop targets
 * @param {InitDragParams} js - Configuration object
 */
function initDrag(js) {
    const drag = mouseDownInfo.drag
    if(!drag) {
        console.warn('initDrag: no drag info');
        let els = document.querySelectorAll(js.sel)
    }
    let srcElements = document.querySelectorAll(js.srcSel)
    if(!srcElements.length) {
        console.warn('initDrag: no elements found');
        return;
    }
    let e0 = srcElements[0]
    const dc = document.createElement('div');
    dc.classList.add('dragged-clone');
    // e0.parentElement.appendChild(dc);
    document.body.appendChild(dc);
    let uRc = e0.getBoundingClientRect();
    for(let i = 1; i < srcElements.length; i++) {
        const el = srcElements[i]
        const rc = el.getBoundingClientRect();
        uRc.left = Math.min(uRc.left, rc.left);
        uRc.top = Math.min(uRc.top, rc.top);
        uRc.right = Math.max(uRc.right, rc.right);
        uRc.bottom = Math.max(uRc.bottom, rc.bottom);
    }
    srcElements.forEach(el => {
        const rc = el.getBoundingClientRect();
        const ce = el.cloneNode(true);
        // Append the clone so computed styles can be resolved if necessary
        dc.appendChild(ce);
        copyComputedStyles(el, ce, cssStyleDeclarationToMap(window.getComputedStyle(ce)));
        ce.classList.remove('drop-target');
        const st = ce.style;
        st.position = 'absolute';
        st.width = rc.width + 'px';
        st.height = rc.height + 'px';
        st.left = (rc.left - uRc.left) + 'px';
        st.top = (rc.top - uRc.top) + 'px';

        el.classList.add('dragged'); // finally mark the element as being dragged (after cloning)
    });
    const cs = dc.style;
    // cs.pointerEvents = 'none';
    cs.left = uRc.left + 'px';
    cs.top = uRc.top + 'px';

    const containerRect = dc.getBoundingClientRect();
    mouseDownInfo.drag = {
        ...drag,
        dragClone: dc,
        draggedElements: Array.from(srcElements),
        offsetX: mouseDownInfo.startX - containerRect.left,
        offsetY: mouseDownInfo.startY - containerRect.top,
        dropSel: js.dropSel,
        currWin: drag.startEvent.target.closest('.window'),
        dropTemplate: js.dropTemplate
    }
}

document.addEventListener('mousemove', (e) => {
    if(debugDrag && !e.buttons) return;
    const mdi = mouseDownInfo
    if(!mdi) return;
    // console.log("mousemove", e.target.id, e.currentTarget.id)
    const dX = e.clientX - mdi.startX;
    const dY = e.clientY - mdi.startY;
    if(widthAdjustment.active) {
        const adjustment = (widthAdjustment.isLeft ? -2 : 2) * dX;
        const newWidth = Math.max(minDesktopWidth, Math.min(maxDesktopWidth, widthAdjustment.startWidth + adjustment));
        saveDesktopWidth(newWidth)
        onDesktopWidthChanged()
        scheduleWindowStateSave()
        return;
    }
    const d = Math.sqrt(dX * dX + dY * dY);
    if(d >= 10) {
        if(!mdi.moved) {
            mdi.moved = true;
            // console.log("Too much movement, canceling click")
            mdi.isValidForClick = false;
            beginDrag(e)
        }
        cancelLongClickTimer()
    }
    const aw = activeWindow;
    if(aw) {
        let st = aw.element.style
        if(aw.type === 'move') {
            const win = aw.element;
            // Check if window is maximized and user hasn't dragged enough yet
            if(win.classList.contains('maximized') && !aw.maximizedRestored) {
                if(d >= 20) {
                    // Calculate relative cursor position within workspace
                    const maximizedWidth = workspaceWidth();
                    const cX = mdi.startX - (window.innerWidth - maximizedWidth) * .5
                    // Restore the window
                    sendToggleMaximize(win)
                    aw.maximizedRestored = true;

                    // adjust window position so cursor stays at same relative position
                    const newLeft = cX - parseInt(st.width) * (cX / maximizedWidth);
                    st.left = newLeft + 'px';
                    // Update tracking for continued dragging
                    aw.initialLeft = newLeft;
                }
            } else {
                let minIn = 100
                st.left = Math.max(- (win.offsetWidth - minIn), Math.min(aw.initialLeft + dX, workspaceWidth() - minIn)) + 'px';
                st.top = Math.max(0, Math.min(aw.initialTop + dY, workspaceHeight() - minIn)) + 'px';
            }
        } else if(aw.type === 'resize') {
            const newWidth = Math.min(Math.max(200, aw.initialWidth + dX), workspaceWidth())
            const newHeight = Math.min(Math.max(150, aw.initialHeight + dY), workspaceHeight())
            st.width = newWidth + 'px';
            st.height = newHeight + 'px';
        }
    } else {
        const drag = mdi.drag;
        if(drag && drag.dragClone) {
            const hitEls = document.elementsFromPoint(e.clientX, e.clientY)
            // console.log("hitEls", hitEls)
            // console.log("hit", elementHitTest())
            let dragScroll = hitEls.find(e => e.matches('.drag-scroll'))
            manageDragScoll(drag, dragScroll)
            const win = !dragScroll && hitEls.find(el => el.matches('.window'))
            if(win && drag.currWin !== win) {
                maybeBringToTop(win)
                drag.currWin = win
            }
            const st = drag.dragClone.style
            st.left = (e.clientX - drag.offsetX) + 'px';
            st.top = (e.clientY - drag.offsetY) + 'px';
            let dt = hitEls.find(e => e.matches(drag.dropSel))
            const dtWin = dt?.closest('.window')
            if(win) {
                if(dtWin && parseInt(dtWin.style.zIndex) < parseInt(win.style.zIndex))
                    dt = undefined
                updateDropTarget(drag, dt, e);
            }
        }
    }
})

function manageDragScoll(drag, target) {
    if(drag.lastScrollTarget!==target) {
        clearTimeout(drag.scrollTimer)
        drag.lastScrollTarget = target;
        if(target) {
            const parent = target.parentElement;
            let children = Array.from(parent.children)
            const index = children.indexOf(target)
            const direction = index === children.length-1 ? 1 : -1;
            const scrollEl = children[children.length-2]
            let step = 10;
            drag.scrollTimer = setInterval(() => {
                // console.log("scroll", direction, target)
                scrollEl.scrollTop += step*direction
                step = Math.min(step + 2, 50)
            }, 100)
        }
    }
}

function updateDropTarget(drag, target, sendEvent) {
    if(drag.lastDropTarget === target)
        return
    let tSel = undefined
    if(target) {
        tSel = buildSelectorFromTemplate(target, drag.dropTemplate)
        // console.log("drop target", tSel)
    }
    sendMouseEventTo(drag.startEvent, EVENT_DRAG, {op: "dropTarget", sel: tSel})
    drag.lastDropTarget = target;
    // console.log("drop", dt)
}

function resetDrag() {
    const drag = mouseDownInfo?.drag
    if(drag) {
        const dEls = drag.draggedElements;
        if(dEls) {
            dEls.forEach(el => el.classList.remove('dragged'));
        }
        let dc = drag.dragClone
        if(dc) {
            dc.remove();
        }
        clearTimeout(drag.scrollTimer)
        // updateDropTarget(d, undefined)
        sendMouseEventTo(drag.startEvent, EVENT_DRAG, {op: "end"})
        /*const lt = drag.lastDropTarget
        if(lt) {
        }*/
        mouseDownInfo.drag = undefined
    }
}

document.addEventListener('mouseup', (e) => {
    cancelLongClickTimer()
    const mdi = mouseDownInfo
    if(!mdi) return;
    // Handle width adjustment end
    if(widthAdjustment.active) {
        widthAdjustment.active = false;
        return;
    }

    // Handle window dragging/resizing end
    const aw = activeWindow;
    if(aw) {
        const win = aw.element;
        const windowId = win.id
        const st = win.style
        if(aw.type === 'move') {
            if(aw.winX !== win.style.left || aw.winY !== win.style.top) {
                // Sync final position to server
                reportWinPos(win)
            }
        } else if(aw.type === 'resize') {
            sendResizeEvent(windowId, parseInt(st.width), parseInt(st.height));
        }
        activeWindow = null
    } else if(mdi.isValidForClick) {
        // console.log(`x-click on ${ e.target.id}`)
        sendMouseEventTo(e, 'x-click')
        e.preventDefault()
    }
    if(!(debugDrag && e.shiftKey)) {
        resetDrag()
        dblCkickValid = mdi.isValidForClick
        mouseDownInfo = undefined;
    }
})

function sendToggleMaximize(win) {
    void htmx.ajax('post', `${ API }/onToggleMaximize?w=${ win.id }`, {
        // swap: 'none'
        source: `#${ win.id } .window-header`
    })
    scheduleWindowStateSave()
}

function sendResizeEvent(windowId, width, height) {
    if(windowId) {
        void htmx.ajax('post', `${ API }/onResize?w=${ windowId }&x=${ width }&y=${ height }`, {
            // swap: 'none'
            source: `#${ windowId } .window-header`
        });
        scheduleWindowStateSave()
    }
}

const WIN_STATE_KEY = 'windowState';

let winSaveTimer = null;

function scheduleWindowStateSave() {
    if(winSaveTimer) {
        clearTimeout(winSaveTimer)
    }
    winSaveTimer = setTimeout(() => {
        // Use async fetch for scheduled saves (no blocking warning)
        // We intentionally don't await - fire and forget
        void saveWindowState();
        winSaveTimer = null;
    }, 2000);
}

// Fetch window state from server and save to localStorage using async fetch
async function saveWindowState() {
    try {
        const response = await fetch(`${ API }/getState`, {
            method: 'POST',
            headers: {
                [SESSION_ID_HEADER]: sessionId
            }
        });

        if(!response.ok) {
            console.error('Failed to fetch window state:', response.statusText);
            return;
        }
        const state = await response.text();
        localStorage.setItem(WIN_STATE_KEY, state);
    } catch(e) {
        console.error('Failed to save window state from server:', e);
    }
}

// Synchronous version using XMLHttpRequest (for beforeunload)
function saveWindowStateSync() {
    try {
        const xhr = new XMLHttpRequest();
        xhr.open('POST', `${ API }/getState`, false)
        xhr.setRequestHeader(SESSION_ID_HEADER, sessionId);
        xhr.send();
        if(xhr.status === 200) {
            localStorage.setItem(WIN_STATE_KEY, xhr.responseText);
        } else {
            console.error('Failed to fetch window state (sync):', xhr.statusText);
        }
    } catch(e) {
        console.error('Failed to save window state from server (sync):', e);
    }
}

// Handle double-click on window header to toggle maximize
document.addEventListener('dblclick', (e) => {
    if(!dblCkickValid){
        return
    }
    const header = e.target.closest('.window-header');
    if(!header || e.target.closest('.window-close') || e.target.closest('.but-min-max')) return;
    sendToggleMaximize(header.closest('.window'))
});


function reportWinPos(win) {
    const windowId = win.id
    if(!windowId) return;
    void htmx.ajax('post', `${ API }/onMove?w=${ windowId }&x=${ parseInt(win.style.left) }&y=${ parseInt(win.style.top) }`, {
        // swap: 'none'
        source: `#${ windowId } .window-header`
    });
    scheduleWindowStateSave()
}

function tryProcessXFnc(d) {
    if(d.includes('<x-fnc')) {
        new DOMParser().parseFromString(d, 'text/html').querySelectorAll('x-fnc').forEach(tag => {
            const fncName = tag.getAttribute('fnc');
            const js = tag.textContent;
            if(fncName && js) {
                const fnc = window[fncName];
                if(typeof fnc === 'function') {
                    try {
                        const jsonData = JSON.parse(js);
                        fnc(jsonData);
                    } catch(e) {
                        console.error(`Failed to process fnc call for ${ fncName }:`, e);
                    }
                } else {
                    console.warn(`Function ${ fncName } not found`);
                }
            }
        });
    }
}

document.body.addEventListener('htmx:afterRequest', (e) => {
    // console.log("htmx:afterRequest", event)
    let d = e.detail
    // Extract and process all <x-fnc> tags from the response body
    let type = typeof d.xhr.response
    if(type === 'string') {
        tryProcessXFnc(d.xhr.response)
    }
    if(d.xhr.getResponseHeader('x-syncstate')) {
        scheduleWindowStateSave()
    }
})

let inSseerror = false;

document.body.addEventListener('htmx:sseMessage', (e) => {
    if(inSseerror) {
        inSseerror = false;
        document.body.classList.remove('sse-error');
    }
    const data = e.detail.data;
    tryProcessXFnc(data);
    // console.log('SSE message received:', data);
});

document.body.addEventListener('htmx:sseError', function(e) {
    document.body.classList.add('sse-error');
    inSseerror = true;
    console.error('SSE error:', e.detail);
})

window.addEventListener('resize', () => {
    onDesktopWidthChanged()
});

// htmx.logAll()
htmx.config.methodsThatUseUrlParams.push("post")
htmx.config.defaultSwapStyle = 'none'

// htmx.config.defaultSettleDelay = 2000;

function getSessionId() {
    let id = sessionStorage.getItem('sessionId');
    if(!id) {
        id = 'session_' + Date.now() + '_' + Math.random().toString(36).substring(2, 11);
        sessionStorage.setItem('sessionId', id);
    }
    return id;
}

const sessionId = getSessionId()

document.body.addEventListener('htmx:configRequest', function(e) {
    e.detail.headers[SESSION_ID_HEADER] = sessionId
});

function isBlobRequest(e) {
    let pI = e.detail.pathInfo
    return pI && pI.requestPath === '/session/thumbnail'
}

document.body.addEventListener('htmx:beforeRequest', function(e) {
    if(isBlobRequest(e)) {
        // console.log('htmx:beforeRequest', e.detail);
        e.detail.xhr.responseType = 'blob';
    }
});

document.body.addEventListener('htmx:beforeSwap', function(e) {
    if(isBlobRequest(e)) {
        const d = e.detail
        // console.log('htmx:beforeSwap', d);
        // e.detail.xhr.responseType = 'blob';
        const blob = d.xhr.response;
        const objectURL = URL.createObjectURL(blob);
        d.target.style.backgroundImage = `url('${ objectURL }')`;
        d.target.dataset.blobUrl = objectURL;
        // setTimeout(() => URL.revokeObjectURL(objectURL), 1000);
        e.preventDefault()
    }
});

document.body.addEventListener('htmx:beforeProcessNode', function(e) {
    const elt = e.detail.elt;
    const sseCon = elt.getAttribute('sse-connect')
    if(sseCon) {
        const url = new URL(sseCon, window.location.origin);
        url.searchParams.set(SESSION_ID_HEADER, sessionId);
        const newSseCon = url.toString();
        elt.setAttribute('sse-connect', newSseCon);
        // console.log('htmx:beforeProcessNode', newSseCon);
    }
});

document.body.addEventListener('htmx:afterSwap', function(e) {
    if(isBlobRequest(e)) {
        console.log('htmx:afterSwap', e.detail);
    }
})

/*
document.body.addEventListener('htmx:beforeOnLoad', function(e) {
    if(isBlobRequest(e)) {
        console.log('beforeOnLoad', e.detail);
        e.preventDefault()
    }
});

document.body.addEventListener('htmx:afterRequest', function(e) {
    if(isBlobRequest(e)) {
        console.log('afterRequest', e.detail);
        e.preventDefault()
    }
});*/

// loadDesktopWidth();

document.addEventListener('DOMContentLoaded', () => {
    // Parse stored window state if present, but always ensure we have a
    // js object with a `page` property. The server expects `st` to be a
    // JSON string, so send it as a single form field named `st`.
    let js = {}
    const st = localStorage.getItem(WIN_STATE_KEY);
    if(st) {
        try {
            js = JSON.parse(st);
            saveDesktopWidth(js.page.x)
        } catch(e) {
            console.error('Failed to parse window state:', e);
        }
    }
    if(!js.page) {
        js.page = {
            x: workspaceWidth(), y: workspaceHeight()
        }
    }

    void htmx.ajax('post', `${ API }/connect?data=${ JSON.stringify(js) }`, {
        // swap: 'none',
        values: {
            data: JSON.stringify(js)
        }
    });

    bindDragAndDropEvents(document.body, dragPrevent)
})

window.addEventListener('beforeunload', () => {
    // Use synchronous version to ensure state is saved before page unloads
    if(winSaveTimer) {
        clearTimeout(winSaveTimer);
        saveWindowStateSync();
    }
});

document.addEventListener('contextmenu', function(e) {
    e.preventDefault();
});

/*
window.addEventListener('keydown', (e) => {
    console.log(e)
    e.preventDefault()
})
*/

/*
htmx.defineExtension('oob-delay', {
    onEvent: function(name, evt) {
        if(name === 'htmx:oobBeforeSwap') {
            let f = evt.detail.fragment
            const delay = f.getAttribute && f.getAttribute('hx-swap-delay');
            if(delay) {
                evt.detail.shouldSwap = false;
                const target = evt.detail.target;
                const fragment = f;
                const swapSpec = htmx.getAttributeValue(target, 'hx-swap') || 'innerHTML';

                setTimeout(() => {
                    htmx.swap(target, swapSpec, fragment);
                    htmx.settleImmediately([fragment]);
                }, parseInt(delay));
            }
        }
    }
});
*/

(function() {
    function maybeRemoveMe(elt) {
        const timing = elt.getAttribute('remove-me') || elt.getAttribute('data-remove-me')
        if(timing) {
            setTimeout(function() {
                elt.parentElement.removeChild(elt)
            }, htmx.parseInterval(timing))
        }
    }

    htmx.defineExtension('remove-me', {
        onEvent: function(name, evt) {
            if(name === 'htmx:afterProcessNode') {
                const elt = evt.detail.elt
                if(elt.getAttribute) {
                    maybeRemoveMe(elt)
                    if(elt.querySelectorAll) {
                        const children = elt.querySelectorAll('[remove-me], [data-remove-me]')
                        for(let i = 0; i < children.length; i++) {
                            maybeRemoveMe(children[i])
                        }
                    }
                }
            }
            return true
        }
    })
})()

document.body.addEventListener('htmx:oobAfterSwap', (event) => {
    const e = event.detail.target.querySelector('[role="menu"], [role="dialog"]')
    if(e) {
        initPopupFocus(e);
    }
});

document.body.addEventListener('htmx:oobErrorNoTarget', (event) => {
    console.log('Content:', safeStringify(event.detail.content));
});

const observer = new MutationObserver(function(ml) {
    ml.forEach(function(m) {
        m.removedNodes.forEach(function(node) {
            if(node.nodeType === 1 && node.dataset && node.dataset.blobUrl) {
                console.log("Revoke blob URL:", node.dataset.blobUrl);
                URL.revokeObjectURL(node.dataset.blobUrl);
            }
            if(node.querySelectorAll) {
                node.querySelectorAll('[data-blob-url]').forEach(function(el) {
                    // console.log("Revoke blob from child:", el.dataset.blobUrl);
                    URL.revokeObjectURL(el.dataset.blobUrl);
                });
            }
        });
    });
});

observer.observe(document.body, {
    childList: true,
    subtree: true
});

function bindDragAndDropEvents(e, fn){
    ['dragenter', 'dragover', 'dragleave', 'dragend', 'drop'].forEach(
        n => e.addEventListener(n, fn)
    );
}

function dragPrevent(ev){
    const dt = ev.dataTransfer
    const t = ev.type
    if(t==='dragenter'){
        dt.effectAllowed = 'none'
    }else if(t==='dragover'){
        dt.dropEffect = 'none';
    }
    ev.preventDefault();
    ev.stopPropagation();
}

function test(js) {
    console.log(js);
}
