<script setup>
import { Icon } from "@iconify/vue";
import { computed, ref, watch } from "vue";
import {
  useTreeDnd
} from "../../../../../composables/useTreeDnd";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger
} from "../../../../ui/dropdown-menu";
import { ExpressionEditor } from "../../../../ui/expression-editor";
import ValidationRulesField from "../../../ValidationRulesField.vue";
import { Button } from "../../../../ui/button";
import { Field, FieldLabel } from "../../../../ui/field";
import { IconPicker } from "../../../../ui/icon-picker";
import {
  InputGroup,
  InputGroupAddon,
  InputGroupButton,
  InputGroupInput,
  InputGroupNumberField
} from "../../../../ui/input-group";
import { Locale as LocaleField } from "../../../../ui/locale";
import { Markdown } from "../../../../ui/markdown";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue
} from "../../../../ui/select";
import { Switch } from "../../../../ui/switch";
import { getStructFieldDescription, getStructFieldTitle } from "../../../schema";
import { DEFAULT_FIELD_ORIENTATION, FIELD_ORIENTATION_OPTIONS } from "../../../utils/common";
import { FILES_VAR, FILE_VAR, JSON_VAR, UPLOAD_JSON_VAR, schema } from "./schema";
defineOptions({ name: "ShwfedUploadFieldConfig" });
const value = defineModel({ type: null, ...{ required: true } });
const fieldSchema = schema(() => {
});
const fieldTitle = (f) => getStructFieldTitle(fieldSchema, f) ?? f;
const fieldDescription = (f) => getStructFieldDescription(fieldSchema, f);
const pathText = computed({
  get: () => value.value.binding ?? "",
  set: (next) => {
    const trimmed = next.trim();
    if (trimmed.length === 0) {
      const { binding: _omit, ...rest } = value.value;
      value.value = rest;
    } else {
      value.value = { ...value.value, binding: trimmed };
    }
  }
});
const uploadEnabled = computed(() => value.value.upload != null);
function setUploadEnabled(on) {
  if (on) {
    if (value.value.upload) return;
    value.value = { ...value.value, upload: { request: "", handle: "" } };
  } else {
    const { upload: _omit, ...rest } = value.value;
    value.value = rest;
  }
}
const uploadRequestText = computed({
  get: () => value.value.upload?.request ?? "",
  set: (next) => {
    const current = value.value.upload;
    if (!current) return;
    value.value = { ...value.value, upload: { ...current, request: next } };
  }
});
const uploadHandleText = computed({
  get: () => value.value.upload?.handle ?? "",
  set: (next) => {
    const current = value.value.upload;
    if (!current) return;
    value.value = { ...value.value, upload: { ...current, handle: next } };
  }
});
const uploadFilenameText = computed({
  get: () => value.value.upload?.filename ?? "",
  set: (next) => {
    const current = value.value.upload;
    if (!current) return;
    if (next.length === 0) {
      const { filename: _omit, ...rest } = current;
      value.value = { ...value.value, upload: rest };
    } else {
      value.value = { ...value.value, upload: { ...current, filename: next } };
    }
  }
});
const templates = computed(() => value.value.templates ?? []);
function setTemplates(next) {
  if (next.length === 0) {
    const { templates: _omit, ...rest } = value.value;
    value.value = rest;
  } else {
    value.value = { ...value.value, templates: next };
  }
}
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(
  () => templates.value.length,
  (len) => {
    if (len === rowKeys.value.length) return;
    rowKeys.value = Array.from({ length: len }, () => makeId());
  },
  { immediate: true }
);
function addTemplate() {
  setTemplates([...templates.value, { request: "" }]);
  rowKeys.value = [...rowKeys.value, makeId()];
}
function removeTemplate(index) {
  setTemplates(templates.value.filter((_, i) => i !== index));
  rowKeys.value = rowKeys.value.filter((_, i) => i !== index);
}
function move(from, to) {
  if (from === to) return;
  if (from < 0 || to < 0 || from >= templates.value.length || to >= templates.value.length) return;
  const next = [...templates.value];
  const [entry] = next.splice(from, 1);
  if (!entry) return;
  next.splice(to, 0, entry);
  setTemplates(next);
  const keys = [...rowKeys.value];
  const [key] = keys.splice(from, 1);
  if (!key) return;
  keys.splice(to, 0, key);
  rowKeys.value = keys;
}
function patchTemplate(index, patch) {
  setTemplates(templates.value.map((entry, i) => i === index ? patch(entry) : entry));
}
const TEMPLATE_ROW_KIND = "shwfed-upload-template";
const rowId = (i) => rowKeys.value[i] ?? `idx-${i}`;
function onTemplateRowDrop(e) {
  if (e.source.kind !== TEMPLATE_ROW_KIND || e.target.kind !== TEMPLATE_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 templateDnd = useTreeDnd({ onRowDrop: onTemplateRowDrop });
const pickDragHandle = (el) => el.querySelector(".drag-handle");
function templateRowConfig(i) {
  return {
    kind: TEMPLATE_ROW_KIND,
    canDrop: (src) => src.kind === TEMPLATE_ROW_KIND,
    blockInstructions: (src) => {
      const block = ["make-child", "reparent"];
      if (src.id === rowId(i)) block.push("reorder-above", "reorder-below");
      return block;
    },
    dragHandle: pickDragHandle
  };
}
function updateTemplateRequest(index, request) {
  patchTemplate(index, (entry) => ({ ...entry, request }));
}
function updateTemplateDownload(index, download) {
  patchTemplate(index, (entry) => {
    if (download.length === 0) {
      const { download: _omit, ...rest } = entry;
      return rest;
    }
    return { ...entry, download };
  });
}
function updateTemplateIcon(index, icon) {
  patchTemplate(index, (entry) => {
    const trimmed = icon.trim();
    if (trimmed.length === 0) {
      const { icon: _omit, ...rest } = entry;
      return rest;
    }
    return { ...entry, icon: trimmed };
  });
}
function updateTemplateLabel(index, label) {
  patchTemplate(index, (entry) => {
    if (label == null || label.length === 0) {
      const { label: _omit, ...rest } = entry;
      return rest;
    }
    return { ...entry, label };
  });
}
const MIME_GROUPS = [
  { id: "pdf", label: "PDF", mimes: ["application/pdf"] },
  { id: "docx", label: "DOCX", mimes: ["application/vnd.openxmlformats-officedocument.wordprocessingml.document"] },
  { id: "doc", label: "DOC", mimes: ["application/msword"] },
  { id: "xlsx", label: "XLSX", mimes: ["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"] },
  { id: "xls", label: "XLS", mimes: ["application/vnd.ms-excel"] },
  { id: "pptx", label: "PPTX", mimes: ["application/vnd.openxmlformats-officedocument.presentationml.presentation"] },
  { id: "ppt", label: "PPT", mimes: ["application/vnd.ms-powerpoint"] },
  { id: "zip", label: "ZIP", mimes: ["application/zip", "application/x-zip-compressed"] },
  { id: "rar", label: "RAR", mimes: ["application/vnd.rar", "application/x-rar-compressed"] },
  { id: "ofd", label: "OFD", mimes: ["application/ofd"] },
  { id: "xml", label: "XML", mimes: ["application/xml"] },
  { id: "png", label: "PNG", mimes: ["image/png"] },
  { id: "jpeg", label: "JPEG", mimes: ["image/jpeg", "image/jpg"] }
];
const GROUP_BY_ID = new Map(MIME_GROUPS.map((g) => [g.id, g]));
const acceptList = computed(() => value.value.accept ?? []);
const selectedGroupIds = computed(
  () => MIME_GROUPS.filter((g) => g.mimes.every((m) => acceptList.value.includes(m))).map((g) => g.id)
);
const selectedGroupLabels = computed(
  () => selectedGroupIds.value.map((id) => GROUP_BY_ID.get(id)?.label).filter(Boolean).join("\u3001")
);
function onAcceptChange(next) {
  const ids = Array.isArray(next) ? next.filter((v) => typeof v === "string") : typeof next === "string" ? [next] : [];
  const mimes = ids.flatMap((id) => GROUP_BY_ID.get(id)?.mimes ?? []);
  if (mimes.length === 0) {
    const { accept: _omit, ...rest } = value.value;
    value.value = rest;
  } else {
    value.value = { ...value.value, accept: mimes };
  }
}
function pickUnitFor(bytes) {
  if (bytes === void 0) return "MB";
  return bytes < 1024 * 1024 ? "KB" : "MB";
}
function multiplierFor(unit) {
  return unit === "KB" ? 1024 : 1024 * 1024;
}
function makeSizeBinding(prop) {
  const unit = ref(pickUnitFor(value.value[prop]));
  const amount = computed({
    get: () => {
      const v = value.value[prop];
      return v === void 0 ? void 0 : v / multiplierFor(unit.value);
    },
    set: (next) => {
      if (next === void 0 || !(next > 0)) {
        const { [prop]: _omit, ...rest } = value.value;
        value.value = rest;
        return;
      }
      value.value = { ...value.value, [prop]: Math.round(next * multiplierFor(unit.value)) };
    }
  });
  return { unit, amount };
}
const perFile = makeSizeBinding("maxFileSize");
const total = makeSizeBinding("maxTotalSize");
function setMaxFiles(v) {
  if (v === void 0 || !(v > 0)) {
    const { maxFiles: _omit, ...rest } = value.value;
    value.value = rest;
  } else {
    value.value = { ...value.value, maxFiles: Math.round(v) };
  }
}
function setMultiple(v) {
  if (!v) {
    const { multiple: _omit, ...rest } = value.value;
    value.value = rest;
  } else {
    value.value = { ...value.value, multiple: true };
  }
}
</script>

<template>
  <div class="flex flex-col gap-3">
    <div class="grid grid-cols-2 gap-3">
      <Field orientation="vertical">
        <FieldLabel class="text-xs text-zinc-500">
          <template
            v-if="fieldDescription('displayName')"
            #tooltip
          >
            <Markdown
              :source="fieldDescription('displayName')"
              block
              class="prose prose-sm prose-zinc"
            />
          </template>
          {{ fieldTitle("displayName") }}
        </FieldLabel>
        <InputGroup>
          <InputGroupInput
            :model-value="value.displayName ?? ''"
            placeholder="例：附件"
            @update:model-value="(v) => {
  const s = String(v ?? '');
  value = { ...value, displayName: s.length > 0 ? s : void 0 };
}"
          />
        </InputGroup>
      </Field>

      <Field orientation="vertical">
        <FieldLabel class="text-xs text-zinc-500">
          <template #tooltip>
            <Markdown
              source="写入表单状态的嵌套键路径，使用 `.` 分隔，例如 `attachments.5du4fbuv`"
              block
              class="prose prose-sm prose-zinc"
            />
          </template>
          {{ fieldTitle("binding") }}
        </FieldLabel>
        <InputGroup>
          <InputGroupInput
            v-model="pathText"
            placeholder="例：attachments.5du4fbuv"
            class="font-mono"
          />
        </InputGroup>
      </Field>
    </div>

    <div class="grid grid-cols-3 gap-3">
      <Field orientation="vertical">
        <FieldLabel class="text-xs text-zinc-500">
          <template
            v-if="fieldDescription('label')"
            #tooltip
          >
            <Markdown
              :source="fieldDescription('label')"
              block
              class="prose prose-sm prose-zinc"
            />
          </template>
          {{ fieldTitle("label") }}
        </FieldLabel>
        <LocaleField
          translate-hint="form field label"
          :model-value="value.label"
          @update:model-value="(v) => value = { ...value, label: v }"
        />
      </Field>

      <Field orientation="vertical">
        <FieldLabel class="text-xs text-zinc-500">
          <template
            v-if="fieldDescription('tooltip')"
            #tooltip
          >
            <Markdown
              :source="fieldDescription('tooltip')"
              block
              class="prose prose-sm prose-zinc"
            />
          </template>
          {{ fieldTitle("tooltip") }}
        </FieldLabel>
        <LocaleField
          markdown
          translate-hint="form field tooltip"
          :model-value="value.tooltip"
          @update:model-value="(v) => value = { ...value, tooltip: v }"
        />
      </Field>

      <Field orientation="vertical">
        <FieldLabel class="text-xs text-zinc-500">
          <template
            v-if="fieldDescription('description')"
            #tooltip
          >
            <Markdown
              :source="fieldDescription('description')"
              block
              class="prose prose-sm prose-zinc"
            />
          </template>
          {{ fieldTitle("description") }}
        </FieldLabel>
        <LocaleField
          markdown
          translate-hint="upload zone description"
          :model-value="value.description"
          @update:model-value="(v) => value = { ...value, description: v }"
        />
      </Field>
    </div>

    <div class="grid grid-cols-3 gap-3">
      <Field orientation="vertical">
        <FieldLabel class="text-xs text-zinc-500">
          <template
            v-if="fieldDescription('orientation')"
            #tooltip
          >
            <Markdown
              :source="fieldDescription('orientation')"
              block
              class="prose prose-sm prose-zinc"
            />
          </template>
          {{ fieldTitle("orientation") }}
        </FieldLabel>
        <Select
          :model-value="value.orientation ?? DEFAULT_FIELD_ORIENTATION"
          @update:model-value="(v) => value = { ...value, orientation: v }"
        >
          <SelectTrigger class="w-full">
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            <SelectItem
              v-for="opt in FIELD_ORIENTATION_OPTIONS"
              :key="opt.value"
              :value="opt.value"
            >
              {{ opt.label }}
            </SelectItem>
          </SelectContent>
        </Select>
      </Field>

      <Field orientation="vertical">
        <FieldLabel class="text-xs text-zinc-500">
          <template
            v-if="fieldDescription('multiple')"
            #tooltip
          >
            <Markdown
              :source="fieldDescription('multiple')"
              block
              class="prose prose-sm prose-zinc"
            />
          </template>
          {{ fieldTitle("multiple") }}
        </FieldLabel>
        <div>
          <Switch
            :model-value="value.multiple ?? false"
            @update:model-value="setMultiple"
          />
        </div>
      </Field>

      <Field orientation="vertical">
        <FieldLabel class="text-xs text-zinc-500">
          <template
            v-if="fieldDescription('maxFiles')"
            #tooltip
          >
            <Markdown
              :source="fieldDescription('maxFiles')"
              block
              class="prose prose-sm prose-zinc"
            />
          </template>
          {{ fieldTitle("maxFiles") }}
        </FieldLabel>
        <InputGroup>
          <InputGroupNumberField
            :model-value="value.maxFiles"
            :min="1"
            @update:model-value="setMaxFiles"
          />
        </InputGroup>
      </Field>
    </div>

    <div class="grid grid-cols-3 gap-3">
      <Field orientation="vertical">
        <FieldLabel class="text-xs text-zinc-500">
          <template
            v-if="fieldDescription('accept')"
            #tooltip
          >
            <Markdown
              :source="fieldDescription('accept')"
              block
              class="prose prose-sm prose-zinc"
            />
          </template>
          {{ fieldTitle("accept") }}
        </FieldLabel>
        <Select
          multiple
          :model-value="selectedGroupIds"
          @update:model-value="onAcceptChange"
        >
          <SelectTrigger class="w-full">
            <SelectValue placeholder="不限制">
              <span v-if="selectedGroupLabels.length > 0">{{ selectedGroupLabels }}</span>
              <span
                v-else
                class="text-zinc-400"
              >不限制</span>
            </SelectValue>
          </SelectTrigger>
          <SelectContent>
            <SelectItem
              v-for="group in MIME_GROUPS"
              :key="group.id"
              :value="group.id"
            >
              {{ group.label }}
            </SelectItem>
          </SelectContent>
        </Select>
      </Field>

      <Field orientation="vertical">
        <FieldLabel class="text-xs text-zinc-500">
          <template
            v-if="fieldDescription('maxFileSize')"
            #tooltip
          >
            <Markdown
              :source="fieldDescription('maxFileSize')"
              block
              class="prose prose-sm prose-zinc"
            />
          </template>
          {{ fieldTitle("maxFileSize") }}
        </FieldLabel>
        <InputGroup>
          <InputGroupNumberField
            :model-value="perFile.amount.value"
            :min="0"
            @update:model-value="(v) => perFile.amount.value = v"
          />
          <InputGroupAddon align="inline-end">
            <DropdownMenu>
              <DropdownMenuTrigger as-child>
                <InputGroupButton
                  variant="ghost"
                  class="pr-1.5! text-xs"
                >
                  {{ perFile.unit.value }}
                  <Icon
                    icon="lucide:chevron-down"
                    class="size-3"
                  />
                </InputGroupButton>
              </DropdownMenuTrigger>
              <DropdownMenuContent align="end">
                <DropdownMenuItem @select="perFile.unit.value = 'KB'">
                  KB
                </DropdownMenuItem>
                <DropdownMenuItem @select="perFile.unit.value = 'MB'">
                  MB
                </DropdownMenuItem>
              </DropdownMenuContent>
            </DropdownMenu>
          </InputGroupAddon>
        </InputGroup>
      </Field>

      <Field orientation="vertical">
        <FieldLabel class="text-xs text-zinc-500">
          <template
            v-if="fieldDescription('maxTotalSize')"
            #tooltip
          >
            <Markdown
              :source="fieldDescription('maxTotalSize')"
              block
              class="prose prose-sm prose-zinc"
            />
          </template>
          {{ fieldTitle("maxTotalSize") }}
        </FieldLabel>
        <InputGroup>
          <InputGroupNumberField
            :model-value="total.amount.value"
            :min="0"
            @update:model-value="(v) => total.amount.value = v"
          />
          <InputGroupAddon align="inline-end">
            <DropdownMenu>
              <DropdownMenuTrigger as-child>
                <InputGroupButton
                  variant="ghost"
                  class="pr-1.5! text-xs"
                >
                  {{ total.unit.value }}
                  <Icon
                    icon="lucide:chevron-down"
                    class="size-3"
                  />
                </InputGroupButton>
              </DropdownMenuTrigger>
              <DropdownMenuContent align="end">
                <DropdownMenuItem @select="total.unit.value = 'KB'">
                  KB
                </DropdownMenuItem>
                <DropdownMenuItem @select="total.unit.value = 'MB'">
                  MB
                </DropdownMenuItem>
              </DropdownMenuContent>
            </DropdownMenu>
          </InputGroupAddon>
        </InputGroup>
      </Field>
    </div>

    <!-- Immediate upload -->
    <div class="flex items-center gap-2">
      <h3 class="text-xs font-medium text-zinc-500">
        {{ fieldTitle("upload") }}
      </h3>
      <Switch
        size="sm"
        :model-value="uploadEnabled"
        aria-label="启用即时上传"
        @update:model-value="setUploadEnabled"
      />
    </div>

    <div
      v-if="uploadEnabled"
      class="flex flex-col gap-3"
    >
      <div class="grid grid-cols-2 gap-3">
        <Field orientation="vertical">
          <FieldLabel class="text-xs text-zinc-500">
            <template #tooltip>
              <Markdown
                source="返回 `HttpRequest` 的 CEL 表达式，可用 `files` 引用本次选择的文件，例如 `http.post(url).body(form({&quot;files&quot;: files}))`"
                block
                class="prose prose-sm prose-zinc"
              />
            </template>
            上传请求
          </FieldLabel>
          <ExpressionEditor
            :model-value="uploadRequestText"
            placeholder="例：http.post('/api/upload').body(form({ 'files': files }))"
            result-type="HttpRequest"
            :extra-vars="{ files: FILES_VAR }"
            multiline
            class="min-h-16"
            @update:model-value="(v) => uploadRequestText = v"
          />
        </Field>

        <Field orientation="vertical">
          <FieldLabel class="text-xs text-zinc-500">
            <template #tooltip>
              <Markdown
                source="返回**列表**的 CEL 表达式，可用 `json` 引用上传响应；映射为追加到绑定值的文件项"
                block
                class="prose prose-sm prose-zinc"
              />
            </template>
            处理响应
          </FieldLabel>
          <ExpressionEditor
            :model-value="uploadHandleText"
            placeholder="例：json.data.map(d, { 'cacheKey': d.key, 'filename': d.fileOriginalName })"
            result-type="list"
            :extra-vars="{ json: UPLOAD_JSON_VAR }"
            multiline
            class="min-h-16"
            @update:model-value="(v) => uploadHandleText = v"
          />
        </Field>
      </div>

      <Field orientation="vertical">
        <FieldLabel class="text-xs text-zinc-500">
          <template #tooltip>
            <Markdown
              source="返回 `string` 的 CEL 表达式，可用 `file` 引用单个文件项；用于展示文件名。留空时回退到 `filename` / `name` 字段"
              block
              class="prose prose-sm prose-zinc"
            />
          </template>
          文件名
        </FieldLabel>
        <ExpressionEditor
          :model-value="uploadFilenameText"
          placeholder="例：file.filename"
          result-type="string"
          :extra-vars="{ file: FILE_VAR }"
          class="min-h-10"
          @update:model-value="(v) => uploadFilenameText = v"
        />
      </Field>
    </div>

    <!-- Download templates -->
    <Field orientation="vertical">
      <FieldLabel class="text-xs text-zinc-500">
        <template #tooltip>
          <Markdown
            :source="fieldDescription('templates') ?? '\u4E0B\u8F7D\u6A21\u677F\u5217\u8868\uFF1B\u6BCF\u4E00\u9879\u6E32\u67D3\u4E00\u4E2A\u4E0B\u8F7D\u6309\u94AE'"
            block
            class="prose prose-sm prose-zinc"
          />
        </template>
        {{ fieldTitle("templates") }}
      </FieldLabel>

      <div class="flex flex-col gap-2">
        <div
          v-for="(template, index) in templates"
          :key="rowKeys[index]"
          :ref="templateDnd.rowRef(rowId(index), templateRowConfig(index))"
          data-slot="upload-template"
          class="relative flex items-start gap-2 rounded border border-zinc-200 bg-zinc-50/40 p-2"
          :data-instruction="templateDnd.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="upload-template-drag-handle"
          />

          <div class="flex min-w-0 flex-1 flex-col gap-2">
            <div class="grid grid-cols-2 gap-2">
              <Field orientation="vertical">
                <FieldLabel class="text-xs text-zinc-500">
                  <template #tooltip>
                    <Markdown
                      source="返回 `HttpRequest` 的 CEL 表达式。未配置「下载请求」时直接发起并下载其响应"
                      block
                      class="prose prose-sm prose-zinc"
                    />
                  </template>
                  请求
                </FieldLabel>
                <ExpressionEditor
                  :model-value="template.request"
                  placeholder="例：http.get('https://api.example.com/template.xlsx')"
                  result-type="HttpRequest"
                  class="min-h-10"
                  @update:model-value="(v) => updateTemplateRequest(index, v)"
                />
              </Field>

              <Field orientation="vertical">
                <FieldLabel class="text-xs text-zinc-500">
                  <template #tooltip>
                    <Markdown
                      source="可选的第二步：用第一步响应 `json` 中的凭据构造真正的下载请求，例如 `json.data.key`"
                      block
                      class="prose prose-sm prose-zinc"
                    />
                  </template>
                  下载请求
                </FieldLabel>
                <ExpressionEditor
                  :model-value="template.download ?? ''"
                  placeholder="例：http.get('https://api.example.com/download').query('key', json.data.key)"
                  result-type="HttpRequest"
                  :extra-vars="{ json: JSON_VAR }"
                  class="min-h-10"
                  @update:model-value="(v) => updateTemplateDownload(index, v)"
                />
              </Field>
            </div>

            <div class="grid grid-cols-2 gap-2">
              <Field orientation="vertical">
                <FieldLabel class="text-xs text-zinc-500">
                  模板按钮图标
                </FieldLabel>
                <IconPicker
                  :model-value="template.icon ?? ''"
                  @update:model-value="(v) => updateTemplateIcon(index, v)"
                />
              </Field>

              <Field orientation="vertical">
                <FieldLabel class="text-xs text-zinc-500">
                  模板按钮文本
                </FieldLabel>
                <LocaleField
                  translate-hint="download template button label"
                  :model-value="template.label"
                  @update:model-value="(v) => updateTemplateLabel(index, v)"
                />
              </Field>
            </div>
          </div>

          <Button
            variant="ghost"
            size="sm"
            class="absolute top-1 right-1 size-6 p-0 text-red-600 hover:text-red-700"
            aria-label="移除模板"
            @click="removeTemplate(index)"
          >
            <Icon icon="fluent:delete-20-regular" />
          </Button>
        </div>

        <Button
          type="button"
          data-slot="upload-template-add"
          class="w-full justify-center"
          @click="addTemplate"
        >
          <Icon icon="fluent:add-20-regular" />
          <span>增加</span>
        </Button>
      </div>
    </Field>

    <div class="grid grid-cols-3 gap-3">
      <Field orientation="vertical">
        <FieldLabel class="text-xs text-zinc-500">
          <template #tooltip>
            <Markdown
              :source="fieldDescription('required') ?? '\u8FD4\u56DE `true` \u65F6\u663E\u793A\u5FC5\u586B\u661F\u53F7\u5E76\u8981\u6C42\u975E\u7A7A'"
              block
              class="prose prose-sm prose-zinc"
            />
          </template>
          {{ fieldTitle("required") }}
        </FieldLabel>
        <ExpressionEditor
          :model-value="value.required ?? ''"
          placeholder="例：form.type == 'company'"
          result-type="bool"
          class="min-h-10"
          @update:model-value="(v) => value = { ...value, required: v.length > 0 ? v : void 0 }"
        />
      </Field>

      <Field orientation="vertical">
        <FieldLabel class="text-xs text-zinc-500">
          <template #tooltip>
            <Markdown
              :source="fieldDescription('hidden') ?? '\u8FD4\u56DE `true` \u65F6\u5B57\u6BB5\u5728\u6240\u6709\u5E03\u5C40\u4E2D\u90FD\u4E0D\u6E32\u67D3'"
              block
              class="prose prose-sm prose-zinc"
            />
          </template>
          {{ fieldTitle("hidden") }}
        </FieldLabel>
        <ExpressionEditor
          :model-value="value.hidden ?? ''"
          placeholder="例：form.role == 'guest'"
          result-type="bool"
          class="min-h-10"
          @update:model-value="(v) => value = { ...value, hidden: v.length > 0 ? v : void 0 }"
        />
      </Field>

      <Field orientation="vertical">
        <FieldLabel class="text-xs text-zinc-500">
          <template #tooltip>
            <Markdown
              :source="fieldDescription('disabled') ?? '\u8FD4\u56DE `true` \u65F6\u4E0A\u4F20\u533A\u57DF\u4ECD\u7136\u6E32\u67D3\u4F46\u4E0D\u53EF\u64CD\u4F5C'"
              block
              class="prose prose-sm prose-zinc"
            />
          </template>
          {{ fieldTitle("disabled") }}
        </FieldLabel>
        <ExpressionEditor
          :model-value="value.disabled ?? ''"
          placeholder="例：form.status == 'locked'"
          result-type="bool"
          class="min-h-10"
          @update:model-value="(v) => value = { ...value, disabled: v.length > 0 ? v : void 0 }"
        />
      </Field>

      <Field orientation="vertical">
        <FieldLabel class="text-xs text-zinc-500">
          <template #tooltip>
            <Markdown
              :source="fieldDescription('readonly') ?? '\u8FD4\u56DE `true` \u65F6\u4EC5\u4EE5\u6587\u4EF6\u5217\u8868\u5C55\u793A\u5F53\u524D\u503C'"
              block
              class="prose prose-sm prose-zinc"
            />
          </template>
          {{ fieldTitle("readonly") }}
        </FieldLabel>
        <ExpressionEditor
          :model-value="value.readonly ?? ''"
          placeholder="例：form.id != null"
          result-type="bool"
          class="min-h-10"
          @update:model-value="(v) => value = { ...value, readonly: v.length > 0 ? v : void 0 }"
        />
      </Field>
    </div>

    <Field orientation="vertical">
      <FieldLabel class="text-xs text-zinc-500">
        <template #tooltip>
          <Markdown
            :source="fieldDescription('validations')"
            block
            class="prose prose-sm prose-zinc"
          />
        </template>
        {{ fieldTitle("validations") }}
      </FieldLabel>
      <ValidationRulesField
        :model-value="value.validations"
        @update:model-value="(v) => value = { ...value, validations: v.length > 0 ? v : void 0 }"
      />
    </Field>
  </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>
