<script setup>
import { computed } from "vue";
import { Icon } from "@iconify/vue";
import { cn } from "../../../utils/cn";
defineOptions({ name: "UiTreeNode", inheritAttrs: false });
const props = defineProps({
  node: { type: null, required: true },
  depth: { type: Number, required: true },
  posInSet: { type: Number, required: true },
  setSize: { type: Number, required: true },
  parentKey: { type: [String, null], required: true },
  getKey: { type: Function, required: true },
  getChildren: { type: Function, required: false },
  hasLoader: { type: Boolean, required: true },
  selectionMode: { type: String, required: true },
  cascade: { type: String, required: true },
  selectable: { type: Function, required: true },
  draggable: { type: Function, required: true },
  isExpanded: { type: Function, required: true },
  isSelectedKey: { type: Function, required: true },
  isVisuallySelected: { type: Function, required: true },
  isIndeterminate: { type: Function, required: true },
  resolvedChildren: { type: Function, required: true },
  loadStateOf: { type: Function, required: true },
  filterPredicate: { type: Function, required: false },
  filterQuery: { type: String, required: true },
  isExpandable: { type: Function, required: true },
  tree: { type: Object, required: true },
  focusedKey: { type: [String, null], required: true },
  showFocusedRow: { type: Boolean, required: true }
});
const emit = defineEmits(["toggle-expand", "toggle-select", "focus-row"]);
defineSlots();
const key = computed(() => props.getKey(props.node));
const expanded = computed(() => props.isExpanded(key.value));
const expandable = computed(() => props.isExpandable(props.node));
const loadState = computed(() => props.loadStateOf(key.value));
const loading = computed(() => loadState.value?.status === "loading");
const errorState = computed(() => loadState.value?.status === "error" ? loadState.value.error : null);
const selectedFlag = computed(() => props.isVisuallySelected(props.node, props.depth));
const indeterminateFlag = computed(() => props.isIndeterminate(props.node, props.depth));
const userSelectable = computed(() => props.selectable(props.node, props.depth));
const subtreeMatches = computed(() => {
  if (!props.filterPredicate || !props.filterQuery) return true;
  if (props.filterPredicate(props.node, props.filterQuery)) return true;
  const kids = props.resolvedChildren(props.node);
  if (!kids || kids.length === 0) return false;
  return kids.some((c) => deepMatches(c));
});
function deepMatches(n) {
  if (!props.filterPredicate || !props.filterQuery) return true;
  if (props.filterPredicate(n, props.filterQuery)) return true;
  const kids = props.resolvedChildren(n);
  if (!kids || kids.length === 0) return false;
  return kids.some((c) => deepMatches(c));
}
const selfMatches = computed(() => {
  if (!props.filterPredicate || !props.filterQuery) return true;
  return props.filterPredicate(props.node, props.filterQuery);
});
const slotPayload = computed(() => ({
  node: props.node,
  depth: props.depth,
  expanded: expanded.value,
  loading: loading.value,
  selected: selectedFlag.value,
  indeterminate: indeterminateFlag.value,
  tree: props.tree
}));
const actionsPayload = computed(() => ({
  node: props.node,
  depth: props.depth,
  tree: props.tree
}));
function onToggleArrow() {
  if (!expandable.value) return;
  emit("toggle-expand", key.value);
}
function onRowClick() {
  if (expandable.value && !userSelectable.value) {
    emit("toggle-expand", key.value);
    return;
  }
  if (props.selectionMode !== "none" && userSelectable.value) {
    emit("toggle-select", key.value);
    return;
  }
  emit("focus-row", key.value);
}
const ariaSelected = computed(() => {
  if (props.selectionMode === "none") return void 0;
  if (indeterminateFlag.value) return "mixed";
  return selectedFlag.value ? "true" : "false";
});
const childArr = computed(() => props.resolvedChildren(props.node));
const showChildren = computed(
  () => expandable.value && (expanded.value || !selfMatches.value && subtreeMatches.value)
);
</script>

