<script setup lang="ts">
import * as LucideIcons from "lucide-vue-next";
import { ArrowRight, Loader2 } from "lucide-vue-next";
import { computed, nextTick, ref, watch } from "vue";
import { router, usePage } from "@inertiajs/vue3";
import { DialogContent, DialogPortal, DialogRoot } from "reka-ui";
import type { NavigationItem } from "../registry";
import DialogOverlay from "./ui/DialogOverlay.vue";
import { useBuilderPrefix } from "../composables/useBuilderPrefix";
import { useTranslations } from "../composables/useTranslations";

const props = defineProps<{ open: boolean }>();
const emit = defineEmits<{ "update:open": [boolean] }>();

const page = usePage<{ navigation?: NavigationItem[] }>();
const { t } = useTranslations();

const inputRef = ref<HTMLInputElement | null>(null);
const itemRefs = ref<(HTMLElement | null)[]>([]);
const query = ref("");
const activeIndex = ref(0);
const isSearching = ref(false);
const searchError = ref(false);

type NavEntry = {
  kind: "nav";
  label: string;
  route: string;
  icon: string | null;
  group: string;
};

type RecordEntry = {
  kind: "record";
  id: string | number;
  title: string;
  subtitle?: string | null;
  route: string;
  groupLabel: string;
  isFirstInGroup: boolean;
};

type AnyEntry = NavEntry | RecordEntry;

type SearchResultItem = {
  id: string | number;
  title: string;
  subtitle?: string | null;
  route: string;
};

type SearchResultGroup = {
  label: string;
  slug: string;
  items: SearchResultItem[];
};

const recordGroups = ref<SearchResultGroup[]>([]);
let debounceTimer: ReturnType<typeof setTimeout> | null = null;

const navEntries = computed((): NavEntry[] => {
  const nav = page.props.navigation ?? [];
  const result: NavEntry[] = [];

  for (const item of nav) {
    const children = item.children ?? [];

    if (children.length === 0) {
      if (item.route) {
        result.push({
          kind: "nav",
          label: item.label,
          route: item.route,
          icon: item.icon ?? null,
          group: t("builder.command_palette.navigation"),
        });
      }
    } else {
      for (const child of children) {
        if (child.route) {
          result.push({
            kind: "nav",
            label: child.label,
            route: child.route,
            icon: child.icon ?? null,
            group: item.label,
          });
        }
      }
    }
  }

  return result;
});

const filteredNavEntries = computed((): NavEntry[] => {
  const q = query.value.toLowerCase().trim();
  if (!q) return navEntries.value;
  return navEntries.value.filter((e) => e.label.toLowerCase().includes(q));
});

// Pre-computed flat list with group headers for record results
const flatItems = computed((): AnyEntry[] => {
  const nav = filteredNavEntries.value;
  const records: RecordEntry[] = [];

  for (const group of recordGroups.value) {
    group.items.forEach((item, i) => {
      records.push({
        kind: "record",
        id: item.id,
        title: item.title,
        subtitle: item.subtitle,
        route: item.route,
        groupLabel: group.label,
        isFirstInGroup: i === 0,
      });
    });
  }

  return [...nav, ...records];
});

const isEmpty = computed(
  (): boolean => !isSearching.value && flatItems.value.length === 0,
);

const hasNavResults = computed(
  (): boolean => filteredNavEntries.value.length > 0,
);
const hasRecordResults = computed((): boolean => recordGroups.value.length > 0);

watch(
  () => query.value,
  (q) => {
    if (debounceTimer) {
      clearTimeout(debounceTimer);
    }

    if (q.trim().length < 2) {
      recordGroups.value = [];
      isSearching.value = false;
      searchError.value = false;
      return;
    }

    isSearching.value = true;
    searchError.value = false;

    debounceTimer = setTimeout(async () => {
      try {
        const prefix = useBuilderPrefix();
        const response = await fetch(
          `/${prefix}/search?q=${encodeURIComponent(q.trim())}`,
          { headers: { Accept: "application/json" } },
        );
        if (!response.ok) {
          throw new Error(`Search request failed: ${response.status}`);
        }
        const data = (await response.json()) as {
          groups: SearchResultGroup[];
        };
        recordGroups.value = data.groups;
      } catch {
        recordGroups.value = [];
        searchError.value = true;
      } finally {
        isSearching.value = false;
      }
    }, 300);
  },
);

