import { defineStore } from "pinia"
import { computed, ref } from "vue"

/**
 * State for a single ObjectBuilder instance. Multiple ObjectBuilders
 * can coexist on a page; each is keyed by `builderId`.
 *
 * The schema is a flat ordered list of typed key/value entries. The
 * helper `toRecord()` flattens entries into a plain JS object.
 */
export type ObjectBuilderEntryType =
  | "string"
  | "number"
  | "boolean"
  | "array"
  | "object"
  | "null"

export interface ObjectBuilderEntry {
  id: string
  key: string
  type: ObjectBuilderEntryType
  value: unknown
}

export interface ObjectBuilderSchema {
  entries: ObjectBuilderEntry[]
}

/** Identifies which entry is currently selected for editing. */
export interface ObjectBuilderSelection {
  id: string | null
}

let idCounter = 0
function nextId(prefix: string): string {
  idCounter += 1
  return `${prefix}-${Date.now()}-${idCounter}`
}

/** Test helper to make ids deterministic. */
export function __resetObjectBuilderIdCounter(): void {
  idCounter = 0
}

/** Sensible default values for each entry type. */
export function defaultValueForType(type: ObjectBuilderEntryType): unknown {
  switch (type) {
    case "string":
      return ""
    case "number":
      return 0
    case "boolean":
      return false
    case "array":
      return []
    case "object":
      return {}
    case "null":
      return null
  }
}

function emptySchema(): ObjectBuilderSchema {
  return { entries: [] }
}

/**
 * Normalise a plain `Record<string, unknown>` into an
 * `ObjectBuilderSchema`. Used when consumers pass a raw object via
 * `modelValue`.
 */
export function recordToSchema(
  record: Record<string, unknown>,
): ObjectBuilderSchema {
  const entries: ObjectBuilderEntry[] = []
  for (const [key, value] of Object.entries(record)) {
    entries.push({
      id: nextId("obj-entry"),
      key,
      type: detectType(value),
      value,
    })
  }
  return { entries }
}

/** Flatten an `ObjectBuilderSchema` into a plain JS object. */
export function toRecord(schema: ObjectBuilderSchema): Record<string, unknown> {
  const out: Record<string, unknown> = {}
  for (const entry of schema.entries) {
    if (!entry.key) {
      continue
    }
    out[entry.key] = entry.value
  }
  return out
}

function detectType(value: unknown): ObjectBuilderEntryType {
  if (value === null) {
    return "null"
  }
  if (Array.isArray(value)) {
    return "array"
  }
  const t = typeof value
  if (t === "string") {
    return "string"
  }
  if (t === "number") {
    return "number"
  }
  if (t === "boolean") {
    return "boolean"
  }
  return "object"
}

export const useObjectBuilderStore = defineStore(
  "gx-modui/objectBuilder",
  () => {
    const builders = ref<Record<string, ObjectBuilderSchema>>({})
    const selections = ref<Record<string, ObjectBuilderSelection>>({})

    function ensureBuilder(builderId: string): ObjectBuilderSchema {
      if (!builders.value[builderId]) {
        builders.value[builderId] = emptySchema()
        selections.value[builderId] = { id: null }
      }
      return builders.value[builderId]!
    }

    function loadSchema(
      builderId: string,
      schema: ObjectBuilderSchema,
    ): void {
      builders.value[builderId] = {
        entries: schema.entries.map((entry) => ({ ...entry })),
      }
      selections.value[builderId] = { id: null }
    }

    function loadRecord(
      builderId: string,
      record: Record<string, unknown>,
    ): void {
      loadSchema(builderId, recordToSchema(record))
    }

    function resetBuilder(builderId: string): void {
      delete builders.value[builderId]
      delete selections.value[builderId]
    }

    /** Append a new entry of `type` and return its id. */
    function addEntry(
      builderId: string,
      type: ObjectBuilderEntryType,
    ): string {
      const schema = ensureBuilder(builderId)
      const id = nextId("obj-entry")
      schema.entries = [
        ...schema.entries,
        {
          id,
          key: "",
          type,
          value: defaultValueForType(type),
        },
      ]
      return id
    }

    /** Remove an entry by id. */
    function removeEntry(builderId: string, entryId: string): void {
      const schema = ensureBuilder(builderId)
      schema.entries = schema.entries.filter((e) => e.id !== entryId)
      const sel = selections.value[builderId]
      if (sel?.id === entryId) {
        selections.value[builderId] = { id: null }
      }
    }

    /** Move an entry from `fromIndex` to `toIndex`. */
    function reorderEntry(
      builderId: string,
      fromIndex: number,
      toIndex: number,
    ): void {
      const schema = ensureBuilder(builderId)
      const list = schema.entries
      if (
        fromIndex < 0 ||
        fromIndex >= list.length ||
        toIndex < 0 ||
        toIndex >= list.length ||
        fromIndex === toIndex
      ) {
        return
      }
      const next = [...list]
      const [moved] = next.splice(fromIndex, 1)
      if (moved !== undefined) {
        next.splice(toIndex, 0, moved)
        schema.entries = next
      }
    }

    /** Patch an entry's fields. When `type` changes, the value resets. */
    function updateEntry(
      builderId: string,
      entryId: string,
      patch: Partial<Omit<ObjectBuilderEntry, "id">>,
    ): void {
      const schema = ensureBuilder(builderId)
      const idx = schema.entries.findIndex((e) => e.id === entryId)
      if (idx < 0) {
        return
      }
      const current = schema.entries[idx]!
      const next: ObjectBuilderEntry = { ...current, ...patch }
      if (patch.type && patch.type !== current.type && patch.value === undefined) {
        next.value = defaultValueForType(patch.type)
      }
      const list = [...schema.entries]
      list[idx] = next
      schema.entries = list
    }

    function select(builderId: string, id: string | null): void {
      selections.value[builderId] = { id }
    }

    function getSelection(builderId: string): ObjectBuilderSelection {
      return selections.value[builderId] ?? { id: null }
    }

    function getSchema(builderId: string): ObjectBuilderSchema {
      return ensureBuilder(builderId)
    }

    return {
      builders,
      selections,
      ensureBuilder,
      loadSchema,
      loadRecord,
      resetBuilder,
      addEntry,
      removeEntry,
      reorderEntry,
      updateEntry,
      select,
      getSelection,
      getSchema,
    }
  },
)

/** Composable wrapper around `useObjectBuilderStore` for one builder. */
export function useObjectBuilder(builderId: string) {
  const store = useObjectBuilderStore()
  store.ensureBuilder(builderId)
  return {
    schema: computed<ObjectBuilderSchema>(() => store.getSchema(builderId)),
    selection: computed<ObjectBuilderSelection>(() =>
      store.getSelection(builderId),
    ),
    addEntry: (type: ObjectBuilderEntryType) => store.addEntry(builderId, type),
    removeEntry: (id: string) => store.removeEntry(builderId, id),
    reorderEntry: (from: number, to: number) =>
      store.reorderEntry(builderId, from, to),
    updateEntry: (
      id: string,
      patch: Partial<Omit<ObjectBuilderEntry, "id">>,
    ) => store.updateEntry(builderId, id, patch),
    select: (id: string | null) => store.select(builderId, id),
    loadSchema: (schema: ObjectBuilderSchema) =>
      store.loadSchema(builderId, schema),
    loadRecord: (record: Record<string, unknown>) =>
      store.loadRecord(builderId, record),
    reset: () => store.resetBuilder(builderId),
  }
}
