<script setup lang="ts">
import { ref, nextTick } from "vue";

const props = defineProps<{
  modelValue: string[];
  fetchTags: (q: string) => Promise<string[]>;
}>();

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

const inputValue = ref("");
const suggestions = ref<string[]>([]);
const showSuggestions = ref(false);
const inputRef = ref<HTMLInputElement | null>(null);
const selectedIndex = ref(-1);

let fetchDebounce: ReturnType<typeof setTimeout> | null = null;

function sanitize(raw: string): string {
  return raw.trim().toLowerCase().replace(/\s+/g, "-").replace(/^#+/, "");
}

function addTag(raw: string) {
  const tag = sanitize(raw);
  if (!tag) return;
  if (!props.modelValue.includes(tag)) {
    emit("update:modelValue", [...props.modelValue, tag]);
  }
  inputValue.value = "";
  suggestions.value = [];
  showSuggestions.value = false;
}

function removeTag(tag: string) {
  emit("update:modelValue", props.modelValue.filter((t) => t !== tag));
}

function onKeydown(e: KeyboardEvent) {
  if (showSuggestions.value && e.key === "ArrowDown") {
    e.preventDefault();
    selectedIndex.value = Math.min(selectedIndex.value + 1, suggestions.value.length - 1);
    return;
  }
  if (showSuggestions.value && e.key === "ArrowUp") {
    e.preventDefault();
    selectedIndex.value = Math.max(selectedIndex.value - 1, -1);
    return;
  }
  if (e.key === "Enter" || e.key === ",") {
    e.preventDefault();
    if (showSuggestions.value && selectedIndex.value >= 0) {
      addTag(suggestions.value[selectedIndex.value]);
    } else {
      const val = inputValue.value.replace(/,$/, "");
      if (val.trim()) addTag(val);
    }
  } else if (e.key === "Backspace" && !inputValue.value) {
    const tags = props.modelValue;
    if (tags.length > 0) {
      emit("update:modelValue", tags.slice(0, -1));
    }
  } else if (e.key === "Escape") {
    showSuggestions.value = false;
    selectedIndex.value = -1;
  }
}

function onInput() {
  const q = inputValue.value.trim().replace(/^#+/, "");
  if (fetchDebounce !== null) clearTimeout(fetchDebounce);
  if (!q) {
    suggestions.value = [];
    showSuggestions.value = false;
    return;
  }
  fetchDebounce = setTimeout(async () => {
    try {
      const results = await props.fetchTags(q);
      suggestions.value = results.filter((t) => !props.modelValue.includes(t));
      showSuggestions.value = suggestions.value.length > 0;
      selectedIndex.value = -1;
    } catch {
      suggestions.value = [];
    }
  }, 200);
}

function onBlur() {
  // Small delay to allow click on suggestion
  setTimeout(() => {
    showSuggestions.value = false;
    // Commit any pending text on blur
    if (inputValue.value.trim()) {
      addTag(inputValue.value);
    }
  }, 150);
}

function focusInput() {
  nextTick(() => inputRef.value?.focus());
}
</script>

<template>
  <div class="tag-input-wrapper" @click="focusInput">
    <span
      v-for="tag in modelValue"
      :key="tag"
      class="tag-chip"
    >
      #{{ tag }}
      <button
        type="button"
        class="tag-chip-remove"
        @click.stop="removeTag(tag)"
        tabindex="-1"
      >&times;</button>
    </span>
    <div class="tag-input-inner">
      <input
        ref="inputRef"
        v-model="inputValue"
        type="text"
        class="tag-input-field"
        placeholder="Add tag..."
        @keydown="onKeydown"
        @input="onInput"
        @blur="onBlur"
        autocomplete="off"
        autocorrect="off"
        autocapitalize="off"
        spellcheck="false"
      />
      <ul v-if="showSuggestions" class="tag-autocomplete">
        <li
          v-for="(s, i) in suggestions"
          :key="s"
          :class="['tag-autocomplete-item', { selected: i === selectedIndex }]"
          @mousedown.prevent="addTag(s)"
        >
          #{{ s }}
        </li>
      </ul>
    </div>
  </div>
</template>

<style scoped>
.tag-input-wrapper {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  padding: 0.35rem 0.6rem;
  border: 1px solid var(--fs-border-color);
  border-radius: var(--fs-radius-sm);
  background: var(--fs-surface-raised);
  cursor: text;
  min-height: 2.25rem;
}
.tag-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.2rem;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--fs-accent) 15%, transparent);
  border: 1px solid var(--fs-accent);
  color: var(--fs-accent);
  font-size: 0.8rem;
  white-space: nowrap;
}
.tag-chip-remove {
  border: none;
  background: none;
  color: inherit;
  cursor: pointer;
  padding: 0;
  line-height: 1;
  font-size: 0.85rem;
  opacity: 0.7;
}
.tag-chip-remove:hover {
  opacity: 1;
}
.tag-input-inner {
  position: relative;
  flex: 1;
  min-width: 80px;
}
.tag-input-field {
  width: 100%;
  border: none;
  outline: none;
  background: transparent;
  color: var(--fs-text-primary);
  font-size: 0.875rem;
  padding: 0;
}
.tag-autocomplete {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  min-width: 160px;
  max-width: 280px;
  background: var(--fs-surface-raised);
  border: 1px solid var(--fs-border-color);
  border-radius: var(--fs-radius-sm);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  list-style: none;
  margin: 0;
  padding: 0.25rem 0;
  z-index: 100;
}
.tag-autocomplete-item {
  padding: 0.35rem 0.75rem;
  font-size: 0.85rem;
  cursor: pointer;
  color: var(--fs-text-primary);
}
.tag-autocomplete-item:hover,
.tag-autocomplete-item.selected {
  background: var(--fs-surface-hover);
  color: var(--fs-accent);
}
</style>