<template>
  <li
    v-if="subtreeMatches"
    role="treeitem"
    :aria-expanded="expandable ? expanded : void 0"
    :aria-selected="ariaSelected"
    :aria-level="depth + 1"
    :aria-setsize="setSize"
    :aria-posinset="posInSet"
    :data-tree-key="key"
    :data-tree-focused="focusedKey === key ? '' : void 0"
    class="select-none"
  >
    <div
      v-if="subtreeMatches"
      :class="cn(
  'group flex items-center justify-between gap-2 rounded px-1 py-1 outline-none transition-colors',
  selectionMode !== 'none' && userSelectable && 'cursor-pointer hover:bg-zinc-100/70',
  selectedFlag && !indeterminateFlag && 'bg-(--primary)/10 hover:bg-(--primary)/15',
  showFocusedRow && focusedKey === key && 'bg-(--primary)/10'
)"
      tabindex="-1"
      @click.stop="onRowClick"
    >
      <!-- LEFT: indent + arrow + selector + #node slot -->
      <div class="flex items-center gap-1 min-w-0 flex-1">
        <span
          aria-hidden="true"
          class="inline-block shrink-0"
          :style="{ width: `${depth * 16}px` }"
        />
        <button
          v-if="expandable"
          type="button"
          class="inline-flex size-4 shrink-0 items-center justify-center rounded text-zinc-500 hover:text-zinc-900"
          :aria-label="expanded ? 'Collapse' : 'Expand'"
          @click.stop="onToggleArrow"
        >
          <Icon
            :icon="expanded ? 'fluent:chevron-down-16-filled' : 'fluent:chevron-right-16-filled'"
            class="size-3"
          />
        </button>
        <span
          v-else
          aria-hidden="true"
          class="inline-block size-4 shrink-0"
        />

        <!-- selection is driven entirely by row click; selection state is
             conveyed via the row background highlight. -->

        <div class="min-w-0 flex-1">
          <slot
            name="node"
            v-bind="slotPayload"
          >
            <!-- Sensible fallback: stringified node, mostly for debugging. -->
            <span class="text-sm">{{ String(node) }}</span>
          </slot>
        </div>
      </div>

      <!-- RIGHT: #actions slot (optional) -->
      <div
        v-if="$slots.actions"
        class="flex items-center gap-1 shrink-0"
      >
        <slot
          name="actions"
          v-bind="actionsPayload"
        />
      </div>
    </div>

    <!-- Children container -->
    <ul
      v-if="showChildren"
      role="group"
      class="m-0 list-none p-0"
    >
      <template v-if="loading">
        <li
          role="treeitem"
          aria-busy="true"
          class="px-1 py-1"
          :style="{ paddingLeft: `${(depth + 1) * 16 + 16}px` }"
        >
          <slot
            name="loading-children"
            :node="node"
          >
            <span class="text-xs text-zinc-500 inline-flex items-center gap-1">
              <Icon
                icon="svg-spinners:90-ring-with-bg"
                class="size-3"
              />
              Loading…
            </span>
          </slot>
        </li>
      </template>
      <template v-else-if="errorState !== null">
        <li
          role="treeitem"
          class="px-1 py-1"
          :style="{ paddingLeft: `${(depth + 1) * 16 + 16}px` }"
        >
          <slot
            name="error"
            :node="node"
            :error="errorState"
            :retry="() => tree.retry(key)"
          />
        </li>
      </template>
      <template v-else-if="childArr && childArr.length">
        <UiTreeNode
          v-for="(child, idx) in childArr"
          :key="getKey(child)"
          :node="child"
          :depth="depth + 1"
          :pos-in-set="idx + 1"
          :set-size="childArr.length"
          :parent-key="key"
          :get-key="getKey"
          :get-children="getChildren"
          :has-loader="hasLoader"
          :selection-mode="selectionMode"
          :cascade="cascade"
          :selectable="selectable"
          :draggable="draggable"
          :is-expanded="isExpanded"
          :is-selected-key="isSelectedKey"
          :is-visually-selected="isVisuallySelected"
          :is-indeterminate="isIndeterminate"
          :resolved-children="resolvedChildren"
          :load-state-of="loadStateOf"
          :filter-predicate="filterPredicate"
          :filter-query="filterQuery"
          :is-expandable="isExpandable"
          :tree="tree"
          :focused-key="focusedKey"
          :show-focused-row="showFocusedRow"
          @toggle-expand="(k) => emit('toggle-expand', k)"
          @toggle-select="(k) => emit('toggle-select', k)"
          @focus-row="(k) => emit('focus-row', k)"
        >
          <!-- Forward each slot explicitly. Listing the names keeps vue-tsc
               happy and matches the public slot surface in `types.ts`. -->
          <template
            v-if="$slots.node"
            #node="slotProps"
          >
            <slot
              name="node"
              v-bind="slotProps"
            />
          </template>
          <template
            v-if="$slots.actions"
            #actions="slotProps"
          >
            <slot
              name="actions"
              v-bind="slotProps"
            />
          </template>
          <template
            v-if="$slots.error"
            #error="slotProps"
          >
            <slot
              name="error"
              v-bind="slotProps"
            />
          </template>
          <template
            v-if="$slots['loading-children']"
            #loading-children="slotProps"
          >
            <slot
              name="loading-children"
              v-bind="slotProps"
            />
          </template>
        </UiTreeNode>
      </template>
    </ul>
  </li>
</template>
