<script setup>
import { computed, ref, watch } from "vue";
import { Icon } from "@iconify/vue";
import {
  useTreeDnd
} from "../../composables/useTreeDnd";
import { Button } from "../ui/button";
import { ExpressionEditor } from "../ui/expression-editor";
import { Field, FieldLabel } from "../ui/field";
import { Switch } from "../ui/switch";
import { Locale as LocaleField } from "../ui/locale";
import { Markdown } from "../ui/markdown";
import { getStructFieldDescription, getStructFieldTitle } from "./utils/schema-meta";
import { validationRuleStruct } from "./utils/common";
defineOptions({ name: "ShwfedValidationRulesField" });
const RULE_META = validationRuleStruct(() => {
});
const whenTitle = getStructFieldTitle(RULE_META, "when") ?? "\u6821\u9A8C\u6761\u4EF6";
const whenDescription = getStructFieldDescription(RULE_META, "when");
const props = defineProps({
  modelValue: { type: Array, required: false }
});
const emit = defineEmits(["update:modelValue"]);
const rules = computed(() => props.modelValue ?? []);
const EMPTY_MESSAGE = [{ locale: "zh", message: "" }];
function makeId() {
  if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
    const r = Math.random() * 16 | 0;
    return (c === "x" ? r : r & 3 | 8).toString(16);
  });
}
const rowKeys = ref([]);
watch(
  () => rules.value.length,
  (len) => {
    if (len === rowKeys.value.length) return;
    rowKeys.value = Array.from({ length: len }, () => makeId());
  },
  { immediate: true }
);
function addRule() {
  emit("update:modelValue", [...rules.value, { when: "", message: EMPTY_MESSAGE }]);
  rowKeys.value = [...rowKeys.value, makeId()];
}
function removeRule(index) {
  emit("update:modelValue", rules.value.filter((_, i) => i !== index));
  rowKeys.value = rowKeys.value.filter((_, i) => i !== index);
}
function updateWhen(index, when) {
  emit("update:modelValue", rules.value.map((rule, i) => i === index ? { ...rule, when } : rule));
}
function updateWarning(index, warning) {
  emit("update:modelValue", rules.value.map((rule, i) => i === index ? { ...rule, warning } : rule));
}
function updateMessage(index, message) {
  emit("update:modelValue", rules.value.map((rule, i) => i === index ? { ...rule, message } : rule));
}
function move(from, to) {
  if (from === to) return;
  if (from < 0 || to < 0 || from >= rules.value.length || to >= rules.value.length) return;
  const next = [...rules.value];
  const [rule] = next.splice(from, 1);
  if (!rule) return;
  next.splice(to, 0, rule);
  emit("update:modelValue", next);
  const keys = [...rowKeys.value];
  const [key] = keys.splice(from, 1);
  if (!key) return;
  keys.splice(to, 0, key);
  rowKeys.value = keys;
}
const ROW_KIND = "shwfed-validation-rule";
const rowId = (i) => rowKeys.value[i] ?? `idx-${i}`;
function onRowDrop(e) {
  if (e.source.kind !== ROW_KIND || e.target.kind !== ROW_KIND) return;
  const from = rowKeys.value.findIndex((k) => k === e.source.id);
  const to = rowKeys.value.findIndex((k) => k === e.target.id);
  if (from < 0 || to < 0) return;
  if (e.instruction === "reorder-above") move(from, from < to ? to - 1 : to);
  else if (e.instruction === "reorder-below") move(from, from <= to ? to : to + 1);
}
const dnd = useTreeDnd({ onRowDrop });
const pickDragHandle = (el) => el.querySelector(".drag-handle");
function rowConfig(i) {
  return {
    kind: ROW_KIND,
    canDrop: (src) => src.kind === ROW_KIND,
    blockInstructions: (src) => {
      const block = ["make-child", "reparent"];
      if (src.id === rowId(i)) block.push("reorder-above", "reorder-below");
      return block;
    },
    dragHandle: pickDragHandle
  };
}
</script>

<template>
  <div class="flex flex-col gap-2">
    <div
      v-for="(rule, index) in rules"
      :key="rowKeys[index]"
      :ref="dnd.rowRef(rowId(index), rowConfig(index))"
      data-slot="validation-rule"
      class="relative flex items-start gap-2 rounded border border-zinc-200 bg-zinc-50/40 p-2"
      :data-instruction="dnd.instructionFor(rowId(index)) ?? void 0"
    >
      <Icon
        icon="fluent:re-order-dots-vertical-20-regular"
        class="drag-handle mt-2.5 size-4 shrink-0 cursor-grab text-zinc-400"
        data-slot="validation-rule-drag-handle"
      />

      <!--
        `items-start` keeps each Field at its own content height — without it the
        grid stretches both cells to the taller (multi-row message) and the
        floating label centers on that inflated height instead of its control.
        With the natural height restored, the standard floating behaviour is
        what we want: the label rests centered (acting as the placeholder) while
        the field is empty + inactive, and floats up to the top-border notch on
        focus or once a value is present. The `when` editor therefore carries no
        explicit placeholder — the label IS the placeholder (the syntax contract
        lives in its tooltip); a separate placeholder would sit under the
        resting label and double up.
      -->
      <div class="grid min-w-0 flex-1 grid-cols-2 items-start gap-2">
        <Field orientation="floating">
          <FieldLabel>
            <template
              v-if="whenDescription"
              #tooltip
            >
              <Markdown
                :source="whenDescription"
                block
                class="prose prose-sm prose-zinc"
              />
            </template>
            {{ whenTitle }}
          </FieldLabel>
          <ExpressionEditor
            :model-value="rule.when"
            result-type="bool"
            class="min-h-10"
            @update:model-value="(v) => updateWhen(index, v)"
          />
        </Field>

        <!--
          The message is a multi-row Locale — one row per language — so a single
          shared label can't center over the stack. `floating` gives each row
          its own per-language floating label instead; the "消息" semantic is
          carried by the column position opposite 校验条件.
        -->
        <LocaleField
          floating
          markdown
          translate-hint="form field validation message"
          :model-value="rule.message"
          @update:model-value="(v) => updateMessage(index, v)"
        />
      </div>

      <!--
        Severity switch — off (default) is error (blocks submit, reds the
        field), on is warning (yellow hint, never blocks). Stacked label keeps
        it self-describing in the narrow action column.
      -->
      <label
        class="mt-1 flex shrink-0 cursor-pointer flex-col items-center gap-1 text-xs text-zinc-600 select-none"
        data-slot="validation-rule-severity"
      >
        <Switch
          size="sm"
          :model-value="rule.warning ?? false"
          @update:model-value="(v) => updateWarning(index, v)"
        />
        仅警告
      </label>

      <Button
        variant="ghost"
        size="sm"
        class="mt-1.5 text-red-600 hover:text-red-700"
        aria-label="移除"
        @click="removeRule(index)"
      >
        <Icon icon="fluent:delete-20-regular" />
      </Button>
    </div>

    <Button
      type="button"
      data-slot="validation-add"
      class="w-full justify-center"
      @click="addRule"
    >
      <Icon icon="fluent:add-20-regular" />
      <span>添加校验规则</span>
    </Button>
  </div>
</template>

<style scoped>
[data-instruction=reorder-above]:before,[data-instruction=reorder-below]:after{background:var(--primary,#2563eb);content:"";height:2px;left:0;pointer-events:none;position:absolute;right:0;z-index:1}[data-instruction=reorder-above]:before{top:-5px}[data-instruction=reorder-below]:after{bottom:-5px}
</style>
