describe('__getRequestQueue / RequestQueue unit tests', function() {

    beforeEach(function() {
        setupTest();
    });

    afterEach(function() {
        cleanupTest();
    });

    it('allows first request when queue is empty', function () {
        let div = createProcessedHTML('<div hx-get="/test"></div>')
        let ctx = htmx.__createRequestContext(div, new Event('click'))
        let queue = htmx.__getRequestQueue(div)

        let result = queue.issue(ctx, 'queue first')

        assert.isTrue(result)
    })

    it('queues request with "queue all" strategy', function () {
        let div = createProcessedHTML('<div hx-get="/test"></div>')
        let queue = htmx.__getRequestQueue(div)

        // Issue first request
        let ctx1 = htmx.__createRequestContext(div, new Event('click'))
        queue.issue(ctx1, 'queue all')

        // Queue second request
        let ctx2 = htmx.__createRequestContext(div, new Event('click'))
        let result = queue.issue(ctx2, 'queue all')

        assert.isFalse(result)
        assert.equal(ctx2.status, 'queued')
    })

    it('drops request with "drop" strategy', function () {
        let div = createProcessedHTML('<div hx-get="/test"></div>')
        let queue = htmx.__getRequestQueue(div)

        // Issue first request
        let ctx1 = htmx.__createRequestContext(div, new Event('click'))
        queue.issue(ctx1, 'drop')

        // Drop second request
        let ctx2 = htmx.__createRequestContext(div, new Event('click'))
        let result = queue.issue(ctx2, 'drop')

        assert.isFalse(result)
        assert.equal(ctx2.status, 'dropped')
    })

    it('queues only last with "queue last" strategy', function () {
        let div = createProcessedHTML('<div hx-get="/test"></div>')
        let queue = htmx.__getRequestQueue(div)

        // Issue first request
        let ctx1 = htmx.__createRequestContext(div, new Event('click'))
        queue.issue(ctx1, 'queue last')

        // Queue second request
        let ctx2 = htmx.__createRequestContext(div, new Event('click'))
        queue.issue(ctx2, 'queue last')

        // Queue third request (should drop ctx2)
        let ctx3 = htmx.__createRequestContext(div, new Event('click'))
        let result = queue.issue(ctx3, 'queue last')

        assert.isFalse(result)
        assert.equal(ctx2.status, 'dropped')
        assert.equal(ctx3.status, 'queued')
    })

    it('replaces current request with "replace" strategy', function () {
        let div = createProcessedHTML('<div hx-get="/test"></div>')
        let queue = htmx.__getRequestQueue(div)

        // Issue first request
        let ctx1 = htmx.__createRequestContext(div, new Event('click'))
        ctx1.request = {abort: () => { ctx1.aborted = true }}
        queue.issue(ctx1, 'replace')

        // Replace with second request
        let ctx2 = htmx.__createRequestContext(div, new Event('click'))
        let result = queue.issue(ctx2, 'replace')

        assert.isTrue(result)
        assert.isTrue(ctx1.aborted)
    })

    it('defaults to "queue first" when strategy not specified', function () {
        let div = createProcessedHTML('<div hx-get="/test"></div>')
        let queue = htmx.__getRequestQueue(div)

        // Issue first request
        let ctx1 = htmx.__createRequestContext(div, new Event('click'))
        queue.issue(ctx1, 'queue first')

        // Queue second request
        let ctx2 = htmx.__createRequestContext(div, new Event('click'))
        queue.issue(ctx2, 'queue first')

        // Third request should be dropped (not queued)
        let ctx3 = htmx.__createRequestContext(div, new Event('click'))
        let result = queue.issue(ctx3, 'queue first')

        assert.isFalse(result)
        assert.equal(ctx2.status, 'queued')
        assert.equal(ctx3.status, 'dropped')
    })

    it('hasMore returns truthy when queue has requests', function () {
        let div = createProcessedHTML('<div hx-get="/test"></div>')
        let queue = htmx.__getRequestQueue(div)

        let ctx1 = htmx.__createRequestContext(div, new Event('click'))
        queue.issue(ctx1, 'queue all')

        let ctx2 = htmx.__createRequestContext(div, new Event('click'))
        queue.issue(ctx2, 'queue all')

        assert.isOk(queue.more())
    })

    it('hasMore returns falsey when queue is empty', function () {
        let div = createProcessedHTML('<div hx-get="/test"></div>')
        let queue = htmx.__getRequestQueue(div)

        assert.isNotOk(queue.more())
    })

    it('finish returns next queued request', function () {
        let div = createProcessedHTML('<div hx-get="/test"></div>')
        let queue = htmx.__getRequestQueue(div)

        let ctx1 = htmx.__createRequestContext(div, new Event('click'))
        queue.issue(ctx1, 'queue all')

        let ctx2 = htmx.__createRequestContext(div, new Event('click'))
        queue.issue(ctx2, 'queue all')

        queue.finish(ctx1)
        let next = queue.next()

        assert.equal(next, ctx2)
    })

    it('nextRequest clears current request', function () {
        let div = createProcessedHTML('<div hx-get="/test"></div>')
        let queue = htmx.__getRequestQueue(div)

        let ctx1 = htmx.__createRequestContext(div, new Event('click'))
        queue.issue(ctx1, 'queue all')

        let ctx2 = htmx.__createRequestContext(div, new Event('click'))
        queue.issue(ctx2, 'queue all')

        queue.finish(ctx1)
        queue.next()

        // Should now allow a new request
        let ctx3 = htmx.__createRequestContext(div, new Event('click'))
        let result = queue.issue(ctx3, 'queue first')

        assert.isTrue(result)
    })

    it('abortCurrentRequest calls abort on current request', function () {
        let div = createProcessedHTML('<div hx-get="/test"></div>')
        let queue = htmx.__getRequestQueue(div)

        let ctx = htmx.__createRequestContext(div, new Event('click'))
        queue.issue(ctx, 'queue first')

        queue.abort()

        assert.isTrue(ctx.request.signal.aborted)
    })

    it('returns same queue for same element', function () {
        let div = createProcessedHTML('<div hx-get="/test"></div>')

        let queue1 = htmx.__getRequestQueue(div)
        let queue2 = htmx.__getRequestQueue(div)

        assert.equal(queue1, queue2)
    })

    it('returns different queue for different elements', function () {
        let div1 = createProcessedHTML('<div hx-get="/test1"></div>')
        let div2 = createProcessedHTML('<div hx-get="/test2"></div>')

        let queue1 = htmx.__getRequestQueue(div1)
        let queue2 = htmx.__getRequestQueue(div2)

        assert.notEqual(queue1, queue2)
    })

    it('hx-sync="drop" without selector uses drop strategy', function () {
        let div = createProcessedHTML('<div hx-get="/test" hx-sync="drop"></div>')
        let queue = htmx.__getRequestQueue(div)
        let ctx1 = htmx.__createRequestContext(div, new Event('click'))
        queue.issue(ctx1, htmx.__determineSyncStrategy(div))

        let ctx2 = htmx.__createRequestContext(div, new Event('click'))
        let result = queue.issue(ctx2, htmx.__determineSyncStrategy(div))

        assert.isFalse(result)
        assert.equal(ctx2.status, 'dropped')
    })

    it('hx-sync="abort" without selector uses abort strategy', function () {
        let div = createProcessedHTML('<div hx-get="/test" hx-sync="abort"></div>')
        let queue = htmx.__getRequestQueue(div)
        let ctx1 = htmx.__createRequestContext(div, new Event('click'))
        ctx1.request = {abort: () => { ctx1.aborted = true }}
        queue.issue(ctx1, htmx.__determineSyncStrategy(div))

        let ctx2 = htmx.__createRequestContext(div, new Event('click'))
        let result = queue.issue(ctx2, htmx.__determineSyncStrategy(div))

        assert.isFalse(result)
        assert.equal(ctx2.status, 'dropped')
    })

    it('hx-sync="selector:drop" uses drop strategy', function () {
        let container = createProcessedHTML('<div id="c"><div id="btn" hx-get="/test" hx-sync="#c:drop"></div></div>')
        let btn = container.querySelector('#btn')
        assert.equal(htmx.__determineSyncStrategy(btn), 'drop')
    })

    it('uses selector from hx-sync for queue', function () {
        let container = createProcessedHTML('<div id="container"><div id="btn1" hx-get="/test1" hx-sync="#container:drop"></div><div id="btn2" hx-get="/test2" hx-sync="#container:drop"></div></div>')
        let btn1 = container.querySelector('#btn1')
        let btn2 = container.querySelector('#btn2')

        let queue1 = htmx.__getRequestQueue(btn1)
        let queue2 = htmx.__getRequestQueue(btn2)

        assert.equal(queue1, queue2)
    })

    it('inherited hx-sync="this:replace" resolves queue to declaring parent', function () {
        let parent = createProcessedHTML('<div id="parent" hx-sync:inherited="this:replace"><div id="a" hx-get="/a"></div><div id="b" hx-get="/b"></div></div>')
        let a = parent.querySelector('#a')
        let b = parent.querySelector('#b')

        let queueA = htmx.__getRequestQueue(a)
        let queueB = htmx.__getRequestQueue(b)

        // Both children should share the parent's queue
        assert.equal(queueA, queueB)
        assert.equal(htmx.__htmxProp(parent).rq, queueA)
        assert.equal(htmx.__determineSyncStrategy(a), 'replace')
    })

    it('abort strategy: allows first abort request when queue is empty', function () {
        let div = createProcessedHTML('<div hx-get="/test"></div>')
        let queue = htmx.__getRequestQueue(div)

        let ctx = htmx.__createRequestContext(div, new Event('click'))
        ctx.request = {abort: () => { ctx.aborted = true }}
        let result = queue.issue(ctx, 'abort')

        assert.isTrue(result)
        assert.equal(ctx.queueStrategy, 'abort')
    })

    it('abort strategy: any request can abort an abortable request', function () {
        let div = createProcessedHTML('<div hx-get="/test"></div>')
        let queue = htmx.__getRequestQueue(div)

        // Issue abort request
        let ctx1 = htmx.__createRequestContext(div, new Event('click'))
        ctx1.request = {abort: () => { ctx1.aborted = true }}
        queue.issue(ctx1, 'abort')

        // Issue drop request - should abort the abort request
        let ctx2 = htmx.__createRequestContext(div, new Event('click'))
        ctx2.request = {abort: () => { ctx2.aborted = true }}
        let result = queue.issue(ctx2, 'drop')

        assert.isTrue(result)
        assert.isTrue(ctx1.aborted)
    })

    it('abort strategy: another abort request drops when abort request is in flight', function () {
        let div = createProcessedHTML('<div hx-get="/test"></div>')
        let queue = htmx.__getRequestQueue(div)

        // Issue abort request
        let ctx1 = htmx.__createRequestContext(div, new Event('click'))
        ctx1.aborted = false
        ctx1.request = {abort: () => { ctx1.aborted = true }}
        queue.issue(ctx1, 'abort')

        // Issue another abort request - should be dropped
        let ctx2 = htmx.__createRequestContext(div, new Event('click'))
        ctx2.aborted = false
        ctx2.request = {abort: () => { ctx2.aborted = true }}
        let result = queue.issue(ctx2, 'abort')

        assert.isFalse(result)
        assert.equal(ctx2.status, 'dropped')
        assert.isFalse(ctx1.aborted)
    })

    it('abort strategy: abort request drops itself if non-abortable request is in flight', function () {
        let div = createProcessedHTML('<div hx-get="/test"></div>')
        let queue = htmx.__getRequestQueue(div)

        // Issue drop request (not abortable)
        let ctx1 = htmx.__createRequestContext(div, new Event('click'))
        ctx1.aborted = false
        ctx1.request = {abort: () => { ctx1.aborted = true }}
        queue.issue(ctx1, 'drop')

        // Issue abort request - should be dropped
        let ctx2 = htmx.__createRequestContext(div, new Event('click'))
        ctx2.aborted = false
        ctx2.request = {abort: () => { ctx2.aborted = true }}
        let result = queue.issue(ctx2, 'abort')

        assert.isFalse(result)
        assert.equal(ctx2.status, 'dropped')
        assert.isFalse(ctx1.aborted)
    })

    it('abort strategy: replace request can abort an abortable request', function () {
        let div = createProcessedHTML('<div hx-get="/test"></div>')
        let queue = htmx.__getRequestQueue(div)

        // Issue abort request
        let ctx1 = htmx.__createRequestContext(div, new Event('click'))
        ctx1.request = {abort: () => { ctx1.aborted = true }}
        queue.issue(ctx1, 'abort')

        // Issue replace request - should abort the abort request
        let ctx2 = htmx.__createRequestContext(div, new Event('click'))
        ctx2.request = {abort: () => { ctx2.aborted = true }}
        let result = queue.issue(ctx2, 'replace')

        assert.isTrue(result)
        assert.isTrue(ctx1.aborted)
    })

    it('abort strategy: queue-all request can abort an abortable request', function () {
        let div = createProcessedHTML('<div hx-get="/test"></div>')
        let queue = htmx.__getRequestQueue(div)

        // Issue abort request
        let ctx1 = htmx.__createRequestContext(div, new Event('click'))
        ctx1.request = {abort: () => { ctx1.aborted = true }}
        queue.issue(ctx1, 'abort')

        // Issue queue-all request - should abort the abort request
        let ctx2 = htmx.__createRequestContext(div, new Event('click'))
        ctx2.request = {abort: () => { ctx2.aborted = true }}
        let result = queue.issue(ctx2, 'queue all')

        assert.isTrue(result)
        assert.isTrue(ctx1.aborted)
    })

    it('abort strategy: abort request drops itself when replace request is in flight', function () {
        let div = createProcessedHTML('<div hx-get="/test"></div>')
        let queue = htmx.__getRequestQueue(div)

        // Issue replace request
        let ctx1 = htmx.__createRequestContext(div, new Event('click'))
        ctx1.aborted = false
        ctx1.request = {abort: () => { ctx1.aborted = true }}
        queue.issue(ctx1, 'replace')

        // Issue abort request - should be dropped
        let ctx2 = htmx.__createRequestContext(div, new Event('click'))
        ctx2.aborted = false
        ctx2.request = {abort: () => { ctx2.aborted = true }}
        let result = queue.issue(ctx2, 'abort')

        assert.isFalse(result)
        assert.equal(ctx2.status, 'dropped')
        assert.isFalse(ctx1.aborted)
    })

    it('abort strategy: abort request drops itself when queue-first request is in flight', function () {
        let div = createProcessedHTML('<div hx-get="/test"></div>')
        let queue = htmx.__getRequestQueue(div)

        // Issue queue-first request
        let ctx1 = htmx.__createRequestContext(div, new Event('click'))
        ctx1.aborted = false
        ctx1.request = {abort: () => { ctx1.aborted = true }}
        queue.issue(ctx1, 'queue first')

        // Issue abort request - should be dropped
        let ctx2 = htmx.__createRequestContext(div, new Event('click'))
        ctx2.aborted = false
        ctx2.request = {abort: () => { ctx2.aborted = true }}
        let result = queue.issue(ctx2, 'abort')

        assert.isFalse(result)
        assert.equal(ctx2.status, 'dropped')
        assert.isFalse(ctx1.aborted)
    })

    // hx-sync value parsing tests

    it('hx-sync="this" defaults to queue first strategy', function () {
        let div = createProcessedHTML('<div hx-get="/test" hx-sync="this"></div>')
        assert.equal(htmx.__determineSyncStrategy(div), 'queue first')
    })

    it('hx-sync="this" uses same element for queue', function () {
        let div = createProcessedHTML('<div hx-get="/test" hx-sync="this"></div>')
        let queue = htmx.__getRequestQueue(div)
        assert.isOk(queue)
    })

    it('hx-sync="this:drop" uses drop strategy', function () {
        let div = createProcessedHTML('<div hx-get="/test" hx-sync="this:drop"></div>')
        assert.equal(htmx.__determineSyncStrategy(div), 'drop')
    })

    it('hx-sync="this:replace" uses replace strategy', function () {
        let div = createProcessedHTML('<div hx-get="/test" hx-sync="this:replace"></div>')
        assert.equal(htmx.__determineSyncStrategy(div), 'replace')
    })

    it('hx-sync="this:queue last" uses queue last strategy', function () {
        let div = createProcessedHTML('<div hx-get="/test" hx-sync="this:queue last"></div>')
        assert.equal(htmx.__determineSyncStrategy(div), 'queue last')
    })

    it('hx-sync="closest form" uses closest form for queue with default strategy', function () {
        let form = createProcessedHTML('<form><div id="btn" hx-get="/test" hx-sync="closest form"></div></form>')
        let btn = form.querySelector('#btn')
        assert.equal(htmx.__determineSyncStrategy(btn), 'queue first')
        // queue should be on the form, not the button
        let queue = htmx.__getRequestQueue(btn)
        assert.equal(htmx.__htmxProp(form).rq, queue)
    })

    it('hx-sync="closest form:replace" uses closest form with replace strategy', function () {
        let form = createProcessedHTML('<form><div id="btn" hx-get="/test" hx-sync="closest form:replace"></div></form>')
        let btn = form.querySelector('#btn')
        assert.equal(htmx.__determineSyncStrategy(btn), 'replace')
        let queue = htmx.__getRequestQueue(btn)
        assert.equal(htmx.__htmxProp(form).rq, queue)
    })

    it('hx-sync with "this" shares queue between sibling elements synced to parent', function () {
        let container = createProcessedHTML('<div id="c"><div id="a" hx-get="/a" hx-sync="#c:drop"></div><div id="b" hx-get="/b" hx-sync="#c:drop"></div></div>')
        let a = container.querySelector('#a')
        let b = container.querySelector('#b')
        assert.equal(htmx.__getRequestQueue(a), htmx.__getRequestQueue(b))
    })

    it('abort strategy: clears queue when aborting current request', function () {
        let div = createProcessedHTML('<div hx-get="/test"></div>')
        let queue = htmx.__getRequestQueue(div)

        // Issue non-abortable request
        let ctx1 = htmx.__createRequestContext(div, new Event('click'))
        ctx1.aborted = false
        ctx1.request = {abort: () => { ctx1.aborted = true }}
        queue.issue(ctx1, 'drop')

        // Queue some requests
        let ctx2 = htmx.__createRequestContext(div, new Event('click'))
        queue.issue(ctx2, 'queue all')

        let ctx3 = htmx.__createRequestContext(div, new Event('click'))
        queue.issue(ctx3, 'queue all')

        // Issue replace request - should clear queue
        let ctx4 = htmx.__createRequestContext(div, new Event('click'))
        ctx4.aborted = false
        ctx4.request = {abort: () => { ctx4.aborted = true }}
        let result = queue.issue(ctx4, 'replace')

        assert.isTrue(result)
        assert.isTrue(ctx1.aborted)
        assert.equal(ctx2.status, 'dropped')
        assert.equal(ctx3.status, 'dropped')
        assert.isNotOk(queue.more())
    })

});
