<script setup>
import { Icon } from "@iconify/vue";
import { Effect } from "effect";
import { computed, ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { cel as _rawCel } from "../../../../../utils/cel";
import { celBindings, injectCELContext } from "../../../../../utils/cel-context";
import { interpolate } from "../../../../../utils/interpolate";
import { getLocalizedText } from "../../../../../share/locale";
import {
  useTreeDnd
} from "../../../../../composables/useTreeDnd";
import { Button } from "../../../../ui/button";
import { Field, FieldLabel, FieldMessages } from "../../../../ui/field";
import { InputGroupButton } from "../../../../ui/input-group";
import { Markdown } from "../../../../ui/markdown";
import { useFieldValidation } from "../../../utils/validation";
import { SELF_BINDING, useFormState } from "../../../utils/state";
import Row from "./row.vue";
defineOptions({ name: "ShwfedListFieldRuntime" });
const props = defineProps({
  fieldId: { type: String, required: true },
  config: { type: null, required: true }
});
const { locale } = useI18n();
const { state, getAt, setAt } = useFormState();
const inherited = injectCELContext();
const $cel = (expression, context) => _rawCel(expression, { ...celBindings(inherited), ...context });
const labelText = computed(() => getLocalizedText(props.config.label, locale.value));
const tooltipText = computed(
  () => props.config.tooltip ? getLocalizedText(props.config.tooltip, locale.value) : void 0
);
const addLabelText = computed(
  () => getLocalizedText(props.config.addLabel, locale.value) || "\u65B0\u589E"
);
const summaryTemplate = computed(() => getLocalizedText(props.config.locale, locale.value));
const collapsible = computed(() => !!summaryTemplate.value);
function evalBool(expression, label) {
  if (!expression) return false;
  try {
    return Effect.runSync($cel(expression, { form: state.value ?? {} }));
  } catch (err) {
    console.error(`[shwfed-form] failed to evaluate ${label} for ${props.fieldId}:`, err);
    return false;
  }
}
const { isRequired, errors, warnings, visible } = useFieldValidation({
  fieldId: () => props.fieldId,
  config: () => props.config,
  $cel,
  locale: () => locale.value
});
const isDisabled = computed(() => evalBool(props.config.disabled, "disabled"));
const reorderable = computed(() => props.config.reorderable ?? true);
const isScalarRow = computed(() => {
  const fields = props.config.unit.fields;
  if (fields.length !== 1) return false;
  const only = fields[0];
  return only.binding === SELF_BINDING;
});
const localItems = ref([]);
const items = computed(() => {
  const path = props.config.binding;
  if (path == null) return localItems.value;
  const raw = getAt(path);
  if (!Array.isArray(raw)) return [];
  return raw;
});
const rowSummaries = computed(() => {
  const template = summaryTemplate.value;
  if (template == null) return [];
  return items.value.map(
    (item, i) => interpolate(template, (expression) => {
      try {
        const out = Effect.runSync($cel(expression, {
          form: state.value ?? {},
          item: item ?? {},
          index: i
        }));
        return out == null ? "" : String(out);
      } catch (err) {
        console.error(
          `[shwfed-form] list field: failed to evaluate summary expression \`${expression}\` for ${props.fieldId}[${i}]:`,
          err
        );
        return "";
      }
    })
  );
});
function writeItems(next) {
  const path = props.config.binding;
  if (path == null) {
    localItems.value = next;
    return;
  }
  setAt(path, 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(
  () => items.value.length,
  (len) => {
    if (len === rowKeys.value.length) return;
    rowKeys.value = Array.from({ length: len }, () => makeId());
  },
  { immediate: true }
);
const expandedKeys = ref(/* @__PURE__ */ new Set());
function isExpanded(key) {
  return expandedKeys.value.has(key);
}
function toggleExpanded(key) {
  const next = new Set(expandedKeys.value);
  if (next.has(key)) next.delete(key);
  else next.add(key);
  expandedKeys.value = next;
}
const atMax = computed(
  () => props.config.max != null && items.value.length >= props.config.max
);
const atMin = computed(
  () => (props.config.min ?? 0) > 0 && items.value.length <= (props.config.min ?? 0)
);
const canAdd = computed(() => !isDisabled.value && !atMax.value);
const canRemove = computed(() => !isDisabled.value && !atMin.value);
const canReorder = computed(() => !isDisabled.value && reorderable.value);
function append() {
  if (!canAdd.value) return;
  const seed = isScalarRow.value ? null : {};
  writeItems([...items.value, seed]);
  const id = makeId();
  rowKeys.value = [...rowKeys.value, id];
  const next = new Set(expandedKeys.value);
  next.add(id);
  expandedKeys.value = next;
}
function removeAt(i) {
  if (!canRemove.value) return;
  if (i < 0 || i >= items.value.length) return;
  const removedKey = rowKeys.value[i];
  writeItems(items.value.filter((_, j) => j !== i));
  rowKeys.value = rowKeys.value.filter((_, j) => j !== i);
  if (removedKey != null && expandedKeys.value.has(removedKey)) {
    const next = new Set(expandedKeys.value);
    next.delete(removedKey);
    expandedKeys.value = next;
  }
}
function moveRow(from, to) {
  if (!canReorder.value) return;
  if (from === to) return;
  if (from < 0 || to < 0 || from >= items.value.length || to >= items.value.length) return;
  const next = [...items.value];
  const [item] = next.splice(from, 1);
  if (!item) return;
  next.splice(to, 0, item);
  writeItems(next);
  const keys = [...rowKeys.value];
  const [key] = keys.splice(from, 1);
  if (!key) return;
  keys.splice(to, 0, key);
  rowKeys.value = keys;
}
function updateRow(i, next) {
  if (i < 0 || i >= items.value.length) return;
  const arr = [...items.value];
  arr[i] = next;
  writeItems(arr);
}
const ROW_KIND = "shwfed-list-row";
const rowId = (i) => rowKeys.value[i] ?? `idx-${i}`;
function onRowDrop(e) {
  if (!canReorder.value) return;
  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") moveRow(from, from < to ? to - 1 : to);
  else if (e.instruction === "reorder-below") moveRow(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>
  <Field
    orientation="vertical"
    :data-disabled="isDisabled || void 0"
  >
    <FieldLabel
      v-if="labelText"
      :for="fieldId"
      class="text-xs text-zinc-500"
    >
      <template
        v-if="tooltipText"
        #tooltip
      >
        <Markdown
          :source="tooltipText"
          class="prose prose-xs prose-zinc"
        />
      </template>
      {{ labelText }}<span
        v-if="isRequired"
        aria-hidden="true"
        class="text-red-600"
      > *</span>
    </FieldLabel>

    <div class="flex flex-col gap-3">
      <div
        v-for="(item, i) in items"
        :key="rowKeys[i]"
        :ref="dnd.rowRef(rowId(i), rowConfig(i))"
        class="relative rounded border border-zinc-200 bg-zinc-50/40 p-3"
        data-slot="list-row"
        :data-instruction="dnd.instructionFor(rowId(i)) ?? void 0"
      >
        <div class="flex items-center gap-2">
          <Icon
            v-if="reorderable"
            icon="fluent:re-order-dots-vertical-20-regular"
            class="drag-handle size-4 shrink-0 text-zinc-400"
            :class="canReorder ? 'cursor-grab' : 'cursor-not-allowed opacity-50'"
            data-slot="list-row-drag-handle"
          />

          <InputGroupButton
            v-if="collapsible"
            size="icon-xs"
            class="shrink-0"
            data-slot="list-row-collapse"
            :aria-expanded="isExpanded(rowId(i))"
            :aria-label="isExpanded(rowId(i)) ? '\u6298\u53E0' : '\u5C55\u5F00'"
            @click="toggleExpanded(rowId(i))"
          >
            <Icon
              :icon="isExpanded(rowId(i)) ? 'fluent:chevron-down-20-regular' : 'fluent:chevron-right-20-regular'"
            />
          </InputGroupButton>

          <div
            v-if="collapsible"
            class="min-w-0 flex-1"
            data-slot="list-row-summary"
          >
            <Markdown
              :source="rowSummaries[i] ?? ''"
              class="prose prose-xs prose-zinc"
            />
          </div>
          <div
            v-else
            class="min-w-0 flex-1"
          >
            <Row
              :model-value="item"
              :unit="config.unit"
              :index="i"
              @update:model-value="(next) => updateRow(i, next)"
            />
          </div>

          <InputGroupButton
            variant="destructive"
            size="icon-xs"
            class="shrink-0"
            :disabled="!canRemove"
            data-slot="list-row-remove"
            aria-label="删除"
            @click="removeAt(i)"
          >
            <Icon icon="fluent:delete-20-regular" />
          </InputGroupButton>
        </div>

        <div
          v-if="collapsible"
          v-show="isExpanded(rowId(i))"
          class="mt-3 min-w-0"
          data-slot="list-row-content"
        >
          <Row
            :model-value="item"
            :unit="config.unit"
            :index="i"
            @update:model-value="(next) => updateRow(i, next)"
          />
        </div>
      </div>

      <Button
        type="button"
        :disabled="!canAdd"
        data-slot="list-add"
        class="w-full justify-center"
        @click="append"
      >
        <Icon icon="fluent:add-20-regular" />
        <span>{{ addLabelText }}</span>
      </Button>
    </div>
    <FieldMessages
      v-if="visible"
      :errors="errors"
      :warnings="warnings"
    />
  </Field>
</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:-7px}[data-instruction=reorder-below]:after{bottom:-7px}
</style>
