import {
  comfyExpect as expect,
  comfyPageFixture as test
} from '../fixtures/ComfyPage'

test.describe('Node search box', () => {
  test.beforeEach(async ({ comfyPage }) => {
    await comfyPage.setSetting('Comfy.LinkRelease.Action', 'search box')
    await comfyPage.setSetting('Comfy.LinkRelease.ActionShift', 'search box')
    await comfyPage.setSetting('Comfy.NodeSearchBoxImpl', 'default')
  })

  test(`Can trigger on empty canvas double click`, async ({ comfyPage }) => {
    await comfyPage.doubleClickCanvas()
    await expect(comfyPage.searchBox.input).toHaveCount(1)
  })

  test(`Can trigger on group body double click`, async ({ comfyPage }) => {
    await comfyPage.loadWorkflow('single_group_only')
    await comfyPage.page.mouse.dblclick(50, 50, { delay: 5 })
    await comfyPage.nextFrame()
    await expect(comfyPage.searchBox.input).toHaveCount(1)
  })

  test('Can trigger on link release', async ({ comfyPage }) => {
    await comfyPage.disconnectEdge()
    await expect(comfyPage.searchBox.input).toHaveCount(1)
  })

  test('Can add node', async ({ comfyPage }) => {
    await comfyPage.doubleClickCanvas()
    await expect(comfyPage.searchBox.input).toHaveCount(1)
    await comfyPage.searchBox.fillAndSelectFirstNode('KSampler')
    await expect(comfyPage.canvas).toHaveScreenshot('added-node.png')
  })

  test('Can auto link node', async ({ comfyPage }) => {
    await comfyPage.disconnectEdge()
    // Select the second item as the first item is always reroute
    await comfyPage.searchBox.fillAndSelectFirstNode('CLIPTextEncode', {
      suggestionIndex: 0
    })
    await expect(comfyPage.canvas).toHaveScreenshot('auto-linked-node.png')
  })

  test('Can auto link batch moved node', async ({ comfyPage }) => {
    await comfyPage.loadWorkflow('batch_move_links')

    const outputSlot1Pos = {
      x: 304,
      y: 127
    }
    const emptySpacePos = {
      x: 5,
      y: 5
    }
    await comfyPage.page.keyboard.down('Shift')
    await comfyPage.dragAndDrop(outputSlot1Pos, emptySpacePos)
    await comfyPage.page.keyboard.up('Shift')

    // Select the second item as the first item is always reroute
    await comfyPage.searchBox.fillAndSelectFirstNode('Load Checkpoint', {
      suggestionIndex: 0
    })
    await expect(comfyPage.canvas).toHaveScreenshot(
      'auto-linked-node-batch.png'
    )
  })

  test('Link release connecting to node with no slots', async ({
    comfyPage
  }) => {
    await comfyPage.disconnectEdge()
    await expect(comfyPage.searchBox.input).toHaveCount(1)
    await comfyPage.page.locator('.p-chip-remove-icon').click()
    await comfyPage.searchBox.fillAndSelectFirstNode('KSampler')
    await expect(comfyPage.canvas).toHaveScreenshot(
      'added-node-no-connection.png'
    )
  })

  test('Has correct aria-labels on search results', async ({ comfyPage }) => {
    const node = 'Load Checkpoint'
    await comfyPage.doubleClickCanvas()
    await comfyPage.searchBox.input.waitFor({ state: 'visible' })
    await comfyPage.searchBox.input.fill(node)
    await comfyPage.searchBox.dropdown.waitFor({ state: 'visible' })
    // Wait for some time for the auto complete list to update.
    // The auto complete list is debounced and may take some time to update.
    await comfyPage.page.waitForTimeout(500)

    const firstResult = comfyPage.searchBox.dropdown.locator('li').first()
    await expect(firstResult).toHaveAttribute('aria-label', node)
  })

  test('@mobile Can trigger on empty canvas tap', async ({ comfyPage }) => {
    await comfyPage.closeMenu()
    await comfyPage.loadWorkflow('single_ksampler')
    const screenCenter = {
      x: 200,
      y: 400
    }
    await comfyPage.canvas.tap({
      position: screenCenter
    })
    await comfyPage.canvas.tap({
      position: screenCenter
    })
    await comfyPage.page.waitForTimeout(256)
    await expect(comfyPage.searchBox.input).not.toHaveCount(0)
  })

  test.describe('Filtering', () => {
    const expectFilterChips = async (comfyPage, expectedTexts: string[]) => {
      const chips = comfyPage.searchBox.filterChips

      // Check that the number of chips matches the expected count
      await expect(chips).toHaveCount(expectedTexts.length)

      // Verify the text and visibility of each filter chip
      await Promise.all(
        expectedTexts.map(async (text, index) => {
          const chip = chips.nth(index)
          await expect(chip).toContainText(text)
          await expect(chip).toBeVisible()
        })
      )
    }

    test.beforeEach(async ({ comfyPage }) => {
      await comfyPage.doubleClickCanvas()
    })

    test('Can add filter', async ({ comfyPage }) => {
      await comfyPage.searchBox.addFilter('MODEL', 'Input Type')
      await expectFilterChips(comfyPage, ['MODEL'])
    })

    // Flaky test.
    // Sample test failure:
    // https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/12696912248/job/35391990861?pr=2210
    /*
    1) [chromium-2x] › nodeSearchBox.spec.ts:135:5 › Node search box › Filtering › Outer click dismisses filter panel but keeps search box visible

    Error: expect(locator).not.toBeVisible()

    Locator: getByRole('dialog').locator('div').filter({ hasText: 'Add node filter condition' })
    Expected: not visible
    Received: visible
    Call log:
      - expect.not.toBeVisible with timeout 5000ms
      - waiting for getByRole('dialog').locator('div').filter({ hasText: 'Add node filter condition' })


      143 |
      144 |       // Verify the filter selection panel is hidden
    > 145 |       expect(panel.header).not.toBeVisible()
          |                                ^
      146 |
      147 |       // Verify the node search dialog is still visible
      148 |       expect(comfyPage.searchBox.input).toBeVisible()

        at /home/runner/work/ComfyUI_frontend/ComfyUI_frontend/ComfyUI_frontend/browser_tests/nodeSearchBox.spec.ts:145:32
     */
    test.skip('Outer click dismisses filter panel but keeps search box visible', async ({
      comfyPage
    }) => {
      await comfyPage.searchBox.filterButton.click()
      const panel = comfyPage.searchBox.filterSelectionPanel
      await panel.header.waitFor({ state: 'visible' })
      const panelBounds = await panel.header.boundingBox()
      await comfyPage.page.mouse.click(panelBounds!.x - 10, panelBounds!.y - 10)

      // Verify the filter selection panel is hidden
      expect(panel.header).not.toBeVisible()

      // Verify the node search dialog is still visible
      expect(comfyPage.searchBox.input).toBeVisible()
    })

    test('Can add multiple filters', async ({ comfyPage }) => {
      await comfyPage.searchBox.addFilter('MODEL', 'Input Type')
      await comfyPage.searchBox.addFilter('CLIP', 'Output Type')
      await expectFilterChips(comfyPage, ['MODEL', 'CLIP'])
    })

    test('Can remove filter', async ({ comfyPage }) => {
      await comfyPage.searchBox.addFilter('MODEL', 'Input Type')
      await comfyPage.searchBox.removeFilter(0)
      await expectFilterChips(comfyPage, [])
    })

    test.describe('Removing from multiple filters', () => {
      test.beforeEach(async ({ comfyPage }) => {
        await comfyPage.searchBox.addFilter('MODEL', 'Input Type')
        await comfyPage.searchBox.addFilter('CLIP', 'Output Type')
        await comfyPage.searchBox.addFilter('utils', 'Category')
      })

      test('Can remove first filter', async ({ comfyPage }) => {
        await comfyPage.searchBox.removeFilter(0)
        await expectFilterChips(comfyPage, ['CLIP', 'utils'])
        await comfyPage.searchBox.removeFilter(0)
        await expectFilterChips(comfyPage, ['utils'])
        await comfyPage.searchBox.removeFilter(0)
        await expectFilterChips(comfyPage, [])
      })

      test('Can remove middle filter', async ({ comfyPage }) => {
        await comfyPage.searchBox.removeFilter(1)
        await expectFilterChips(comfyPage, ['MODEL', 'utils'])
      })

      test('Can remove last filter', async ({ comfyPage }) => {
        await comfyPage.searchBox.removeFilter(2)
        await expectFilterChips(comfyPage, ['MODEL', 'CLIP'])
      })
    })
  })

  test.describe('Input focus behavior', () => {
    test.beforeEach(async ({ comfyPage }) => {
      await comfyPage.doubleClickCanvas()
    })

    test('focuses input after adding a filter', async ({ comfyPage }) => {
      await comfyPage.searchBox.addFilter('MODEL', 'Input Type')
      await expect(comfyPage.searchBox.input).toHaveFocus()
    })

    test('focuses input after removing a filter', async ({ comfyPage }) => {
      await comfyPage.searchBox.addFilter('MODEL', 'Input Type')
      await comfyPage.searchBox.removeFilter(0)
      await expect(comfyPage.searchBox.input).toHaveFocus()
    })
  })
})

test.describe('Release context menu', () => {
  test.beforeEach(async ({ comfyPage }) => {
    await comfyPage.setSetting('Comfy.LinkRelease.Action', 'context menu')
    await comfyPage.setSetting('Comfy.LinkRelease.ActionShift', 'search box')
    await comfyPage.setSetting('Comfy.NodeSearchBoxImpl', 'default')
  })

  test('Can trigger on link release', async ({ comfyPage }) => {
    await comfyPage.disconnectEdge()
    await comfyPage.page.mouse.move(10, 10)
    await comfyPage.nextFrame()
    await expect(comfyPage.canvas).toHaveScreenshot(
      'link-release-context-menu.png'
    )
  })

  test('Can search and add node from context menu', async ({
    comfyPage,
    comfyMouse
  }) => {
    await comfyPage.disconnectEdge()
    await comfyMouse.move({ x: 10, y: 10 })
    await comfyPage.clickContextMenuItem('Search')
    await comfyPage.searchBox.fillAndSelectFirstNode('CLIP Prompt')
    await expect(comfyPage.canvas).toHaveScreenshot(
      'link-context-menu-search.png'
    )
  })
})
