<script setup>
import { Icon } from "@iconify/vue";
import { Effect, Fiber, Option } from "effect";
import { computed, ref, shallowRef, watch } from "vue";
import { useI18n } from "vue-i18n";
import { cel as _rawCel } from "../../../../../utils/cel";
import { celBindings, injectCELContext } from "../../../../../utils/cel-context";
import { publishSelection } from "../../../../../utils/selections-registry";
import { getLocalizedText } from "../../../../../share/locale";
import { fetchJsonOption } from "../../../../../share/request";
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "../../../../ui/input-group";
import { Markdown } from "../../../../ui/markdown";
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "../../../../ui/popover";
import { Skeleton } from "../../../../ui/skeleton";
import { Tree as UiTree } from "../../../../ui/tree";
import { useFormState } from "../../../../form/utils/state";
import { interpolateMarkdown } from "../../../utils/runtime";
import { useSharedFetchLayer } from "../../../utils/shared-fetch";
import {
  findWrappedPath,
  getWrappedChildren,
  wrapStructuralAll
} from "../tree-combobox-shared";
defineOptions({ name: "ShwfedTableTreeComboboxSingleRendererRuntime" });
const props = defineProps({
  column: { type: null, required: true },
  ctx: { type: Object, required: true }
});
const { locale, t } = useI18n({
  inheritLocale: true,
  messages: {
    zh: {
      "tree-combobox-single-placeholder": "\u8BF7\u9009\u62E9",
      "tree-combobox-single-search-placeholder": "\u641C\u7D22\u2026",
      "tree-combobox-single-empty": "\u65E0\u53EF\u9009\u9879",
      "tree-combobox-single-load-error": "\u52A0\u8F7D\u5931\u8D25",
      "tree-combobox-single-retry": "\u91CD\u8BD5"
    },
    en: {
      "tree-combobox-single-placeholder": "Select\u2026",
      "tree-combobox-single-search-placeholder": "Search\u2026",
      "tree-combobox-single-empty": "No options",
      "tree-combobox-single-load-error": "Failed to load",
      "tree-combobox-single-retry": "Retry"
    },
    ja: {
      "tree-combobox-single-placeholder": "\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044",
      "tree-combobox-single-search-placeholder": "\u691C\u7D22\u2026",
      "tree-combobox-single-empty": "\u9078\u629E\u80A2\u306A\u3057",
      "tree-combobox-single-load-error": "\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
      "tree-combobox-single-retry": "\u518D\u8A66\u884C"
    }
  }
});
const formState = useFormState();
const inherited = injectCELContext();
const $cel = (expression, context) => _rawCel(expression, { ...celBindings(inherited), ...context });
const sharedFetchLayer = useSharedFetchLayer();
const row = computed(() => props.ctx.row.original);
const rowIndex = computed(() => props.ctx.row.index);
const placeholderText = computed(
  () => props.column.placeholder ? getLocalizedText(props.column.placeholder, locale.value) : t("tree-combobox-single-placeholder")
);
function evalBool(expression, label) {
  if (!expression) return false;
  try {
    return Effect.runSync($cel(expression, { row: row.value, index: rowIndex.value })) === true;
  } catch (e) {
    console.error(`[shwfed-table] tree-combobox-single ${label} failed`, e);
    return false;
  }
}
const isHidden = computed(() => evalBool(props.column.hidden, "hidden"));
const isDisabled = computed(() => evalBool(props.column.disabled, "disabled"));
const isReadonly = computed(() => evalBool(props.column.readonly, "readonly"));
const effectiveReadonly = computed(
  () => isReadonly.value || props.column.derived?.mode === "formula"
);
const model = computed({
  get: () => {
    const raw = formState.getAt(props.column.binding);
    if (raw == null) return void 0;
    return String(raw);
  },
  set: (next) => {
    formState.setAt(props.column.binding, next == null ? void 0 : String(next));
  }
});
function nodeCtx(raw) {
  return { row: row.value, index: rowIndex.value, node: raw };
}
function nodeKey(raw) {
  try {
    return String(Effect.runSync($cel(props.column.nodeKey, nodeCtx(raw))));
  } catch (e) {
    console.error("[shwfed-table] tree-combobox-single nodeKey failed", e);
    return "";
  }
}
function nodeChildrenList(raw) {
  let v;
  try {
    v = Effect.runSync($cel(props.column.nodeChildren, nodeCtx(raw)));
  } catch (e) {
    console.error("[shwfed-table] tree-combobox-single nodeChildren failed", e);
    return void 0;
  }
  if (v == null) return void 0;
  if (!Array.isArray(v)) return void 0;
  return v;
}
function safeInterpolate(tpl, raw) {
  try {
    return interpolateMarkdown(tpl, $cel, nodeCtx(raw));
  } catch (e) {
    console.error("[shwfed-table] tree-combobox-single node interpolation failed", e);
    return tpl;
  }
}
function wrappedLabelText(w) {
  const tpl = getLocalizedText(props.column.nodeLabel, locale.value) ?? "";
  return tpl ? safeInterpolate(tpl, w.raw) : "";
}
function wrappedTooltipText(w) {
  if (!props.column.nodeTooltip) return void 0;
  const tpl = getLocalizedText(props.column.nodeTooltip, locale.value);
  return tpl ? safeInterpolate(tpl, w.raw) : void 0;
}
function wrappedKeywords(w) {
  const expr = props.column.keywords;
  if (!expr) return [];
  let v;
  try {
    v = Effect.runSync($cel(expr, nodeCtx(w.raw)));
  } catch (e) {
    console.error("[shwfed-table] tree-combobox-single keywords failed", e);
    return [];
  }
  if (!Array.isArray(v)) return [];
  return v.filter((k) => k != null).map((k) => String(k).trim().toLowerCase()).filter((k) => k.length > 0);
}
function wrappedSelectable(w, _depth) {
  if (isDisabled.value) return false;
  const expr = props.column.nodeSelectable;
  if (!expr) return true;
  try {
    return Effect.runSync($cel(expr, nodeCtx(w.raw)));
  } catch {
    return false;
  }
}
const rawRoots = shallowRef([]);
const loading = ref(false);
const fetchError = ref(null);
async function fetchTree() {
  const dataSource = props.column.dataSource;
  if (!dataSource) {
    rawRoots.value = [];
    return;
  }
  const effect = Effect.gen(function* () {
    let jsonOpt = Option.none();
    if (dataSource.request) {
      jsonOpt = yield* fetchJsonOption(yield* $cel(dataSource.request, {
        row: row.value,
        index: rowIndex.value
      }));
    }
    const dataRaw = yield* $cel(dataSource.data, {
      row: row.value,
      index: rowIndex.value,
      json: jsonOpt
    });
    return Array.isArray(dataRaw) ? dataRaw : [];
  });
  const program = Effect.gen(function* () {
    const showMaskFiber = yield* Effect.fork(
      Effect.sync(() => {
        loading.value = true;
      }).pipe(Effect.delay("30 millis"))
    );
    return yield* Effect.ensuring(effect, Fiber.interrupt(showMaskFiber));
  }).pipe(
    Effect.ensuring(Effect.sync(() => {
      loading.value = false;
    }))
  );
  try {
    const result = await Effect.runPromise(Effect.provide(program, sharedFetchLayer));
    rawRoots.value = result;
    fetchError.value = null;
  } catch (e) {
    console.warn("[shwfed-table] tree-combobox-single dataSource fetch failed", e);
    fetchError.value = e;
  }
}
watch(
  () => [props.column.dataSource?.request, props.column.dataSource?.data],
  () => {
    void fetchTree();
  },
  { immediate: true }
);
const roots = computed(
  () => wrapStructuralAll(rawRoots.value, {
    nodeKey,
    nodeChildren: nodeChildrenList
  })
);
const selectedWrapped = computed(() => {
  const k = model.value;
  if (!k) return void 0;
  const path = findWrappedPath(roots.value, k);
  if (!path || path.length === 0) return void 0;
  return path[path.length - 1];
});
const triggerLabel = computed(
  () => selectedWrapped.value ? wrappedLabelText(selectedWrapped.value) : ""
);
publishSelection(
  props.column.id,
  () => selectedWrapped.value,
  { identity: (w) => w.key, raw: (w) => w.raw }
);
const initialExpanded = computed(() => {
  if (!props.column.expandAll) return [];
  const out = [];
  const stack = [...roots.value];
  while (stack.length) {
    const n = stack.pop();
    if (n.kind === "branch") {
      out.push(n.key);
      stack.push(...n.children);
    }
  }
  return out;
});
const filterQuery = ref("");
function filterPredicate(w, query) {
  const q = query.trim().toLowerCase();
  if (!q) return true;
  if (wrappedLabelText(w).trim().toLowerCase().includes(q)) return true;
  return wrappedKeywords(w).some((k) => k.includes(q));
}
const open = ref(false);
function handleUpdate(next) {
  const k = typeof next === "string" ? next : void 0;
  model.value = k;
  if (k) open.value = false;
}
const showClear = computed(
  () => !isDisabled.value && !loading.value && !!model.value
);
function handleClear() {
  model.value = void 0;
  open.value = false;
}
const hoveredKey = ref(null);
watch(open, (isOpen) => {
  if (!isOpen) hoveredKey.value = null;
});
function collectTooltipNodes(out, siblings) {
  for (const n of siblings) {
    if (wrappedTooltipText(n)) out.push(n.key);
    if (n.kind === "branch") collectTooltipNodes(out, n.children);
  }
}
const anyHasTooltip = computed(() => {
  if (!props.column.nodeTooltip) return false;
  const out = [];
  collectTooltipNodes(out, roots.value);
  return out.length > 0;
});
const hoveredTooltip = computed(() => {
  const k = hoveredKey.value;
  if (!k) return void 0;
  const path = findWrappedPath(roots.value, k);
  if (!path || path.length === 0) return void 0;
  return wrappedTooltipText(path[path.length - 1]);
});
</script>

