import { beforeEach, describe, expect, it } from "vitest"
import {
  __resetIdCounter,
  v1ElementToV2,
  v1ElementsToV2,
  v2ElementToV1,
  v2ElementsToV1,
  v1CardToV2,
  v1CardsToV2,
  v2CardToV1,
  v2CardsToV1,
  v1TabToV2,
  v1TabsToV2,
  v2TabToV1,
  v2TabsToV1,
} from "@/adapters"
import type { V1CardConfig, V1FieldConfig, V1TabConfig } from "@/adapters"

beforeEach(() => {
  __resetIdCounter()
})

describe("element fragment converters", () => {
  it("v1ElementToV2 converts a single field to a dot-notation element", () => {
    const el = v1ElementToV2({ type: "text", name: "first", label: "First" })
    expect(el.type).toBe("input.text")
    expect(el.name).toBe("first")
    expect(el.label).toBe("First")
  })

  it("v1ElementsToV2 converts a list", () => {
    const els = v1ElementsToV2([
      { type: "text", name: "a", label: "A" },
      { type: "select", name: "b", label: "B", options: [{ value: "x", text: "X" }] },
    ])
    expect(els.map((e) => e.type)).toEqual(["input.text", "option-select.select"])
  })

  it("round-trips a single element v1 → v2 → v1", () => {
    const v1: V1FieldConfig = {
      type: "text",
      name: "email",
      label: "Email",
      required: true,
    }
    const back = v2ElementToV1(v1ElementToV2(v1))
    expect(back.type).toBe("text")
    expect(back.name).toBe("email")
    expect(back.required).toBe(true)
  })

  it("v2ElementToV1 leaves a non-array selectOptions untouched (no .map crash)", () => {
    // Some elements carry an object/boolean `selectOptions` rather than a list.
    const el = {
      id: "e1",
      type: "input.text",
      name: "x",
      label: "X",
      selectOptions: { async: true, route: "/opts" },
    } as unknown as Parameters<typeof v2ElementToV1>[0]
    const back = v2ElementToV1(el) as unknown as Record<string, unknown>
    expect(back.options).toBeUndefined()
    // The non-array value is preserved as-is for the v1 consumer.
    expect(back.selectOptions).toEqual({ async: true, route: "/opts" })
  })

  it("v2ElementsToV1 converts a list back", () => {
    const els = v1ElementsToV2([
      { type: "text", name: "a", label: "A" },
      { type: "textarea", name: "b", label: "B" },
    ])
    const back = v2ElementsToV1(els)
    expect(back.map((f) => f.type)).toEqual(["text", "textarea"])
  })
})

