<script setup>
import { Icon } from "@iconify/vue";
import { Effect } from "effect";
import { computed, nextTick, onBeforeUnmount, ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { cel as _rawCel } from "../../../../../utils/cel";
import { celBindings, injectCELContext } from "../../../../../utils/cel-context";
import { getLocalizedText } from "../../../../../share/locale";
import { Field, FieldLabel, FieldMessages } from "../../../../ui/field";
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupNumberField } from "../../../../ui/input-group";
import { Markdown } from "../../../../ui/markdown";
import { useCommitBus } from "../../../utils/commit-bus";
import { DEFAULT_FIELD_ORIENTATION } from "../../../utils/common";
import { useFieldValidation } from "../../../utils/validation";
import { useFormReadonly } from "../../../utils/readonly";
import { useFormState } from "../../../utils/state";
defineOptions({ name: "ShwfedNumberRangeFieldRuntime" });
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 startPlaceholderText = computed(
  () => props.config.startPlaceholder ? getLocalizedText(props.config.startPlaceholder, locale.value) : void 0
);
const endPlaceholderText = computed(
  () => props.config.endPlaceholder ? getLocalizedText(props.config.endPlaceholder, locale.value) : void 0
);
const tooltipText = computed(
  () => props.config.tooltip ? getLocalizedText(props.config.tooltip, locale.value) : void 0
);
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;
  }
}
function evalNumber(expression, label) {
  if (!expression) return void 0;
  try {
    const result = Effect.runSync($cel(expression, { form: state.value ?? {} }));
    return typeof result === "number" && Number.isFinite(result) ? result : void 0;
  } catch (err) {
    console.error(`[shwfed-form] failed to evaluate ${label} for ${props.fieldId}:`, err);
    return void 0;
  }
}
const isDisabled = computed(() => evalBool(props.config.disabled, "disabled"));
const formReadonly = useFormReadonly();
const isReadonly = computed(
  () => props.config.readonly != null ? evalBool(props.config.readonly, "readonly") : formReadonly.value
);
const effectiveReadonly = computed(() => isReadonly.value || props.config.derived?.mode === "formula");
const minValue = computed(() => evalNumber(props.config.min, "min"));
const maxValue = computed(() => evalNumber(props.config.max, "max"));
const { isRequired, errors, warnings, visible } = useFieldValidation({
  fieldId: () => props.fieldId,
  config: () => props.config,
  $cel,
  locale: () => locale.value
});
const formatOptions = computed(() => ({
  useGrouping: false,
  maximumFractionDigits: 20
}));
function asNumber(raw) {
  if (typeof raw === "number" && Number.isFinite(raw)) return raw;
  if (typeof raw === "string" && raw.length > 0) {
    const n = Number(raw);
    return Number.isFinite(n) ? n : void 0;
  }
  return void 0;
}
function asRange(raw) {
  if (!Array.isArray(raw) || raw.length !== 2) return void 0;
  const a = asNumber(raw[0]);
  const b = asNumber(raw[1]);
  if (a == null || b == null) return void 0;
  return [a, b];
}
function shape(n) {
  return props.config.valueAsString ? String(n) : n;
}
const uncontrolled = ref(void 0);
const model = computed({
  get: () => {
    const binding = props.config.binding;
    if (binding == null) return uncontrolled.value;
    if (typeof binding === "string") return asRange(getAt(binding));
    const a = asNumber(getAt(binding[0]));
    const b = asNumber(getAt(binding[1]));
    if (a == null || b == null) return void 0;
    return [a, b];
  },
  set: (next) => {
    const binding = props.config.binding;
    if (binding == null) {
      uncontrolled.value = next;
      return;
    }
    if (typeof binding === "string") {
      setAt(binding, next ? [shape(next[0]), shape(next[1])] : null);
      return;
    }
    setAt(binding[0], next ? shape(next[0]) : null);
    setAt(binding[1], next ? shape(next[1]) : null);
  }
});
const localStart = ref(model.value?.[0]);
const localEnd = ref(model.value?.[1]);
watch(model, (next) => {
  localStart.value = next?.[0];
  localEnd.value = next?.[1];
});
function commit() {
  const s = localStart.value;
  const e = localEnd.value;
  if (typeof s === "number" && Number.isFinite(s) && typeof e === "number" && Number.isFinite(e)) {
    const current = model.value;
    if (current && current[0] === s && current[1] === e) return;
    model.value = [s, e];
  } else if (model.value !== void 0) {
    model.value = void 0;
  }
}
const unregisterCommit = useCommitBus().register(commit);
onBeforeUnmount(() => {
  unregisterCommit();
  commit();
});
function onStartUpdate(v) {
  localStart.value = typeof v === "number" && Number.isFinite(v) ? v : void 0;
}
function onEndUpdate(v) {
  localEnd.value = typeof v === "number" && Number.isFinite(v) ? v : void 0;
}
const startMax = computed(() => {
  const e = localEnd.value;
  const m = maxValue.value;
  if (typeof e === "number" && typeof m === "number") return Math.min(e, m);
  if (typeof e === "number") return e;
  return m;
});
const endMin = computed(() => {
  const s = localStart.value;
  const m = minValue.value;
  if (typeof s === "number" && typeof m === "number") return Math.max(s, m);
  if (typeof s === "number") return s;
  return m;
});
function applyRounding(n, precision, mode) {
  const factor = 10 ** precision;
  switch (mode) {
    case "floor":
      return Math.floor(n * factor) / factor;
    case "ceil":
      return Math.ceil(n * factor) / factor;
    default:
      return Math.round(n * factor) / factor;
  }
}
async function onBlurSide(side) {
  await nextTick();
  const precision = props.config.precision;
  if (precision !== void 0) {
    const ref_ = side === "start" ? localStart : localEnd;
    const current = ref_.value;
    if (current !== void 0) {
      const rounded = applyRounding(current, precision, props.config.roundingMode ?? "round");
      if (rounded !== current) ref_.value = rounded;
    }
  }
  commit();
}
const showClear = computed(
  () => !isDisabled.value && (localStart.value !== void 0 || localEnd.value !== void 0)
);
function handleClear() {
  localStart.value = void 0;
  localEnd.value = void 0;
  if (model.value !== void 0) model.value = void 0;
}
const readonlyText = computed(() => {
  const v = model.value;
  if (!v) return "\u2014";
  return `${v[0]} ~ ${v[1]}`;
});
</script>

