import { describe, it, expect } from "vitest"
import { mount } from "@vue/test-utils"
import { defineComponent, h, nextTick, ref } from "vue"
import {
  Popover,
  PopoverAnchor,
  PopoverArrow,
  PopoverClose,
  PopoverContent,
  PopoverTrigger,
} from "."

describe("Popover", () => {
  describe("rendering", () => {
    it("renders without errors", () => {
      const wrapper = mount(Popover, {
        slots: {
          default: () =>
            h(PopoverTrigger, { asChild: true }, {
              default: () => h("button", "Open"),
            }),
        },
      })
      expect(wrapper.exists()).toBe(true)
    })

    it("renders trigger content", () => {
      const wrapper = mount(Popover, {
        slots: {
          default: () =>
            h(PopoverTrigger, { asChild: true }, {
              default: () =>
                h("button", { class: "trigger-btn" }, "Open Popover"),
            }),
        },
      })
      expect(wrapper.find(".trigger-btn").exists()).toBe(true)
      expect(wrapper.find(".trigger-btn").text()).toBe("Open Popover")
    })
  })

  describe("open state", () => {
    it("renders content in portal when defaultOpen is true", async () => {
      mount(Popover, {
        props: { defaultOpen: true },
        slots: {
          default: () => [
            h(PopoverTrigger, { asChild: true }, {
              default: () => h("button", "Trigger"),
            }),
            h(PopoverContent, null, { default: () => "Popover body" }),
          ],
        },
        attachTo: document.body,
      })
      await nextTick()
      const content = document.body.querySelector("[role=\"dialog\"]")
      expect(content).toBeTruthy()
      expect(document.body.textContent).toContain("Popover body")
    })

    it("renders content with correct base classes", async () => {
      mount(Popover, {
        props: { defaultOpen: true },
        slots: {
          default: () => [
            h(PopoverTrigger, { asChild: true }, {
              default: () => h("button", "Trigger"),
            }),
            h(PopoverContent, null, { default: () => "Content" }),
          ],
        },
        attachTo: document.body,
      })
      await nextTick()
      const content = document.body.querySelector("[role=\"dialog\"]")
      expect(content).toBeTruthy()
      expect(content?.className).toContain("z-50")
      expect(content?.className).toContain("w-72")
      expect(content?.className).toContain("rounded-md")
      expect(content?.className).toContain("border")
      expect(content?.className).toContain("bg-popover")
      expect(content?.className).toContain("p-4")
      expect(content?.className).toContain("text-popover-foreground")
      expect(content?.className).toContain("shadow-md")
    })

    it("merges custom class on PopoverContent", async () => {
      mount(Popover, {
        props: { defaultOpen: true },
        slots: {
          default: () => [
            h(PopoverTrigger, { asChild: true }, {
              default: () => h("button", "Trigger"),
            }),
            h(PopoverContent, { class: "my-popover" }, {
              default: () => "X",
            }),
          ],
        },
        attachTo: document.body,
      })
      await nextTick()
      const content = document.body.querySelector("[role=\"dialog\"]")
      expect(content?.className).toContain("my-popover")
    })

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

  describe("side positioning", () => {
    it.each(["top", "right", "bottom", "left"] as const)(
      "renders with side=%s data attribute",
      async (side) => {
        mount(Popover, {
          props: { defaultOpen: true },
          slots: {
            default: () => [
              h(PopoverTrigger, { asChild: true }, {
                default: () => h("button", "Trigger"),
              }),
              h(PopoverContent, { side }, { default: () => "body" }),
            ],
          },
          attachTo: document.body,
        })
        await nextTick()
        const content = document.body.querySelector("[role=\"dialog\"]")
        expect(content?.getAttribute("data-side")).toBe(side)
      },
    )
  })

  describe("closes on outside click", () => {
    it("closes when DialogClose-style PopoverClose is clicked", async () => {
      const Harness = defineComponent({
        components: { Popover, PopoverTrigger, PopoverContent, PopoverClose },
        setup() {
          const open = ref(true)
          return { open }
        },
        template: `
          <Popover v-model:open="open">
            <PopoverTrigger as-child><button>Trigger</button></PopoverTrigger>
            <PopoverContent>
              <PopoverClose as-child>
                <button class="close-btn">Close</button>
              </PopoverClose>
            </PopoverContent>
          </Popover>
          <span class="state">{{ open ? 'OPEN' : 'CLOSED' }}</span>
        `,
      })
      const wrapper = mount(Harness, { attachTo: document.body })
      await nextTick()
      const closeBtn = document.body.querySelector(".close-btn") as HTMLButtonElement | null
      expect(closeBtn).toBeTruthy()
      closeBtn?.click()
      await nextTick()
      await nextTick()
      expect(wrapper.find(".state").text()).toBe("CLOSED")
    })

    it("closes when escape key is dispatched on content", async () => {
      const Harness = defineComponent({
        components: { Popover, PopoverTrigger, PopoverContent },
        setup() {
          const open = ref(true)
          return { open }
        },
        template: `
          <Popover v-model:open="open">
            <PopoverTrigger as-child><button>Trigger</button></PopoverTrigger>
            <PopoverContent>body</PopoverContent>
          </Popover>
          <span class="state">{{ open ? 'OPEN' : 'CLOSED' }}</span>
        `,
      })
      const wrapper = mount(Harness, { attachTo: document.body })
      await nextTick()
      const content = document.body.querySelector("[role=\"dialog\"]") as HTMLElement | null
      expect(content).toBeTruthy()
      const escape = new KeyboardEvent("keydown", { key: "Escape", bubbles: true })
      content?.dispatchEvent(escape)
      await nextTick()
      await nextTick()
      expect(wrapper.find(".state").text()).toBe("CLOSED")
    })
  })

  describe("PopoverAnchor / PopoverArrow exports", () => {
    it("exports PopoverAnchor", () => {
      expect(PopoverAnchor).toBeTruthy()
    })

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

    it("renders PopoverAnchor as a wrapper element", () => {
      const wrapper = mount(Popover, {
        slots: {
          default: () => [
            h(PopoverAnchor, null, {
              default: () => h("div", { class: "anchor-el" }, "Anchor"),
            }),
            h(PopoverTrigger, { asChild: true }, {
              default: () => h("button", "Trigger"),
            }),
          ],
        },
      })
      expect(wrapper.find(".anchor-el").exists()).toBe(true)
      expect(wrapper.find(".anchor-el").text()).toBe("Anchor")
    })
  })

  describe("PopoverContent default props", () => {
    it("uses align=center by default", async () => {
      mount(Popover, {
        props: { defaultOpen: true },
        slots: {
          default: () => [
            h(PopoverTrigger, { asChild: true }, {
              default: () => h("button", "Trigger"),
            }),
            h(PopoverContent, null, { default: () => "Body" }),
          ],
        },
        attachTo: document.body,
      })
      await nextTick()
      const content = document.body.querySelector("[role=\"dialog\"]")
      // reka-ui sets data-align="center" via popper positioning
      expect(content?.getAttribute("data-align")).toBe("center")
    })
  })
})
