import { expect } from '@playwright/test'

import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
import {
  createMockJob,
  createMockJobs
} from '@e2e/fixtures/helpers/AssetsHelper'
import type {
  JobDetail,
  RawJobListItem
} from '@/platform/remote/comfyui/jobs/jobTypes'

// Legacy coverage backed by AssetsHelper's shadow backend. New assets-sidebar
// browser coverage should use typed route mocks in assetsSidebarTab.spec.ts.

// ---------------------------------------------------------------------------
// Shared fixtures
// ---------------------------------------------------------------------------

const SAMPLE_JOBS: RawJobListItem[] = [
  createMockJob({
    id: 'job-alpha',
    create_time: 1000,
    execution_start_time: 1000,
    execution_end_time: 1010,
    preview_output: {
      filename: 'landscape.png',
      subfolder: '',
      type: 'output',
      nodeId: '1',
      mediaType: 'images'
    },
    outputs_count: 1
  }),
  createMockJob({
    id: 'job-beta',
    create_time: 2000,
    execution_start_time: 2000,
    execution_end_time: 2003,
    preview_output: {
      filename: 'portrait.png',
      subfolder: '',
      type: 'output',
      nodeId: '2',
      mediaType: 'images'
    },
    outputs_count: 1
  }),
  createMockJob({
    id: 'job-gamma',
    create_time: 3000,
    execution_start_time: 3000,
    execution_end_time: 3020,
    preview_output: {
      filename: 'abstract_art.png',
      subfolder: '',
      type: 'output',
      nodeId: '3',
      mediaType: 'images'
    },
    outputs_count: 2
  })
]

const SAMPLE_IMPORTED_FILES = [
  'reference_photo.png',
  'background.jpg',
  'audio_clip.wav'
]

const JOB_GAMMA_DETAIL: JobDetail = {
  ...SAMPLE_JOBS[2],
  outputs: {
    '3': {
      images: [
        {
          filename: 'abstract_art.png',
          subfolder: '',
          type: 'output'
        },
        {
          filename: 'abstract_art_alt.png',
          subfolder: '',
          type: 'output'
        }
      ]
    }
  },
  workflow: {
    extra_data: {
      extra_pnginfo: {
        workflow: {
          version: 0.4,
          last_node_id: 0,
          last_link_id: 0,
          nodes: [],
          links: []
        }
      }
    }
  }
}

const cloudTest = test.extend<{ mockCloudAssetSidebarData: void }>({
  mockCloudAssetSidebarData: async ({ comfyPage }, use) => {
    await comfyPage.assets.mockOutputHistory(SAMPLE_JOBS)
    await comfyPage.assets.mockEmptyCloudAssets()

    await use()

    await comfyPage.assets.clearMocks()
  }
})

// ==========================================================================
// 1. Empty states
// ==========================================================================

test.describe('Assets sidebar - empty states', () => {
  test.beforeEach(async ({ comfyPage }) => {
    await comfyPage.assets.mockEmptyState()
    await comfyPage.setup()
  })

  test.afterEach(async ({ comfyPage }) => {
    await comfyPage.assets.clearMocks()
  })

  test('Shows empty-state copy for generated tab', async ({ comfyPage }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open({ waitForAssets: false })

    await expect(tab.emptyStateTitle('No generated files found')).toBeVisible()
    await expect(tab.emptyStateMessage).toBeVisible()
  })

  test('Shows empty-state copy for imported tab', async ({ comfyPage }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open({ waitForAssets: false })
    await tab.switchToImported()

    await expect(tab.emptyStateTitle('No imported files found')).toBeVisible()
    await expect(tab.emptyStateMessage).toBeVisible()
  })

  test('No asset cards are rendered when empty', async ({ comfyPage }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open({ waitForAssets: false })

    await expect(tab.assetCards).toHaveCount(0)
  })
})

// ==========================================================================
// 2. Tab navigation
// ==========================================================================

