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

/**
 * Leaflet calls DOM APIs that jsdom doesn't implement (getBoundingClientRect,
 * offsetWidth, etc.) and its default icon fix mutates module-level state that
 * crashes in non-browser environments. We replace the `leaflet` module with a
 * minimal mock so the MapPicker's chrome and interaction logic can be tested
 * without booting Leaflet. The full map experience is covered in Storybook.
 */

vi.mock("leaflet/dist/leaflet.css", () => ({}))
vi.mock("leaflet/dist/images/marker-icon.png", () => ({ default: "marker-icon.png" }))
vi.mock("leaflet/dist/images/marker-icon-2x.png", () => ({ default: "marker-icon-2x.png" }))
vi.mock("leaflet/dist/images/marker-shadow.png", () => ({ default: "marker-shadow.png" }))

// Capture registered click handlers so tests can invoke them directly.
const clickHandlers: Array<(e: unknown) => void> = []

const mockMarker = {
  addTo: vi.fn(),
  remove: vi.fn(),
  setLatLng: vi.fn(),
}
mockMarker.addTo.mockReturnValue(mockMarker)

const mockTileLayer = { addTo: vi.fn() }
mockTileLayer.addTo.mockReturnValue(mockTileLayer)

const mockMap = {
  on: vi.fn((event: string, handler: (e: unknown) => void) => {
    if (event === "click") clickHandlers.push(handler)
    return mockMap
  }),
  remove: vi.fn(),
}

const mockL = {
  map: vi.fn().mockReturnValue(mockMap),
  tileLayer: vi.fn().mockReturnValue(mockTileLayer),
  marker: vi.fn().mockReturnValue(mockMarker),
  Icon: { Default: { prototype: {}, mergeOptions: vi.fn() } },
}

vi.mock("leaflet", () => ({ default: mockL }))

// Import the component AFTER the mocks are registered.
const { default: MapPicker } = await import("./MapPicker.vue")

const baseMount = { attachTo: document.body }

