import type { Mock } from 'vitest'
import type { CrawlOptions, CrawlResultItem } from '@/models/datasets'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { checkJinaReaderTaskStatus, createJinaReaderTask } from '@/service/datasets'
import { sleep } from '@/utils'
import JinaReader from '../index'

vi.mock('@/service/datasets', () => ({
  createJinaReaderTask: vi.fn(),
  checkJinaReaderTaskStatus: vi.fn(),
}))

vi.mock('@/utils', () => ({
  sleep: vi.fn(() => Promise.resolve()),
}))

// Mock modal context
const mockSetShowAccountSettingModal = vi.fn()
vi.mock('@/context/modal-context', () => ({
  useModalContext: () => ({
    setShowAccountSettingModal: mockSetShowAccountSettingModal,
  }),
}))

// Mock doc link context
vi.mock('@/context/i18n', () => ({
  useDocLink: () => () => 'https://docs.example.com',
}))

// Note: limit and max_depth are typed as `number | string` in CrawlOptions
// Tests may use number, string, or empty string values to cover all valid cases
const createDefaultCrawlOptions = (overrides: Partial<CrawlOptions> = {}): CrawlOptions => ({
  crawl_sub_pages: true,
  only_main_content: true,
  includes: '',
  excludes: '',
  limit: 10,
  max_depth: 2,
  use_sitemap: false,
  ...overrides,
})

const createCrawlResultItem = (overrides: Partial<CrawlResultItem> = {}): CrawlResultItem => ({
  title: 'Test Page Title',
  markdown: '# Test Content\n\nThis is test markdown content.',
  description: 'Test description',
  source_url: 'https://example.com/page',
  ...overrides,
})

const createDefaultProps = (overrides: Partial<Parameters<typeof JinaReader>[0]> = {}) => ({
  onPreview: vi.fn(),
  checkedCrawlResult: [] as CrawlResultItem[],
  onCheckedCrawlResultChange: vi.fn(),
  onJobIdChange: vi.fn(),
  crawlOptions: createDefaultCrawlOptions(),
  onCrawlOptionsChange: vi.fn(),
  ...overrides,
})

