import { describe, it, expect, beforeEach, vi } from 'vitest'
import { fireEvent, render, screen } from '@testing-library/react'
import { Note } from './widget-note'

beforeEach(() => {
  // jsdom doesn't lay anything out, so scrollHeight / clientHeight default to
  // 0 / 0 and the overflow detector won't fire by default. Each test that
  // wants to exercise the overflow branch stubs these getters explicitly.
})

describe('<Note>', () => {
  it('renders the children unclamped when no overflow is detected', () => {
    render(<Note>Short text</Note>)
    expect(screen.getByText('Short text')).toBeTruthy()
    expect(screen.queryByText('Show more')).toBeNull()
  })

  it('shows the Show more toggle when content overflows', () => {
    // Force scrollHeight > clientHeight on the typography element.
    Object.defineProperty(HTMLElement.prototype, 'scrollHeight', {
      configurable: true,
      get: () => 200,
    })
    Object.defineProperty(HTMLElement.prototype, 'clientHeight', {
      configurable: true,
      get: () => 60,
    })
    render(<Note>{'a '.repeat(400)}</Note>)
    expect(screen.getByText('Show more')).toBeTruthy()
    fireEvent.click(screen.getByText('Show more'))
    expect(screen.getByText('Show less')).toBeTruthy()
  })

  it('honors custom labels', () => {
    Object.defineProperty(HTMLElement.prototype, 'scrollHeight', {
      configurable: true,
      get: () => 200,
    })
    Object.defineProperty(HTMLElement.prototype, 'clientHeight', {
      configurable: true,
      get: () => 60,
    })
    render(
      <Note labels={{ showMore: 'Reveal', showLess: 'Hide' }}>long text</Note>,
    )
    expect(screen.getByText('Reveal')).toBeTruthy()
    fireEvent.click(screen.getByText('Reveal'))
    expect(screen.getByText('Hide')).toBeTruthy()
  })

  it('renders string children verbatim (no markdown parsing on base Note)', () => {
    const { container } = render(<Note>{'This is **literal** text'}</Note>)
    // Base Note no longer parses markdown — asterisks render verbatim.
    expect(container.querySelector('strong')).toBeNull()
    expect(screen.getByText('This is **literal** text')).toBeTruthy()
  })

  it('passes through JSX children verbatim', () => {
    render(
      <Note>
        <span data-testid='raw'>**not parsed**</span>
      </Note>,
    )
    // Asterisks should remain literal because the children are JSX.
    expect(screen.getByTestId('raw').textContent).toBe('**not parsed**')
  })

  describe('lineClamp={0}', () => {
    it('does not render the Show more toggle when content overflows', () => {
      Object.defineProperty(HTMLElement.prototype, 'scrollHeight', {
        configurable: true,
        get: () => 200,
      })
      Object.defineProperty(HTMLElement.prototype, 'clientHeight', {
        configurable: true,
        get: () => 60,
      })
      render(<Note lineClamp={0}>{'a '.repeat(400)}</Note>)
      expect(screen.queryByText('Show more')).toBeNull()
      expect(screen.queryByText('Show less')).toBeNull()
    })

    it('does not apply line clamping styles', () => {
      const { container } = render(
        <Note lineClamp={0}>{'a '.repeat(400)}</Note>,
      )
      const textBox = container.querySelector('p')?.parentElement
      const style = textBox ? window.getComputedStyle(textBox) : null
      expect(style?.getPropertyValue('-webkit-line-clamp')).toBeFalsy()
    })

    it('default behavior (lineClamp omitted) still clamps and shows toggle on overflow', () => {
      Object.defineProperty(HTMLElement.prototype, 'scrollHeight', {
        configurable: true,
        get: () => 200,
      })
      Object.defineProperty(HTMLElement.prototype, 'clientHeight', {
        configurable: true,
        get: () => 60,
      })
      render(<Note>{'a '.repeat(400)}</Note>)
      expect(screen.getByText('Show more')).toBeTruthy()
    })
  })

  it('does not throw when ResizeObserver is missing', () => {
    const original = globalThis.ResizeObserver
    // @ts-expect-error force-undefined for the duration of the test
    delete globalThis.ResizeObserver
    try {
      // We don't expect anything specific to render — just that this doesn't crash.
      expect(() => render(<Note>x</Note>)).not.toThrow()
    } finally {
      globalThis.ResizeObserver = original
    }
    void vi
  })
})

describe('<Note.Markdown>', () => {
  it('renders bold markdown from a content string', () => {
    const { container } = render(
      <Note.Markdown content='This is **important** info' />,
    )
    expect(container.querySelector('strong')?.textContent).toBe('important')
  })

  it('renders markdown links with secure target/rel defaults', () => {
    const { container } = render(
      <Note.Markdown content='See [docs](https://example.com)' />,
    )
    const link = container.querySelector('a')
    expect(link?.getAttribute('href')).toBe('https://example.com')
    expect(link?.getAttribute('target')).toBe('_blank')
    expect(link?.getAttribute('rel')).toBe('noopener noreferrer')
  })

  it('strips images from markdown', () => {
    const { container } = render(
      <Note.Markdown content='caption text ![alt](https://example.com/x.png)' />,
    )
    expect(container.querySelector('img')).toBeNull()
    expect(screen.getByText(/caption text/)).toBeTruthy()
  })

  it('clamps and shows the Show more toggle on overflow (same shell as base)', () => {
    Object.defineProperty(HTMLElement.prototype, 'scrollHeight', {
      configurable: true,
      get: () => 200,
    })
    Object.defineProperty(HTMLElement.prototype, 'clientHeight', {
      configurable: true,
      get: () => 60,
    })
    render(<Note.Markdown content={'long content '.repeat(80)} />)
    expect(screen.getByText('Show more')).toBeTruthy()
  })
})
