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

/**
 * A single cell in the grid layout. The shape mirrors the
 * `GridItem` used by `GridViewCard` but is flattened down to the
 * minimum a designer cares about: a label, a column/row position,
 * and a column/row span.
 */
export interface GridBuilderItem {
  id: string
  /** Display label shown in the canvas. */
  label: string
  /** 12-column grid x position (0-indexed). */
  x: number
  /** Row position (0-indexed). */
  y: number
  /** Width in column units (1-12). */
  w: number
  /** Height in row units. */
  h: number
}

/** Full grid schema produced by the builder. */
export interface GridBuilderSchema {
  /** Number of columns in the grid. Default 12. */
  cols: number
  /** Pixel height of a single row. Default 60. */
  rowHeight: number
  /** Ordered list of grid items. */
  items: GridBuilderItem[]
}

/** Identifies which grid item is currently selected. */
export interface GridBuilderSelection {
  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 __resetGridBuilderIdCounter(): void {
  idCounter = 0
}

function emptySchema(): GridBuilderSchema {
  return { cols: 12, rowHeight: 60, items: [] }
}

export const useGridBuilderStore = defineStore(
  "gx-modui/gridBuilder",
  () => {
    /**
     * Per-builder state, keyed by `builderId`. Lets multiple builders
     * coexist on the same page without colliding.
     */
    const builders = ref<Record<string, GridBuilderSchema>>({})
    const selections = ref<Record<string, GridBuilderSelection>>({})

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

    function loadSchema(builderId: string, schema: GridBuilderSchema): void {
      builders.value[builderId] = {
        cols: schema.cols,
        rowHeight: schema.rowHeight,
        items: schema.items.map((it) => ({ ...it })),
      }
      selections.value[builderId] = { id: null }
    }

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

    /**
     * Append a fresh grid item at the next available row, sized to
     * `template`. Returns the new item's id.
     */
    function addItem(
      builderId: string,
      template?: Partial<Pick<GridBuilderItem, "w" | "h" | "label">>,
    ): string {
      const schema = ensureBuilder(builderId)
      const w = clampColumns(template?.w ?? 1, schema.cols)
      const h = Math.max(1, template?.h ?? 1)
      const y = nextFreeRow(schema)
      const id = nextId("gi")
      const item: GridBuilderItem = {
        id,
        label: template?.label ?? `Cell ${schema.items.length + 1}`,
        x: 0,
        y,
        w,
        h,
      }
      schema.items = [...schema.items, item]
      return id
    }

    /** Remove an item by id. */
    function removeItem(builderId: string, itemId: string): void {
      const schema = ensureBuilder(builderId)
      schema.items = schema.items.filter((it) => it.id !== itemId)
      const sel = selections.value[builderId]
      if (sel?.id === itemId) {
        selections.value[builderId] = { id: null }
      }
    }

    /** Patch a grid item's properties. */
    function updateItem(
      builderId: string,
      itemId: string,
      patch: Partial<Omit<GridBuilderItem, "id">>,
    ): void {
      const schema = ensureBuilder(builderId)
      const idx = schema.items.findIndex((it) => it.id === itemId)
      if (idx < 0) {
        return
      }
      const current = schema.items[idx]!
      const merged: GridBuilderItem = {
        ...current,
        ...patch,
      }
      // Clamp coordinates to keep the item inside the grid.
      merged.w = clampColumns(merged.w, schema.cols)
      merged.h = Math.max(1, merged.h)
      merged.x = Math.max(0, Math.min(merged.x, schema.cols - merged.w))
      merged.y = Math.max(0, merged.y)
      const next = [...schema.items]
      next[idx] = merged
      schema.items = next
    }

    /** Update the grid-level `cols` value. */
    function setCols(builderId: string, cols: number): void {
      const schema = ensureBuilder(builderId)
      const safe = Math.max(1, Math.floor(cols))
      schema.cols = safe
      // Re-clamp every item so nothing overflows the new column count.
      schema.items = schema.items.map((it) => {
        const w = clampColumns(it.w, safe)
        const x = Math.max(0, Math.min(it.x, safe - w))
        return { ...it, w, x }
      })
    }

    /** Update the grid-level `rowHeight` value. */
    function setRowHeight(builderId: string, rowHeight: number): void {
      const schema = ensureBuilder(builderId)
      schema.rowHeight = Math.max(1, Math.floor(rowHeight))
    }

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

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

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

    return {
      builders,
      selections,
      ensureBuilder,
      loadSchema,
      resetBuilder,
      addItem,
      removeItem,
      updateItem,
      setCols,
      setRowHeight,
      select,
      getSelection,
      getSchema,
    }
  },
)

function clampColumns(w: number, cols: number): number {
  if (!Number.isFinite(w)) {
    return 1
  }
  return Math.max(1, Math.min(Math.floor(w), cols))
}

/**
 * Returns the first row index where a new full-width item could be
 * placed without overlapping. The MVP only cares about appending —
 * we use the max occupied row + height.
 */
function nextFreeRow(schema: GridBuilderSchema): number {
  if (schema.items.length === 0) {
    return 0
  }
  return schema.items.reduce((max, it) => Math.max(max, it.y + it.h), 0)
}

/** Composable wrapping store access for a single builder instance. */
export function useGridBuilder(builderId: string) {
  const store = useGridBuilderStore()
  store.ensureBuilder(builderId)
  return {
    schema: computed<GridBuilderSchema>(() => store.getSchema(builderId)),
    selection: computed<GridBuilderSelection>(() =>
      store.getSelection(builderId),
    ),
    addItem: (template?: Partial<Pick<GridBuilderItem, "w" | "h" | "label">>) =>
      store.addItem(builderId, template),
    removeItem: (id: string) => store.removeItem(builderId, id),
    updateItem: (id: string, patch: Partial<Omit<GridBuilderItem, "id">>) =>
      store.updateItem(builderId, id, patch),
    setCols: (cols: number) => store.setCols(builderId, cols),
    setRowHeight: (rowHeight: number) =>
      store.setRowHeight(builderId, rowHeight),
    select: (id: string | null) => store.select(builderId, id),
    loadSchema: (schema: GridBuilderSchema) =>
      store.loadSchema(builderId, schema),
    reset: () => store.resetBuilder(builderId),
  }
}