describe("card fragment converters", () => {
  const cardV1: V1CardConfig = {
    type: "fields_list",
    title: "Profile",
    fieldsList: [
      { type: "text", name: "first", label: "First" },
      { type: "email", name: "email", label: "Email" },
    ],
  }

  it("v1CardToV2 returns a self-contained bundle", () => {
    const bundle = v1CardToV2(cardV1)
    expect(bundle.ids).toHaveLength(1)
    const card = bundle.cards[bundle.ids[0]!]!
    expect(card.cardType).toBe("element-list")
    const elementList = (card as unknown as Record<string, unknown>)
      .elementList as string[]
    expect(elementList).toHaveLength(2)
    // Elements are resolvable from the bundle's element map.
    expect(bundle.elements[elementList[0]!]!.type).toBe("input.text")
  })

  it("v1CardsToV2 converts a list of cards", () => {
    const bundle = v1CardsToV2([cardV1, { type: "fields_list", title: "More" }])
    expect(bundle.ids).toHaveLength(2)
  })

  it("v1CardsToV2 accepts a single card (not wrapped in an array)", () => {
    const bundle = v1CardsToV2(cardV1)
    expect(bundle.ids).toHaveLength(1)
    expect(bundle.cards[bundle.ids[0]!]!.cardType).toBe("element-list")
  })

  it("v1CardsToV2 tolerates null/undefined", () => {
    expect(
      v1CardsToV2(undefined as unknown as V1CardConfig).ids,
    ).toHaveLength(0)
  })

  it("round-trips a card v1 → v2 → v1", () => {
    const bundle = v1CardToV2(cardV1)
    const back = v2CardToV1(bundle.ids[0]!, bundle.cards, bundle.elements)!
    expect(back.type).toBe("fields_list")
    expect(back.title).toBe("Profile")
    expect(back.fieldsList).toHaveLength(2)
    expect(back.fieldsList?.[1]?.type).toBe("email")
  })

  it("v2CardsToV1 converts the bundle ids back to v1 cards", () => {
    const bundle = v1CardsToV2([cardV1])
    const back = v2CardsToV1(bundle.ids, bundle.cards, bundle.elements)
    expect(back).toHaveLength(1)
    expect(back[0]!.type).toBe("fields_list")
  })

  it("v2CardsToV1 also accepts full card objects (like v1CardsToV2)", () => {
    const bundle = v1CardsToV2([cardV1])
    const cardObjects = bundle.ids.map((id) => bundle.cards[id]!)
    const back = v2CardsToV1(cardObjects, bundle.cards, bundle.elements)
    expect(back).toHaveLength(1)
    expect(back[0]!.type).toBe("fields_list")
    expect(back[0]!.fieldsList).toHaveLength(2)
  })

  it("v2CardsToV1 accepts a single card/id (not wrapped in an array)", () => {
    const bundle = v1CardToV2(cardV1)
    const cardObject = bundle.cards[bundle.ids[0]!]!
    // Single object
    const fromObject = v2CardsToV1(cardObject, bundle.cards, bundle.elements)
    expect(fromObject).toHaveLength(1)
    expect(fromObject[0]!.type).toBe("fields_list")
    // Single id string
    const fromId = v2CardsToV1(bundle.ids[0]!, bundle.cards, bundle.elements)
    expect(fromId).toHaveLength(1)
    expect(fromId[0]!.type).toBe("fields_list")
  })

  it("v2CardToV1 accepts a full card object", () => {
    const bundle = v1CardToV2(cardV1)
    const cardObject = bundle.cards[bundle.ids[0]!]!
    const back = v2CardToV1(cardObject, bundle.cards, bundle.elements)!
    expect(back.type).toBe("fields_list")
    expect(back.title).toBe("Profile")
  })

  it("v2CardToV1 returns undefined for an unknown id", () => {
    expect(v2CardToV1("missing", {}, {})).toBeUndefined()
  })

  it("regenerates ids when an element collides with its parent card id", () => {
    // Pathological v1: a field shares the card's id.
    const bundle = v1CardsToV2([
      {
        id: "dup",
        type: "fields_list",
        fieldsList: [
          { id: "dup", type: "text", name: "a", label: "A" },
          { id: "dup", type: "text", name: "b", label: "B" },
        ],
      },
    ])
    const allIds = [
      ...Object.keys(bundle.cards),
      ...Object.keys(bundle.elements),
    ]
    // No id appears twice across cards + elements.
    expect(new Set(allIds).size).toBe(allIds.length)
    // The card kept "dup"; the two colliding fields were reassigned.
    expect(Object.keys(bundle.cards)).toEqual(["dup"])
    expect(Object.keys(bundle.elements)).toHaveLength(2)
  })
})

