{{--
    Page-wide barcode-scanner capture.

    A USB HID scanner is, to the browser, a keyboard: it types the code
    character by character wherever the cursor happens to be, then sends
    Enter. This listens for that pattern on the whole document — not inside
    any one component — so a scan is never lost because focus was somewhere
    else (a card, a button, nothing at all) when it arrived.

    Consumers register a handler for the surface they own:

        window.VellixScan?.register(this.$root, (code) => {
            this.handleScan(code);
            return true;
        });

    `el` is used only to decide WHICH registered handler a scan belongs to
    when more than one is on the page — see resolve() below. A handler that
    declines a scan (a modal is open, the surface is disabled) should still
    return true: it has genuinely handled the scan by choosing to do nothing
    with it, which is different from "no handler exists at all" and stops
    the scan being tried against some unrelated, wrong surface.

    An element that already owns its own Enter handling (the POS/Sales Order
    search boxes, the order-form grid header) carries `data-scan-ignore` so
    this listener steps aside entirely while it has focus — otherwise a scan
    typed into that box would be handled twice.
--}}
@if (auth('web')->check())
    <script>
        (() => {
            if (window.__vellixScanCapture) {
                return;
            }

            window.__vellixScanCapture = true;

            // A human cannot type this fast. A scanner sends its whole code in
            // well under 100ms; MIN_LENGTH keeps a single accidental fast
            // keystroke (or a scan that never left the label prefix) from being
            // treated as a code by itself.
            const MAX_INTER_KEY_GAP_MS = 35;
            const MAX_TOTAL_DURATION_MS = 150;
            const MIN_LENGTH = 3;

            const handlers = [];

            function register(el, fn) {
                const entry = { el, fn };
                handlers.push(entry);

                return () => {
                    const i = handlers.indexOf(entry);
                    if (i !== -1) {
                        handlers.splice(i, 1);
                    }
                };
            }

            // Which registered surface a scan belongs to: whichever one
            // contains the focused element, wins. Failing that — focus is on
            // nothing in particular, which is the common case right after a
            // sale completes — the sole registered surface gets it, or the
            // most recently registered one if several are somehow live at
            // once (there should only ever be one full-screen terminal or
            // form on a page at a time).
            function resolve() {
                const active = document.activeElement;

                for (let i = handlers.length - 1; i >= 0; i--) {
                    if (handlers[i].el && active && handlers[i].el.contains(active)) {
                        return handlers[i].fn;
                    }
                }

                if (handlers.length === 0) {
                    return null;
                }

                return handlers[handlers.length - 1].fn;
            }

            function dispatch(code) {
                const fn = resolve();
                if (fn) {
                    fn(code);
                }
            }

            window.VellixScan = { register, dispatch };

            let buffer = '';
            let bufferStartedAt = 0;
            let lastKeyAt = 0;

            function reset() {
                buffer = '';
                bufferStartedAt = 0;
            }

            function ownsItsOwnInput(target) {
                return !! (target && target.closest && target.closest('[data-scan-ignore]'));
            }

            document.addEventListener(
                'keydown',
                (event) => {
                    if (event.ctrlKey || event.metaKey || event.altKey) {
                        reset();
                        return;
                    }

                    if (ownsItsOwnInput(event.target)) {
                        reset();
                        return;
                    }

                    const now = Date.now();
                    const gap = now - lastKeyAt;

                    if (buffer !== '' && gap > MAX_INTER_KEY_GAP_MS) {
                        reset();
                    }

                    if (event.key === 'Enter' || event.key === 'Tab') {
                        const elapsed = bufferStartedAt ? now - bufferStartedAt : 0;
                        const code = buffer;
                        reset();
                        lastKeyAt = now;

                        if (code.length >= MIN_LENGTH && elapsed <= MAX_TOTAL_DURATION_MS) {
                            event.preventDefault();
                            event.stopPropagation();
                            dispatch(code);
                        }

                        return;
                    }

                    // A key that is not a single printable character (Shift,
                    // Backspace, an arrow key, …) never appears mid-scan — a
                    // real scanner sends nothing but the code and the
                    // terminator. Treat it as the human-typing signal it is.
                    if (event.key.length !== 1) {
                        reset();
                        lastKeyAt = now;
                        return;
                    }

                    if (buffer === '') {
                        bufferStartedAt = now;
                    }

                    buffer += event.key;
                    lastKeyAt = now;
                },
                { capture: true },
            );
        })();
    </script>
@endif
