import { renderHook, screen } from "@testing-library/react"
import React, { useEffect } from "react"
import { describe, expect, test, vi } from "vitest"
import { render as BaseRender, RenderOptions } from "../../../tests/render"
import { createMedia } from "./createMedia"

const BREAKPOINTS = {
  xs: 0,
  sm: 640,
  md: 768,
  lg: 1025,
  xl: 1280,
  "2xl": 1440,
  container: 1600,
  "3xl": 1910,
}

const {
  createMediaStyle,
  breakpoints,
  Media,
  useSizeContextSelector,
  SizeProvider,
  MediaContextProvider,
} = createMedia(BREAKPOINTS, {
  initialWindowSize: { width: window.innerWidth, height: window.innerHeight },
})

const render = (ui: React.ReactElement, options?: RenderOptions) => {
  return BaseRender(
    <MediaContextProvider>
      <SizeProvider>{ui}</SizeProvider>
    </MediaContextProvider>,
    options,
  )
}

describe("Media", () => {
  test("does not fire effects when not matched", () => {
    const callback = vi.fn()

    const Component = () => {
      useEffect(() => {
        callback()
      }, [])
      return <div>Hello world</div>
    }

    render(
      <Media greaterThan="lg">
        <Component />
      </Media>,
      { viewport: { width: breakpoints.sm } },
    )

    expect(callback).not.toHaveBeenCalled()
  })

  describe("at", () => {
    const AtSm = () => (
      <Media at="sm">
        <button>Click me</button>
      </Media>
    )

    test('does not match "sm"', () => {
      render(<AtSm />, { viewport: { width: breakpoints.md } })
      expect(
        screen.queryByRole("button", { name: "Click me" }),
      ).not.toBeInTheDocument()
    })

    test('matches "sm"', () => {
      render(<AtSm />, { viewport: { width: breakpoints.sm } })
      const button = screen.getByRole("button", { name: "Click me" })
      expect(button).toBeInTheDocument()
      expect(button.parentElement).toHaveClass("fresnel-at-sm")
    })

    test('matches "sm" with render props', () => {
      render(
        <Media at="sm">
          {(mediaClassName: string) => (
            <button className={mediaClassName}>Click me</button>
          )}
        </Media>,
        { viewport: { width: breakpoints.sm } },
      )
      const button = screen.getByRole("button", { name: "Click me" })
      expect(button).toBeInTheDocument()
      expect(button).toHaveClass("fresnel-at-sm")
    })
  })

  describe("lessThan", () => {
    const LessThan = () => (
      <Media lessThan="sm">
        <button>Click me</button>
      </Media>
    )

    test('does not match "md"', () => {
      render(<LessThan />, { viewport: { width: breakpoints.md } })
      expect(
        screen.queryByRole("button", { name: "Click me" }),
      ).not.toBeInTheDocument()
    })

    test('does not match "sm"', () => {
      render(<LessThan />, { viewport: { width: breakpoints.sm } })
      expect(
        screen.queryByRole("button", { name: "Click me" }),
      ).not.toBeInTheDocument()
    })

    test('matches less than "sm"', () => {
      render(<LessThan />, { viewport: { width: breakpoints.sm - 1 } })
      const button = screen.getByRole("button", { name: "Click me" })
      expect(button).toBeInTheDocument()
      expect(button.parentElement).toHaveClass("fresnel-lessThan-sm")
    })
  })

  describe("greterThan", () => {
    const GreaterThanLg = () => (
      <Media greaterThan="lg">
        <button>Click me</button>
      </Media>
    )

    test('does not match "lg"', () => {
      render(<GreaterThanLg />, { viewport: { width: breakpoints.lg } })
      expect(
        screen.queryByRole("button", { name: "Click me" }),
      ).not.toBeInTheDocument()
    })

    test('matches "xl"', () => {
      render(<GreaterThanLg />, { viewport: { width: breakpoints.xl } })
      const button = screen.getByRole("button", { name: "Click me" })
      expect(button).toBeInTheDocument()
      expect(button.parentElement).toHaveClass("fresnel-greaterThan-lg")
    })
  })

  describe("greaterThanOrEqual", () => {
    const GreaterThanOrEqualLg = () => (
      <Media greaterThanOrEqual="lg">
        <button>Click me</button>
      </Media>
    )

    test('does not match "md"', () => {
      render(<GreaterThanOrEqualLg />, { viewport: { width: breakpoints.md } })
      expect(
        screen.queryByRole("button", { name: "Click me" }),
      ).not.toBeInTheDocument()
    })

    test('matches "lg"', () => {
      render(<GreaterThanOrEqualLg />, { viewport: { width: breakpoints.lg } })
      const button = screen.getByRole("button", { name: "Click me" })
      expect(button).toBeInTheDocument()
      expect(button.parentElement).toHaveClass("fresnel-greaterThanOrEqual-lg")
    })

    test('matches "xl"', () => {
      render(<GreaterThanOrEqualLg />, { viewport: { width: breakpoints.xl } })
      const button = screen.getByRole("button", { name: "Click me" })
      expect(button).toBeInTheDocument()
      expect(button.parentElement).toHaveClass("fresnel-greaterThanOrEqual-lg")
    })
  })

  describe("between", () => {
    describe("start-inclusive", () => {
      const BetweenMdAndLg = () => (
        <Media between={["md", "lg"]}>
          <button>Click me</button>
        </Media>
      )

      test('does not match "sm"', () => {
        render(<BetweenMdAndLg />, { viewport: { width: breakpoints.sm } })
        expect(
          screen.queryByRole("button", { name: "Click me" }),
        ).not.toBeInTheDocument()
      })

      test('matches "md"', () => {
        render(<BetweenMdAndLg />, { viewport: { width: breakpoints.md } })
        const button = screen.getByRole("button", { name: "Click me" })
        expect(button).toBeInTheDocument()
        expect(button.parentElement).toHaveClass("fresnel-between-md-lg")
      })

      test('does not match "lg"', () => {
        render(<BetweenMdAndLg />, { viewport: { width: breakpoints.lg } })
        expect(
          screen.queryByRole("button", { name: "Click me" }),
        ).not.toBeInTheDocument()
      })

      test('does not match "xl"', () => {
        render(<BetweenMdAndLg />, { viewport: { width: breakpoints.xl } })
        expect(
          screen.queryByRole("button", { name: "Click me" }),
        ).not.toBeInTheDocument()
      })
    })
  })

  describe("multiple medias", () => {
    test("not matching", () => {
      render(
        <>
          <Media at="sm">Media - 1</Media>
          <Media at="sm">Media - 2</Media>
          <Media at="sm">Media - 3</Media>
        </>,
      )

      expect(screen.queryByText(/Media/)).not.toBeInTheDocument()
    })
  })
})

describe("createMediaStyle", () => {
  test("generates media styles", () => {
    expect(createMediaStyle()).toMatchSnapshot()
  })
})

describe("useSizeContextSelector", () => {
  test("more than lg", () => {
    const { result } = renderHook(
      () => useSizeContextSelector(state => state.width < breakpoints.lg),
      { wrapper: ({ children }) => <SizeProvider>{children}</SizeProvider> },
    )
    expect(result.current).toBe(false)
  })
})
