import React from 'react'
import { LinkPreview, LinkPreviewStatus } from 'stream-chat'
import { describe, expect, it, vi } from 'vitest'

import { renderWithProviders, screen, fireEvent } from '../../test/utils'
import { LOADING_LABEL } from '../LinkAttachment/components/_shared/CardShell'

import { CustomLinkPreviewList } from '.'

const dismissPreview = vi.fn()

// `LinkPreviewsManager`'s static predicates are left real (`previewIsLoading`
// is just `status === 'loading'`), so these cases exercise the actual mapping
// from a `LinkPreview` to `LinkAttachment.Composer` props rather than a stub of
// it. Only the two composer hooks are faked.
const previews: LinkPreview[] = []

vi.mock('stream-chat-react', () => ({
  useMessageComposer: () => ({
    linkPreviewsManager: { state: {}, dismissPreview },
  }),
  useStateStore: () => ({ linkPreviews: previews }),
}))

const stage = (
  preview: Partial<LinkPreview> & { status: LinkPreviewStatus }
) => {
  previews.length = 0
  previews.push({ og_scrape_url: 'https://example.com/story', ...preview })
  return renderWithProviders(<CustomLinkPreviewList />)
}

describe('CustomLinkPreviewList', () => {
  it('reserves the hero while the scrape is still in flight', () => {
    // A LOADING preview has no `image_url` yet. Keying the layout off the image
    // alone dropped the hero for the whole ~1.5s debounce + scrape window, so
    // the skeleton was two bare text bars that then snapped open to the 250px
    // featured box (MES-1349).
    const { container } = stage({ status: LinkPreviewStatus.LOADING })

    const shell = screen.getByLabelText(LOADING_LABEL)
    expect(shell).toHaveAttribute('aria-busy', 'true')
    expect(shell.className).toContain('h-[250px]')
    // Hero placeholder + the two chin bars: without the hero there are two.
    expect(container.querySelectorAll('.animate-pulse')).toHaveLength(3)
  })

  it('keeps the featured hero once a preview loads with an image', () => {
    stage({
      status: LinkPreviewStatus.LOADED,
      title: 'Example story',
      image_url: 'https://cdn.example.com/story.jpg',
    })

    expect(screen.getByText('Example story')).toBeInTheDocument()
    expect(screen.getByRole('img')).toBeInTheDocument()
    expect(screen.queryByLabelText(LOADING_LABEL)).toBeNull()
  })

  it('falls back to the compact card when a loaded preview has no image', () => {
    stage({ status: LinkPreviewStatus.LOADED, title: 'Example story' })

    expect(screen.getByText('Example story')).toBeInTheDocument()
    expect(screen.queryByRole('img')).toBeNull()
  })

  it('is not a scroller of its own: the composer card scrolls as one piece (MES-1037)', () => {
    // The inverted layout contract — see this component's doc comment. While
    // this list scrolled and shrank on its own, a staged preview was cropped to
    // keep the textarea above the keyboard instead of scrolling with it.
    const { container } = stage({
      status: LinkPreviewStatus.LOADED,
      title: 'Example story',
      image_url: 'https://cdn.example.com/story.jpg',
    })

    const list = container.querySelector('.messaging-link-preview-list')
    expect(list).not.toHaveClass('overflow-y-auto')
    expect(list).not.toHaveClass('min-h-0')
  })

  it('is dismiss-only: the staged card never navigates', () => {
    // The deliberate behaviour change in MES-1349 — see this component's own
    // doc comment for why (MES-1358 §7 Q7, touch hazard). Asserted so the
    // anchor cannot be reinstated by accident.
    stage({
      status: LinkPreviewStatus.LOADED,
      title: 'Example story',
      image_url: 'https://cdn.example.com/story.jpg',
    })

    expect(screen.queryByRole('link')).toBeNull()
    const controls = screen.getAllByRole('button')
    expect(controls).toHaveLength(1)
    expect(controls[0]).toHaveAttribute('aria-label', 'Dismiss attachment')

    fireEvent.click(controls[0])
    expect(dismissPreview).toHaveBeenCalledWith('https://example.com/story')
  })
})