<template>
  <Field
    :orientation="config.orientation ?? DEFAULT_FIELD_ORIENTATION"
    :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
      v-if="effectiveReadonly"
      class="min-h-9 py-1.5 text-sm text-zinc-700"
    >
      {{ readonlyText }}
    </div>
    <InputGroup
      v-else
      class="group/number-range-field"
      :data-disabled="isDisabled ? 'true' : void 0"
    >
      <InputGroupNumberField
        :id="fieldId"
        :model-value="localStart"
        :step="config.step"
        :step-snapping="config.step !== void 0"
        :min="minValue"
        :max="startMax"
        :format-options="formatOptions"
        :disabled="isDisabled"
        :placeholder="startPlaceholderText"
        @update:model-value="onStartUpdate"
        @blur="onBlurSide('start')"
      />
      <Icon
        :icon="config.rangeSeparatorIcon ?? 'lucide:arrow-right'"
        class="mx-1 size-3.5 shrink-0 text-zinc-400"
      />
      <InputGroupNumberField
        :model-value="localEnd"
        :step="config.step"
        :step-snapping="config.step !== void 0"
        :min="endMin"
        :max="maxValue"
        :format-options="formatOptions"
        :disabled="isDisabled"
        :placeholder="endPlaceholderText"
        @update:model-value="onEndUpdate"
        @blur="onBlurSide('end')"
      />
      <InputGroupAddon
        v-if="showClear"
        align="inline-end"
        class="[@media(hover:hover)]:opacity-0 transition-opacity group-hover/number-range-field:opacity-100 focus-within:opacity-100"
      >
        <InputGroupButton
          size="icon-xs"
          data-slot="number-range-field-clear"
          class="text-zinc-500 hover:text-zinc-700"
          tabindex="-1"
          @mousedown.prevent
          @click.stop="handleClear"
        >
          <Icon icon="fluent:dismiss-20-regular" />
        </InputGroupButton>
      </InputGroupAddon>
    </InputGroup>
    <FieldMessages
      v-if="visible"
      :errors="errors"
      :warnings="warnings"
    />
  </Field>
</template>