describe("tab fragment converters", () => {
  const tabsV1: V1TabConfig[] = [
    {
      title: "Tab A",
      tabId: "a",
      cards: [
        {
          type: "fields_list",
          fieldsList: [{ type: "text", name: "x", label: "X" }],
        },
      ],
    },
    {
      title: "Tab B",
      tabId: "b",
      cards: [{ type: "fields_list", fieldsList: [] }],
    },
  ]

  function rootBag(bundle: {
    rootCardId: string
    cards: Record<string, unknown>
  }): Record<string, unknown> {
    return bundle.cards[bundle.rootCardId] as Record<string, unknown>
  }

  it("v1TabsToV2 returns a tab-list root builder schema", () => {
    const bundle = v1TabsToV2(tabsV1)
    const root = rootBag(bundle)
    expect(root.cardType).toBe("tab-list")
    const tabList = root.tabList as string[]
    expect(tabList).toEqual(["a", "b"])
    // A card_list tab whose single body card is a fields_list collapses
    // back into an element-list tab card.
    expect(
      (bundle.cards["a"] as unknown as Record<string, unknown>).cardType,
    ).toBe("element-list")
  })

  it("v1TabToV2 produces a root with a single tab", () => {
    const bundle = v1TabToV2(tabsV1[0]!)
    expect((rootBag(bundle).tabList as string[])).toHaveLength(1)
  })

  it("v1TabsToV2 handles tabs that carry a fieldsList directly", () => {
    const bundle = v1TabsToV2([
      {
        id: "tab-card-1",
        title: "Tab 1",
        fieldsList: [{ type: "checkbox", label: "Checkbox" }],
      } as unknown as V1TabConfig,
    ])
    // The tab itself becomes an element-list card referenced by the root.
    const root = rootBag(bundle)
    expect((root.tabList as string[])[0]).toBe("tab-card-1")
    const tabCard = bundle.cards["tab-card-1"] as unknown as Record<
      string,
      unknown
    >
    expect(tabCard.cardType).toBe("element-list")
    const elementList = tabCard.elementList as string[]
    expect(elementList).toHaveLength(1)
    expect(bundle.elements[elementList[0]!]!.type).toBe("boolean.checkbox")
  })

  it("round-trips tabs v1 → v2 → v1", () => {
    const bundle = v1TabsToV2(tabsV1)
    const tabList = rootBag(bundle).tabList as string[]
    const back = v2TabsToV1(tabList, bundle.cards, bundle.elements)
    expect(back).toHaveLength(2)
    expect(back[0]!.title).toBe("Tab A")
    expect(back[0]!.tabId).toBe("a")
    // Tab A was a card-list tab → unwrapped to its nested fields_list card.
    expect(back[0]!.cards?.[0]?.type).toBe("fields_list")
    expect(back[0]!.cards?.[0]?.fieldsList?.[0]?.name).toBe("x")
  })

  it("v2TabToV1 converts a single tab by id", () => {
    const bundle = v1TabsToV2(tabsV1)
    const firstTabId = (rootBag(bundle).tabList as string[])[0]!
    const back = v2TabToV1(firstTabId, bundle.cards, bundle.elements)!
    expect(back.title).toBe("Tab A")
  })

  it("v2TabsToV1 expands a tab-list card (by id or object) into its tabs", () => {
    const cards = {
      "root-1": {
        id: "root-1",
        cardType: "tab-list",
        title: "",
        tabList: ["t1", "t2"],
      },
      t1: {
        id: "t1",
        cardType: "element-list",
        title: "Tab 1",
        elementList: ["el-1"],
      },
      t2: {
        id: "t2",
        cardType: "element-list",
        title: "Tab 2",
        elementList: [],
      },
    } as unknown as Record<string, import("@/definitions/cards").DisplayCard>
    const elements = {
      "el-1": { id: "el-1", type: "boolean.switch", label: "Switch" },
    } as unknown as Record<string, import("@/definitions/elements").CardElement>

    // Pass the root tab-list card id.
    const byId = v2TabsToV1("root-1", cards, elements)
    expect(byId.map((t) => t.tabId)).toEqual(["t1", "t2"])
    // Pass the root tab-list card object.
    const byObject = v2TabsToV1(cards["root-1"]!, cards, elements)
    expect(byObject.map((t) => t.title)).toEqual(["Tab 1", "Tab 2"])
    // Each expanded tab is a card_list holding a fields_list card.
    expect((byId[0]! as Record<string, unknown>).type).toBe("card_list")
    expect((byId[0]!.cards?.[0]?.fieldsList as unknown[]).length).toBe(1)
  })

  it("v2TabsToV1 still accepts page-level tab descriptors", () => {
    // Descriptor shape `{ title, tabId, cards: DisplayCard[] }`.
    const inner = v1CardToV2({
      type: "fields_list",
      fieldsList: [{ type: "text", name: "x", label: "X" }],
    })
    const descriptor = {
      title: "Desc",
      tabId: "d",
      cards: [inner.cards[inner.ids[0]!]!],
    }
    const back = v2TabsToV1([descriptor], inner.cards, inner.elements)
    expect(back[0]!.title).toBe("Desc")
    expect(back[0]!.cards?.[0]?.type).toBe("fields_list")
  })

  it("v2TabsToV1 accepts tab-list card ids whose tabs are element-list cards", () => {
    // Mirrors the FormBuilder rootTabsContainer schema: a tab-list root whose
    // tabList references element-list "tab" cards directly.
    const cards = {
      "root-1": {
        id: "root-1",
        cardType: "tab-list",
        title: "",
        tabList: ["tab-card-1", "tab-card-2"],
      },
      "tab-card-1": {
        id: "tab-card-1",
        cardType: "element-list",
        title: "Tab 1",
        elementList: ["el-1", "el-2"],
      },
      "tab-card-2": {
        id: "tab-card-2",
        cardType: "element-list",
        title: "Tab 2",
        elementList: [],
      },
    } as unknown as Record<string, import("@/definitions/cards").DisplayCard>
    const elements = {
      "el-1": { id: "el-1", type: "boolean.checkbox", label: "Checkbox" },
      "el-2": { id: "el-2", type: "boolean.switch", label: "Switch" },
    } as unknown as Record<string, import("@/definitions/elements").CardElement>

    const tabList = (cards["root-1"] as unknown as Record<string, unknown>)
      .tabList as string[]
    const back = v2TabsToV1(tabList, cards, elements)
    expect(back).toHaveLength(2)
    expect(back[0]!.title).toBe("Tab 1")
    expect(back[0]!.tabId).toBe("tab-card-1")
    // Backward compat: every tab is a card_list holding a fields_list card.
    expect((back[0]! as Record<string, unknown>).type).toBe("card_list")
    const fieldsList = back[0]!.cards?.[0]?.fieldsList as Array<
      Record<string, unknown>
    >
    expect(back[0]!.cards?.[0]?.type).toBe("fields_list")
    expect(fieldsList).toHaveLength(2)
    expect(fieldsList[0]!.type).toBe("checkbox")
    expect(fieldsList[1]!.type).toBe("switch")
    // Empty element-list tab → card_list with an (empty) fields_list card.
    expect((back[1]! as Record<string, unknown>).type).toBe("card_list")
    expect(back[1]!.cards?.[0]?.type).toBe("fields_list")
    expect(back[1]!.cards?.[0]?.fieldsList ?? []).toEqual([])
  })

  it("round-trips an element-list tab v2 → v1 → v2 via fieldsList", () => {
    const cards = {
      "tab-card-1": {
        id: "tab-card-1",
        cardType: "element-list",
        title: "Tab 1",
        elementList: ["el-1"],
      },
    } as unknown as Record<string, import("@/definitions/cards").DisplayCard>
    const elements = {
      "el-1": { id: "el-1", type: "boolean.checkbox", label: "Checkbox" },
    } as unknown as Record<string, import("@/definitions/elements").CardElement>

    const v1Tabs = v2TabsToV1(["tab-card-1"], cards, elements)
    // Feeding the v1 tabs (with fieldsList) back through v1TabsToV2 reproduces
    // an element-list tab card under a tab-list root.
    const bundle = v1TabsToV2(v1Tabs)
    const root = bundle.cards[bundle.rootCardId] as unknown as Record<
      string,
      unknown
    >
    const tabId = (root.tabList as string[])[0]!
    const tabCard = bundle.cards[tabId] as unknown as Record<string, unknown>
    expect(tabCard.cardType).toBe("element-list")
    const elementList = tabCard.elementList as string[]
    expect(bundle.elements[elementList[0]!]!.type).toBe("boolean.checkbox")
  })
})
