import { describe, it, expect } from "vitest"
import { mount } from "@vue/test-utils"
import { h, nextTick } from "vue"
import {
  Tooltip,
  TooltipArrow,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "."

describe("Tooltip", () => {
  describe("rendering", () => {
    it("renders without errors", () => {
      const wrapper = mount(TooltipProvider, {
        slots: {
          default: () =>
            h(Tooltip, null, {
              default: () =>
                h(TooltipTrigger, { asChild: true }, {
                  default: () => h("button", "Hover me"),
                }),
            }),
        },
      })
      expect(wrapper.exists()).toBe(true)
    })

    it("renders trigger content", () => {
      const wrapper = mount(TooltipProvider, {
        slots: {
          default: () =>
            h(Tooltip, null, {
              default: () =>
                h(TooltipTrigger, { asChild: true }, {
                  default: () =>
                    h("button", { class: "trigger-btn" }, "Hover me"),
                }),
            }),
        },
      })
      expect(wrapper.find(".trigger-btn").exists()).toBe(true)
      expect(wrapper.find(".trigger-btn").text()).toBe("Hover me")
    })
  })

  describe("open state", () => {
    it("renders content in portal when defaultOpen is true", async () => {
      mount(TooltipProvider, {
        slots: {
          default: () =>
            h(Tooltip, { defaultOpen: true }, {
              default: () => [
                h(TooltipTrigger, { asChild: true }, {
                  default: () => h("button", "Trigger"),
                }),
                h(TooltipContent, null, { default: () => "Tooltip text" }),
              ],
            }),
        },
        attachTo: document.body,
      })
      await nextTick()
      const content = document.body.querySelector("[data-dismissable-layer]")
      expect(content).toBeTruthy()
      expect(document.body.textContent).toContain("Tooltip text")
    })

    it("renders content with expected base classes", async () => {
      mount(TooltipProvider, {
        slots: {
          default: () =>
            h(Tooltip, { defaultOpen: true }, {
              default: () => [
                h(TooltipTrigger, { asChild: true }, {
                  default: () => h("button", "Trigger"),
                }),
                h(TooltipContent, null, { default: () => "Content" }),
              ],
            }),
        },
        attachTo: document.body,
      })
      await nextTick()
      const content = document.body.querySelector("[data-dismissable-layer]")
      expect(content).toBeTruthy()
      expect(content?.className).toContain("z-50")
      expect(content?.className).toContain("overflow-hidden")
      expect(content?.className).toContain("rounded-md")
      expect(content?.className).toContain("border")
      expect(content?.className).toContain("bg-popover")
      expect(content?.className).toContain("px-3")
      expect(content?.className).toContain("py-1.5")
      expect(content?.className).toContain("text-sm")
      expect(content?.className).toContain("text-popover-foreground")
      expect(content?.className).toContain("shadow-md")
    })

    it("merges custom class on TooltipContent", async () => {
      mount(TooltipProvider, {
        slots: {
          default: () =>
            h(Tooltip, { defaultOpen: true }, {
              default: () => [
                h(TooltipTrigger, { asChild: true }, {
                  default: () => h("button", "Trigger"),
                }),
                h(TooltipContent, { class: "my-tip" }, {
                  default: () => "x",
                }),
              ],
            }),
        },
        attachTo: document.body,
      })
      await nextTick()
      const content = document.body.querySelector("[data-dismissable-layer]")
      expect(content?.className).toContain("my-tip")
    })

    it("does not render content when defaultOpen is false", async () => {
      mount(TooltipProvider, {
        slots: {
          default: () =>
            h(Tooltip, null, {
              default: () => [
                h(TooltipTrigger, { asChild: true }, {
                  default: () => h("button", "Trigger"),
                }),
                h(TooltipContent, null, { default: () => "Hidden tip" }),
              ],
            }),
        },
        attachTo: document.body,
      })
      await nextTick()
      expect(document.body.textContent).not.toContain("Hidden tip")
    })
  })

  describe("hover / focus interactions", () => {
    it("shows on focus (since reka-ui shows tooltip on focus)", async () => {
      mount(TooltipProvider, {
        props: { delayDuration: 0 },
        slots: {
          default: () =>
            h(Tooltip, null, {
              default: () => [
                h(TooltipTrigger, { asChild: true }, {
                  default: () => h("button", { class: "tt-trigger" }, "T"),
                }),
                h(TooltipContent, null, { default: () => "Focus body" }),
              ],
            }),
        },
        attachTo: document.body,
      })
      await nextTick()
      const trigger = document.body.querySelector(".tt-trigger") as HTMLButtonElement
      trigger.focus()
      await nextTick()
      await new Promise<void>(resolve => setTimeout(resolve, 30))
      await nextTick()
      expect(document.body.textContent).toContain("Focus body")
    })

    it("shows on mouseenter (pointer hover)", async () => {
      mount(TooltipProvider, {
        props: { delayDuration: 0 },
        slots: {
          default: () =>
            h(Tooltip, null, {
              default: () => [
                h(TooltipTrigger, { asChild: true }, {
                  default: () => h("button", { class: "tt-trigger" }, "T"),
                }),
                h(TooltipContent, null, { default: () => "Hover body" }),
              ],
            }),
        },
        attachTo: document.body,
      })
      await nextTick()
      const trigger = document.body.querySelector(".tt-trigger") as HTMLButtonElement
      // jsdom may lack PointerEvent. Reka-ui also opens tooltip via focus.
      trigger.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true }))
      trigger.dispatchEvent(new MouseEvent("mousemove", { bubbles: true }))
      trigger.focus()
      await nextTick()
      await new Promise<void>(resolve => setTimeout(resolve, 30))
      await nextTick()
      expect(document.body.textContent).toContain("Hover body")
    })

    it("respects delayDuration prop on TooltipProvider", async () => {
      const provider = mount(TooltipProvider, {
        props: { delayDuration: 500 },
        slots: {
          default: () =>
            h(Tooltip, null, {
              default: () => [
                h(TooltipTrigger, { asChild: true }, {
                  default: () => h("button", { class: "tt-trigger" }, "T"),
                }),
                h(TooltipContent, null, { default: () => "Delayed body" }),
              ],
            }),
        },
        attachTo: document.body,
      })
      expect(provider.exists()).toBe(true)
    })
  })

  describe("sides", () => {
    it.each(["top", "right", "bottom", "left"] as const)(
      "renders side=%s data attribute",
      async (side) => {
        mount(TooltipProvider, {
          slots: {
            default: () =>
              h(Tooltip, { defaultOpen: true }, {
                default: () => [
                  h(TooltipTrigger, { asChild: true }, {
                    default: () => h("button", "T"),
                  }),
                  h(TooltipContent, { side }, { default: () => "body" }),
                ],
              }),
          },
          attachTo: document.body,
        })
        await nextTick()
        const content = document.body.querySelector("[data-dismissable-layer]")
        expect(content?.getAttribute("data-side")).toBe(side)
      },
    )
  })

  describe("exports", () => {
    it("exports TooltipArrow", () => {
      expect(TooltipArrow).toBeTruthy()
    })
  })
})
