<script setup>
import { Icon } from "@iconify/vue";
import { computed, ref, useSlots, useTemplateRef, watch } from "vue";
import { buildCheckEnvironment, evaluateExpression } from "../../../share/expression";
import { injectCELContext, useScopeAncestry, useSelectionRoster } from "../../../utils/cel-context";
import { Markdown } from "../markdown";
import CodeMirrorInput from "./CodeMirrorInput.vue";
import { buildScopeLookup } from "./scope-refs";
import { buildSelectionLookup } from "./selection-refs";
import { buildScopeEntries, buildSelectionEntries, buildVarEntries } from "./picker-entries";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "../command";
import { InputGroup, InputGroupAddon, InputGroupButton } from "../input-group";
import { Popover, PopoverContent, PopoverTrigger } from "../popover";
defineOptions({ name: "UiExpressionEditor" });
const props = defineProps({
  class: { type: [Boolean, null, String, Object, Array], required: false, skipCheck: true },
  defaultValue: { type: String, required: false },
  modelValue: { type: String, required: false },
  multiline: { type: Boolean, required: false },
  placeholder: { type: String, required: false },
  resultType: { type: [String, Array], required: false },
  extraVars: { type: Object, required: false },
  unlistedVarsAreDyn: { type: Boolean, required: false }
});
const emits = defineEmits(["update:modelValue"]);
const celContext = injectCELContext();
const scopeAncestry = useScopeAncestry();
const selectionRoster = useSelectionRoster();
const varEntries = computed(() => buildVarEntries(celContext, props.extraVars));
const scopeEntries = computed(() => buildScopeEntries(scopeAncestry.value.slice(1)));
const selectionEntries = computed(() => buildSelectionEntries(selectionRoster.value));
const scopeLookup = computed(() => buildScopeLookup(scopeAncestry.value));
const selectionLookup = computed(() => buildSelectionLookup(selectionRoster.value));
const hasVars = computed(() => varEntries.value.length > 0 || scopeEntries.value.length > 0 || selectionEntries.value.length > 0);
const checkEnvironment = computed(() => {
  const vars = /* @__PURE__ */ new Map();
  for (const [name, entry] of Object.entries(celContext)) vars.set(name, entry.type);
  for (const [name, spec] of Object.entries(props.extraVars ?? {})) vars.set(name, spec.type);
  return buildCheckEnvironment(
    [...vars].map(([name, type]) => ({ name, type })),
    { unlistedVariablesAreDyn: props.unlistedVarsAreDyn }
  );
});
const validationError = computed(() => {
  const value = props.modelValue ?? props.defaultValue ?? "";
  if (value.trim() === "") return null;
  return evaluateExpression(checkEnvironment.value, value, props.resultType);
});
const open = ref(false);
const entries = ref([]);
const shownVars = computed(() => entries.value.filter((e) => e.group === "var"));
const shownScopes = computed(() => entries.value.filter((e) => e.group === "scope"));
const shownSelections = computed(() => entries.value.filter((e) => e.group === "selection"));
const hoveredName = ref(null);
const editorRef = useTemplateRef("editor");
function insertVariable(text) {
  editorRef.value?.insertAtSelection(text);
  open.value = false;
}
watch(open, (v) => {
  if (v) {
    entries.value = [...varEntries.value, ...selectionEntries.value, ...scopeEntries.value];
    hoveredName.value = entries.value[0]?.id ?? null;
  }
});
const hoveredEntry = computed(
  () => entries.value.find((e) => e.id === hoveredName.value) ?? null
);
const hoveredDescription = computed(() => hoveredEntry.value?.description ?? "");
const resultTypeLabel = computed(() => {
  const rt = props.resultType;
  if (!rt) return null;
  if (Array.isArray(rt)) return rt.length > 0 ? rt.join(" | ") : null;
  return rt;
});
const slots = useSlots();
const showAddon = computed(() => hasVars.value || !!resultTypeLabel.value || !!slots.trailing);
const addonAlign = computed(
  () => props.multiline ? "block-end" : "inline-end"
);
</script>