describe("MapPicker (direct leaflet, no wrapper)", () => {
  beforeEach(() => {
    clickHandlers.length = 0
    vi.clearAllMocks()
    // Restore return values that vi.clearAllMocks() wiped.
    mockMap.on.mockImplementation((event: string, handler: (e: unknown) => void) => {
      if (event === "click") clickHandlers.push(handler)
      return mockMap
    })
    mockMarker.addTo.mockReturnValue(mockMarker)
    mockTileLayer.addTo.mockReturnValue(mockTileLayer)
    mockL.map.mockReturnValue(mockMap)
    mockL.tileLayer.mockReturnValue(mockTileLayer)
    mockL.marker.mockReturnValue(mockMarker)
  })

  describe("rendering", () => {
    it("mounts without crashing under jsdom", async () => {
      const wrapper = mount(MapPicker, {
        ...baseMount,
        props: { label: "Location" },
      })
      await flushPromises()
      expect(wrapper.exists()).toBe(true)
      expect(
        wrapper.find("[data-testid='map-picker-container']").exists(),
      ).toBe(true)
      expect(wrapper.find("[data-testid='map-picker-map']").exists()).toBe(true)
      wrapper.unmount()
    })

    it("renders the FieldFrame label", () => {
      const wrapper = mount(MapPicker, {
        ...baseMount,
        props: { label: "Location" },
      })
      expect(wrapper.text()).toContain("Location")
      wrapper.unmount()
    })

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

    it("renders help text", () => {
      const wrapper = mount(MapPicker, {
        ...baseMount,
        props: { label: "Location", helpText: "Click to drop a pin" },
      })
      expect(wrapper.text()).toContain("Click to drop a pin")
      wrapper.unmount()
    })

    it("renders errors from props and flips the border to destructive", () => {
      const wrapper = mount(MapPicker, {
        ...baseMount,
        props: { label: "Location", errors: ["Required"] },
      })
      expect(wrapper.text()).toContain("Required")
      expect(
        wrapper
          .find("[data-testid='map-picker-container']")
          .classes()
          .join(" "),
      ).toContain("border-destructive")
      wrapper.unmount()
    })

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

    it("works without any props at all", () => {
      const wrapper = mount(MapPicker, baseMount)
      expect(
        wrapper.find("[data-testid='map-picker-container']").exists(),
      ).toBe(true)
      wrapper.unmount()
    })
  })

  describe("modelValue", () => {
    it("renders the formatted lat/lng readout when modelValue is set", () => {
      const wrapper = mount(MapPicker, {
        ...baseMount,
        props: {
          label: "Location",
          modelValue: { lat: 40.7128, lng: -74.006 },
        },
      })
      expect(wrapper.find("[data-testid='map-picker-latlng']").text()).toBe(
        "40.7128, -74.0060",
      )
      wrapper.unmount()
    })

    it("does not render the readout when modelValue is null", () => {
      const wrapper = mount(MapPicker, {
        ...baseMount,
        props: { label: "Location", modelValue: null },
      })
      expect(
        wrapper.find("[data-testid='map-picker-latlng']").exists(),
      ).toBe(false)
      wrapper.unmount()
    })

    it("places a marker when modelValue is set on mount", async () => {
      mount(MapPicker, {
        ...baseMount,
        props: {
          label: "Location",
          modelValue: { lat: 40.7128, lng: -74.006 },
        },
      })
      await flushPromises()
      expect(mockL.marker).toHaveBeenCalledWith([40.7128, -74.006])
    })

    it("does not place a marker when modelValue is null", async () => {
      mount(MapPicker, {
        ...baseMount,
        props: { label: "Location" },
      })
      await flushPromises()
      expect(mockL.marker).not.toHaveBeenCalled()
    })

    it("emits update:modelValue when the map click handler fires", async () => {
      const wrapper = mount(MapPicker, {
        ...baseMount,
        props: { label: "Location" },
      })
      await flushPromises()

      const handler = clickHandlers[clickHandlers.length - 1]
      handler?.({ latlng: { lat: 12.34, lng: 56.78 } })
      await wrapper.vm.$nextTick()

      const emitted = wrapper.emitted("update:modelValue")
      expect(emitted).toBeTruthy()
      expect(emitted![emitted!.length - 1]).toEqual([
        { lat: 12.34, lng: 56.78 },
      ])
      wrapper.unmount()
    })

    it("does not emit update:modelValue when readonly", async () => {
      const wrapper = mount(MapPicker, {
        ...baseMount,
        props: { label: "Location", readonly: true },
      })
      await flushPromises()

      const handler = clickHandlers[clickHandlers.length - 1]
      handler?.({ latlng: { lat: 1, lng: 2 } })
      await wrapper.vm.$nextTick()

      expect(wrapper.emitted("update:modelValue")).toBeUndefined()
      wrapper.unmount()
    })

    it("does not emit update:modelValue when disabled", async () => {
      const wrapper = mount(MapPicker, {
        ...baseMount,
        props: { label: "Location", disabled: true },
      })
      await flushPromises()

      const handler = clickHandlers[clickHandlers.length - 1]
      handler?.({ latlng: { lat: 1, lng: 2 } })
      await wrapper.vm.$nextTick()

      expect(wrapper.emitted("update:modelValue")).toBeUndefined()
      wrapper.unmount()
    })
  })

  describe("state matrix", () => {
    it("marks the container as disabled via data attribute", () => {
      const wrapper = mount(MapPicker, {
        ...baseMount,
        props: { label: "Location", disabled: true },
      })
      const container = wrapper.find("[data-testid='map-picker-container']")
      expect(container.attributes("data-disabled")).toBe("true")
      wrapper.unmount()
    })

    it("marks the container as readonly via data attribute", () => {
      const wrapper = mount(MapPicker, {
        ...baseMount,
        props: { label: "Location", readonly: true },
      })
      const container = wrapper.find("[data-testid='map-picker-container']")
      expect(container.attributes("data-readonly")).toBe("true")
      wrapper.unmount()
    })

    it("applies the height prop as inline style on the map container", () => {
      const wrapper = mount(MapPicker, {
        ...baseMount,
        props: { label: "Location", height: "30rem" },
      })
      const map = wrapper.find("[data-testid='map-picker-map']")
      expect(map.attributes("style")).toContain("height: 30rem")
      wrapper.unmount()
    })
  })
})