<template>
  <div class="p-[0.125rem] w-full">
    <span
      v-if="isHidden"
      class="block h-7 w-full"
    />
    <span
      v-else-if="effectiveReadonly"
      class="flex items-center h-7 w-full px-2 text-[0.75rem] text-zinc-700 truncate"
    >
      {{ triggerLabel || "\u2014" }}
    </span>
    <Popover
      v-else
      v-model:open="open"
    >
      <PopoverAnchor as-child>
        <InputGroup
          class="group/tree-combobox-single h-7 rounded border-zinc-200/30 hover:border-zinc-200 focus-within:border-zinc-200 transition-colors ease-out duration-180"
          :data-disabled="isDisabled ? 'true' : void 0"
        >
          <PopoverTrigger as-child>
            <InputGroupInput
              :model-value="triggerLabel"
              :disabled="isDisabled"
              :placeholder="placeholderText"
              class="h-7 text-[0.75rem] md:text-[0.75rem] px-2 cursor-pointer text-left"
              readonly
            />
          </PopoverTrigger>
          <InputGroupAddon
            v-if="loading"
            align="inline-end"
            data-slot="tree-combobox-single-loading"
            class="text-zinc-400"
          >
            <Icon
              icon="fluent:spinner-ios-20-regular"
              class="size-3 animate-spin"
            />
          </InputGroupAddon>
          <InputGroupAddon
            v-else-if="showClear"
            align="inline-end"
            class="[@media(hover:hover)]:opacity-0 transition-opacity group-hover/tree-combobox-single:opacity-100 focus-within:opacity-100"
          >
            <InputGroupButton
              size="icon-xs"
              data-slot="tree-combobox-single-clear"
              class="size-4 text-zinc-500 hover:text-zinc-700"
              tabindex="-1"
              @mousedown.prevent
              @click.stop="handleClear"
            >
              <Icon
                icon="fluent:dismiss-20-regular"
                class="size-3"
              />
            </InputGroupButton>
          </InputGroupAddon>
        </InputGroup>
      </PopoverAnchor>
      <PopoverContent
        class="p-0"
        :style="{ width: 'var(--reka-popover-trigger-width)' }"
      >
        <div
          data-slot="tree-combobox-single-search"
          class="flex h-7 items-center gap-2 border-b border-zinc-200 px-2"
        >
          <Icon
            icon="fluent:search-20-filled"
            class="size-3 shrink-0 opacity-50"
          />
          <input
            v-model="filterQuery"
            :disabled="isDisabled"
            :placeholder="t('tree-combobox-single-search-placeholder')"
            class="flex h-7 w-full bg-transparent text-[0.75rem] outline-hidden placeholder:text-zinc-500 disabled:cursor-not-allowed disabled:opacity-50"
          >
        </div>
        <div class="flex flex-col p-1">
          <div
            v-if="fetchError && roots.length === 0"
            class="min-h-7 px-2 py-1 text-[0.75rem] text-red-500"
          >
            {{ t("tree-combobox-single-load-error") }}
          </div>
          <div
            v-else-if="loading && roots.length === 0"
            class="flex flex-col gap-1 px-2 py-1"
            aria-busy="true"
          >
            <Skeleton class="h-4 w-2/3" />
            <Skeleton class="h-4 w-1/2" />
            <Skeleton class="h-4 w-3/5" />
          </div>

          <UiTree
            v-else
            :roots="roots"
            :get-key="(w) => w.key"
            :get-children="getWrappedChildren"
            selection-mode="single"
            :model-value="model"
            :selectable="wrappedSelectable"
            :filter-predicate="filterPredicate"
            :filter-query="filterQuery"
            :initial-expanded="initialExpanded"
            :show-focused-row="false"
            :class="isDisabled ? 'pointer-events-none opacity-60' : void 0"
            class="max-h-72 overflow-y-auto"
            @update:model-value="handleUpdate"
          >
            <template #node="{ node, selected }">
              <span
                class="flex flex-1 min-w-0 items-center justify-between gap-2"
                @mouseenter="hoveredKey = node.key"
                @focusin="hoveredKey = node.key"
              >
                <Markdown
                  :source="wrappedLabelText(node)"
                  class="prose prose-xs prose-zinc text-zinc-700 min-w-0"
                />
                <Icon
                  v-if="selected"
                  icon="fluent:checkmark-20-regular"
                  class="size-3 shrink-0 text-zinc-700"
                />
              </span>
            </template>

            <template #empty>
              <div class="min-h-7 px-2 py-1 text-[0.75rem] text-zinc-400">
                {{ t("tree-combobox-single-empty") }}
              </div>
            </template>

            <template #error="{ error, retry }">
              <span class="inline-flex items-center gap-1 text-xs text-red-500">
                {{ t("tree-combobox-single-load-error") }}
                <button
                  type="button"
                  class="underline hover:text-red-600"
                  @click.stop="retry"
                >
                  {{ t("tree-combobox-single-retry") }}
                </button>
                <span class="sr-only">{{ String(error) }}</span>
              </span>
            </template>
          </UiTree>
        </div>
        <div
          v-if="anyHasTooltip && hoveredTooltip"
          data-slot="tree-combobox-single-tooltip"
          class="border-t border-zinc-200 px-2 py-1.5"
        >
          <Markdown
            :source="hoveredTooltip"
            block
            class="prose prose-xs prose-zinc"
          />
        </div>
      </PopoverContent>
    </Popover>
  </div>
</template>