test.describe('Assets sidebar - tab navigation', () => {
  test.beforeEach(async ({ comfyPage }) => {
    await comfyPage.assets.mockOutputHistory(SAMPLE_JOBS)
    await comfyPage.assets.mockInputFiles(SAMPLE_IMPORTED_FILES)
    await comfyPage.setup()
  })

  test.afterEach(async ({ comfyPage }) => {
    await comfyPage.assets.clearMocks()
  })

  test('Generated tab is active by default', async ({ comfyPage }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    await expect(tab.generatedTab).toHaveAttribute('aria-selected', 'true')
    await expect(tab.importedTab).toHaveAttribute('aria-selected', 'false')
  })

  test('Can switch between Generated and Imported tabs', async ({
    comfyPage
  }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    // Switch to Imported
    await tab.switchToImported()
    await expect(tab.importedTab).toHaveAttribute('aria-selected', 'true')
    await expect(tab.generatedTab).toHaveAttribute('aria-selected', 'false')

    // Switch back to Generated
    await tab.switchToGenerated()
    await expect(tab.generatedTab).toHaveAttribute('aria-selected', 'true')
  })

  test('Search is cleared when switching tabs', async ({ comfyPage }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    // Type search in Generated tab
    await tab.searchInput.fill('landscape')
    await expect(tab.searchInput).toHaveValue('landscape')

    // Switch to Imported tab
    await tab.switchToImported()
    await expect(tab.searchInput).toHaveValue('')
  })
})

// ==========================================================================
// 3. Asset display - grid view
// ==========================================================================

test.describe('Assets sidebar - grid view display', () => {
  test.beforeEach(async ({ comfyPage }) => {
    await comfyPage.assets.mockOutputHistory(SAMPLE_JOBS)
    await comfyPage.assets.mockInputFiles(SAMPLE_IMPORTED_FILES)
    await comfyPage.setup()
  })

  test.afterEach(async ({ comfyPage }) => {
    await comfyPage.assets.clearMocks()
  })

  test('Displays generated assets as cards in grid view', async ({
    comfyPage
  }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    await expect.poll(() => tab.assetCards.count()).toBeGreaterThanOrEqual(1)
  })

  test('Displays imported files when switching to Imported tab', async ({
    comfyPage
  }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()
    await tab.switchToImported()

    // Wait for imported assets to render
    await expect(tab.assetCards.first()).toBeVisible()

    // Imported tab should show the mocked files
    await expect.poll(() => tab.assetCards.count()).toBeGreaterThanOrEqual(1)
  })

  test('Displays svg outputs', async ({ comfyPage }) => {
    await comfyPage.assets.mockOutputHistory([
      createMockJob({
        id: 'job-alpha',
        create_time: 1000,
        execution_start_time: 1000,
        execution_end_time: 1010,
        preview_output: {
          filename: 'logo.svg',
          subfolder: '',
          type: 'output',
          nodeId: '1',
          mediaType: 'images'
        },
        outputs_count: 1
      })
    ])

    const tab = comfyPage.menu.assetsTab
    await tab.open()

    await expect(tab.assetCards.locator('.pi-image')).toBeVisible()
  })
})

// ==========================================================================
// 4. View mode toggle (grid <-> list)
// ==========================================================================

test.describe('Assets sidebar - view mode toggle', () => {
  test.beforeEach(async ({ comfyPage }) => {
    await comfyPage.assets.mockOutputHistory(SAMPLE_JOBS)
    await comfyPage.assets.mockInputFiles([])
    await comfyPage.setup()
  })

  test.afterEach(async ({ comfyPage }) => {
    await comfyPage.assets.clearMocks()
  })

  test('Can switch to list view via settings menu', async ({ comfyPage }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    // Open settings menu and select list view
    await tab.openSettingsMenu()
    await tab.listViewOption.click()

    // List view items should now be visible
    await expect(tab.listViewItems.first()).toBeVisible()
  })

  test('Can switch back to grid view', async ({ comfyPage }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    // Switch to list view
    await tab.openSettingsMenu()
    await tab.listViewOption.click()
    await expect(tab.listViewItems.first()).toBeVisible()

    // Switch back to grid view (settings popover is still open)
    await tab.gridViewOption.click()
    await tab.waitForAssets()

    // Grid cards (with data-selected attribute) should be visible again
    await expect(tab.assetCards.first()).toBeVisible()
  })
})