// Reset active index when results change
watch(flatItems, () => {
  activeIndex.value = 0;
  itemRefs.value = [];
  nextTick(() => inputRef.value?.focus());
});

watch(
  () => props.open,
  (open) => {
    if (open) {
      query.value = "";
      activeIndex.value = 0;
      itemRefs.value = [];
      recordGroups.value = [];
      isSearching.value = false;
      searchError.value = false;
      if (debounceTimer) {
        clearTimeout(debounceTimer);
      }
      nextTick(() => inputRef.value?.focus());
    } else {
      if (debounceTimer) {
        clearTimeout(debounceTimer);
      }
    }
  },
);

function getIcon(name: string | null) {
  if (!name) return null;
  return (LucideIcons as Record<string, unknown>)[name] ?? null;
}

function navigate(route: string) {
  emit("update:open", false);
  router.visit(route);
}

// Keydown on the search input
function handleInputKeydown(e: KeyboardEvent) {
  const max = flatItems.value.length - 1;

  if (e.key === "ArrowDown") {
    e.preventDefault();
    if (max >= 0) {
      activeIndex.value = 0;
      nextTick(() => itemRefs.value[0]?.focus());
    }
  } else if (e.key === "ArrowUp") {
    e.preventDefault();
    if (max >= 0) {
      activeIndex.value = max;
      nextTick(() => itemRefs.value[max]?.focus());
    }
  } else if (e.key === "Enter") {
    e.preventDefault();
    const item = flatItems.value[activeIndex.value];
    if (item) navigate(item.route);
  }
}

// Keydown on a result button
function handleItemKeydown(e: KeyboardEvent, i: number) {
  const max = flatItems.value.length - 1;

  if (e.key === "ArrowDown") {
    e.preventDefault();
    if (i < max) {
      activeIndex.value = i + 1;
      itemRefs.value[i + 1]?.focus();
    }
  } else if (e.key === "ArrowUp") {
    e.preventDefault();
    if (i > 0) {
      activeIndex.value = i - 1;
      itemRefs.value[i - 1]?.focus();
    } else {
      inputRef.value?.focus();
    }
  } else if (e.key === "Enter") {
    e.preventDefault();
    const item = flatItems.value[i];
    if (item) navigate(item.route);
  } else if (e.key.length === 1 && !e.metaKey && !e.ctrlKey && !e.altKey) {
    // Regular character typed while item is focused — hand off to the input
    inputRef.value?.focus();
  }
}
</script>

