import type {Ref} from 'vue';
import {computed, nextTick, ref} from 'vue';

import type {ChatInputTriggerGroup, ChatInputTriggerMatch, ChatInputTriggerSymbol,} from './types';

const TRIGGER_SYMBOLS = new Set<ChatInputTriggerSymbol>(['#', '$', '/', '@']);
const TRIGGER_BOUNDARY = /[\s，。！？；：,.!?;:()[\]{}]/;

export interface ChatInputTriggerOptions {
  disabled: Ref<boolean>;
  groups: Ref<ChatInputTriggerGroup[]>;
  inputRef: Ref<undefined | unknown>;
  text: Ref<string>;
}

/**
 * 管理聊天输入框快捷触发状态。
 *
 * @param options 触发器依赖
 * @return 输入触发状态与操作方法
 */
export function useChatInputTrigger(options: ChatInputTriggerOptions) {
  const activeMatch = ref<ChatInputTriggerMatch>();
  const activeIndex = ref(0);

  const activeGroup = computed(() => {
    if (!activeMatch.value) {
      return undefined;
    }
    return options.groups.value.find(
      (group) => group.symbol === activeMatch.value?.symbol,
    );
  });

  const visibleItems = computed(() => {
    const group = activeGroup.value;
    if (!group) {
      return [];
    }
    const keyword = activeMatch.value?.keyword.trim().toLowerCase() || '';
    if (!keyword) {
      return group.items;
    }
    return group.items.filter((item) => {
      const label = item.label.toLowerCase();
      const description = item.description?.toLowerCase() || '';
      return label.includes(keyword) || description.includes(keyword);
    });
  });

  const activePanel = computed(() => {
    const group = activeGroup.value;
    if (!activeMatch.value || !group) {
      return undefined;
    }
    return {
      groupLabel: group.label,
      keyword: activeMatch.value.keyword,
      symbol: activeMatch.value.symbol,
    };
  });

  function getTextareaElement() {
    const rawRef = options.inputRef.value as
      | undefined
      | { textarea?: HTMLTextAreaElement };
    return rawRef?.textarea;
  }

  function close() {
    activeMatch.value = undefined;
    activeIndex.value = 0;
  }

  function sync() {
    if (options.disabled.value) {
      close();
      return;
    }
    const textarea = getTextareaElement();
    const cursor = textarea?.selectionStart ?? options.text.value.length;
    const match = findTriggerMatch(options.text.value, cursor);
    if (!match) {
      close();
      return;
    }
    activeMatch.value = match;
    activeIndex.value = Math.min(
      activeIndex.value,
      Math.max(visibleItems.value.length - 1, 0),
    );
  }

  async function replaceTriggerText(replacement = '') {
    const match = activeMatch.value;
    if (!match) {
      return;
    }
    const textarea = getTextareaElement();
    const cursor = textarea?.selectionStart ?? options.text.value.length;
    const before = options.text.value.slice(0, match.start);
    const after = options.text.value.slice(cursor);
    const nextText = `${before}${replacement}${after}`;
    const nextCursor = before.length + replacement.length;
    options.text.value = nextText;
    close();
    await nextTick();
    textarea?.focus();
    textarea?.setSelectionRange(nextCursor, nextCursor);
  }

  function move(delta: number) {
    const total = visibleItems.value.length;
    if (!activeMatch.value || total === 0) {
      return;
    }
    activeIndex.value = (activeIndex.value + delta + total) % total;
  }

  function setActiveIndex(index: number) {
    if (index < 0 || index >= visibleItems.value.length) {
      return;
    }
    activeIndex.value = index;
  }

  function findTriggerMatch(
    text: string,
    cursor: number,
  ): ChatInputTriggerMatch | undefined {
    const beforeCursor = text.slice(0, cursor);
    for (let index = beforeCursor.length - 1; index >= 0; index--) {
      const char = beforeCursor[index];
      if (!char) {
        continue;
      }
      if (TRIGGER_BOUNDARY.test(char)) {
        return undefined;
      }
      if (!TRIGGER_SYMBOLS.has(char as ChatInputTriggerSymbol)) {
        continue;
      }
      const previous = index === 0 ? '' : beforeCursor[index - 1];
      if (previous && !TRIGGER_BOUNDARY.test(previous)) {
        return undefined;
      }
      return {
        keyword: beforeCursor.slice(index + 1),
        start: index,
        symbol: char as ChatInputTriggerSymbol,
      };
    }
    return undefined;
  }

  return {
    activeIndex,
    activePanel,
    close,
    move,
    replaceTriggerText,
    setActiveIndex,
    sync,
    visibleItems,
  };
}