// ==========================================================================
// 5. Search functionality
// ==========================================================================

test.describe('Assets sidebar - search', () => {
  test.beforeEach(async ({ comfyPage }) => {
    await comfyPage.assets.mockOutputHistory(SAMPLE_JOBS)
    await comfyPage.assets.mockInputFiles([])
    await comfyPage.setup()
  })

  test.afterEach(async ({ comfyPage }) => {
    await comfyPage.assets.clearMocks()
  })

  test('Search input is visible', async ({ comfyPage }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    await expect(tab.searchInput).toBeVisible()
  })

  test('Filtering assets by search query reduces displayed count', async ({
    comfyPage
  }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    const initialCount = await tab.assetCards.count()

    // Search for a specific filename that matches only one asset
    await tab.searchInput.fill('landscape')

    // Wait for filter to reduce the count
    await expect.poll(() => tab.assetCards.count()).toBeLessThan(initialCount)
  })

  test('Clearing search restores all assets', async ({ comfyPage }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    const initialCount = await tab.assetCards.count()

    // Filter then clear
    await tab.searchInput.fill('landscape')
    await expect.poll(() => tab.assetCards.count()).toBeLessThan(initialCount)

    await tab.searchInput.fill('')
    await expect(tab.assetCards).toHaveCount(initialCount)
  })

  test('Search with no matches shows empty state', async ({ comfyPage }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    await tab.searchInput.fill('nonexistent_file_xyz')
    await expect(tab.assetCards).toHaveCount(0)
  })
})

// ==========================================================================
// 6. Asset selection
// ==========================================================================

test.describe('Assets sidebar - selection', () => {
  test.beforeEach(async ({ comfyPage }) => {
    await comfyPage.assets.mockOutputHistory(SAMPLE_JOBS)
    await comfyPage.assets.mockInputFiles([])
    await comfyPage.setup()
  })

  test.afterEach(async ({ comfyPage }) => {
    await comfyPage.assets.clearMocks()
  })

  test('Clicking an asset card selects it', async ({ comfyPage }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    // Click first asset card
    await tab.assetCards.first().click()

    // Should have data-selected="true"
    await expect(tab.selectedCards).toHaveCount(1)
  })

  test('Ctrl+click adds to selection', async ({ comfyPage }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    const cards = tab.assetCards
    await expect.poll(() => cards.count()).toBeGreaterThanOrEqual(2)

    // Click first card
    await cards.first().click()
    await expect(tab.selectedCards).toHaveCount(1)

    // Ctrl+click second card
    await cards.nth(1).click({ modifiers: ['ControlOrMeta'] })
    await expect(tab.selectedCards).toHaveCount(2)
  })

  test('Selection shows footer with count and actions', async ({
    comfyPage
  }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    // Select an asset
    await tab.assetCards.first().click()

    // Footer should show selection count
    await expect(tab.selectionCountButton).toBeVisible()
  })

  test('Deselect all clears selection', async ({ comfyPage }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    // Select an asset
    await tab.assetCards.first().click()
    await expect(tab.selectedCards).toHaveCount(1)

    // Hover over the selection count button to reveal "Deselect all"
    await tab.selectionCountButton.hover()
    await expect(tab.deselectAllButton).toBeVisible()

    // Click "Deselect all"
    await tab.deselectAllButton.click()
    await expect(tab.selectedCards).toHaveCount(0)
  })

  test('Selection is cleared when switching tabs', async ({ comfyPage }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    // Select an asset
    await tab.assetCards.first().click()
    await expect(tab.selectedCards).toHaveCount(1)

    // Switch to Imported tab
    await tab.switchToImported()

    // Switch back - selection should be cleared
    await tab.switchToGenerated()
    await tab.waitForAssets()
    await expect(tab.selectedCards).toHaveCount(0)
  })
})

// ==========================================================================
// 7. Context menu
// ==========================================================================