<template>
  <DialogRoot :open="open" @update:open="emit('update:open', $event)">
    <DialogPortal>
      <DialogOverlay />
      <DialogContent
        class="fixed left-1/2 top-[8%] z-50 w-full max-w-2xl -translate-x-1/2 overflow-hidden rounded-2xl border bg-background shadow-2xl duration-200 data-[state=closed]:animate-out data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95"
        @open-auto-focus.prevent
      >
        <!-- Search input -->
        <div class="border-b px-5 py-4">
          <input
            ref="inputRef"
            v-model="query"
            type="text"
            :placeholder="t('builder.command_palette.placeholder')"
            class="w-full bg-transparent text-base text-foreground outline-none placeholder:text-muted-foreground"
            @keydown="handleInputKeydown"
          />
        </div>

        <!-- Results list -->
        <div class="max-h-96 overflow-y-auto">
          <!-- Loading state -->
          <div v-if="isSearching" class="flex items-center justify-center py-6">
            <Loader2 class="size-4 animate-spin text-muted-foreground" />
          </div>

          <template v-else>
            <!-- Search error state -->
            <p
              v-if="searchError"
              class="py-10 text-center text-sm text-destructive"
            >
              {{ t("builder.command_palette.search_error") }}
            </p>

            <!-- Empty state -->
            <p
              v-else-if="isEmpty"
              class="py-10 text-center text-sm text-muted-foreground"
            >
              {{ t("builder.command_palette.no_results") }}
            </p>

            <template v-else>
              <!-- Navigation group header (only shown when record results also exist) -->
              <p
                v-if="hasNavResults && hasRecordResults"
                class="px-5 pb-1 pt-3 text-xs font-medium text-muted-foreground"
              >
                {{ t("builder.command_palette.navigation") }}
              </p>

              <template
                v-for="(item, i) in flatItems"
                :key="
                  item.kind === 'nav' ? item.route : `${item.kind}-${item.id}`
                "
              >
                <!-- Record group header -->
                <p
                  v-if="item.kind === 'record' && item.isFirstInGroup"
                  class="px-5 pb-1 pt-3 text-xs font-medium text-muted-foreground"
                >
                  {{ item.groupLabel }}
                </p>

                <!-- Nav item -->
                <button
                  v-if="item.kind === 'nav'"
                  :ref="
                    (el) => {
                      itemRefs[i] = el as HTMLElement | null;
                    }
                  "
                  tabindex="-1"
                  type="button"
                  class="flex w-full cursor-pointer items-center gap-4 px-5 py-3.5 text-sm text-foreground outline-none transition-colors focus:bg-accent focus:text-accent-foreground"
                  :class="{
                    'bg-accent/50': i === activeIndex,
                  }"
                  @keydown="handleItemKeydown($event, i)"
                  @focus="activeIndex = i"
                  @mouseenter="activeIndex = i"
                  @click="navigate(item.route)"
                >
                  <component
                    :is="getIcon(item.icon) ?? ArrowRight"
                    class="size-4 shrink-0 text-muted-foreground"
                  />
                  {{ item.label }}
                </button>

                <!-- Record item -->
                <button
                  v-else
                  :ref="
                    (el) => {
                      itemRefs[i] = el as HTMLElement | null;
                    }
                  "
                  tabindex="-1"
                  type="button"
                  class="flex w-full cursor-pointer items-center gap-4 px-5 py-3 text-sm text-foreground outline-none transition-colors focus:bg-accent focus:text-accent-foreground"
                  :class="{
                    'bg-accent/50': i === activeIndex,
                  }"
                  @keydown="handleItemKeydown($event, i)"
                  @focus="activeIndex = i"
                  @mouseenter="activeIndex = i"
                  @click="navigate(item.route)"
                >
                  <ArrowRight class="size-4 shrink-0 text-muted-foreground" />
                  <span class="flex min-w-0 flex-col text-left">
                    <span class="truncate">{{ item.title }}</span>
                    <span
                      v-if="item.subtitle"
                      class="truncate text-xs text-muted-foreground"
                      >{{ item.subtitle }}</span
                    >
                  </span>
                </button>
              </template>
            </template>
          </template>
        </div>

        <!-- Footer -->
        <div
          class="flex items-center justify-end gap-4 border-t px-5 py-3 text-xs text-muted-foreground"
        >
          <span class="flex items-center gap-1.5">
            {{ t("builder.command_palette.jump_to") }}
            <kbd class="rounded border bg-muted px-1.5 py-0.5 font-mono">↵</kbd>
          </span>
          <span class="flex items-center gap-1.5">
            {{ t("builder.command_palette.navigate") }}
            <kbd class="rounded border bg-muted px-1.5 py-0.5 font-mono"
              >↑↓</kbd
            >
          </span>
          <span class="flex items-center gap-1.5">
            {{ t("builder.command_palette.close") }}
            <kbd class="rounded border bg-muted px-1.5 py-0.5 font-mono"
              >Esc</kbd
            >
          </span>
        </div>
      </DialogContent>
    </DialogPortal>
  </DialogRoot>
</template>
