<script setup lang="ts">
/**
 * ElementListBuilder — a simpler sibling of FormBuilder.
 *
 * The schema is just a flat list of CardElements (no cards, no tabs,
 * no nested cards). Useful for forms-as-arrays and list-of-fields
 * configs.
 *
 * Three-pane layout:
 *   - LEFT  (Palette):    Clickable list of element categories from
 *                         CardElementMetadataMap. Click to append.
 *   - MIDDLE (Canvas):    Flat list of elements with selection.
 *   - RIGHT (Inspector):  Property panel mirroring FormBuilder's
 *                         (label, name, placeholder, helpText,
 *                         required, readonly, disabled).
 *
 * State is owned by `elementListBuilderStore`. The builder accepts an
 * initial schema via `modelValue` and emits the current schema via
 * `update:modelValue` on every mutation.
 */
import { computed, onMounted, ref, watch } from "vue"
import { ArrowDown, ArrowUp, Plus, Trash2, X } from "@lucide/vue"
import type { CardElement } from "@/definitions/elements"
import { CardElementMetadataMap } from "@/definitions/elements"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
  Card,
  CardContent,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Switch } from "@/components/ui/switch"
import { Textarea } from "@/components/ui/textarea"
import {
  useElementListBuilder,
  type ElementListBuilderSchema,
} from "@/stores/elementListBuilderStore"
import { cn } from "@/lib/utils"

export interface ElementListBuilderProps {
  /** Initial / current schema. Two-way bindable via v-model. */
  modelValue?: ElementListBuilderSchema
  /**
   * Identifier used to key the elementListBuilderStore. Defaults to a
   * stable auto-generated id so multiple builders on the same page
   * don't collide.
   */
  builderId?: string
  label?: string
  name?: string | null
  item?: Record<string, unknown>
  classList?: string[] | string
}

const props = withDefaults(defineProps<ElementListBuilderProps>(), {
  label: "Element List Builder",
  item: () => ({}),
})

const model = defineModel<ElementListBuilderSchema | undefined>("modelValue")

let nextAutoId = 0
function makeAutoId(): string {
  nextAutoId += 1
  return `element-list-builder-${Date.now()}-${nextAutoId}`
}
const fallbackId = ref<string>(makeAutoId())

const builderId = computed<string>(() => props.builderId ?? fallbackId.value)

const builder = useElementListBuilder(builderId.value)

function isElementListBuilderSchema(
  value: unknown,
): value is ElementListBuilderSchema {
  if (typeof value !== "object" || value === null) {
    return false
  }
  const v = value as Record<string, unknown>
  return Array.isArray(v.elements)
}

/** Seed the store from the initial modelValue. */
onMounted(() => {
  const seed = model.value
  if (isElementListBuilderSchema(seed)) {
    builder.loadSchema(seed)
  }
})

/** Push schema changes back via v-model. */
watch(
  () => builder.schema.value,
  (next) => {
    model.value = {
      elements: next.elements.map(
        (el) =>
          ({ ...(el as unknown as Record<string, unknown>) }) as unknown as CardElement,
      ),
    }
  },
  { deep: true },
)

const elements = computed<CardElement[]>(() => builder.schema.value.elements)

/**
 * Display-time view of `elements` with the fields the template needs
 * narrowed to plain strings. Doing the casts here keeps the template
 * free of `as unknown as` expressions that confuse the template
 * compiler around `{` `}` characters.
 */
interface ElementRow {
  id: string
  label: string
  type: string
}
const elementRows = computed<ElementRow[]>(() =>
  elements.value.map((el) => {
    const bag = el as unknown as Record<string, unknown>
    return {
      id: (bag.id as string) ?? "",
      label: (bag.label as string) ?? "(unnamed)",
      type: (bag.type as string) ?? "",
    }
  }),
)

const paletteEntries = computed(() =>
  (
    Object.entries(CardElementMetadataMap) as Array<
      [
        keyof typeof CardElementMetadataMap,
        (typeof CardElementMetadataMap)[keyof typeof CardElementMetadataMap],
      ]
    >
  ).map(([type, meta]) => ({ type, meta })),
)

const selectedElement = computed<CardElement | null>(() => {
  const sel = builder.selection.value
  if (!sel.id) {
    return null
  }
  return (
    elements.value.find(
      (el) => (el as unknown as { id: string }).id === sel.id,
    ) ?? null
  )
})

const selectedIndex = computed<number>(() => {
  const sel = builder.selection.value
  if (!sel.id) {
    return -1
  }
  return elements.value.findIndex(
    (el) => (el as unknown as { id: string }).id === sel.id,
  )
})

const canMoveDown = computed<boolean>(() => {
  return (
    selectedIndex.value >= 0 && selectedIndex.value < elements.value.length - 1
  )
})