<template>
  <div class="flex flex-col gap-1.5">
    <InputGroup>
      <InputGroupAddon
        v-if="$slots.leading"
        align="inline-start"
      >
        <slot name="leading" />
      </InputGroupAddon>
      <CodeMirrorInput
        ref="editor"
        :model-value="props.modelValue"
        :default-value="props.defaultValue"
        :placeholder="props.placeholder"
        :multiline="props.multiline"
        :scope-lookup="scopeLookup"
        :selection-lookup="selectionLookup"
        :class="props.class"
        @update:model-value="(v) => emits('update:modelValue', v)"
      />
      <InputGroupAddon
        v-if="showAddon"
        :align="addonAlign"
        class="justify-end gap-1.5"
      >
        <Popover
          v-if="hasVars"
          v-model:open="open"
        >
          <PopoverTrigger as-child>
            <InputGroupButton
              size="icon-xs"
              aria-label="Inspect available variables"
            >
              <Icon icon="fluent:braces-variable-20-regular" />
            </InputGroupButton>
          </PopoverTrigger>
          <PopoverContent
            align="end"
            :side-offset="6"
            class="group/popover w-80 p-0 data-[side=top]:**:data-[slot=command-input-wrapper]:border-t data-[side=top]:**:data-[slot=command-input-wrapper]:border-b-0"
          >
            <Command class="rounded group-data-[side=top]/popover:flex-col-reverse">
              <CommandInput placeholder="搜索可用变量" />
              <CommandList class="max-h-48 overflow-y-auto">
                <CommandEmpty>No variables.</CommandEmpty>
                <CommandGroup>
                  <CommandItem
                    v-for="entry in shownVars"
                    :key="entry.id"
                    :value="entry.display"
                    class="cursor-pointer gap-2"
                    @select="insertVariable(entry.insert)"
                    @mouseenter="hoveredName = entry.id"
                    @focus="hoveredName = entry.id"
                  >
                    <Icon
                      icon="fluent:braces-variable-20-regular"
                      class="size-3.5 shrink-0 text-zinc-400"
                    />
                    <span class="font-mono text-xs text-zinc-800">{{ entry.display }}</span>
                    <span class="flex-1 truncate text-xs text-zinc-500">{{ entry.label }}</span>
                    <span class="rounded bg-purple-100 px-1.5 py-0.5 font-mono text-[10px] leading-none text-purple-700 select-none">
                      {{ entry.type }}
                    </span>
                  </CommandItem>
                </CommandGroup>
                <CommandGroup
                  v-if="shownSelections.length > 0"
                  heading="选中项"
                >
                  <CommandItem
                    v-for="entry in shownSelections"
                    :key="entry.id"
                    :value="`${entry.display} ${entry.id}`"
                    class="cursor-pointer gap-2"
                    @select="insertVariable(entry.insert)"
                    @mouseenter="hoveredName = entry.id"
                    @focus="hoveredName = entry.id"
                  >
                    <Icon
                      icon="fluent:cursor-click-20-regular"
                      class="size-3.5 shrink-0 text-teal-500"
                    />
                    <span class="flex-1 truncate text-xs text-zinc-700">{{ entry.display }}</span>
                    <span class="rounded bg-teal-100 px-1.5 py-0.5 font-mono text-[10px] leading-none text-teal-700 select-none">
                      {{ entry.type }}
                    </span>
                  </CommandItem>
                </CommandGroup>
                <CommandGroup
                  v-if="shownScopes.length > 0"
                  heading="跨层引用"
                >
                  <CommandItem
                    v-for="entry in shownScopes"
                    :key="entry.id"
                    :value="`${entry.display} ${entry.id}`"
                    class="cursor-pointer gap-2"
                    @select="insertVariable(entry.insert)"
                    @mouseenter="hoveredName = entry.id"
                    @focus="hoveredName = entry.id"
                  >
                    <Icon
                      icon="fluent:link-20-regular"
                      class="size-3.5 shrink-0 text-zinc-400"
                    />
                    <span class="flex-1 truncate text-xs text-zinc-700">{{ entry.display }}</span>
                    <span class="rounded bg-purple-100 px-1.5 py-0.5 font-mono text-[10px] leading-none text-purple-700 select-none">
                      {{ entry.type }}
                    </span>
                  </CommandItem>
                </CommandGroup>
              </CommandList>
              <div
                v-if="hoveredEntry"
                class="border-t border-zinc-200 px-3 py-2 group-data-[side=top]/popover:border-t-0 group-data-[side=top]/popover:border-b"
              >
                <div class="flex items-center gap-2">
                  <span class="text-xs font-medium text-zinc-700">{{ hoveredEntry.label }}</span>
                  <span class="ml-auto shrink-0 rounded bg-purple-100 px-1.5 py-0.5 font-mono text-[10px] leading-none text-purple-700 select-none">
                    {{ hoveredEntry.type }}
                  </span>
                </div>
                <Markdown
                  v-if="hoveredDescription"
                  :source="hoveredDescription"
                  class="prose prose-zinc prose-sm mt-1 text-xs"
                />
              </div>
            </Command>
          </PopoverContent>
        </Popover>
        <span
          v-if="resultTypeLabel"
          class="rounded bg-purple-100 px-1.5 py-0.5 font-mono text-[10px] leading-none text-purple-700 select-none"
        >
          {{ resultTypeLabel }}
        </span>
        <slot name="trailing" />
      </InputGroupAddon>
    </InputGroup>
    <p
      v-if="validationError"
      class="text-xs text-red-500"
    >
      {{ validationError }}
    </p>
  </div>
</template>
