<script setup lang="ts">
/**
 * ConditionsEditor — inspector pane for editing a `conditionParams` array
 * (and the surrounding `conditionGroupLogic`) on any element, card, or tab
 * container. Mirrors the legacy `Components/Mod/v2` JSON shape so the
 * resulting object slots straight back into v2 JSON without translation.
 *
 * Each row edits one `ConditionParam`:
 *   - `name`  — data field name to compare against
 *   - `logic` — the operator
 *   - `value` — comparison value (hidden for unary operators)
 *
 * A row can also be a **sub-condition** group: flipping the "Group of
 * conditions" switch sets `subCondition: true`, hides the name/logic/value
 * inputs, and reveals a recursive ConditionsEditor for the row's nested
 * `conditionParams` (with its own `conditionGroupLogic`). The engine in
 * `useDisplayConditions.ts` already evaluates this shape — see the
 * `param.subCondition` branch there.
 */
import { computed } from "vue"
import { Plus, Trash2 } from "@lucide/vue"
import type {
  ConditionGroupLogic,
  ConditionLogic,
  ConditionParam,
} from "@/definitions/shared/conditions"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Switch } from "@/components/ui/switch"

export interface ConditionsEditorProps {
  /** Current condition list. */
  modelValue?: ConditionParam[]
  /** Group logic (AND / OR). */
  groupLogic?: ConditionGroupLogic
  /** Optional id prefix so multiple editors on one page get distinct DOM ids. */
  idPrefix?: string
  /**
   * Nesting depth for recursive sub-condition editors. Drives left-indent and
   * differentiates DOM ids in deeply nested groups. Defaults to 0 (top-level).
   */
  depth?: number
}

const props = withDefaults(defineProps<ConditionsEditorProps>(), {
  idPrefix: "fb-cond",
  depth: 0,
})

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

const rows = computed<ConditionParam[]>(() => props.modelValue ?? [])

/** Logic operators surfaced in the dropdown. Mirrors the v2 schema. */
const LOGIC_OPTIONS: ReadonlyArray<{ value: ConditionLogic; label: string }> = [
  { value: "==", label: "Equals (==)" },
  { value: "!=", label: "Not equals (!=)" },
  { value: ">", label: "Greater than (>)" },
  { value: ">=", label: "Greater than or equal (>=)" },
  { value: "<", label: "Less than (<)" },
  { value: "<=", label: "Less than or equal (<=)" },
  { value: "in-array", label: "In array" },
  { value: "not-in-array", label: "Not in array" },
  { value: "starts-with", label: "Starts with" },
  { value: "ends-with", label: "Ends with" },
  { value: "contains", label: "Contains" },
  { value: "not-contains", label: "Does not contain" },
  { value: "matches", label: "Matches regex" },
  { value: "is-truthy", label: "Is truthy" },
  { value: "is-falsy", label: "Is falsy" },
  { value: "is-null", label: "Is null" },
  { value: "is-not-null", label: "Is not null" },
  { value: "is-odd", label: "Is odd" },
  { value: "is-even", label: "Is even" },
  { value: "g0length", label: "Length > 0" },
  { value: "zero-length", label: "Length == 0" },
  { value: "length-eq-value", label: "Length equals value" },
  { value: "length-gt-value", label: "Length greater than value" },
  { value: "length-lt-value", label: "Length less than value" },
]

/** Operators that don't read a `value` from the user. */
const UNARY_LOGICS = new Set<ConditionLogic>([
  "is-truthy",
  "is-falsy",
  "is-null",
  "is-not-null",
  "is-odd",
  "is-even",
  "g0length",
  "n0length",
  "zero-length",
])

function isUnary(logic: ConditionLogic | undefined): boolean {
  return Boolean(logic && UNARY_LOGICS.has(logic))
}

function emitRows(next: ConditionParam[]): void {
  emit("update:modelValue", next)
}

function onAddRow(): void {
  const next: ConditionParam[] = [
    ...rows.value,
    { name: "", logic: "==", value: "" },
  ]
  emitRows(next)
}

function onRemoveRow(index: number): void {
  const next = rows.value.slice()
  next.splice(index, 1)
  emitRows(next)
}

function onFieldUpdate(
  index: number,
  patch: Partial<ConditionParam>,
): void {
  const next = rows.value.map((row, i) =>
    i === index ? ({ ...row, ...patch } as ConditionParam) : row,
  )
  emitRows(next)
}

function onGroupLogicChange(value: string): void {
  if (value === "AND" || value === "OR") {
    emit("update:groupLogic", value)
  }
}

/**
 * Toggle a row between flat condition and nested sub-condition group. When
 * turning ON we seed an empty nested group with AND logic; when turning OFF
 * we discard the nested config (the row reverts to its existing name/logic/
 * value, which were preserved through the toggle).
 */
function onSubConditionToggle(index: number, value: boolean): void {
  if (value) {
    onFieldUpdate(index, {
      subCondition: true,
      conditionParams: rows.value[index]?.conditionParams ?? [],
      conditionGroupLogic:
        rows.value[index]?.conditionGroupLogic ?? "AND",
    })
  } else {
    const next = rows.value.map((row, i) => {
      if (i !== index) return row
      const copy: ConditionParam = { ...row }
      delete copy.subCondition
      delete copy.conditionParams
      delete copy.conditionGroupLogic
      return copy
    })
    emitRows(next)
  }
}

function onNestedConditionsUpdate(
  index: number,
  next: ConditionParam[],
): void {
  onFieldUpdate(index, { conditionParams: next })
}

function onNestedGroupLogicUpdate(
  index: number,
  next: ConditionGroupLogic,
): void {
  onFieldUpdate(index, { conditionGroupLogic: next })
}