test.describe('Assets sidebar - context menu', () => {
  test.beforeEach(async ({ comfyPage }) => {
    await comfyPage.assets.mockOutputHistory(SAMPLE_JOBS)
    await comfyPage.assets.mockInputFiles([])
    await comfyPage.setup()
  })

  test.afterEach(async ({ comfyPage }) => {
    await comfyPage.assets.clearMocks()
  })

  test('Right-clicking an asset shows context menu', async ({ comfyPage }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    // Right-click first asset
    await tab.assetCards.first().click({ button: 'right' })

    // Context menu should appear with standard items
    const contextMenu = comfyPage.page.locator('.p-contextmenu')
    await expect(contextMenu).toBeVisible()
  })

  test('Context menu contains Download action for output asset', async ({
    comfyPage
  }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    await tab.assetCards.first().click({ button: 'right' })
    await comfyPage.page
      .locator('.p-contextmenu')
      .waitFor({ state: 'visible', timeout: 3000 })

    await expect(tab.contextMenuItem('Download')).toBeVisible()
  })

  test('Context menu contains Inspect action for image assets', async ({
    comfyPage
  }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    await tab.assetCards.first().click({ button: 'right' })
    await comfyPage.page
      .locator('.p-contextmenu')
      .waitFor({ state: 'visible', timeout: 3000 })

    await expect(tab.contextMenuItem('Inspect asset')).toBeVisible()
  })

  test('Context menu contains Delete action for output assets', async ({
    comfyPage
  }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    await tab.assetCards.first().click({ button: 'right' })
    await comfyPage.page
      .locator('.p-contextmenu')
      .waitFor({ state: 'visible', timeout: 3000 })

    await expect(tab.contextMenuItem('Delete')).toBeVisible()
  })

  test('Context menu contains Copy job ID for output assets', async ({
    comfyPage
  }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    await tab.assetCards.first().click({ button: 'right' })
    await comfyPage.page
      .locator('.p-contextmenu')
      .waitFor({ state: 'visible', timeout: 3000 })

    await expect(tab.contextMenuItem('Copy job ID')).toBeVisible()
  })

  test('Context menu contains workflow actions for output assets', async ({
    comfyPage
  }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    await tab.assetCards.first().click({ button: 'right' })

    const contextMenu = comfyPage.page.locator('.p-contextmenu')
    await expect(contextMenu).toBeVisible()

    await expect(
      tab.contextMenuItem('Open as workflow in new tab')
    ).toBeVisible()
    await expect(tab.contextMenuItem('Export workflow')).toBeVisible()
  })

  test('Cancelling export-workflow filename prompt does not show an error toast', async ({
    comfyPage
  }) => {
    // job-gamma is the first card; its detail carries a valid workflow so
    // extraction succeeds and the filename prompt opens.
    await comfyPage.assets.mockJobDetail('job-gamma', JOB_GAMMA_DETAIL)

    const tab = comfyPage.menu.assetsTab
    await tab.open()

    await tab.assetCards.first().click({ button: 'right' })
    await tab.contextMenuItem('Export workflow').click()

    const promptDialog = comfyPage.page.getByRole('dialog', {
      name: 'Export Workflow'
    })
    await expect(promptDialog).toBeVisible()

    await comfyPage.page.keyboard.press('Escape')
    await expect(promptDialog).toBeHidden()

    await expect(comfyPage.toast.toastErrors).toBeHidden({ timeout: 1500 })
  })

  test('Confirming export-workflow prompt downloads the file and shows a success toast', async ({
    comfyPage
  }) => {
    await comfyPage.assets.mockJobDetail('job-gamma', JOB_GAMMA_DETAIL)

    const tab = comfyPage.menu.assetsTab
    await tab.open()

    await tab.assetCards.first().click({ button: 'right' })
    await tab.contextMenuItem('Export workflow').click()

    const promptDialog = comfyPage.page.getByRole('dialog', {
      name: 'Export Workflow'
    })
    await expect(promptDialog).toBeVisible()

    const downloadPromise = comfyPage.page.waitForEvent('download')
    await promptDialog.getByRole('button', { name: 'Confirm' }).click()

    const download = await downloadPromise
    expect(download.suggestedFilename()).toBe('abstract_art.json')

    await expect(comfyPage.toast.toastSuccesses).toBeVisible()
  })

  test('Export-workflow shows a warning toast when the asset has no workflow', async ({
    comfyPage
  }) => {
    // Strip the workflow field so extraction yields null and the export
    // action returns { success: false, error: 'No workflow…' }.
    const { workflow: _, ...detailWithoutWorkflow } = JOB_GAMMA_DETAIL
    await comfyPage.assets.mockJobDetail('job-gamma', detailWithoutWorkflow)

    const tab = comfyPage.menu.assetsTab
    await tab.open()

    await tab.assetCards.first().click({ button: 'right' })
    await tab.contextMenuItem('Export workflow').click()

    // Filename prompt should be skipped: extraction fails before the prompt.
    await expect(comfyPage.toast.toastWarnings).toBeVisible()
    await expect(comfyPage.toast.toastSuccesses).toBeHidden({ timeout: 1500 })
  })

  test('Bulk context menu shows when multiple assets selected', async ({
    comfyPage
  }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    const cards = tab.assetCards
    await expect.poll(() => cards.count()).toBeGreaterThanOrEqual(2)

    // Dismiss any toasts that appeared after asset loading
    await tab.dismissToasts()

    // Multi-select: use keyboard.down/up so useKeyModifier('Control') detects
    // the modifier — click({ modifiers }) only sets the mouse event flag and
    // does not fire a keydown event that VueUse tracks.
    await cards.first().click()
    await comfyPage.page.keyboard.down('Control')
    await cards.nth(1).click()
    await comfyPage.page.keyboard.up('Control')

    // Verify multi-selection took effect and footer is stable before right-clicking
    await expect(tab.selectedCards).toHaveCount(2)
    await expect(tab.selectionFooter).toBeVisible()

    // Use dispatchEvent instead of click({ button: 'right' }) to avoid any
    // overlay intercepting the event, and assert directly without toPass.
    const contextMenu = comfyPage.page.locator('.p-contextmenu')
    await cards.first().dispatchEvent('contextmenu', {
      bubbles: true,
      cancelable: true,
      button: 2
    })
    await expect(contextMenu).toBeVisible()

    // Bulk menu should show bulk download action
    await expect(tab.contextMenuItem('Download all')).toBeVisible()
  })
})

