import { describe, it, expect } from "vitest"
import { mount } from "@vue/test-utils"
import { h, nextTick } from "vue"
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "."

function mountAccordion(props: Record<string, unknown> = {}) {
  return mount(Accordion, {
    props: { type: "single", collapsible: true, defaultValue: "item-1", ...props },
    slots: {
      default: () => [
        h(AccordionItem, { value: "item-1" }, {
          default: () => [
            h(AccordionTrigger, null, { default: () => "Section 1" }),
            h(AccordionContent, null, { default: () => "Content 1" }),
          ],
        }),
        h(AccordionItem, { value: "item-2" }, {
          default: () => [
            h(AccordionTrigger, null, { default: () => "Section 2" }),
            h(AccordionContent, null, { default: () => "Content 2" }),
          ],
        }),
        h(AccordionItem, { value: "item-3", disabled: true }, {
          default: () => [
            h(AccordionTrigger, null, { default: () => "Section 3" }),
            h(AccordionContent, null, { default: () => "Content 3" }),
          ],
        }),
      ],
    },
  })
}

describe("Accordion", () => {
  it("renders without errors", () => {
    const wrapper = mountAccordion()
    expect(wrapper.exists()).toBe(true)
  })

  it("renders three accordion items", () => {
    const wrapper = mountAccordion()
    const triggers = wrapper.findAll("button[data-state]")
    expect(triggers).toHaveLength(3)
  })

  it("renders trigger text", () => {
    const wrapper = mountAccordion()
    expect(wrapper.text()).toContain("Section 1")
    expect(wrapper.text()).toContain("Section 2")
    expect(wrapper.text()).toContain("Section 3")
  })

  it("default open item content is visible", () => {
    const wrapper = mountAccordion()
    expect(wrapper.text()).toContain("Content 1")
  })

  describe("AccordionItem", () => {
    it("has border-b class", () => {
      const wrapper = mountAccordion()
      const item = wrapper.find("[data-state]")
      expect(item.classes()).toContain("border-b")
    })

    it("accepts custom class", () => {
      const wrapper = mount(Accordion, {
        props: { type: "single", collapsible: true, defaultValue: "item-a" },
        slots: {
          default: () => h(AccordionItem, { value: "item-a", class: "my-item" }, {
            default: () => [
              h(AccordionTrigger, null, { default: () => "A" }),
              h(AccordionContent, null, { default: () => "AC" }),
            ],
          }),
        },
      })
      const item = wrapper.find("[data-state]")
      expect(item.classes()).toContain("my-item")
      expect(item.classes()).toContain("border-b")
    })
  })

  describe("AccordionTrigger", () => {
    it("has base classes", () => {
      const wrapper = mountAccordion()
      const trigger = wrapper.find("button[data-state]")
      const classes = trigger.classes()
      for (const cls of [
        "flex",
        "flex-1",
        "items-center",
        "justify-between",
        "py-4",
        "font-medium",
        "transition-all",
        "hover:underline",
      ]) {
        expect(classes).toContain(cls)
      }
    })

    it("renders chevron icon", () => {
      const wrapper = mountAccordion()
      const svg = wrapper.find("button[data-state] svg")
      expect(svg.exists()).toBe(true)
    })

    it("disabled item has disabled trigger", () => {
      const wrapper = mountAccordion()
      const triggers = wrapper.findAll("button[data-state]")
      const disabled = triggers.find((t) => t.text().includes("Section 3"))
      expect(disabled?.attributes("disabled")).toBeDefined()
    })
  })

  describe("AccordionContent", () => {
    it("default open item has region with overflow-hidden", () => {
      const wrapper = mountAccordion()
      const region = wrapper.find('[role="region"]')
      expect(region.exists()).toBe(true)
      expect(region.classes()).toContain("overflow-hidden")
      expect(region.classes()).toContain("text-sm")
    })

    it("renders inner div with padding classes", () => {
      const wrapper = mountAccordion()
      const region = wrapper.find('[role="region"]')
      const inner = region.find("div")
      expect(inner.exists()).toBe(true)
      expect(inner.classes()).toContain("pb-4")
      expect(inner.classes()).toContain("pt-0")
    })
  })

  describe("type=single", () => {
    it("opens and closes a single item", async () => {
      const wrapper = mountAccordion()
      const triggers = wrapper.findAll("button[data-state]")
      const second = triggers.find((t) => t.text().includes("Section 2"))!
      await second.trigger("click")
      await nextTick()
      // After clicking the second section, it should be open.
      expect(second.attributes("data-state")).toBe("open")
    })
  })

  describe("type=multiple", () => {
    it("allows multiple items open at once", async () => {
      const wrapper = mount(Accordion, {
        props: { type: "multiple", defaultValue: ["item-1"] },
        slots: {
          default: () => [
            h(AccordionItem, { value: "item-1" }, {
              default: () => [
                h(AccordionTrigger, null, { default: () => "A" }),
                h(AccordionContent, null, { default: () => "AC" }),
              ],
            }),
            h(AccordionItem, { value: "item-2" }, {
              default: () => [
                h(AccordionTrigger, null, { default: () => "B" }),
                h(AccordionContent, null, { default: () => "BC" }),
              ],
            }),
          ],
        },
      })
      const triggers = wrapper.findAll("button[data-state]")
      const second = triggers.find((t) => t.text().includes("B"))!
      await second.trigger("click")
      await nextTick()
      expect(triggers[0].attributes("data-state")).toBe("open")
      expect(second.attributes("data-state")).toBe("open")
    })
  })

  describe("v-model", () => {
    it("emits update:modelValue when an item is toggled", async () => {
      const wrapper = mount(Accordion, {
        props: { type: "single", collapsible: true, modelValue: "item-1" },
        slots: {
          default: () => [
            h(AccordionItem, { value: "item-1" }, {
              default: () => [
                h(AccordionTrigger, null, { default: () => "A" }),
                h(AccordionContent, null, { default: () => "AC" }),
              ],
            }),
            h(AccordionItem, { value: "item-2" }, {
              default: () => [
                h(AccordionTrigger, null, { default: () => "B" }),
                h(AccordionContent, null, { default: () => "BC" }),
              ],
            }),
          ],
        },
      })
      const triggers = wrapper.findAll("button[data-state]")
      const second = triggers.find((t) => t.text().includes("B"))!
      await second.trigger("click")
      await nextTick()
      expect(wrapper.emitted("update:modelValue")).toBeTruthy()
    })
  })

  describe("custom class on Accordion root", () => {
    it("accepts custom class", () => {
      const wrapper = mount(Accordion, {
        props: { type: "single", collapsible: true, class: "my-accordion" },
        slots: {
          default: () => h(AccordionItem, { value: "x" }, {
            default: () => [
              h(AccordionTrigger, null, { default: () => "X" }),
              h(AccordionContent, null, { default: () => "XC" }),
            ],
          }),
        },
      })
      expect(wrapper.classes()).toContain("my-accordion")
    })
  })
})
