import { describe, it, expect, vi } from "vitest"
import { flushPromises, mount } from "@vue/test-utils"
import AsyncSelectPicker from "./AsyncSelectPicker.vue"

describe("AsyncSelectPicker (props-based composition)", () => {
  describe("rendering", () => {
    it("mounts without error", () => {
      const wrapper = mount(AsyncSelectPicker, { props: { label: "User" } })
      expect(wrapper.exists()).toBe(true)
    })

    it("renders the label via FieldFrame", () => {
      const wrapper = mount(AsyncSelectPicker, { props: { label: "User" } })
      expect(wrapper.text()).toContain("User")
    })

    it("marks required via the FieldFrame label", () => {
      const wrapper = mount(AsyncSelectPicker, {
        props: { label: "User", required: true },
      })
      expect(wrapper.html()).toContain("after:content-['*']")
    })

    it("renders help text", () => {
      const wrapper = mount(AsyncSelectPicker, {
        props: { label: "User", helpText: "Searches the directory" },
      })
      expect(wrapper.text()).toContain("Searches the directory")
    })

    it("renders errors from props", () => {
      const wrapper = mount(AsyncSelectPicker, {
        props: { label: "User", errors: ["Required"] },
      })
      expect(wrapper.text()).toContain("Required")
    })

    it("hides the label when hideLabel is true", () => {
      const wrapper = mount(AsyncSelectPicker, {
        props: { label: "User", hideLabel: true },
      })
      expect(wrapper.find("[data-testid='field-frame-label']").exists()).toBe(
        false,
      )
    })

    it("works without any props at all", () => {
      const wrapper = mount(AsyncSelectPicker)
      expect(wrapper.exists()).toBe(true)
    })
  })

  describe("trigger display", () => {
    it("shows placeholder when no value is selected", () => {
      const wrapper = mount(AsyncSelectPicker, {
        props: { label: "X", placeholder: "Pick one" },
      })
      expect(wrapper.find("[data-testid='async-picker-anchor']").text()).toContain(
        "Pick one",
      )
    })

    it("shows selected label when modelValue matches an initialOption", () => {
      const wrapper = mount(AsyncSelectPicker, {
        props: {
          label: "X",
          modelValue: "u1",
          initialOptions: [{ label: "Alice", value: "u1" }],
        },
      })
      expect(wrapper.find("[data-testid='async-picker-label']").text()).toBe(
        "Alice",
      )
    })
  })

  describe("onSearch callback", () => {
    // The picker's search input lives inside the portalled Combobox panel
    // which jsdom doesn't reliably open via a button click. We assert
    // against the publicly-exposed `trigger` method instead — same code
    // path the @input handler invokes.

    it("calls onSearch after debounce when trigger is invoked", async () => {
      vi.useFakeTimers()
      const onSearch = vi
        .fn()
        .mockResolvedValue([{ label: "Alice", value: "u1" }])
      const wrapper = mount(AsyncSelectPicker, {
        props: { label: "X", onSearch, debounceMs: 200 },
      })
      const vm = wrapper.vm as unknown as { trigger: (q: string) => void }
      vm.trigger("a")
      expect(onSearch).not.toHaveBeenCalled()
      vi.advanceTimersByTime(220)
      await flushPromises()
      expect(onSearch).toHaveBeenCalledWith("a")
      vi.useRealTimers()
    })

    it("respects minSearchLength threshold", async () => {
      vi.useFakeTimers()
      const onSearch = vi.fn().mockResolvedValue([])
      const wrapper = mount(AsyncSelectPicker, {
        props: { label: "X", onSearch, minSearchLength: 3, debounceMs: 50 },
      })
      const vm = wrapper.vm as unknown as { trigger: (q: string) => void }
      vm.trigger("ab")
      vi.advanceTimersByTime(100)
      await flushPromises()
      expect(onSearch).not.toHaveBeenCalled()
      vi.useRealTimers()
    })
  })

  // Combobox content portals — open-state tests covered in Storybook.
})