// ==========================================================================
// 8. Bulk actions (footer)
// ==========================================================================

test.describe('Assets sidebar - bulk actions', () => {
  test.beforeEach(async ({ comfyPage }) => {
    await comfyPage.assets.mockOutputHistory(SAMPLE_JOBS)
    await comfyPage.assets.mockInputFiles([])
    await comfyPage.setup()
  })

  test.afterEach(async ({ comfyPage }) => {
    await comfyPage.assets.clearMocks()
  })

  test('Footer shows download button when assets selected', async ({
    comfyPage
  }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    await tab.assetCards.first().click()

    // Download button in footer should be visible
    await expect(tab.downloadSelectedButton).toBeVisible()
  })

  test('Footer shows delete button when output assets selected', async ({
    comfyPage
  }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    await tab.assetCards.first().click()

    // Delete button in footer should be visible
    await expect(tab.deleteSelectedButton).toBeVisible()
  })

  test('Selection count displays correct number', async ({ comfyPage }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    // Select the two single-output assets (job-alpha, job-beta).
    // The count reflects total outputs, not cards — job-gamma has
    // outputs_count: 2 which would inflate the total.
    const cards = tab.assetCards
    await expect.poll(() => cards.count()).toBeGreaterThanOrEqual(3)

    // Cards are sorted newest-first: gamma (idx 0), beta (1), alpha (2)
    await cards.nth(1).click()
    await comfyPage.page.keyboard.down('Control')
    await cards.nth(2).click()
    await comfyPage.page.keyboard.up('Control')

    // Selection count should show the count
    await expect(tab.selectionCountButton).toBeVisible()
    await expect(tab.selectionCountButton).toHaveText(/Assets Selected:\s*2\b/)
  })
})