function onPaletteClick(type: keyof typeof CardElementMetadataMap): void {
  const newId = builder.addElement(type)
  builder.select(newId)
}

function onCanvasRowClick(elementId: string): void {
  builder.select(elementId)
}

function onMoveUp(): void {
  if (selectedIndex.value <= 0) {
    return
  }
  builder.reorderElement(selectedIndex.value, selectedIndex.value - 1)
}

function onMoveDown(): void {
  if (!canMoveDown.value) {
    return
  }
  builder.reorderElement(selectedIndex.value, selectedIndex.value + 1)
}

function onRemove(): void {
  const sel = builder.selection.value
  if (sel.id) {
    builder.removeElement(sel.id)
  }
}

function onClearSelection(): void {
  builder.select(null)
}

function patchSelected(patch: Partial<CardElement>): void {
  const sel = builder.selection.value
  if (sel.id) {
    builder.updateElement(sel.id, patch)
  }
}

const labelValue = computed<string>(
  () => (selectedElement.value?.label as string) ?? "",
)
const nameValue = computed<string>(
  () => (selectedElement.value?.name as string) ?? "",
)
const placeholderValue = computed<string>(
  () => (selectedElement.value?.placeholder as string) ?? "",
)
const helpTextValue = computed<string>(
  () => (selectedElement.value?.helpText as string) ?? "",
)
const requiredValue = computed<boolean>(() =>
  Boolean(selectedElement.value?.required),
)
const readonlyValue = computed<boolean>(() =>
  Boolean(selectedElement.value?.readonly),
)
const disabledValue = computed<boolean>(() =>
  Boolean(selectedElement.value?.disabled),
)
const selectedTypeLabel = computed<string>(() => {
  const sel = selectedElement.value
  if (!sel) {
    return ""
  }
  return ((sel as unknown as Record<string, unknown>).type as string) ?? ""
})
</script>

