<script setup>
import { Icon } from "@iconify/vue";
import { watchDebounced } from "@vueuse/core";
import { Effect, Option } from "effect";
import { Fetch } from "fx-fetch";
import { computed, ref } from "vue";
import { useI18n } from "vue-i18n";
import { asRequest, fetchJsonOption } from "../../../../../share/request";
import { cel as _rawCel } from "../../../../../utils/cel";
import { celBindings, injectCELContext } from "../../../../../utils/cel-context";
import { publishSelection } from "../../../../../utils/selections-registry";
import { interpolateMarkdown } from "../../../../table/utils/runtime";
import { getLocalizedText } from "../../../../../share/locale";
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList
} from "../../../../ui/command";
import { Field, FieldLabel, FieldMessages } from "../../../../ui/field";
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "../../../../ui/input-group";
import { Markdown } from "../../../../ui/markdown";
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "../../../../ui/popover";
import { DEFAULT_FIELD_ORIENTATION } from "../../../utils/common";
import { useFormReadonly } from "../../../utils/readonly";
import { useFormState } from "../../../utils/state";
import { useFieldValidation } from "../../../utils/validation";
defineOptions({ name: "ShwfedComboboxMultiFieldRuntime" });
const props = defineProps({
  fieldId: { type: String, required: true },
  config: { type: null, required: true }
});
const { locale, t } = useI18n({
  inheritLocale: true,
  messages: {
    zh: {
      "combobox-multi-placeholder": "\u8BF7\u9009\u62E9",
      "combobox-multi-search-placeholder": "\u641C\u7D22\u2026",
      "combobox-multi-empty": "\u65E0\u5339\u914D\u9879",
      "combobox-multi-loading": "\u52A0\u8F7D\u4E2D\u2026"
    },
    en: {
      "combobox-multi-placeholder": "Select\u2026",
      "combobox-multi-search-placeholder": "Search\u2026",
      "combobox-multi-empty": "No matches",
      "combobox-multi-loading": "Loading\u2026"
    },
    ja: {
      "combobox-multi-placeholder": "\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044",
      "combobox-multi-search-placeholder": "\u691C\u7D22\u2026",
      "combobox-multi-empty": "\u4E00\u81F4\u306A\u3057",
      "combobox-multi-loading": "\u8AAD\u307F\u8FBC\u307F\u4E2D\u2026"
    }
  }
});
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 placeholderText = computed(
  () => props.config.placeholder ? getLocalizedText(props.config.placeholder, locale.value) : t("combobox-multi-placeholder")
);
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;
  }
}
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 { isRequired, errors, warnings, visible } = useFieldValidation({
  fieldId: () => props.fieldId,
  config: () => props.config,
  $cel,
  locale: () => locale.value
});
const isStatic = computed(() => props.config.options.kind === "static");
function safeInterpolate(tpl, ctx) {
  try {
    return interpolateMarkdown(tpl, $cel, ctx);
  } catch (err) {
    console.error(`[shwfed-form] failed to interpolate option text for ${props.fieldId}:`, err);
    return tpl;
  }
}
const json = ref(null);
const isLoading = ref(false);
const requestSignature = computed(() => {
  const opts = props.config.options;
  if (opts.kind !== "remote") return null;
  const expr = opts.request;
  if (!expr) return null;
  try {
    const req = asRequest(Effect.runSync($cel(expr, { form: state.value ?? {} })));
    return Option.isSome(req) ? JSON.stringify(req.value.describe()) : null;
  } catch {
    return null;
  }
});
let fetchSeq = 0;
async function fetchOptions() {
  const seq = ++fetchSeq;
  if (requestSignature.value == null) {
    json.value = null;
    isLoading.value = false;
    return;
  }
  const opts = props.config.options;
  const expr = opts.kind === "remote" ? opts.request : void 0;
  if (!expr) {
    json.value = null;
    isLoading.value = false;
    return;
  }
  isLoading.value = true;
  const program = Effect.gen(function* () {
    const body = yield* fetchJsonOption(yield* $cel(expr, { form: state.value ?? {} }));
    return Option.getOrNull(body);
  });
  try {
    const result = await Effect.runPromise(Effect.provide(program, Fetch.layer));
    if (seq === fetchSeq) json.value = result;
  } catch {
    if (seq === fetchSeq) json.value = null;
  } finally {
    if (seq === fetchSeq) isLoading.value = false;
  }
}
function remoteValueOf(option) {
  const opts = props.config.options;
  if (opts.kind !== "remote") return void 0;
  try {
    return Effect.runSync($cel(opts.optionValue, { form: state.value ?? {}, option }));
  } catch (err) {
    console.error(`[shwfed-form] failed to evaluate optionValue for ${props.fieldId}:`, err);
    return void 0;
  }
}
function remoteLabelOf(option) {
  const opts = props.config.options;
  if (opts.kind !== "remote") return "";
  const tpl = getLocalizedText(opts.optionLabel, locale.value) ?? "";
  if (!tpl) return "";
  return interpolateMarkdown(tpl, $cel, { form: state.value ?? {}, option });
}
function remoteKeywordsOf(option) {
  const opts = props.config.options;
  if (opts.kind !== "remote" || !opts.optionKeywords) return void 0;
  try {
    const raw = Effect.runSync($cel(opts.optionKeywords, { form: state.value ?? {}, option }));
    if (!Array.isArray(raw)) return void 0;
    const joined = raw.map((k) => k == null ? "" : String(k)).filter(Boolean).join(" ");
    return joined.length > 0 ? joined : void 0;
  } catch (err) {
    console.error(`[shwfed-form] failed to evaluate optionKeywords for ${props.fieldId}:`, err);
    return void 0;
  }
}
const entries = computed(() => {
  const opts = props.config.options;
  if (opts.kind === "static") {
    return (opts.items ?? []).map((item) => {
      const labelTpl = getLocalizedText(item.label, locale.value) ?? "";
      const ctx = { form: state.value ?? {} };
      const kw = (item.keywords ?? []).filter(Boolean).join(" ");
      return {
        key: item.id,
        value: item.value.value,
        label: labelTpl ? safeInterpolate(labelTpl, ctx) : "",
        keywords: kw.length > 0 ? kw : void 0,
        raw: item
      };
    });
  }
  let raw;
  try {
    raw = Effect.runSync($cel(opts.options, { form: state.value ?? {}, json: json.value }));
  } catch (err) {
    console.error(`[shwfed-form] failed to evaluate options for ${props.fieldId}:`, err);
    return [];
  }
  if (!Array.isArray(raw)) return [];
  return raw.map((option, idx) => ({
    key: String(idx),
    value: remoteValueOf(option),
    label: remoteLabelOf(option),
    keywords: remoteKeywordsOf(option),
    raw: option
  }));
});
const uncontrolled = ref([]);
const model = computed({
  get: () => {
    const path = props.config.binding;
    const raw = path == null ? uncontrolled.value : getAt(path);
    return Array.isArray(raw) ? raw : [];
  },
  set: (next) => {
    const arr = Array.isArray(next) ? next : [];
    const path = props.config.binding;
    if (path == null) {
      uncontrolled.value = arr;
      return;
    }
    setAt(path, arr);
  }
});
watchDebounced(
  requestSignature,
  () => {
    void fetchOptions();
  },
  { debounce: 200, immediate: true }
);
function isEqual(a, b) {
  if (a === b) return true;
  if (a == null || b == null) return false;
  if (typeof a === "object" && typeof b === "object") {
    try {
      return JSON.stringify(a) === JSON.stringify(b);
    } catch {
      return false;
    }
  }
  return false;
}
const selectedEntries = computed(() => {
  const list = [];
  for (const v of model.value) {
    const entry = entries.value.find((e) => isEqual(e.value, v));
    if (entry) list.push(entry);
  }
  return list;
});
const selectedKeys = computed(() => selectedEntries.value.map((e) => e.key));
const selectedKeySet = computed(() => new Set(selectedKeys.value));
publishSelection(
  props.fieldId,
  () => selectedEntries.value.length > 0 ? selectedEntries.value : void 0,
  {
    identity: (list) => list.map((e) => e.key).join("\0"),
    raw: (list) => list.map((e) => e.raw)
  }
);
const triggerLabel = computed(() => {
  const arr = selectedEntries.value;
  if (arr.length === 0) return "";
  if (arr.length === 1) return arr[0].label;
  return `${arr[0].label} +${arr.length - 1}`;
});
const open = ref(false);
function handleSelect(next) {
  const keys = Array.isArray(next) ? next.filter((k) => typeof k === "string") : [];
  const mapped = [];
  for (const key of keys) {
    const entry = entries.value.find((e) => e.key === key);
    if (entry) mapped.push(entry.value);
  }
  model.value = mapped;
}
const showClear = computed(
  () => !isDisabled.value && selectedEntries.value.length > 0
);
function handleClear() {
  model.value = [];
  open.value = false;
}
</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"
    >
      {{ triggerLabel || "\u2014" }}
    </div>
    <Popover
      v-else
      v-model:open="open"
    >
      <PopoverAnchor as-child>
        <InputGroup
          class="group/combobox-multi"
          :data-disabled="isDisabled ? 'true' : void 0"
        >
          <PopoverTrigger as-child>
            <InputGroupInput
              :id="fieldId"
              :model-value="triggerLabel"
              :disabled="isDisabled"
              :placeholder="placeholderText"
              class="cursor-pointer text-left"
              readonly
            />
          </PopoverTrigger>
          <InputGroupAddon
            v-if="isLoading"
            align="inline-end"
            class="text-zinc-400"
          >
            <Icon
              icon="fluent:spinner-ios-20-regular"
              class="animate-spin"
            />
          </InputGroupAddon>
          <InputGroupAddon
            v-else-if="showClear"
            align="inline-end"
            class="[@media(hover:hover)]:opacity-0 transition-opacity group-hover/combobox-multi:opacity-100 focus-within:opacity-100"
          >
            <InputGroupButton
              size="icon-xs"
              data-slot="combobox-multi-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>
      </PopoverAnchor>
      <PopoverContent
        class="p-0"
        :style="{ width: 'var(--reka-popover-trigger-width)' }"
      >
        <Command
          :model-value="selectedKeys"
          :multiple="true"
          :disabled="isDisabled"
          @update:model-value="handleSelect"
        >
          <CommandInput :placeholder="t('combobox-multi-search-placeholder')" />
          <CommandList>
            <div
              v-if="!isStatic && entries.length === 0"
              data-slot="combobox-multi-empty"
              class="py-6 text-center text-sm text-zinc-500"
            >
              {{ isLoading ? t("combobox-multi-loading") : t("combobox-multi-empty") }}
            </div>
            <template v-else>
              <CommandEmpty class="py-6 text-center text-sm text-zinc-500">
                {{ t("combobox-multi-empty") }}
              </CommandEmpty>
              <CommandGroup>
                <CommandItem
                  v-for="entry in entries"
                  :key="entry.key"
                  :value="entry.key"
                  :keywords="entry.keywords"
                >
                  <span class="flex-1">
                    <Markdown :source="entry.label" />
                  </span>
                  <Icon
                    v-if="selectedKeySet.has(entry.key)"
                    icon="fluent:checkmark-20-regular"
                    class="size-4 text-zinc-700"
                  />
                </CommandItem>
              </CommandGroup>
            </template>
          </CommandList>
        </Command>
      </PopoverContent>
    </Popover>
    <FieldMessages
      v-if="visible"
      :errors="errors"
      :warnings="warnings"
    />
  </Field>
</template>
