import { render } from "@testing-library/react"
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
import Highlighter from "./Highlighter"

describe("Highlighter", () => {
  const HIGHLIGHT_CLASS = "customHighlightClass"
  const UNHIGHLIGHT_CLASS = "customUnhighlightClass"

  let consoleError: typeof console.error

  beforeEach(() => {
    consoleError = console.error
    // React DEV warnings should fail tests
    console.error = vi.fn((message: string, ...args: any[]) => {
      consoleError.call(console, message, ...args)
      throw new Error(message)
    })
  })

  afterEach(() => {
    console.error = consoleError
  })

  function renderHighlighter(props: Parameters<typeof Highlighter>[0]) {
    return render(
      <Highlighter
        highlightClassName={HIGHLIGHT_CLASS}
        unhighlightClassName={UNHIGHLIGHT_CLASS}
        {...props}
      />,
    )
  }

  // Helper functions to reduce duplication
  function getHighlights(container: HTMLElement) {
    return container.querySelectorAll(`.${HIGHLIGHT_CLASS}`)
  }

  function expectHighlightCount(container: HTMLElement, count: number) {
    const highlights = getHighlights(container)
    expect(highlights.length).toBe(count)
    return highlights
  }

  function expectNoHighlights(container: HTMLElement, expectedText: string) {
    expectHighlightCount(container, 0)
    expect(container.textContent).toBe(expectedText)
  }

  function expectSingleHighlight(container: HTMLElement, expectedText: string) {
    const highlights = expectHighlightCount(container, 1)
    expect(highlights[0].textContent).toBe(expectedText)
    return highlights[0]
  }

  function renderAndExpectNoHighlights(
    props: Parameters<typeof Highlighter>[0],
    expectedText: string,
  ) {
    const { container } = renderHighlighter(props)
    expectNoHighlights(container, expectedText)
  }

  function renderAndExpectSingleHighlight(
    props: Parameters<typeof Highlighter>[0],
    expectedText: string,
  ) {
    const { container } = renderHighlighter(props)
    return expectSingleHighlight(container, expectedText)
  }

  it("should properly handle an empty array for searchWords", () => {
    renderAndExpectNoHighlights(
      {
        searchWords: [],
        textToHighlight: "This is text",
      },
      "This is text",
    )
  })

  it("should properly handle an empty string for searchWords", () => {
    renderAndExpectNoHighlights(
      {
        searchWords: [""],
        textToHighlight: "This is text",
      },
      "This is text",
    )
  })

  it("should properly handle empty textToHighlight", () => {
    renderAndExpectNoHighlights(
      {
        searchWords: ["search"],
        textToHighlight: "",
      },
      "",
    )
  })

  it("should highlight searchText words that exactly match words in textToHighlight", () => {
    renderAndExpectSingleHighlight(
      {
        searchWords: ["text"],
        textToHighlight: "This is text",
      },
      "text",
    )
  })

  it("should handle unclosed parentheses when autoEscape prop is truthy", () => {
    renderAndExpectSingleHighlight(
      {
        autoEscape: true,
        searchWords: ["("],
        textToHighlight: "(This is text)",
      },
      "(",
    )
  })

  it("should highlight searchText words that partial-match text in textToHighlight", () => {
    renderAndExpectSingleHighlight(
      {
        searchWords: ["Th"],
        textToHighlight: "This is text",
      },
      "Th",
    )
  })

  it("should highlight multiple occurrences of a searchText word", () => {
    const { container } = renderHighlighter({
      searchWords: ["is"],
      textToHighlight: "This is text",
    })

    expectHighlightCount(container, 2)
    expect(container.textContent).toBe("This is text")
  })

  it("should highlight multiple searchText words", () => {
    const { container } = renderHighlighter({
      searchWords: ["This", "text"],
      textToHighlight: "This is text",
    })

    const highlights = expectHighlightCount(container, 2)
    expect(container.textContent).toBe("This is text")
    expect(highlights[0].textContent).toBe("This")
    expect(highlights[1].textContent).toBe("text")
  })

  it("should match terms in a case insensitive way but show their case-sensitive representation", () => {
    renderAndExpectSingleHighlight(
      {
        searchWords: ["this"],
        textToHighlight: "This is text",
      },
      "This",
    )
  })

  it("should use the highlightClassName if specified", () => {
    const { container } = renderHighlighter({
      searchWords: ["text"],
      textToHighlight: "This is text",
    })

    const highlight = container.querySelector("mark")
    expect(highlight).toHaveClass(HIGHLIGHT_CLASS)
  })

  it("should use the highlightStyle if specified", () => {
    const { container } = renderHighlighter({
      highlightStyle: { color: "red" },
      searchWords: ["text"],
      textToHighlight: "This is text",
    })

    const highlight = container.querySelector("mark")
    expect(highlight).toHaveStyle({ color: "red" })
  })

  it("should use the unhighlightStyle if specified", () => {
    const { container } = renderHighlighter({
      unhighlightStyle: { color: "gray" },
      searchWords: ["text"],
      textToHighlight: "This is text",
    })

    // Find the unhighlighted span (not the highlighted mark)
    const spans = container.querySelectorAll("span")
    const unhighlightSpan = Array.from(spans).find(span =>
      span.classList.contains(UNHIGHLIGHT_CLASS),
    )
    expect(unhighlightSpan).toHaveStyle({ color: "gray" })
  })

  it("should use the highlightTag if specified", () => {
    const { container } = renderHighlighter({
      autoEscape: true,
      highlightTag: "span",
      searchWords: ["text"],
      textToHighlight: "This is text",
    })

    const highlights = getHighlights(container)
    expect(highlights[0].tagName).toBe("SPAN")
  })

  it("should support functional components via highlightTag", () => {
    const HighlightTag = ({ children, highlightIndex, ...props }: any) => (
      <span {...props} data-highlight-index={highlightIndex}>
        {children}
      </span>
    )

    const { container } = renderHighlighter({
      autoEscape: true,
      highlightTag: HighlightTag,
      searchWords: ["text"],
      textToHighlight: "This is text",
    })

    const highlights = getHighlights(container)
    expect(highlights[0].tagName).toBe("SPAN")
    expect(highlights[0]).toHaveAttribute("data-highlight-index", "0")
  })

  it("should apply activeClassName to the match specified by activeIndex", () => {
    const activeClassName = "active"
    const { container } = renderHighlighter({
      activeIndex: 1,
      activeClassName,
      searchWords: ["text"],
      textToHighlight: "This is text which should have this text highlighted",
    })

    const highlights = container.querySelectorAll("mark")
    expect(highlights[1]).toHaveClass(activeClassName)
  })

  it("should apply activeStyle to the match specified by activeIndex", () => {
    const activeStyle = { color: "red" }
    const { container } = renderHighlighter({
      activeIndex: 1,
      activeStyle,
      searchWords: ["text"],
      textToHighlight: "This is text which should have this text highlighted",
    })

    const highlights = container.querySelectorAll("mark")
    expect(highlights[1]).toHaveStyle({ color: "red" })
  })

  it("should support caseSensitive search", () => {
    const { container } = renderHighlighter({
      caseSensitive: true,
      searchWords: ["th"],
      textToHighlight: "This the three time",
    })

    const highlights = expectHighlightCount(container, 2)
    expect(highlights[0].textContent).toBe("th")
    expect(highlights[1].textContent).toBe("th")
  })

  it("should support custom findChunks prop function", () => {
    const { container } = renderHighlighter({
      findChunks: () => [
        { start: 0, end: 1 },
        { start: 5, end: 7 },
      ],
      searchWords: ["xxx"],
      textToHighlight: "This is text",
    })

    const highlights = expectHighlightCount(container, 2)
    expect(highlights[0].textContent).toBe("T")
    expect(highlights[1].textContent).toBe("is")

    const { container: container2 } = renderHighlighter({
      findChunks: () => [],
      searchWords: ["This"],
      textToHighlight: "This is text",
    })

    expectHighlightCount(container2, 0)
  })
})