cloudTest.describe('Assets sidebar - cloud exports', { tag: '@cloud' }, () => {
  cloudTest(
    'Single job selection uses preserve naming strategy',
    async ({ comfyPage, mockCloudAssetSidebarData }) => {
      void mockCloudAssetSidebarData
      const exportRequests = await comfyPage.assets.captureAssetExportRequests()

      const tab = comfyPage.menu.assetsTab
      await tab.open()

      await tab.assetCards.first().click()
      await expect(tab.downloadSelectedButton).toBeVisible()

      await tab.downloadSelectedButton.click()

      await expect.poll(() => exportRequests).toHaveLength(1)

      const payload = exportRequests[0]
      expect(payload.job_ids).toEqual(['job-gamma'])
      expect(payload.job_asset_name_filters).toBeUndefined()
      expect(payload.naming_strategy).toBe('preserve')
    }
  )

  cloudTest(
    'Multiple selected assets from one job use preserve naming strategy',
    async ({ comfyPage, mockCloudAssetSidebarData }) => {
      void mockCloudAssetSidebarData
      const exportRequests = await comfyPage.assets.captureAssetExportRequests()
      await comfyPage.assets.mockJobDetail('job-gamma', JOB_GAMMA_DETAIL)

      const tab = comfyPage.menu.assetsTab
      await tab.open()

      await tab.assetCards
        .first()
        .getByRole('button', { name: 'See more outputs' })
        .click()
      await expect(tab.backToAssetsButton).toBeVisible()
      await expect.poll(() => tab.assetCards.count()).toBe(2)

      await tab.assetCards.first().click()
      await comfyPage.page.keyboard.down('Control')
      await tab.assetCards.nth(1).click()
      await comfyPage.page.keyboard.up('Control')

      await expect(tab.selectedCards).toHaveCount(2)
      await tab.downloadSelectedButton.click()

      await expect.poll(() => exportRequests).toHaveLength(1)

      const payload = exportRequests[0]
      expect(payload.job_ids).toEqual(['job-gamma'])
      expect(payload.job_asset_name_filters?.['job-gamma']?.toSorted()).toEqual(
        ['abstract_art.png', 'abstract_art_alt.png']
      )
      expect(payload.naming_strategy).toBe('preserve')
    }
  )

  cloudTest(
    'Multiple selected jobs use job-time naming strategy',
    async ({ comfyPage, mockCloudAssetSidebarData }) => {
      void mockCloudAssetSidebarData
      const exportRequests = await comfyPage.assets.captureAssetExportRequests()

      const tab = comfyPage.menu.assetsTab
      await tab.open()

      await tab.assetCards.nth(1).click()
      await comfyPage.page.keyboard.down('Control')
      await tab.assetCards.nth(2).click()
      await comfyPage.page.keyboard.up('Control')

      await expect(tab.selectedCards).toHaveCount(2)
      await tab.downloadSelectedButton.click()

      await expect.poll(() => exportRequests).toHaveLength(1)

      const payload = exportRequests[0]
      expect(payload.job_ids?.toSorted()).toEqual(['job-alpha', 'job-beta'])
      expect(payload.job_asset_name_filters).toBeUndefined()
      expect(payload.naming_strategy).toBe('group_by_job_time')
    }
  )
})

// ==========================================================================
// 9. Pagination
// ==========================================================================

test.describe('Assets sidebar - pagination', () => {
  test.afterEach(async ({ comfyPage }) => {
    await comfyPage.assets.clearMocks()
  })

  test('initial load fetches first batch with offset 0', async ({
    comfyPage
  }) => {
    const manyJobs = createMockJobs(250)
    await comfyPage.assets.mockOutputHistory(manyJobs)
    await comfyPage.setup()

    // Capture the first history fetch (terminal statuses only).
    // Queue polling also hits /jobs but with status=in_progress,pending.
    const firstRequest = comfyPage.page.waitForRequest((req) => {
      if (!/\/api\/jobs\?/.test(req.url())) return false
      const url = new URL(req.url())
      const status = url.searchParams.get('status') ?? ''
      return status.includes('completed')
    })

    const tab = comfyPage.menu.assetsTab
    await tab.open()

    const req = await firstRequest
    const url = new URL(req.url())
    expect(url.searchParams.get('offset')).toBe('0')
    expect(Number(url.searchParams.get('limit'))).toBeGreaterThan(0)
  })
})

