<script setup lang="ts">
import { ref, computed, watch, onMounted } from "vue";
import { useBuilderPrefix } from "@/composables/useBuilderPrefix";
import { useCsrfToken } from "@/composables/useCsrfToken";
import { useHierarchyContext } from "@/composables/useHierarchyContext";
import { useFormLocale } from "@/composables/useFormLocale";
import { cn } from "@/lib/utils";
import type { FieldDefinition } from "./types";

type Option = { id: string | number; label: string; slug?: string | null };

const props = defineProps<{
  field: FieldDefinition;
  modelValue?: unknown;
  error?: string;
}>();

const emit = defineEmits<{
  "update:modelValue": [value: string | number | null];
}>();

const builderPrefix = useBuilderPrefix();
const csrfToken = useCsrfToken();
const hierarchyCtx = useHierarchyContext();
const formLocale = useFormLocale();

const search = ref("");
const options = ref<Option[]>([]);
const loading = ref(false);
const fetchError = ref<string | null>(null);
const open = ref(false);
const inputEl = ref<HTMLInputElement | null>(null);

let abortController: AbortController | null = null;

const selectedLabel = computed<string>(() => {
  if (props.modelValue == null || props.modelValue === "") return "";
  const found = options.value.find(
    (o) => String(o.id) === String(props.modelValue),
  );
  return found ? found.label : "";
});

async function fetchOptions(q: string): Promise<void> {
  const resource = props.field.relatedResource;
  const labelKey = props.field.relatedLabelKey ?? "name";

  if (!resource) {
    return;
  }

  abortController?.abort();
  abortController = new AbortController();

  loading.value = true;
  fetchError.value = null;

  try {
    const params = new URLSearchParams({ label: labelKey });
    if (q) {
      params.set("search", q);
    }
    if (formLocale) {
      params.set("locale", formLocale);
    }

    const response = await fetch(
      `/${builderPrefix}/${resource}/options?${params}`,
      {
        signal: abortController.signal,
        headers: {
          "X-CSRF-TOKEN": csrfToken,
          Accept: "application/json",
        },
      },
    );

    if (response.ok) {
      const data = (await response.json()) as { options: Option[] };
      options.value = data.options;
    } else {
      fetchError.value = `Failed to load options (${response.status}).`;
    }
  } catch (err) {
    if (err instanceof DOMException && err.name === "AbortError") {
      return;
    }
    fetchError.value = "Failed to load options.";
  } finally {
    loading.value = false;
  }
}

watch(search, (q) => {
  if (open.value) {
    fetchOptions(q);
  }
});

// Load options on mount so the label resolves when an existing ID is set.
onMounted(() => {
  if (props.modelValue != null && props.modelValue !== "") {
    fetchOptions("");
  }
});

function openDropdown(): void {
  if (open.value) return;
  search.value = "";
  open.value = true;

  fetchOptions("");
}

function selectOption(option: Option): void {
  emit("update:modelValue", option.id);
  search.value = "";
  open.value = false;

  if (hierarchyCtx && props.field.name === hierarchyCtx.parentField) {
    hierarchyCtx.setParentOption(option);
  }
}

function clearValue(): void {
  emit("update:modelValue", null);
  search.value = "";
  options.value = [];

  if (hierarchyCtx && props.field.name === hierarchyCtx.parentField) {
    hierarchyCtx.clearParentOption();
  }
}

function handleBlur(): void {
  window.setTimeout(() => {
    open.value = false;
    search.value = "";
  }, 150);
}
</script>

<template>
  <div class="relative">
    <div
      :class="
        cn(
          'border-input bg-background focus-within:ring-ring flex min-h-9 w-full items-center gap-2 rounded-md border px-3 py-1.5 text-sm shadow-xs transition-[color,box-shadow] focus-within:ring-[3px]',
          error && 'border-destructive',
          field.readonly && 'cursor-not-allowed opacity-50',
        )
      "
    >
      <input
        :id="field.name"
        ref="inputEl"
        :value="open ? search : selectedLabel"
        type="text"
        :placeholder="open ? 'Zoeken…' : '— Selecteer —'"
        :readonly="field.readonly"
        class="flex-1 bg-transparent outline-none placeholder:text-muted-foreground"
        @focus="openDropdown"
        @click="openDropdown"
        @input="open && (search = ($event.target as HTMLInputElement).value)"
        @blur="handleBlur"
      />

      <button
        v-if="modelValue"
        type="button"
        class="text-muted-foreground hover:text-foreground shrink-0"
        @mousedown.prevent="clearValue"
      >
        <svg
          class="h-3.5 w-3.5"
          fill="none"
          viewBox="0 0 24 24"
          stroke="currentColor"
          stroke-width="2"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M6 18L18 6M6 6l12 12"
          />
        </svg>
      </button>

      <svg
        v-else
        class="h-4 w-4 shrink-0 opacity-50"
        fill="none"
        viewBox="0 0 24 24"
        stroke="currentColor"
      >
        <path
          stroke-linecap="round"
          stroke-linejoin="round"
          stroke-width="2"
          d="M6 9l6 6 6-6"
        />
      </svg>
    </div>

    <div
      v-if="open"
      class="border-border bg-popover absolute z-50 mt-1 max-h-60 w-full overflow-y-auto rounded-md border shadow-md"
    >
      <div v-if="loading" class="text-muted-foreground px-3 py-2 text-sm">
        Laden…
      </div>

      <div v-else-if="fetchError" class="text-destructive px-3 py-2 text-sm">
        {{ fetchError }}
      </div>

      <div
        v-else-if="options.length === 0"
        class="text-muted-foreground px-3 py-2 text-sm"
      >
        Geen resultaten gevonden.
      </div>

      <button
        v-for="option in options"
        v-else
        :key="String(option.id)"
        type="button"
        class="hover:bg-accent hover:text-accent-foreground flex w-full items-center px-3 py-2 text-sm"
        :class="{
          'bg-accent': String(option.id) === String(modelValue),
        }"
        @mousedown.prevent="selectOption(option)"
      >
        {{ option.label }}
      </button>
    </div>
  </div>
</template>
