<script setup lang="ts">
import { computed, inject, nextTick, ref, watch } from "vue"
import { ChevronDown, ChevronRight, Plus, Trash2 } from "@lucide/vue"
import type { CardElement } from "@/definitions/elements"
import { Button } from "@/components/ui/button"
import { useFormDataStore } from "@/stores/formDataStore"
import { FORM_ID_KEY } from "@/engine/useFormData"
import { cn } from "@/lib/utils"
import RepeatableRow from "./RepeatableRow.vue"

export interface RepeatableBlockProps {
  label?: string
  name?: string | null
  modelValue?: unknown[]
  /** Child element schema for each row. Engine remaps selectOptions → nestedElements. */
  nestedElements?: Array<Record<string, unknown>>
  addButtonLabel?: string
  /** Field names whose values in a row are joined to form the row's header label. */
  optionLabelFields?: ReadonlyArray<string>
  /** Collapse all rows on initial render. */
  collapsedByDefault?: boolean
  item?: Record<string, unknown>
  classList?: string[] | string
}

const props = withDefaults(defineProps<RepeatableBlockProps>(), {
  label: "Repeatable",
  addButtonLabel: "Add row",
  modelValue: () => [],
  nestedElements: () => [],
  optionLabelFields: () => [],
  collapsedByDefault: false,
  item: () => ({}),
})

const emit = defineEmits<{
  (e: "update:modelValue", value: unknown[]): void
}>()

const parentFormId = inject(FORM_ID_KEY, "default")
const store = useFormDataStore()

const rowSchema = computed<CardElement[]>(() =>
  (props.nestedElements ?? []).filter(
    (entry): boolean =>
      typeof entry === "object" &&
      entry !== null &&
      typeof (entry as { type?: unknown }).type === "string",
  ) as unknown as CardElement[],
)

interface RowEntry {
  id: number
  formId: string
  collapsed: boolean
}

let nextRowId = 0

function makeRowFormId(rowId: number): string {
  return `${parentFormId}__${props.name ?? "repeatable"}__${rowId}`
}

function makeRow(initialData?: Record<string, unknown>): RowEntry {
  const id = nextRowId++
  const formId = makeRowFormId(id)
  store.ensureForm(formId)
  if (initialData && Object.keys(initialData).length > 0) {
    store.setInitialValues(formId, initialData)
  }
  return { id, formId, collapsed: props.collapsedByDefault }
}

function buildRows(data: unknown[]): RowEntry[] {
  return data.map((d) => makeRow(typeof d === "object" && d !== null ? (d as Record<string, unknown>) : {}))
}

const rows = ref<RowEntry[]>(
  Array.isArray(props.modelValue) && props.modelValue.length > 0
    ? buildRows(props.modelValue)
    : [],
)

function collectRowData(): unknown[] {
  return rows.value.map((row) => ({ ...(store.forms[row.formId]?.values ?? {}) }))
}

function rowLabel(row: RowEntry, index: number): string {
  const labelFields = props.optionLabelFields ?? []
  if (labelFields.length > 0) {
    const values = store.forms[row.formId]?.values ?? {}
    const parts = labelFields
      .map((f) => values[f])
      .filter((v) => typeof v === "string" && v.trim() !== "")
      .join(" ")
    if (parts) {
      return parts
    }
  }
  return `${props.label} ${index + 1}`
}

let suppressSync = false

watch(
  () => rows.value.map((r) => ({ ...store.forms[r.formId]?.values })),
  () => {
    if (suppressSync) {
      return
    }
    suppressSync = true
    emit("update:modelValue", collectRowData())
    void nextTick(() => {
      suppressSync = false
    })
  },
  { deep: true },
)

watch(
  () => props.modelValue,
  (next) => {
    if (suppressSync || !Array.isArray(next)) {
      return
    }
    if (next.length === rows.value.length) {
      return
    }
    suppressSync = true
    if (next.length > rows.value.length) {
      while (rows.value.length < next.length) {
        const d = next[rows.value.length]
        rows.value.push(makeRow(typeof d === "object" && d !== null ? (d as Record<string, unknown>) : {}))
      }
    } else {
      const removed = rows.value.splice(next.length)
      removed.forEach((r) => store.resetForm(r.formId))
    }
    void nextTick(() => {
      suppressSync = false
    })
  },
)

function addRow(): void {
  rows.value.push(makeRow())
}

function removeRow(rowId: number): void {
  const idx = rows.value.findIndex((r) => r.id === rowId)
  if (idx < 0) {
    return
  }
  const [removed] = rows.value.splice(idx, 1)
  store.resetForm(removed.formId)
}

function toggleRow(rowId: number): void {
  const row = rows.value.find((r) => r.id === rowId)
  if (row) {
    row.collapsed = !row.collapsed
  }
}
</script>

<template>
  <div
    :class="cn('flex flex-col gap-1', props.classList)"
    :data-element-name="props.name ?? undefined"
  >
    <div v-if="rows.length" class="flex flex-col gap-2">
      <div
        v-for="(row, index) in rows"
        :key="row.id"
        class="rounded-md border bg-card"
        data-testid="repeatable-row"
      >
        <!-- Row header -->
        <div
          class="flex cursor-pointer items-center justify-between gap-2 px-3 py-2"
          @click="toggleRow(row.id)"
        >
          <div class="flex items-center gap-1.5 text-sm font-medium">
            <ChevronDown v-if="!row.collapsed" class="size-3.5 shrink-0 text-muted-foreground" />
            <ChevronRight v-else class="size-3.5 shrink-0 text-muted-foreground" />
            <span>{{ rowLabel(row, index) }}</span>
          </div>
          <Button
            variant="ghost"
            size="icon-sm"
            type="button"
            aria-label="Remove row"
            class="shrink-0 text-muted-foreground hover:text-destructive"
            @click.stop="removeRow(row.id)"
          >
            <Trash2 class="size-3.5" />
          </Button>
        </div>

        <!-- Row content -->
        <div v-if="!row.collapsed && rowSchema.length" class="border-t px-3 py-3">
          <RepeatableRow
            :form-id="row.formId"
            :schema="rowSchema"
            :item="props.item"
          />
        </div>
      </div>
    </div>

    <Button
      type="button"
      variant="outline"
      size="sm"
      class="self-start"
      data-testid="repeatable-add"
      @click="addRow"
    >
      <Plus class="size-3.5" />
      {{ props.addButtonLabel }}
    </Button>
  </div>
</template>