// ==========================================================================
// 10. Settings menu visibility
// ==========================================================================

test.describe('Assets sidebar - settings menu', () => {
  test.beforeEach(async ({ comfyPage }) => {
    await comfyPage.assets.mockOutputHistory(SAMPLE_JOBS)
    await comfyPage.assets.mockInputFiles([])
    await comfyPage.setup()
  })

  test.afterEach(async ({ comfyPage }) => {
    await comfyPage.assets.clearMocks()
  })

  test('Settings menu shows view mode options', async ({ comfyPage }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    await tab.openSettingsMenu()

    await expect(tab.listViewOption).toBeVisible()
    await expect(tab.gridViewOption).toBeVisible()
  })
})

// ==========================================================================
// 11. Delete confirmation
// ==========================================================================

test.describe('Assets sidebar - delete confirmation', () => {
  test.beforeEach(async ({ comfyPage }) => {
    await comfyPage.assets.mockOutputHistory(SAMPLE_JOBS)
    await comfyPage.assets.mockDeleteHistory()
    await comfyPage.assets.mockInputFiles([])
    await comfyPage.setup()
  })

  test.afterEach(async ({ comfyPage }) => {
    await comfyPage.assets.clearMocks()
  })

  test('Right-click delete shows confirmation dialog', async ({
    comfyPage
  }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    await tab.assetCards.first().click({ button: 'right' })
    await tab.contextMenuItem('Delete').click()

    const dialog = comfyPage.confirmDialog.root
    await expect(dialog).toBeVisible()
    await expect(dialog.getByText('Delete this asset?')).toBeVisible()
    await expect(
      dialog.getByText('This asset will be permanently removed.')
    ).toBeVisible()
  })

  test('Confirming delete removes asset and shows success toast', async ({
    comfyPage
  }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    const initialCount = await tab.assetCards.count()

    await tab.assetCards.first().click({ button: 'right' })
    await tab.contextMenuItem('Delete').click()

    const dialog = comfyPage.confirmDialog.root
    await expect(dialog).toBeVisible()

    await comfyPage.confirmDialog.delete.click()

    await expect(dialog).toBeHidden()
    await expect(tab.assetCards).toHaveCount(initialCount - 1)

    const successToast = comfyPage.page.locator('.p-toast-message-success')
    await expect(successToast).toBeVisible()
  })

  test('Cancelling delete preserves asset', async ({ comfyPage }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    const initialCount = await tab.assetCards.count()

    await tab.assetCards.first().click({ button: 'right' })
    await tab.contextMenuItem('Delete').click()

    const dialog = comfyPage.confirmDialog.root
    await expect(dialog).toBeVisible()

    await comfyPage.confirmDialog.reject.click()

    await expect(dialog).toBeHidden()
    await expect(tab.assetCards).toHaveCount(initialCount)
  })
})

// ==========================================================================
// 12. Media type filter (cloud-only)
// ==========================================================================

const MIXED_MEDIA_JOBS: RawJobListItem[] = [
  createMockJob({
    id: 'job-image',
    create_time: 1000,
    execution_start_time: 1000,
    execution_end_time: 1010,
    preview_output: {
      filename: 'photo.png',
      subfolder: '',
      type: 'output',
      nodeId: '1',
      mediaType: 'images'
    },
    outputs_count: 1
  }),
  createMockJob({
    id: 'job-video',
    create_time: 2000,
    execution_start_time: 2000,
    execution_end_time: 2010,
    preview_output: {
      filename: 'clip.mp4',
      subfolder: '',
      type: 'output',
      nodeId: '2',
      mediaType: 'video'
    },
    outputs_count: 1
  }),
  createMockJob({
    id: 'job-audio',
    create_time: 3000,
    execution_start_time: 3000,
    execution_end_time: 3010,
    preview_output: {
      filename: 'track.mp3',
      subfolder: '',
      type: 'output',
      nodeId: '3',
      mediaType: 'audio'
    },
    outputs_count: 1
  })
]

