"""JavaScript generator for touch-to-nav conversion: swipe, drag,"""

# AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/js/touch.ipynb.

# %% auto #0
__all__ = ['generate_touch_nav_js']

# %% ../../nbs/js/touch.ipynb #t1000003
from typing import Tuple

from ..core.html_ids import CardStackHtmlIds
from ..core.button_ids import CardStackButtonIds
from cjm_fasthtml_card_stack.core.constants import (
    TOUCH_SWIPE_THRESHOLD, TOUCH_MOMENTUM_MIN_VELOCITY,
    TOUCH_MOMENTUM_FRICTION, TOUCH_PINCH_THRESHOLD,
    TOUCH_VELOCITY_SAMPLES,
)

# %% ../../nbs/js/touch.ipynb #t1000005
def generate_touch_nav_js(
    ids: CardStackHtmlIds,  # HTML IDs for this card stack instance
    button_ids: CardStackButtonIds,  # Button IDs for navigation triggers
    disable_in_modes: Tuple[str, ...] = (),  # Mode names where touch nav is suppressed
) -> str:  # JavaScript code fragment for touch navigation
    """Generate JS for touch gesture to navigation conversion."""
    # Build mode check (same pattern as scroll.ipynb)
    if disable_in_modes:
        modes_array = ', '.join(f"'{m}'" for m in disable_in_modes)
        mode_check = f"""
        function isTouchDisabled() {{
            if (typeof window.kbNav !== 'undefined') {{
                const state = window.kbNav.getState();
                const disabledModes = [{modes_array}];
                return state && disabledModes.includes(state.currentMode);
            }}
            return false;
        }}
        """
        mode_guard = "if (isTouchDisabled()) return;"
        momentum_mode_guard = (
            "if (typeof isTouchDisabled === 'function' && isTouchDisabled()) "
            "{ _touchState.momentumId = null; return; }"
        )
    else:
        mode_check = ""
        mode_guard = ""
        momentum_mode_guard = ""

    return f"""
        // === Touch Navigation ===
        // Uses Pointer Events + setPointerCapture so that events survive
        // HTMX OOB DOM swaps that replace the original touch target mid-drag.
        const _touchState = {{
            pointers: new Map(),
            primaryId: null,
            active: false,
            startY: 0,
            startX: 0,
            lastY: 0,
            lastStepY: 0,
            stepDistance: 100,
            isNavigating: false,
            isPinching: false,
            pinchStartDist: 0,
            stepsTriggered: 0,
            history: [],
            momentumId: null,
            momentumAccum: 0,
        }};
        const _TOUCH_SWIPE_THRESHOLD = {TOUCH_SWIPE_THRESHOLD};
        const _TOUCH_MOMENTUM_MIN_VEL = {TOUCH_MOMENTUM_MIN_VELOCITY};
        const _TOUCH_MOMENTUM_FRICTION = {TOUCH_MOMENTUM_FRICTION};
        const _TOUCH_PINCH_THRESHOLD = {TOUCH_PINCH_THRESHOLD};
        const _TOUCH_VEL_SAMPLES = {TOUCH_VELOCITY_SAMPLES};
        {mode_check}
        function _getTouchStepDistance() {{
            const slot = document.querySelector(
                '#' + CSS.escape('{ids.card_stack}') + ' .viewport-slot[tabindex=\\\"0\\\"]'
            );
            if (slot) {{
                const h = slot.getBoundingClientRect().height;
                if (h > 0) return h;
            }}
            return 100;
        }}

        function _getPinchDistance() {{
            if (_touchState.pointers.size < 2) return 0;
            const pts = Array.from(_touchState.pointers.values());
            const dx = pts[0].x - pts[1].x;
            const dy = pts[0].y - pts[1].y;
            return Math.sqrt(dx * dx + dy * dy);
        }}

        function _stopMomentum() {{
            if (_touchState.momentumId) {{
                cancelAnimationFrame(_touchState.momentumId);
                _touchState.momentumId = null;
            }}
        }}

        function _fireTouchNav(direction) {{
            const btnId = direction === 'down'
                ? '{button_ids.nav_down}' : '{button_ids.nav_up}';
            const btn = document.getElementById(btnId);
            if (btn) btn.click();
        }}

        function setupTouchNavigation() {{
            const cardStack = document.getElementById('{ids.card_stack}');
            if (!cardStack) return;

            // Abort previous listeners (handles re-setup from afterSettle
            // and IIFE re-execution from HTMX page navigation).
            if (cardStack._touchNavAbort) cardStack._touchNavAbort.abort();
            const controller = new AbortController();
            cardStack._touchNavAbort = controller;
            const sig = {{ signal: controller.signal }};

            cardStack.addEventListener('pointerdown', function(evt) {{
                if (evt.pointerType !== 'touch') return;
                {mode_guard}
                _stopMomentum();

                _touchState.pointers.set(evt.pointerId, {{ x: evt.clientX, y: evt.clientY }});

                if (_touchState.pointers.size === 2) {{
                    // Second finger — switch to pinch mode
                    _touchState.isPinching = true;
                    _touchState.isNavigating = false;
                    _touchState.active = false;
                    _touchState.pinchStartDist = _getPinchDistance();
                    // Capture both pointers to survive DOM changes
                    for (const id of _touchState.pointers.keys()) {{
                        try {{ cardStack.setPointerCapture(id); }} catch (e) {{}}
                    }}
                    evt.preventDefault();
                    return;
                }}

                if (_touchState.pointers.size === 1) {{
                    _touchState.primaryId = evt.pointerId;
                    _touchState.active = true;
                    _touchState.startY = evt.clientY;
                    _touchState.startX = evt.clientX;
                    _touchState.lastY = evt.clientY;
                    _touchState.lastStepY = evt.clientY;
                    _touchState.isNavigating = false;
                    _touchState.isPinching = false;
                    _touchState.stepsTriggered = 0;
                    _touchState.history = [];
                    _touchState.stepDistance = _getTouchStepDistance();
                }}
            }}, sig);

            cardStack.addEventListener('pointermove', function(evt) {{
                if (evt.pointerType !== 'touch') return;
                {mode_guard}

                // Update tracked pointer position
                if (_touchState.pointers.has(evt.pointerId)) {{
                    _touchState.pointers.set(evt.pointerId, {{ x: evt.clientX, y: evt.clientY }});
                }}

                // --- Pinch mode ---
                if (_touchState.isPinching && _touchState.pointers.size >= 2) {{
                    evt.preventDefault();
                    const dist = _getPinchDistance();
                    const delta = dist - _touchState.pinchStartDist;
                    if (Math.abs(delta) >= _TOUCH_PINCH_THRESHOLD) {{
                        if (delta > 0) {{
                            if (ns.increaseScale) ns.increaseScale();
                        }} else {{
                            if (ns.decreaseScale) ns.decreaseScale();
                        }}
                        _touchState.pinchStartDist = dist;
                    }}
                    return;
                }}

                // --- Single-finger drag ---
                if (!_touchState.active || evt.pointerId !== _touchState.primaryId) return;

                const deltaY = evt.clientY - _touchState.startY;
                const deltaX = evt.clientX - _touchState.startX;

                // Direction lock: decide vertical vs horizontal
                if (!_touchState.isNavigating) {{
                    const totalDist = Math.abs(deltaY) + Math.abs(deltaX);
                    if (totalDist < 10) return;
                    if (Math.abs(deltaX) > Math.abs(deltaY)) {{
                        // Horizontal — abort touch nav
                        _touchState.active = false;
                        return;
                    }}
                    _touchState.isNavigating = true;
                    // Capture pointer on the card stack so events survive
                    // HTMX OOB swaps that replace elements under the finger
                    try {{ cardStack.setPointerCapture(evt.pointerId); }} catch (e) {{}}
                }}

                evt.preventDefault();

                // Velocity tracking via history buffer
                _touchState.history.push({{ t: evt.timeStamp, y: evt.clientY }});
                if (_touchState.history.length > _TOUCH_VEL_SAMPLES) {{
                    _touchState.history.shift();
                }}
                _touchState.lastY = evt.clientY;

                // Step threshold: one navigation per focused-slot-height
                const stepDelta = evt.clientY - _touchState.lastStepY;
                if (Math.abs(stepDelta) >= _touchState.stepDistance) {{
                    // Finger up (negative delta) = nav_down (next card)
                    const dir = stepDelta < 0 ? 'down' : 'up';
                    _fireTouchNav(dir);
                    _touchState.lastStepY += (stepDelta < 0 ? -1 : 1) * _touchState.stepDistance;
                    _touchState.stepsTriggered++;
                }}
            }}, sig);

            cardStack.addEventListener('pointerup', function(evt) {{
                if (evt.pointerType !== 'touch') return;
                _touchState.pointers.delete(evt.pointerId);

                // --- Pinch ending ---
                if (_touchState.isPinching) {{
                    if (_touchState.pointers.size < 2) {{
                        _touchState.isPinching = false;
                        if (_touchState.pointers.size === 1) {{
                            // One finger remains — reset to single-touch tracking
                            const remaining = _touchState.pointers.entries().next().value;
                            _touchState.primaryId = remaining[0];
                            _touchState.active = true;
                            _touchState.startY = remaining[1].y;
                            _touchState.startX = remaining[1].x;
                            _touchState.lastY = remaining[1].y;
                            _touchState.lastStepY = remaining[1].y;
                            _touchState.isNavigating = false;
                            _touchState.stepsTriggered = 0;
                            _touchState.history = [];
                            _touchState.stepDistance = _getTouchStepDistance();
                        }} else {{
                            _touchState.active = false;
                        }}
                    }}
                    return;
                }}

                if (!_touchState.active || evt.pointerId !== _touchState.primaryId) return;
                _touchState.active = false;

                if (!_touchState.isNavigating) return;

                // Compute velocity from history buffer
                let velocity = 0;
                const hist = _touchState.history;
                if (hist.length >= 2) {{
                    const first = hist[0];
                    const last = hist[hist.length - 1];
                    const dt = Math.max(1, last.t - first.t);
                    velocity = (last.y - first.y) / dt;
                }}

                // Simple swipe: no drag steps triggered but enough distance
                if (_touchState.stepsTriggered === 0) {{
                    const totalDelta = _touchState.lastY - _touchState.startY;
                    if (Math.abs(totalDelta) >= _TOUCH_SWIPE_THRESHOLD) {{
                        _fireTouchNav(totalDelta < 0 ? 'down' : 'up');
                    }}
                    return;
                }}

                // Momentum: continue navigating with deceleration
                const absVel = Math.abs(velocity);
                if (absVel >= _TOUCH_MOMENTUM_MIN_VEL) {{
                    const dir = velocity < 0 ? 'down' : 'up';
                    let curVel = absVel;
                    let lastFrame = performance.now();
                    const stepDist = _touchState.stepDistance;
                    _touchState.momentumAccum = 0;

                    function momentumTick(now) {{
                        {momentum_mode_guard}
                        const dt = now - lastFrame;
                        lastFrame = now;
                        // Time-normalized friction: consistent across frame rates
                        curVel *= Math.pow(_TOUCH_MOMENTUM_FRICTION, dt / 16);
                        _touchState.momentumAccum += curVel * dt;

                        if (_touchState.momentumAccum >= stepDist) {{
                            _touchState.momentumAccum -= stepDist;
                            _fireTouchNav(dir);
                        }}

                        if (curVel >= _TOUCH_MOMENTUM_MIN_VEL * 0.1) {{
                            _touchState.momentumId = requestAnimationFrame(momentumTick);
                        }} else {{
                            _touchState.momentumId = null;
                        }}
                    }}

                    _touchState.momentumId = requestAnimationFrame(momentumTick);
                }}
            }}, sig);

            cardStack.addEventListener('pointercancel', function(evt) {{
                if (evt.pointerType !== 'touch') return;
                _touchState.pointers.delete(evt.pointerId);
                if (_touchState.pointers.size === 0) {{
                    _touchState.active = false;
                    _touchState.isNavigating = false;
                    _touchState.isPinching = false;
                    _stopMomentum();
                }}
            }}, sig);
        }}

        // Expose for master coordinator
        ns._setupTouchNav = setupTouchNavigation;
    """