<template>
  <div
    :class="
      cn(
        'gxp-element-list-builder grid gap-3 rounded-lg border bg-card',
        props.classList,
      )
    "
    style="grid-template-columns: 220px 1fr 280px; min-height: 24rem"
    data-testid="element-list-builder"
    :data-element-name="props.name ?? undefined"
  >
    <aside
      class="flex flex-col gap-2 border-r p-3 overflow-y-auto"
      data-testid="element-list-builder-palette"
    >
      <h4
        class="text-xs font-semibold uppercase tracking-wide text-muted-foreground"
      >
        {{ props.label }}
      </h4>
      <div class="flex flex-col gap-1">
        <button
          v-for="entry in paletteEntries"
          :key="entry.type"
          type="button"
          class="flex items-start gap-2 rounded-md border bg-background px-2 py-1.5 text-left text-xs transition-colors hover:border-primary hover:bg-accent/40"
          :data-testid="`element-list-builder-palette-${entry.type}`"
          @click="onPaletteClick(entry.type)"
        >
          <Plus class="mt-0.5 h-3.5 w-3.5 shrink-0" />
          <span class="flex min-w-0 flex-col gap-0.5">
            <span class="font-medium">{{ entry.meta.displayLabel }}</span>
            <span class="line-clamp-2 text-[10px] text-muted-foreground">
              {{ entry.meta.description }}
            </span>
          </span>
        </button>
      </div>
    </aside>

    <section
      class="flex flex-col gap-3 overflow-y-auto p-3"
      data-testid="element-list-builder-canvas"
    >
      <header class="flex items-center justify-between">
        <h4
          class="text-xs font-semibold uppercase tracking-wide text-muted-foreground"
        >
          Elements
        </h4>
        <Badge variant="outline" class="font-mono text-[10px]">
          builder/element-list-builder
        </Badge>
      </header>

      <p
        v-if="elements.length === 0"
        class="rounded-md border border-dashed bg-background py-6 text-center text-xs text-muted-foreground"
        data-testid="element-list-builder-canvas-empty"
      >
        Click an element in the palette to add it.
      </p>

      <ul
        v-else
        class="flex flex-col gap-1 text-xs"
        data-testid="element-list-builder-canvas-list"
      >
        <li
          v-for="(row, index) in elementRows"
          :key="row.id"
          class="flex cursor-pointer items-center gap-2 rounded border bg-background px-2 py-1.5 transition-colors"
          :class="
            builder.selection.value.id === row.id
              ? 'border-primary bg-accent/30'
              : ''
          "
          :data-testid="`element-list-builder-row-${row.id}`"
          @click="onCanvasRowClick(row.id)"
        >
          <span class="w-6 text-right tabular-nums text-muted-foreground">
            {{ index + 1 }}.
          </span>
          <span class="font-medium">{{ row.label }}</span>
          <Badge variant="secondary" class="ml-auto font-mono text-[9px]">
            {{ row.type }}
          </Badge>
        </li>
      </ul>
    </section>

    <aside
      class="flex flex-col gap-2 overflow-y-auto border-l p-3"
      data-testid="element-list-builder-inspector"
    >
      <header class="flex items-center justify-between">
        <h4
          class="text-xs font-semibold uppercase tracking-wide text-muted-foreground"
        >
          Inspector
        </h4>
        <Button
          v-if="builder.selection.value.id"
          variant="ghost"
          size="icon-sm"
          aria-label="Clear selection"
          @click="onClearSelection"
        >
          <X class="h-3.5 w-3.5" />
        </Button>
      </header>

      <p
        v-if="!selectedElement"
        class="py-3 text-xs text-muted-foreground"
        data-testid="element-list-builder-inspector-empty"
      >
        Select an element on the canvas to edit its properties.
      </p>

      <div
        v-else
        class="flex flex-col gap-3"
        data-testid="element-list-builder-inspector-fields"
      >
        <Card class="border-dashed">
          <CardHeader class="py-2">
            <CardTitle class="font-mono text-xs uppercase tracking-wide">
              {{ selectedTypeLabel }}
            </CardTitle>
          </CardHeader>
          <CardContent class="flex flex-col gap-2 pb-3">
            <div class="flex flex-col gap-1">
              <Label for="elb-label">Label</Label>
              <Input
                id="elb-label"
                :model-value="labelValue"
                data-testid="element-list-builder-inspector-label"
                @input="
                  (e: Event) =>
                    patchSelected({
                      label: (e.target as HTMLInputElement).value,
                    })
                "
              />
            </div>
            <div class="flex flex-col gap-1">
              <Label for="elb-name">Field name</Label>
              <Input
                id="elb-name"
                :model-value="nameValue"
                data-testid="element-list-builder-inspector-name"
                @input="
                  (e: Event) =>
                    patchSelected({
                      name: (e.target as HTMLInputElement).value,
                    })
                "
              />
            </div>
            <div class="flex flex-col gap-1">
              <Label for="elb-placeholder">Placeholder</Label>
              <Input
                id="elb-placeholder"
                :model-value="placeholderValue"
                data-testid="element-list-builder-inspector-placeholder"
                @input="
                  (e: Event) =>
                    patchSelected({
                      placeholder: (e.target as HTMLInputElement).value,
                    })
                "
              />
            </div>
            <div class="flex flex-col gap-1">
              <Label for="elb-help">Help text</Label>
              <Textarea
                id="elb-help"
                :model-value="helpTextValue"
                data-testid="element-list-builder-inspector-help"
                @input="
                  (e: Event) =>
                    patchSelected({
                      helpText: (e.target as HTMLTextAreaElement).value,
                    })
                "
              />
            </div>

            <div
              class="flex items-center justify-between rounded border bg-background px-2 py-1"
            >
              <Label for="elb-required">Required</Label>
              <Switch
                id="elb-required"
                :model-value="requiredValue"
                data-testid="element-list-builder-inspector-required"
                @update:model-value="(v: boolean) => patchSelected({ required: v })"
              />
            </div>
            <div
              class="flex items-center justify-between rounded border bg-background px-2 py-1"
            >
              <Label for="elb-readonly">Read-only</Label>
              <Switch
                id="elb-readonly"
                :model-value="readonlyValue"
                @update:model-value="(v: boolean) => patchSelected({ readonly: v })"
              />
            </div>
            <div
              class="flex items-center justify-between rounded border bg-background px-2 py-1"
            >
              <Label for="elb-disabled">Disabled</Label>
              <Switch
                id="elb-disabled"
                :model-value="disabledValue"
                @update:model-value="(v: boolean) => patchSelected({ disabled: v })"
              />
            </div>
          </CardContent>
        </Card>

        <div class="flex items-center gap-1">
          <Button
            variant="outline"
            size="sm"
            :disabled="selectedIndex <= 0"
            data-testid="element-list-builder-move-up"
            @click="onMoveUp"
          >
            <ArrowUp class="h-3.5 w-3.5" />
          </Button>
          <Button
            variant="outline"
            size="sm"
            :disabled="!canMoveDown"
            data-testid="element-list-builder-move-down"
            @click="onMoveDown"
          >
            <ArrowDown class="h-3.5 w-3.5" />
          </Button>
          <Button
            variant="destructive"
            size="sm"
            class="ml-auto"
            data-testid="element-list-builder-remove"
            @click="onRemove"
          >
            <Trash2 class="h-3.5 w-3.5" />
            Remove
          </Button>
        </div>
      </div>
    </aside>
  </div>
</template>
