import { describe, it, expect } from "vitest"
import { mount } from "@vue/test-utils"
import { defineComponent, h, nextTick, ref } from "vue"
import {
  DropdownMenu,
  DropdownMenuCheckboxItem,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuPortal,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuSeparator,
  DropdownMenuShortcut,
  DropdownMenuSub,
  DropdownMenuSubContent,
  DropdownMenuSubTrigger,
  DropdownMenuTrigger,
} from "."

function mountDropdownMenu(props: Record<string, unknown> = {}) {
  return mount(DropdownMenu, {
    props: { defaultOpen: true, ...props },
    slots: {
      default: () => [
        h(DropdownMenuTrigger, { asChild: true }, {
          default: () => h("button", "Open Menu"),
        }),
        h(DropdownMenuContent, null, {
          default: () => [
            h(DropdownMenuLabel, null, { default: () => "My Account" }),
            h(DropdownMenuSeparator),
            h(DropdownMenuItem, null, { default: () => "Profile" }),
            h(DropdownMenuItem, null, { default: () => "Settings" }),
            h(DropdownMenuSeparator),
            h(DropdownMenuItem, null, { default: () => "Logout" }),
          ],
        }),
      ],
    },
    attachTo: document.body,
  })
}

describe("DropdownMenu", () => {
  describe("rendering", () => {
    it("renders without errors", () => {
      const wrapper = mount(DropdownMenu, { slots: { default: "content" } })
      expect(wrapper.exists()).toBe(true)
    })

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

  describe("DropdownMenuShortcut", () => {
    it("renders with correct classes", () => {
      const wrapper = mount(DropdownMenuShortcut, {
        slots: { default: "Ctrl+K" },
      })
      expect(wrapper.text()).toBe("Ctrl+K")
      expect(wrapper.classes()).toContain("ml-auto")
      expect(wrapper.classes()).toContain("text-xs")
      expect(wrapper.classes()).toContain("tracking-widest")
      expect(wrapper.classes()).toContain("opacity-60")
    })

    it("renders as a span element", () => {
      const wrapper = mount(DropdownMenuShortcut, {
        slots: { default: "Ctrl+K" },
      })
      expect(wrapper.element.tagName).toBe("SPAN")
    })

    it("merges custom class", () => {
      const wrapper = mount(DropdownMenuShortcut, {
        props: { class: "custom-shortcut" },
        slots: { default: "Ctrl+K" },
      })
      expect(wrapper.classes()).toContain("custom-shortcut")
      expect(wrapper.classes()).toContain("ml-auto")
    })
  })

  describe("DropdownMenu with open content", () => {
    it("renders items in portal when open", async () => {
      mountDropdownMenu()
      await nextTick()
      const items = document.body.querySelectorAll("[role=\"menuitem\"]")
      expect(items.length).toBe(3)
      expect(items[0]?.textContent).toContain("Profile")
      expect(items[1]?.textContent).toContain("Settings")
      expect(items[2]?.textContent).toContain("Logout")
    })

    it("renders DropdownMenuContent with correct base classes", async () => {
      mountDropdownMenu()
      await nextTick()
      const content = document.body.querySelector("[role=\"menu\"]")
      expect(content).toBeTruthy()
      expect(content?.className).toContain("z-50")
      expect(content?.className).toContain("min-w-32")
      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("p-1")
      expect(content?.className).toContain("text-popover-foreground")
      expect(content?.className).toContain("shadow-md")
    })

    it("renders DropdownMenuItem with correct base classes", async () => {
      mountDropdownMenu()
      await nextTick()
      const item = document.body.querySelector("[role=\"menuitem\"]")
      expect(item).toBeTruthy()
      expect(item?.className).toContain("relative")
      expect(item?.className).toContain("flex")
      expect(item?.className).toContain("cursor-default")
      expect(item?.className).toContain("select-none")
      expect(item?.className).toContain("items-center")
      expect(item?.className).toContain("rounded-sm")
      expect(item?.className).toContain("px-2")
      expect(item?.className).toContain("py-1.5")
      expect(item?.className).toContain("text-sm")
      expect(item?.className).toContain("outline-none")
      expect(item?.className).toContain("transition-colors")
    })

    it("renders DropdownMenuLabel with correct classes", async () => {
      mountDropdownMenu()
      await nextTick()
      expect(document.body.textContent).toContain("My Account")
      const allElements = document.body.querySelectorAll("*")
      let labelEl: Element | null = null
      allElements.forEach((el) => {
        if (el.textContent === "My Account" && el.children.length === 0) {
          labelEl = el
        }
      })
      expect(labelEl).toBeTruthy()
      const matched = labelEl as Element | null
      expect(matched?.className).toContain("px-2")
      expect(matched?.className).toContain("py-1.5")
      expect(matched?.className).toContain("text-sm")
      expect(matched?.className).toContain("font-semibold")
    })

    it("renders DropdownMenuSeparator with correct classes", async () => {
      mountDropdownMenu()
      await nextTick()
      const separators = document.body.querySelectorAll("[role=\"separator\"]")
      expect(separators.length).toBeGreaterThanOrEqual(2)
      const separator = separators[0]
      expect(separator?.className).toContain("-mx-1")
      expect(separator?.className).toContain("my-1")
      expect(separator?.className).toContain("h-px")
      expect(separator?.className).toContain("bg-muted")
    })

    it("merges custom class on DropdownMenuContent", async () => {
      mount(DropdownMenu, {
        props: { defaultOpen: true },
        slots: {
          default: () => [
            h(DropdownMenuTrigger, { asChild: true }, {
              default: () => h("button", "T"),
            }),
            h(DropdownMenuContent, { class: "my-menu" }, {
              default: () => h(DropdownMenuItem, null, { default: () => "Item" }),
            }),
          ],
        },
        attachTo: document.body,
      })
      await nextTick()
      const content = document.body.querySelector("[role=\"menu\"]")
      expect(content?.className).toContain("my-menu")
    })
  })

  describe("item click closes menu", () => {
    it("closes menu when item is clicked (v-model open syncs)", async () => {
      const Harness = defineComponent({
        components: {
          DropdownMenu,
          DropdownMenuTrigger,
          DropdownMenuContent,
          DropdownMenuItem,
        },
        setup() {
          const open = ref(true)
          return { open }
        },
        template: `
          <DropdownMenu v-model:open="open">
            <DropdownMenuTrigger as-child><button>T</button></DropdownMenuTrigger>
            <DropdownMenuContent>
              <DropdownMenuItem class="click-item">Click me</DropdownMenuItem>
            </DropdownMenuContent>
          </DropdownMenu>
          <span class="state">{{ open ? 'OPEN' : 'CLOSED' }}</span>
        `,
      })
      const wrapper = mount(Harness, { attachTo: document.body })
      await nextTick()
      const item = document.body.querySelector(".click-item") as HTMLElement | null
      expect(item).toBeTruthy()
      item?.click()
      await nextTick()
      await nextTick()
      expect(wrapper.find(".state").text()).toBe("CLOSED")
    })
  })

  describe("checkbox item", () => {
    it("renders DropdownMenuCheckboxItem with checked state", async () => {
      mount(DropdownMenu, {
        props: { defaultOpen: true },
        slots: {
          default: () => [
            h(DropdownMenuTrigger, { asChild: true }, {
              default: () => h("button", "T"),
            }),
            h(DropdownMenuContent, null, {
              default: () =>
                h(DropdownMenuCheckboxItem, { modelValue: true }, {
                  default: () => "Checked item",
                }),
            }),
          ],
        },
        attachTo: document.body,
      })
      await nextTick()
      const cbItem = document.body.querySelector("[role=\"menuitemcheckbox\"]")
      expect(cbItem).toBeTruthy()
      expect(cbItem?.getAttribute("aria-checked")).toBe("true")
    })

    it("emits update:modelValue when checkbox item toggled", async () => {
      const Harness = defineComponent({
        components: {
          DropdownMenu,
          DropdownMenuTrigger,
          DropdownMenuContent,
          DropdownMenuCheckboxItem,
        },
        setup() {
          const open = ref(true)
          const checked = ref(false)
          return { open, checked }
        },
        template: `
          <DropdownMenu v-model:open="open">
            <DropdownMenuTrigger as-child><button>T</button></DropdownMenuTrigger>
            <DropdownMenuContent>
              <DropdownMenuCheckboxItem v-model="checked" class="cb">
                Notifications
              </DropdownMenuCheckboxItem>
            </DropdownMenuContent>
          </DropdownMenu>
          <span class="state">{{ checked ? 'ON' : 'OFF' }}</span>
        `,
      })
      const wrapper = mount(Harness, { attachTo: document.body })
      await nextTick()
      expect(wrapper.find(".state").text()).toBe("OFF")
      const cb = document.body.querySelector(".cb") as HTMLElement | null
      cb?.click()
      await nextTick()
      await nextTick()
      expect(wrapper.find(".state").text()).toBe("ON")
    })
  })

  describe("radio items", () => {
    it("renders radio group with selected value", async () => {
      mount(DropdownMenu, {
        props: { defaultOpen: true },
        slots: {
          default: () => [
            h(DropdownMenuTrigger, { asChild: true }, {
              default: () => h("button", "T"),
            }),
            h(DropdownMenuContent, null, {
              default: () =>
                h(DropdownMenuRadioGroup, { modelValue: "b" }, {
                  default: () => [
                    h(DropdownMenuRadioItem, { value: "a" }, { default: () => "A" }),
                    h(DropdownMenuRadioItem, { value: "b" }, { default: () => "B" }),
                  ],
                }),
            }),
          ],
        },
        attachTo: document.body,
      })
      await nextTick()
      const items = document.body.querySelectorAll("[role=\"menuitemradio\"]")
      expect(items.length).toBe(2)
      const checked = Array.from(items).find(
        el => el.getAttribute("aria-checked") === "true",
      )
      expect(checked?.textContent).toContain("B")
    })
  })

  describe("sub-menu", () => {
    it("opens sub-menu and renders sub-content", async () => {
      mount(DropdownMenu, {
        props: { defaultOpen: true },
        slots: {
          default: () => [
            h(DropdownMenuTrigger, { asChild: true }, {
              default: () => h("button", "T"),
            }),
            h(DropdownMenuContent, null, {
              default: () =>
                h(DropdownMenuSub, { defaultOpen: true }, {
                  default: () => [
                    h(DropdownMenuSubTrigger, null, {
                      default: () => "More tools",
                    }),
                    h(DropdownMenuSubContent, null, {
                      default: () =>
                        h(DropdownMenuItem, null, {
                          default: () => "Save page",
                        }),
                    }),
                  ],
                }),
            }),
          ],
        },
        attachTo: document.body,
      })
      await nextTick()
      expect(document.body.textContent).toContain("More tools")
      expect(document.body.textContent).toContain("Save page")
    })
  })

  describe("DropdownMenuGroup", () => {
    it("renders group element in open menu", async () => {
      mount(DropdownMenu, {
        props: { defaultOpen: true },
        slots: {
          default: () => [
            h(DropdownMenuTrigger, { asChild: true }, {
              default: () => h("button", "T"),
            }),
            h(DropdownMenuContent, null, {
              default: () =>
                h(DropdownMenuGroup, null, {
                  default: () => [
                    h(DropdownMenuItem, null, { default: () => "A" }),
                    h(DropdownMenuItem, null, { default: () => "B" }),
                  ],
                }),
            }),
          ],
        },
        attachTo: document.body,
      })
      await nextTick()
      const group = document.body.querySelector("[role=\"group\"]")
      expect(group).toBeTruthy()
      expect(group?.textContent).toContain("A")
      expect(group?.textContent).toContain("B")
    })
  })

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