const inputId = (suffix: string, index: number): string =>
  `${props.idPrefix}-${suffix}-${index}`
</script>

<template>
  <div
    class="flex flex-col gap-2"
    data-testid="form-builder-conditions-editor"
  >
    <div
      v-if="rows.length >= 2"
      class="flex items-center justify-between gap-2 rounded border bg-background px-2 py-1"
    >
      <Label :for="`${props.idPrefix}-group-logic`" class="text-xs">
        Match
      </Label>
      <select
        :id="`${props.idPrefix}-group-logic`"
        class="h-7 rounded-md border border-input bg-background px-2 text-xs shadow-sm focus:outline-none focus:ring-1 focus:ring-ring"
        :value="props.groupLogic ?? 'AND'"
        data-testid="form-builder-conditions-group-logic"
        @change="(e: Event) => onGroupLogicChange((e.target as HTMLSelectElement).value)"
      >
        <option value="AND">All (AND)</option>
        <option value="OR">Any (OR)</option>
      </select>
    </div>

    <p
      v-if="rows.length === 0"
      class="rounded border border-dashed bg-muted/30 px-2.5 py-3 text-center text-[11px] leading-snug text-muted-foreground"
      data-testid="form-builder-conditions-empty"
    >
      <template v-if="props.depth === 0">
        No conditions — this always renders. Add a condition to control when
        it's shown.
      </template>
      <template v-else>
        Empty group — add nested conditions below.
      </template>
    </p>

    <div
      v-for="(row, index) in rows"
      :key="index"
      class="flex flex-col gap-1.5 rounded border bg-background p-2"
      :class="row.subCondition ? 'border-primary/40 bg-primary/[0.03]' : ''"
      :data-testid="`form-builder-condition-row-${index}`"
      :data-sub-condition="row.subCondition ? 'true' : undefined"
    >
      <div class="flex items-center justify-between gap-1">
        <span class="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
          {{ row.subCondition ? "Group" : "Condition" }} {{ index + 1 }}
        </span>
        <Button
          type="button"
          variant="ghost"
          size="icon-sm"
          class="h-6 w-6 text-destructive"
          aria-label="Remove condition"
          :data-testid="`form-builder-condition-remove-${index}`"
          @click="onRemoveRow(index)"
        >
          <Trash2 class="h-3 w-3" />
        </Button>
      </div>

      <!-- Sub-condition toggle: flips this row between a single condition
           and a nested group with its own condition list + group logic. -->
      <div class="flex items-center justify-between rounded border bg-background px-2 py-1">
        <Label
          :for="inputId('sub', index)"
          class="cursor-pointer text-[11px]"
        >
          Group of conditions
        </Label>
        <Switch
          :id="inputId('sub', index)"
          :model-value="Boolean(row.subCondition)"
          :data-testid="`form-builder-condition-sub-${index}`"
          @update:model-value="(v: boolean) => onSubConditionToggle(index, v)"
        />
      </div>

      <!-- Flat condition: regular name / logic / value inputs -->
      <template v-if="!row.subCondition">
        <div class="flex flex-col gap-1">
          <Label :for="inputId('name', index)" class="text-[11px]">
            Field name
          </Label>
          <Input
            :id="inputId('name', index)"
            :model-value="row.name"
            placeholder="e.g. first_name"
            :data-testid="`form-builder-condition-name-${index}`"
            @input="(e: Event) => onFieldUpdate(index, { name: (e.target as HTMLInputElement).value })"
          />
        </div>

        <div class="flex flex-col gap-1">
          <Label :for="inputId('logic', index)" class="text-[11px]">
            Logic
          </Label>
          <select
            :id="inputId('logic', index)"
            class="flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm transition-colors focus:outline-none focus:ring-1 focus:ring-ring"
            :value="row.logic"
            :data-testid="`form-builder-condition-logic-${index}`"
            @change="(e: Event) => onFieldUpdate(index, { logic: (e.target as HTMLSelectElement).value as ConditionLogic })"
          >
            <option
              v-for="opt in LOGIC_OPTIONS"
              :key="opt.value"
              :value="opt.value"
            >
              {{ opt.label }}
            </option>
          </select>
        </div>

        <div
          v-if="!isUnary(row.logic)"
          class="flex flex-col gap-1"
        >
          <Label :for="inputId('value', index)" class="text-[11px]">
            Value
          </Label>
          <Input
            :id="inputId('value', index)"
            :model-value="row.value == null ? '' : String(row.value)"
            placeholder="Comparison value"
            :data-testid="`form-builder-condition-value-${index}`"
            @input="(e: Event) => onFieldUpdate(index, { value: (e.target as HTMLInputElement).value })"
          />
        </div>
      </template>

      <!-- Sub-condition group: recursive ConditionsEditor for the nested list. -->
      <div
        v-else
        class="ml-2 mt-1 border-l-2 border-primary/30 pl-2"
        :data-testid="`form-builder-condition-nested-${index}`"
      >
        <ConditionsEditor
          :model-value="row.conditionParams ?? []"
          :group-logic="row.conditionGroupLogic ?? 'AND'"
          :id-prefix="`${props.idPrefix}-${index}`"
          :depth="props.depth + 1"
          @update:model-value="(next: ConditionParam[]) => onNestedConditionsUpdate(index, next)"
          @update:group-logic="(next: ConditionGroupLogic) => onNestedGroupLogicUpdate(index, next)"
        />
      </div>
    </div>

    <Button
      type="button"
      variant="outline"
      size="sm"
      class="self-start"
      data-testid="form-builder-condition-add"
      @click="onAddRow"
    >
      <Plus class="h-3.5 w-3.5" />
      Add condition
    </Button>
  </div>
</template>