// Filter button is guarded by isCloud (compile-time). The cloud CI project
// cannot use comfyPageFixture (auth required). Enable once cloud E2E infra
// supports authenticated comfyPage setup.
test.describe('Assets sidebar - media type filter', () => {
  test.fixme(true, 'Requires DISTRIBUTION=cloud build with auth bypass')

  test.beforeEach(async ({ comfyPage }) => {
    await comfyPage.assets.mockOutputHistory(MIXED_MEDIA_JOBS)
    await comfyPage.assets.mockInputFiles([])
    await comfyPage.setup()
  })

  test.afterEach(async ({ comfyPage }) => {
    await comfyPage.assets.clearMocks()
  })

  test('Filter menu shows media type options', async ({ comfyPage }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    await tab.openFilterMenu()

    await expect(tab.filterCheckbox('Image')).toBeVisible()
    await expect(tab.filterCheckbox('Video')).toBeVisible()
    await expect(tab.filterCheckbox('Audio')).toBeVisible()
    await expect(tab.filterCheckbox('3D')).toBeVisible()
  })

  test('Unchecking image filter hides image assets', async ({ comfyPage }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    const initialCount = tab.assetCards
    await expect(
      initialCount,
      'All three mixed-media jobs should render'
    ).toHaveCount(3)

    // Open filter menu and enable only image filter (selecting a filter
    // restricts to that type only, hiding unselected types)
    await tab.openFilterMenu()
    await tab.filterCheckbox('Image').click()

    // Only the image asset should remain
    await expect(tab.assetCards).toHaveCount(1, { timeout: 5000 })
    await expect(tab.getAssetCardByName('photo.png')).toBeVisible()
  })

  test('Re-enabling filter restores hidden assets', async ({ comfyPage }) => {
    const tab = comfyPage.menu.assetsTab
    await tab.open()

    const initialCount = await tab.assetCards.count()

    // Enable image filter to restrict to images only
    await tab.openFilterMenu()
    await tab.filterCheckbox('Image').click()
    await expect(tab.assetCards).toHaveCount(1, { timeout: 5000 })

    // Uncheck image filter to remove all filters (restores all assets)
    await tab.filterCheckbox('Image').click()
    await expect(tab.assetCards).toHaveCount(initialCount, { timeout: 5000 })
  })
})

test.describe('Assets sidebar - drag and drop', () => {
  test('Dragging outputs from assets skips upload', async ({ comfyPage }) => {
    await comfyPage.assets.mockOutputHistory([
      createMockJob({
        id: 'job',
        preview_output: {
          filename: `test.png`,
          type: 'temp',
          nodeId: '1',
          mediaType: 'images'
        }
      })
    ])
    await comfyPage.page.route('**/upload/image', (route) => {
      expect(true, 'file is not uploaded').toBe(false)
      return route.fulfill({ status: 405 })
    })

    await comfyPage.workflow.loadWorkflow('widgets/load_image_widget')

    await comfyPage.canvas.focus()
    await comfyPage.page.keyboard.press('.')
    const { assetsTab } = comfyPage.menu
    await assetsTab.open()
    await assetsTab.waitForAssets()
    await expect(assetsTab.assetCards).toHaveCount(1)

    const targetPosition =
      (await comfyPage.canvasOps.getNodeCenterByTitle('Load Image')) ??
      undefined

    await assetsTab.assetCards.dragTo(comfyPage.canvas, { targetPosition })

    const nodes = await comfyPage.nodeOps.getNodeRefsByType('LoadImage')
    const fileComboWidget = await nodes[0].getWidget(0)
    await expect.poll(() => fileComboWidget.getValue()).toBe('test.png [temp]')
  })
})
