import { describe, it, expect } from "vitest"
import { mount } from "@vue/test-utils"
import { defineComponent, h, nextTick, ref } from "vue"
import {
  Command,
  CommandDialog,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandSeparator,
  CommandShortcut,
} from "."

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

    it("has correct base classes", () => {
      const wrapper = mount(Command, { slots: { default: "content" } })
      const el = wrapper.element
      expect(el.className).toContain("flex")
      expect(el.className).toContain("h-full")
      expect(el.className).toContain("w-full")
      expect(el.className).toContain("flex-col")
      expect(el.className).toContain("overflow-hidden")
      expect(el.className).toContain("rounded-md")
      expect(el.className).toContain("bg-popover")
      expect(el.className).toContain("text-popover-foreground")
    })

    it("merges custom class", () => {
      const wrapper = mount(Command, {
        props: { class: "custom-command" },
        slots: { default: "content" },
      })
      expect(wrapper.element.className).toContain("custom-command")
      expect(wrapper.element.className).toContain("flex")
    })
  })

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

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

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

  describe("composition", () => {
    it("renders a basic command structure with input, list, group, and items", () => {
      const wrapper = mount(Command, {
        slots: {
          default: () => [
            h(CommandInput, { placeholder: "Type a command..." }),
            h(CommandList, null, {
              default: () => [
                h(CommandEmpty, null, { default: () => "No results found." }),
                h(CommandGroup, { heading: "Suggestions" }, {
                  default: () => [
                    h(CommandItem, { value: "calendar" }, { default: () => "Calendar" }),
                    h(CommandItem, { value: "search" }, { default: () => "Search" }),
                  ],
                }),
                h(CommandSeparator),
                h(CommandGroup, { heading: "Settings" }, {
                  default: () => [
                    h(CommandItem, { value: "profile" }, { default: () => "Profile" }),
                    h(CommandItem, { value: "settings" }, { default: () => "Settings" }),
                  ],
                }),
              ],
            }),
          ],
        },
      })

      expect(wrapper.exists()).toBe(true)
      expect(wrapper.find("[cmdk-input-wrapper]").exists()).toBe(true)
      expect(wrapper.text()).toContain("Suggestions")
      expect(wrapper.text()).toContain("Settings")
      expect(wrapper.text()).toContain("Calendar")
      expect(wrapper.text()).toContain("Search")
      expect(wrapper.text()).toContain("Profile")
    })

    it("renders CommandInput with search icon and placeholder", () => {
      const wrapper = mount(Command, {
        slots: {
          default: () => [
            h(CommandInput, { placeholder: "Search..." }),
          ],
        },
      })
      const wrapperEl = wrapper.find("[cmdk-input-wrapper]")
      expect(wrapperEl.exists()).toBe(true)
      const input = wrapper.find("input")
      expect(input.exists()).toBe(true)
      expect(input.attributes("placeholder")).toBe("Search...")
    })

    it("CommandSeparator renders separator role", () => {
      const wrapper = mount(Command, {
        slots: {
          default: () => [
            h(CommandSeparator),
          ],
        },
      })
      expect(wrapper.find("[role=\"separator\"]").exists()).toBe(true)
    })
  })

  describe("filtering / search", () => {
    it("filters items based on search input", async () => {
      const Harness = defineComponent({
        components: { Command, CommandInput, CommandList, CommandItem, CommandGroup },
        template: `
          <Command>
            <CommandInput ref="inputRef" placeholder="Search..." />
            <CommandList>
              <CommandGroup heading="Items">
                <CommandItem value="calendar">Calendar</CommandItem>
                <CommandItem value="search">Search</CommandItem>
                <CommandItem value="settings">Settings</CommandItem>
              </CommandGroup>
            </CommandList>
          </Command>
        `,
      })
      const wrapper = mount(Harness, { attachTo: document.body })
      await nextTick()
      // After initial mount, items registered.
      const input = wrapper.find("input")
      await input.setValue("cal")
      await nextTick()
      await nextTick()
      const allText = wrapper.text()
      expect(allText).toContain("Calendar")
      expect(allText).not.toContain("Settings")
    })

    it("renders CommandEmpty when search has no results", async () => {
      const Harness = defineComponent({
        components: { Command, CommandInput, CommandList, CommandItem, CommandEmpty },
        template: `
          <Command>
            <CommandInput placeholder="Search..." />
            <CommandList>
              <CommandEmpty>No results.</CommandEmpty>
              <CommandItem value="alpha">Alpha</CommandItem>
              <CommandItem value="beta">Beta</CommandItem>
            </CommandList>
          </Command>
        `,
      })
      const wrapper = mount(Harness, { attachTo: document.body })
      await nextTick()
      const input = wrapper.find("input")
      await input.setValue("zzz-not-here")
      await nextTick()
      await nextTick()
      expect(wrapper.text()).toContain("No results.")
    })

    it("does NOT render CommandEmpty when search is empty", async () => {
      const wrapper = mount(Command, {
        slots: {
          default: () => [
            h(CommandInput, { placeholder: "Search..." }),
            h(CommandList, null, {
              default: () => [
                h(CommandEmpty, null, { default: () => "No results." }),
                h(CommandItem, { value: "a" }, { default: () => "A" }),
              ],
            }),
          ],
        },
      })
      await nextTick()
      expect(wrapper.text()).not.toContain("No results.")
    })
  })

  describe("item selection / keyboard nav", () => {
    it("emits select when CommandItem is selected", async () => {
      const Harness = defineComponent({
        components: { Command, CommandList, CommandItem },
        setup() {
          const selected = ref<string | null>(null)
          function handleSelect(value: string): void {
            selected.value = value
          }
          return { selected, handleSelect }
        },
        template: `
          <Command>
            <CommandList>
              <CommandItem value="alpha" class="alpha" @select="handleSelect('alpha')">Alpha</CommandItem>
              <CommandItem value="beta" class="beta" @select="handleSelect('beta')">Beta</CommandItem>
            </CommandList>
          </Command>
          <span class="state">{{ selected }}</span>
        `,
      })
      const wrapper = mount(Harness, { attachTo: document.body })
      await nextTick()
      const alpha = wrapper.find(".alpha").element as HTMLElement
      alpha.click()
      await nextTick()
      await nextTick()
      expect(wrapper.find(".state").text()).toBe("alpha")
    })

    it("clears search input when an item is selected", async () => {
      const Harness = defineComponent({
        components: { Command, CommandInput, CommandList, CommandItem },
        template: `
          <Command>
            <CommandInput placeholder="Search..." />
            <CommandList>
              <CommandItem value="alpha" class="alpha">Alpha</CommandItem>
            </CommandList>
          </Command>
        `,
      })
      const wrapper = mount(Harness, { attachTo: document.body })
      await nextTick()
      const input = wrapper.find("input")
      await input.setValue("alp")
      await nextTick()
      const alpha = wrapper.find(".alpha").element as HTMLElement
      alpha.click()
      await nextTick()
      await nextTick()
      expect((input.element as HTMLInputElement).value).toBe("")
    })
  })

  describe("CommandDialog", () => {
    it("renders content when defaultOpen", async () => {
      mount(CommandDialog, {
        props: { defaultOpen: true },
        slots: {
          default: () => [
            h(CommandInput, { placeholder: "Type to search..." }),
            h(CommandList, null, {
              default: () =>
                h(CommandItem, { value: "x" }, { default: () => "Open something" }),
            }),
          ],
        },
        attachTo: document.body,
      })
      await nextTick()
      expect(document.body.textContent).toContain("Open something")
    })

    it("does not render content when closed", async () => {
      mount(CommandDialog, {
        slots: {
          default: () =>
            h(CommandList, null, {
              default: () =>
                h(CommandItem, { value: "x" }, { default: () => "HiddenCmd" }),
            }),
        },
        attachTo: document.body,
      })
      await nextTick()
      expect(document.body.textContent).not.toContain("HiddenCmd")
    })
  })
})