describe('JinaReader', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    vi.useFakeTimers({ shouldAdvanceTime: true })
  })

  afterEach(() => {
    vi.useRealTimers()
  })

  describe('Rendering', () => {
    it('should render without crashing', () => {
      const props = createDefaultProps()

      render(<JinaReader {...props} />)

      expect(screen.getByText('datasetCreation.stepOne.website.jinaReaderTitle'))!.toBeInTheDocument()
    })

    it('should render header with configuration button', () => {
      const props = createDefaultProps()

      render(<JinaReader {...props} />)

      expect(screen.getByText('datasetCreation.stepOne.website.configureJinaReader'))!.toBeInTheDocument()
    })

    it('should render URL input field', () => {
      const props = createDefaultProps()

      render(<JinaReader {...props} />)

      expect(screen.getByRole('textbox'))!.toBeInTheDocument()
    })

    it('should render run button', () => {
      const props = createDefaultProps()

      render(<JinaReader {...props} />)

      expect(screen.getByRole('button', { name: /run/i }))!.toBeInTheDocument()
    })

    it('should render options section', () => {
      const props = createDefaultProps()

      render(<JinaReader {...props} />)

      expect(screen.getByText('datasetCreation.stepOne.website.options'))!.toBeInTheDocument()
    })

    it('should render doc link to Jina Reader', () => {
      const props = createDefaultProps()

      render(<JinaReader {...props} />)

      const docLink = screen.getByRole('link')
      expect(docLink)!.toHaveAttribute('href', 'https://jina.ai/reader')
    })

    it('should not render crawling or result components initially', () => {
      const props = createDefaultProps()

      render(<JinaReader {...props} />)

      expect(screen.queryByText(/totalPageScraped/i)).not.toBeInTheDocument()
    })
  })

  describe('Props', () => {
    it('should call onCrawlOptionsChange when options change', async () => {
      const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime })
      const onCrawlOptionsChange = vi.fn()
      const props = createDefaultProps({ onCrawlOptionsChange })

      render(<JinaReader {...props} />)

      // Find the limit input by its associated label text
      const limitLabel = screen.queryByText('datasetCreation.stepOne.website.limit')

      if (limitLabel) {
        // The limit input is a number input (spinbutton role) within the same container
        const limitInput = limitLabel.closest('div')?.parentElement?.querySelector('input[type="number"]')

        if (limitInput) {
          await user.clear(limitInput)
          await user.type(limitInput, '20')

          expect(onCrawlOptionsChange).toHaveBeenCalled()
        }
      }
      else {
        // Options might not be visible, just verify component renders
        // Options might not be visible, just verify component renders
        expect(screen.getByText('datasetCreation.stepOne.website.options'))!.toBeInTheDocument()
      }
    })

    it('should execute crawl task when checkedCrawlResult is provided', async () => {
      const checkedItem = createCrawlResultItem({ source_url: 'https://checked.com' })
      const mockCreateTask = createJinaReaderTask as Mock
      mockCreateTask.mockResolvedValueOnce({
        data: {
          title: 'Test',
          content: 'Test content',
          description: 'Test desc',
          url: 'https://example.com',
        },
      })

      const props = createDefaultProps({
        checkedCrawlResult: [checkedItem],
      })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://example.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      // Assert - crawl task should be created even with pre-checked results
      await waitFor(() => {
        expect(mockCreateTask).toHaveBeenCalled()
      })
    })

    it('should use default crawlOptions limit in validation', () => {
      const props = createDefaultProps({
        crawlOptions: createDefaultCrawlOptions({ limit: '' }),
      })

      render(<JinaReader {...props} />)

      // Assert - component renders with empty limit
      // Assert - component renders with empty limit
      expect(screen.getByRole('textbox'))!.toBeInTheDocument()
    })
  })

  describe('State Management', () => {
    it('should transition from init to running state when run is clicked', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      let resolvePromise: () => void
      const taskPromise = new Promise((resolve) => {
        resolvePromise = () => resolve({ data: { title: 'T', content: 'C', description: 'D', url: 'https://example.com' } })
      })
      mockCreateTask.mockImplementation(() => taskPromise)

      const props = createDefaultProps()

      render(<JinaReader {...props} />)
      const urlInput = screen.getAllByRole('textbox')[0]
      await userEvent.type(urlInput!, 'https://example.com')

      const runButton = screen.getByRole('button', { name: /run/i })
      fireEvent.click(runButton)

      // Assert - crawling indicator should appear
      await waitFor(() => {
        expect(screen.getByText(/totalPageScraped/i))!.toBeInTheDocument()
      })

      // Cleanup - resolve the promise and wait for component to finish
      resolvePromise!()
      await waitFor(() => {
        expect(screen.queryByText(/totalPageScraped/i)).not.toBeInTheDocument()
      })
    })

    it('should transition to finished state after successful crawl', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      mockCreateTask.mockResolvedValueOnce({
        data: {
          title: 'Test Page',
          content: 'Test content',
          description: 'Test description',
          url: 'https://example.com',
        },
      })

      const props = createDefaultProps()

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://example.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(screen.getByText(/selectAll|resetAll/i))!.toBeInTheDocument()
      })
    })

    it('should update crawl result state during polling', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      const mockCheckStatus = checkJinaReaderTaskStatus as Mock

      mockCreateTask.mockResolvedValueOnce({ job_id: 'test-job-123' })
      mockCheckStatus
        .mockResolvedValueOnce({
          status: 'running',
          current: 1,
          total: 3,
          data: [createCrawlResultItem()],
        })
        .mockResolvedValueOnce({
          status: 'completed',
          current: 3,
          total: 3,
          data: [
            createCrawlResultItem({ source_url: 'https://example.com/1' }),
            createCrawlResultItem({ source_url: 'https://example.com/2' }),
            createCrawlResultItem({ source_url: 'https://example.com/3' }),
          ],
        })

      const onCheckedCrawlResultChange = vi.fn()
      const onJobIdChange = vi.fn()
      const props = createDefaultProps({ onCheckedCrawlResultChange, onJobIdChange })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://example.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(onJobIdChange).toHaveBeenCalledWith('test-job-123')
      })

      await waitFor(() => {
        expect(onCheckedCrawlResultChange).toHaveBeenCalled()
      })
    })

    it('should fold options when step changes from init', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      mockCreateTask.mockResolvedValueOnce({
        data: {
          title: 'Test',
          content: 'Content',
          description: 'Desc',
          url: 'https://example.com',
        },
      })

      const props = createDefaultProps()

      render(<JinaReader {...props} />)

      // Options should be visible initially
      // Options should be visible initially
      expect(screen.getByText('datasetCreation.stepOne.website.crawlSubPage'))!.toBeInTheDocument()

      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://example.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      // Assert - options should be folded after crawl starts
      await waitFor(() => {
        expect(screen.queryByText('datasetCreation.stepOne.website.crawlSubPage')).not.toBeInTheDocument()
      })
    })
  })

  // Side Effects and Cleanup Tests
  describe('Side Effects and Cleanup', () => {
    it('should call sleep during polling', async () => {
      const mockSleep = sleep as Mock
      const mockCreateTask = createJinaReaderTask as Mock
      const mockCheckStatus = checkJinaReaderTaskStatus as Mock

      mockCreateTask.mockResolvedValueOnce({ job_id: 'test-job' })
      mockCheckStatus
        .mockResolvedValueOnce({ status: 'running', current: 1, total: 2, data: [] })
        .mockResolvedValueOnce({ status: 'completed', current: 2, total: 2, data: [] })

      const props = createDefaultProps()

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://example.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(mockSleep).toHaveBeenCalledWith(2500)
      })
    })

    it('should update controlFoldOptions when step changes', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      let resolvePromise: () => void
      const taskPromise = new Promise((resolve) => {
        resolvePromise = () => resolve({ data: { title: 'T', content: 'C', description: 'D', url: 'https://example.com' } })
      })
      mockCreateTask.mockImplementation(() => taskPromise)

      const props = createDefaultProps()

      render(<JinaReader {...props} />)

      // Initially options should be visible
      // Initially options should be visible
      expect(screen.getByText('datasetCreation.stepOne.website.options'))!.toBeInTheDocument()

      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://example.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      // Assert - the crawling indicator should appear
      await waitFor(() => {
        expect(screen.getByText(/totalPageScraped/i))!.toBeInTheDocument()
      })

      // Cleanup - resolve the promise
      resolvePromise!()
      await waitFor(() => {
        expect(screen.queryByText(/totalPageScraped/i)).not.toBeInTheDocument()
      })
    })
  })

  // Callback Stability and Memoization Tests
  describe('Callback Stability', () => {
    it('should maintain stable handleSetting callback', () => {
      const props = createDefaultProps()

      const { rerender } = render(<JinaReader {...props} />)
      const configButton = screen.getByText('datasetCreation.stepOne.website.configureJinaReader')
      fireEvent.click(configButton)

      expect(mockSetShowAccountSettingModal).toHaveBeenCalledTimes(1)

      // Rerender and click again
      rerender(<JinaReader {...props} />)
      fireEvent.click(configButton)

      expect(mockSetShowAccountSettingModal).toHaveBeenCalledTimes(2)
    })

    it('should memoize checkValid callback based on crawlOptions', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      mockCreateTask.mockResolvedValue({ data: { title: 'T', content: 'C', description: 'D', url: 'https://a.com' } })

      const props = createDefaultProps()

      const { rerender } = render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://example.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(mockCreateTask).toHaveBeenCalledTimes(1)
      })

      // Rerender with same options
      rerender(<JinaReader {...props} />)

      // Assert - component should still work correctly
      // Assert - component should still work correctly
      expect(screen.getByRole('textbox'))!.toBeInTheDocument()
    })
  })

  // User Interactions and Event Handlers Tests
  describe('User Interactions', () => {
    it('should open account settings when configuration button is clicked', async () => {
      const props = createDefaultProps()

      render(<JinaReader {...props} />)
      const configButton = screen.getByText('datasetCreation.stepOne.website.configureJinaReader')
      await userEvent.click(configButton)

      expect(mockSetShowAccountSettingModal).toHaveBeenCalledWith({
        payload: 'data-source',
      })
    })

    it('should handle URL input and run button click', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      mockCreateTask.mockResolvedValueOnce({
        data: {
          title: 'Test',
          content: 'Content',
          description: 'Desc',
          url: 'https://test.com',
        },
      })

      const props = createDefaultProps()

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://test.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(mockCreateTask).toHaveBeenCalledWith({
          url: 'https://test.com',
          options: props.crawlOptions,
        })
      })
    })

    it('should handle preview action on crawled result', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      const onPreview = vi.fn()
      const crawlResultData = {
        title: 'Preview Test',
        content: '# Content',
        description: 'Preview desc',
        url: 'https://preview.com',
      }

      mockCreateTask.mockResolvedValueOnce({ data: crawlResultData })

      const props = createDefaultProps({ onPreview })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://preview.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      // Assert - result should be displayed
      await waitFor(() => {
        expect(screen.getByText('Preview Test'))!.toBeInTheDocument()
      })

      const previewButton = screen.getByText('datasetCreation.stepOne.website.preview')
      await userEvent.click(previewButton)

      expect(onPreview).toHaveBeenCalled()
    })

    it('should handle checkbox changes in options', async () => {
      const onCrawlOptionsChange = vi.fn()
      const props = createDefaultProps({
        onCrawlOptionsChange,
        crawlOptions: createDefaultCrawlOptions({ crawl_sub_pages: false }),
      })

      render(<JinaReader {...props} />)

      const checkbox = screen.getByRole('checkbox', { name: /crawlSubPage/ })
      fireEvent.click(checkbox)

      expect(onCrawlOptionsChange).toHaveBeenCalled()
    })

    it('should toggle options visibility when clicking options header', async () => {
      const props = createDefaultProps()

      render(<JinaReader {...props} />)

      // Options content should be visible initially
      // Options content should be visible initially
      expect(screen.getByText('datasetCreation.stepOne.website.crawlSubPage'))!.toBeInTheDocument()

      const optionsHeader = screen.getByText('datasetCreation.stepOne.website.options')
      await userEvent.click(optionsHeader)

      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      // Assert - options should be hidden
      expect(screen.queryByText('datasetCreation.stepOne.website.crawlSubPage')).not.toBeInTheDocument()

      await userEvent.click(optionsHeader)

      // Options should be visible again
      // Options should be visible again
      expect(screen.getByText('datasetCreation.stepOne.website.crawlSubPage'))!.toBeInTheDocument()
    })
  })

  // API Calls Tests
  describe('API Calls', () => {
    it('should call createJinaReaderTask with correct parameters', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      mockCreateTask.mockResolvedValueOnce({
        data: { title: 'T', content: 'C', description: 'D', url: 'https://api-test.com' },
      })

      const crawlOptions = createDefaultCrawlOptions({ limit: 5, max_depth: 3 })
      const props = createDefaultProps({ crawlOptions })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://api-test.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(mockCreateTask).toHaveBeenCalledWith({
          url: 'https://api-test.com',
          options: crawlOptions,
        })
      })
    })

    it('should handle direct data response from API', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      const onCheckedCrawlResultChange = vi.fn()

      mockCreateTask.mockResolvedValueOnce({
        data: {
          title: 'Direct Result',
          content: '# Direct Content',
          description: 'Direct desc',
          url: 'https://direct.com',
        },
      })

      const props = createDefaultProps({ onCheckedCrawlResultChange })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://direct.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(onCheckedCrawlResultChange).toHaveBeenCalledWith([
          expect.objectContaining({
            title: 'Direct Result',
            source_url: 'https://direct.com',
          }),
        ])
      })
    })

    it('should handle job_id response and poll for status', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      const mockCheckStatus = checkJinaReaderTaskStatus as Mock
      const onJobIdChange = vi.fn()

      mockCreateTask.mockResolvedValueOnce({ job_id: 'poll-job-123' })
      mockCheckStatus.mockResolvedValueOnce({
        status: 'completed',
        current: 2,
        total: 2,
        data: [
          createCrawlResultItem({ source_url: 'https://p1.com' }),
          createCrawlResultItem({ source_url: 'https://p2.com' }),
        ],
      })

      const props = createDefaultProps({ onJobIdChange })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://poll-test.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(onJobIdChange).toHaveBeenCalledWith('poll-job-123')
      })

      await waitFor(() => {
        expect(mockCheckStatus).toHaveBeenCalledWith('poll-job-123')
      })
    })

    it('should handle failed status from polling', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      const mockCheckStatus = checkJinaReaderTaskStatus as Mock

      mockCreateTask.mockResolvedValueOnce({ job_id: 'fail-job' })
      mockCheckStatus.mockResolvedValueOnce({
        status: 'failed',
        message: 'Crawl failed due to network error',
      })

      const props = createDefaultProps()

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://fail-test.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(screen.getByText('datasetCreation.stepOne.website.exceptionErrorTitle'))!.toBeInTheDocument()
      })

      expect(screen.getByText('Crawl failed due to network error'))!.toBeInTheDocument()
    })

    it('should handle API error during status check', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      const mockCheckStatus = checkJinaReaderTaskStatus as Mock

      mockCreateTask.mockResolvedValueOnce({ job_id: 'error-job' })
      mockCheckStatus.mockRejectedValueOnce({
        json: () => Promise.resolve({ message: 'API Error occurred' }),
      })

      const props = createDefaultProps()

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://error-test.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(screen.getByText('datasetCreation.stepOne.website.exceptionErrorTitle'))!.toBeInTheDocument()
      })
    })

    it('should limit total to crawlOptions.limit', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      const mockCheckStatus = checkJinaReaderTaskStatus as Mock
      const onCheckedCrawlResultChange = vi.fn()

      mockCreateTask.mockResolvedValueOnce({ job_id: 'limit-job' })
      mockCheckStatus.mockResolvedValueOnce({
        status: 'completed',
        current: 100,
        total: 100,
        data: Array.from({ length: 100 }, (_, i) =>
          createCrawlResultItem({ source_url: `https://example.com/${i}` })),
      })

      const props = createDefaultProps({
        onCheckedCrawlResultChange,
        crawlOptions: createDefaultCrawlOptions({ limit: 5 }),
      })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://limit-test.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(onCheckedCrawlResultChange).toHaveBeenCalled()
      })
    })
  })

  // Component Memoization Tests
  describe('Component Memoization', () => {
    it('should be wrapped with React.memo', () => {
      // Assert - React.memo components have $$typeof Symbol(react.memo)
      expect(JinaReader.$$typeof?.toString()).toBe('Symbol(react.memo)')
      expect((JinaReader as unknown as { type: unknown }).type).toBeDefined()
    })
  })

  // Edge Cases and Error Handling Tests
  describe('Edge Cases and Error Handling', () => {
    it('should show error for empty URL', async () => {
      const props = createDefaultProps()

      render(<JinaReader {...props} />)
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      // Assert - Toast should be shown (mocked via Toast component)
      await waitFor(() => {
        expect(createJinaReaderTask).not.toHaveBeenCalled()
      })
    })

    it('should show error for invalid URL format', async () => {
      const props = createDefaultProps()

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'invalid-url')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(createJinaReaderTask).not.toHaveBeenCalled()
      })
    })

    it('should show error for URL without protocol', async () => {
      const props = createDefaultProps()

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'example.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(createJinaReaderTask).not.toHaveBeenCalled()
      })
    })

    it('should accept URL with http:// protocol', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      mockCreateTask.mockResolvedValueOnce({
        data: { title: 'T', content: 'C', description: 'D', url: 'http://example.com' },
      })

      const props = createDefaultProps()

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'http://example.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(mockCreateTask).toHaveBeenCalled()
      })
    })

    it('should show error when limit is empty', async () => {
      const props = createDefaultProps({
        crawlOptions: createDefaultCrawlOptions({ limit: '' }),
      })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://example.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(createJinaReaderTask).not.toHaveBeenCalled()
      })
    })

    it('should show error when limit is null', async () => {
      const props = createDefaultProps({
        crawlOptions: createDefaultCrawlOptions({ limit: null as unknown as number }),
      })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://example.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(createJinaReaderTask).not.toHaveBeenCalled()
      })
    })

    it('should show error when limit is undefined', async () => {
      const props = createDefaultProps({
        crawlOptions: createDefaultCrawlOptions({ limit: undefined as unknown as number }),
      })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://example.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(createJinaReaderTask).not.toHaveBeenCalled()
      })
    })

    it('should handle API throwing an exception', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      mockCreateTask.mockRejectedValueOnce(new Error('Network error'))
      // Suppress console output during test to avoid noisy logs
      const consoleSpy = vi.spyOn(console, 'log').mockImplementation(vi.fn())

      const props = createDefaultProps()

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://exception-test.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(screen.getByText('datasetCreation.stepOne.website.exceptionErrorTitle'))!.toBeInTheDocument()
      })

      consoleSpy.mockRestore()
    })

    it('should handle status response without status field', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      const mockCheckStatus = checkJinaReaderTaskStatus as Mock

      mockCreateTask.mockResolvedValueOnce({ job_id: 'no-status-job' })
      mockCheckStatus.mockResolvedValueOnce({
        // No status field
        message: 'Unknown error',
      })

      const props = createDefaultProps()

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://no-status-test.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(screen.getByText('datasetCreation.stepOne.website.exceptionErrorTitle'))!.toBeInTheDocument()
      })
    })

    it('should show unknown error when error message is empty', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      const mockCheckStatus = checkJinaReaderTaskStatus as Mock

      mockCreateTask.mockResolvedValueOnce({ job_id: 'empty-error-job' })
      mockCheckStatus.mockResolvedValueOnce({
        status: 'failed',
        // No message
      })

      const props = createDefaultProps()

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://empty-error-test.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(screen.getByText('datasetCreation.stepOne.website.unknownError'))!.toBeInTheDocument()
      })
    })

    it('should handle empty data array from API', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      const mockCheckStatus = checkJinaReaderTaskStatus as Mock
      const onCheckedCrawlResultChange = vi.fn()

      mockCreateTask.mockResolvedValueOnce({ job_id: 'empty-data-job' })
      mockCheckStatus.mockResolvedValueOnce({
        status: 'completed',
        current: 0,
        total: 0,
        data: [],
      })

      const props = createDefaultProps({ onCheckedCrawlResultChange })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://empty-data-test.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(onCheckedCrawlResultChange).toHaveBeenCalledWith([])
      })
    })

    it('should handle null data from running status', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      const mockCheckStatus = checkJinaReaderTaskStatus as Mock
      const onCheckedCrawlResultChange = vi.fn()

      mockCreateTask.mockResolvedValueOnce({ job_id: 'null-data-job' })
      mockCheckStatus
        .mockResolvedValueOnce({
          status: 'running',
          current: 0,
          total: 5,
          data: null,
        })
        .mockResolvedValueOnce({
          status: 'completed',
          current: 5,
          total: 5,
          data: [createCrawlResultItem()],
        })

      const props = createDefaultProps({ onCheckedCrawlResultChange })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://null-data-test.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(onCheckedCrawlResultChange).toHaveBeenCalledWith([])
      })
    })

    it('should return empty array when completed job has undefined data', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      const mockCheckStatus = checkJinaReaderTaskStatus as Mock
      const onCheckedCrawlResultChange = vi.fn()

      mockCreateTask.mockResolvedValueOnce({ job_id: 'undefined-data-job' })
      mockCheckStatus.mockResolvedValueOnce({
        status: 'completed',
        current: 0,
        total: 0,
        // data is undefined - should fallback to empty array
      })

      const props = createDefaultProps({ onCheckedCrawlResultChange })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://undefined-data-test.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(onCheckedCrawlResultChange).toHaveBeenCalledWith([])
      })
    })

    it('should show zero current progress when crawlResult is not yet available', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      const mockCheckStatus = checkJinaReaderTaskStatus as Mock
      let resolveCheckStatus: () => void
      const checkStatusPromise = new Promise((resolve) => {
        resolveCheckStatus = () => resolve({ status: 'completed', current: 0, total: 0, data: [] })
      })

      mockCreateTask.mockResolvedValueOnce({ job_id: 'zero-current-job' })
      mockCheckStatus.mockImplementation(() => checkStatusPromise)

      const props = createDefaultProps({
        crawlOptions: createDefaultCrawlOptions({ limit: 10 }),
      })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://zero-current-test.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      // Assert - should show 0/10 when crawlResult is undefined
      await waitFor(() => {
        expect(screen.getByText(/totalPageScraped.*0\/10/))!.toBeInTheDocument()
      })

      // Cleanup - resolve the promise
      resolveCheckStatus!()
      await waitFor(() => {
        expect(screen.queryByText(/totalPageScraped/i)).not.toBeInTheDocument()
      })
    })

    it('should show 0/0 progress when limit is zero string', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      const mockCheckStatus = checkJinaReaderTaskStatus as Mock
      let resolveCheckStatus: () => void
      const checkStatusPromise = new Promise((resolve) => {
        resolveCheckStatus = () => resolve({ status: 'completed', current: 0, total: 0, data: [] })
      })

      mockCreateTask.mockResolvedValueOnce({ job_id: 'zero-total-job' })
      mockCheckStatus.mockImplementation(() => checkStatusPromise)

      const props = createDefaultProps({
        crawlOptions: createDefaultCrawlOptions({ limit: '0' }),
      })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://zero-total-test.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      // Assert - should show 0/0 when limit parses to 0
      await waitFor(() => {
        expect(screen.getByText(/totalPageScraped.*0\/0/))!.toBeInTheDocument()
      })

      // Cleanup - resolve the promise
      resolveCheckStatus!()
      await waitFor(() => {
        expect(screen.queryByText(/totalPageScraped/i)).not.toBeInTheDocument()
      })
    })

    it('should complete successfully when result data is undefined', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      const mockCheckStatus = checkJinaReaderTaskStatus as Mock
      const onCheckedCrawlResultChange = vi.fn()

      mockCreateTask.mockResolvedValueOnce({ job_id: 'undefined-result-data-job' })
      mockCheckStatus.mockResolvedValueOnce({
        status: 'completed',
        current: 0,
        total: 0,
        time_consuming: 1.5,
        // data is undefined - should fallback to empty array
      })

      const props = createDefaultProps({ onCheckedCrawlResultChange })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://undefined-result-data-test.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      // Assert - should complete and show results even if empty
      await waitFor(() => {
        expect(screen.getByText(/scrapTimeInfo/i))!.toBeInTheDocument()
      })
    })

    it('should use limit as total when crawlResult total is not available', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      const mockCheckStatus = checkJinaReaderTaskStatus as Mock
      let resolveCheckStatus: () => void
      const checkStatusPromise = new Promise((resolve) => {
        resolveCheckStatus = () => resolve({ status: 'completed', current: 0, total: 0, data: [] })
      })

      mockCreateTask.mockResolvedValueOnce({ job_id: 'no-total-job' })
      mockCheckStatus.mockImplementation(() => checkStatusPromise)

      const props = createDefaultProps({
        crawlOptions: createDefaultCrawlOptions({ limit: 15 }),
      })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://no-total-test.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      // Assert - should use limit (15) as total
      await waitFor(() => {
        expect(screen.getByText(/totalPageScraped.*0\/15/))!.toBeInTheDocument()
      })

      // Cleanup - resolve the promise
      resolveCheckStatus!()
      await waitFor(() => {
        expect(screen.queryByText(/totalPageScraped/i)).not.toBeInTheDocument()
      })
    })

    it('should fallback to limit when crawlResult has zero total', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      const mockCheckStatus = checkJinaReaderTaskStatus as Mock
      let resolveCheckStatus: () => void
      const checkStatusPromise = new Promise((resolve) => {
        resolveCheckStatus = () => resolve({ status: 'completed', current: 0, total: 0, data: [] })
      })

      mockCreateTask.mockResolvedValueOnce({ job_id: 'both-zero-job' })
      mockCheckStatus
        .mockResolvedValueOnce({
          status: 'running',
          current: 0,
          total: 0,
          data: [],
        })
        .mockImplementationOnce(() => checkStatusPromise)

      const props = createDefaultProps({
        crawlOptions: createDefaultCrawlOptions({ limit: 5 }),
      })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://both-zero-test.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      // Assert - should show progress indicator
      await waitFor(() => {
        expect(screen.getByText(/totalPageScraped/))!.toBeInTheDocument()
      })

      // Cleanup - resolve the promise
      resolveCheckStatus!()
      await waitFor(() => {
        expect(screen.queryByText(/totalPageScraped/i)).not.toBeInTheDocument()
      })
    })

    it('should construct result item from direct data response', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      const onCheckedCrawlResultChange = vi.fn()

      mockCreateTask.mockResolvedValueOnce({
        data: {
          title: 'Direct Title',
          content: '# Direct Content',
          description: 'Direct desc',
          url: 'https://direct-array.com',
        },
      })

      const props = createDefaultProps({ onCheckedCrawlResultChange })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://direct-array.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      // Assert - should construct result item from direct response
      await waitFor(() => {
        expect(onCheckedCrawlResultChange).toHaveBeenCalledWith([
          expect.objectContaining({
            title: 'Direct Title',
            source_url: 'https://direct-array.com',
          }),
        ])
      })
    })
  })

  describe('Prop Variations', () => {
    it('should handle different limit values in crawlOptions', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      mockCreateTask.mockResolvedValueOnce({
        data: { title: 'T', content: 'C', description: 'D', url: 'https://limit.com' },
      })

      const props = createDefaultProps({
        crawlOptions: createDefaultCrawlOptions({ limit: 100 }),
      })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://limit.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(mockCreateTask).toHaveBeenCalledWith(
          expect.objectContaining({
            options: expect.objectContaining({ limit: 100 }),
          }),
        )
      })
    })

    it('should handle different max_depth values', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      mockCreateTask.mockResolvedValueOnce({
        data: { title: 'T', content: 'C', description: 'D', url: 'https://depth.com' },
      })

      const props = createDefaultProps({
        crawlOptions: createDefaultCrawlOptions({ max_depth: 5 }),
      })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://depth.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(mockCreateTask).toHaveBeenCalledWith(
          expect.objectContaining({
            options: expect.objectContaining({ max_depth: 5 }),
          }),
        )
      })
    })

    it('should handle crawl_sub_pages disabled', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      mockCreateTask.mockResolvedValueOnce({
        data: { title: 'T', content: 'C', description: 'D', url: 'https://nosub.com' },
      })

      const props = createDefaultProps({
        crawlOptions: createDefaultCrawlOptions({ crawl_sub_pages: false }),
      })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://nosub.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(mockCreateTask).toHaveBeenCalledWith(
          expect.objectContaining({
            options: expect.objectContaining({ crawl_sub_pages: false }),
          }),
        )
      })
    })

    it('should handle use_sitemap enabled', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      mockCreateTask.mockResolvedValueOnce({
        data: { title: 'T', content: 'C', description: 'D', url: 'https://sitemap.com' },
      })

      const props = createDefaultProps({
        crawlOptions: createDefaultCrawlOptions({ use_sitemap: true }),
      })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://sitemap.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(mockCreateTask).toHaveBeenCalledWith(
          expect.objectContaining({
            options: expect.objectContaining({ use_sitemap: true }),
          }),
        )
      })
    })

    it('should handle includes and excludes patterns', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      mockCreateTask.mockResolvedValueOnce({
        data: { title: 'T', content: 'C', description: 'D', url: 'https://patterns.com' },
      })

      const props = createDefaultProps({
        crawlOptions: createDefaultCrawlOptions({
          includes: '/docs/*',
          excludes: '/api/*',
        }),
      })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://patterns.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(mockCreateTask).toHaveBeenCalledWith(
          expect.objectContaining({
            options: expect.objectContaining({
              includes: '/docs/*',
              excludes: '/api/*',
            }),
          }),
        )
      })
    })

    it('should handle pre-selected crawl results', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      const existingResult = createCrawlResultItem({ source_url: 'https://existing.com' })

      mockCreateTask.mockResolvedValueOnce({
        data: { title: 'New', content: 'C', description: 'D', url: 'https://new.com' },
      })

      const props = createDefaultProps({
        checkedCrawlResult: [existingResult],
      })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://new.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(mockCreateTask).toHaveBeenCalled()
      })
    })

    it('should handle string type limit value', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      mockCreateTask.mockResolvedValueOnce({
        data: { title: 'T', content: 'C', description: 'D', url: 'https://string-limit.com' },
      })

      const props = createDefaultProps({
        crawlOptions: createDefaultCrawlOptions({ limit: '25' }),
      })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://string-limit.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(mockCreateTask).toHaveBeenCalled()
      })
    })
  })

  // Display and UI State Tests
  describe('Display and UI States', () => {
    it('should show crawling progress during running state', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      const mockCheckStatus = checkJinaReaderTaskStatus as Mock
      let resolveCheckStatus: () => void
      const checkStatusPromise = new Promise((resolve) => {
        resolveCheckStatus = () => resolve({ status: 'completed', current: 0, total: 0, data: [] })
      })

      mockCreateTask.mockResolvedValueOnce({ job_id: 'progress-job' })
      mockCheckStatus.mockImplementation(() => checkStatusPromise)

      const props = createDefaultProps({
        crawlOptions: createDefaultCrawlOptions({ limit: 10 }),
      })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://progress.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(screen.getByText(/totalPageScraped.*0\/10/))!.toBeInTheDocument()
      })

      // Cleanup - resolve the promise
      resolveCheckStatus!()
      await waitFor(() => {
        expect(screen.queryByText(/totalPageScraped/i)).not.toBeInTheDocument()
      })
    })

    it('should display time consumed after crawl completion', async () => {
      const mockCreateTask = createJinaReaderTask as Mock

      mockCreateTask.mockResolvedValueOnce({
        data: { title: 'T', content: 'C', description: 'D', url: 'https://time.com' },
      })

      const props = createDefaultProps()

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://time.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(screen.getByText(/scrapTimeInfo/i))!.toBeInTheDocument()
      })
    })

    it('should display crawled results list after completion', async () => {
      const mockCreateTask = createJinaReaderTask as Mock

      mockCreateTask.mockResolvedValueOnce({
        data: {
          title: 'Result Page',
          content: '# Content',
          description: 'Description',
          url: 'https://result.com',
        },
      })

      const props = createDefaultProps()

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://result.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(screen.getByText('Result Page'))!.toBeInTheDocument()
      })
    })

    it('should show error message component when crawl fails', async () => {
      const mockCreateTask = createJinaReaderTask as Mock

      mockCreateTask.mockRejectedValueOnce(new Error('Failed'))
      // Suppress console output during test to avoid noisy logs
      vi.spyOn(console, 'log').mockImplementation(vi.fn())

      const props = createDefaultProps()

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://fail.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      await waitFor(() => {
        expect(screen.getByText('datasetCreation.stepOne.website.exceptionErrorTitle'))!.toBeInTheDocument()
      })
    })
  })

  describe('Integration', () => {
    it('should complete full crawl workflow with job polling', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      const mockCheckStatus = checkJinaReaderTaskStatus as Mock
      const onCheckedCrawlResultChange = vi.fn()
      const onJobIdChange = vi.fn()
      const onPreview = vi.fn()

      mockCreateTask.mockResolvedValueOnce({ job_id: 'full-workflow-job' })
      mockCheckStatus
        .mockResolvedValueOnce({
          status: 'running',
          current: 2,
          total: 5,
          data: [
            createCrawlResultItem({ source_url: 'https://page1.com', title: 'Page 1' }),
            createCrawlResultItem({ source_url: 'https://page2.com', title: 'Page 2' }),
          ],
        })
        .mockResolvedValueOnce({
          status: 'completed',
          current: 5,
          total: 5,
          time_consuming: 3.5,
          data: [
            createCrawlResultItem({ source_url: 'https://page1.com', title: 'Page 1' }),
            createCrawlResultItem({ source_url: 'https://page2.com', title: 'Page 2' }),
            createCrawlResultItem({ source_url: 'https://page3.com', title: 'Page 3' }),
            createCrawlResultItem({ source_url: 'https://page4.com', title: 'Page 4' }),
            createCrawlResultItem({ source_url: 'https://page5.com', title: 'Page 5' }),
          ],
        })

      const props = createDefaultProps({
        onCheckedCrawlResultChange,
        onJobIdChange,
        onPreview,
      })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://full-workflow.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      // Assert - job id should be set
      await waitFor(() => {
        expect(onJobIdChange).toHaveBeenCalledWith('full-workflow-job')
      })

      // Assert - final results should be displayed
      await waitFor(() => {
        expect(screen.getByText('Page 1'))!.toBeInTheDocument()
        expect(screen.getByText('Page 5'))!.toBeInTheDocument()
      })

      // Assert - checked results should be updated
      expect(onCheckedCrawlResultChange).toHaveBeenLastCalledWith(
        expect.arrayContaining([
          expect.objectContaining({ source_url: 'https://page1.com' }),
          expect.objectContaining({ source_url: 'https://page5.com' }),
        ]),
      )
    })

    it('should handle select all and deselect all in results', async () => {
      const mockCreateTask = createJinaReaderTask as Mock
      const onCheckedCrawlResultChange = vi.fn()

      mockCreateTask.mockResolvedValueOnce({
        data: { title: 'Single', content: 'C', description: 'D', url: 'https://single.com' },
      })

      const props = createDefaultProps({ onCheckedCrawlResultChange })

      render(<JinaReader {...props} />)
      const input = screen.getByRole('textbox')
      await userEvent.type(input, 'https://single.com')
      await userEvent.click(screen.getByRole('button', { name: /run/i }))

      // Wait for results
      await waitFor(() => {
        expect(screen.getByText('Single'))!.toBeInTheDocument()
      })

      const selectAllCheckbox = screen.getByText(/selectAll|resetAll/i)
      await userEvent.click(selectAllCheckbox)

      expect(onCheckedCrawlResultChange).toHaveBeenCalled()
    })
  })
})
