<script setup lang="ts">
import { Mic, Send, Square, X } from "@lucide/vue";
import { nextTick, onMounted, onUnmounted, ref } from "vue";

import { useVoiceRecorder } from "../composables/useVoiceRecorder";
import { api, transcribeVoice } from "../services/api";

defineProps<{
  loading: boolean;
  disabled: boolean;
}>();

const emit = defineEmits<{
  send: [message: string, complete: (success: boolean) => void];
  stop: [];
}>();

const input = ref("");
const textarea = ref<HTMLTextAreaElement | null>(null);
const voiceEnabled = ref(false);
const voiceMaxDuration = ref(60);
const transcribing = ref(false);
const voiceError = ref("");

const { recording, elapsedSeconds, start: startRecording, stop: stopRecording, cancel: cancelRecording } = useVoiceRecorder(
  async (audio) => {
    transcribing.value = true;
    voiceError.value = "";
    try {
      const result = await transcribeVoice(audio);
      insertTranscription(result.text);
    } catch (error) {
      voiceError.value = error instanceof Error ? error.message : "语音识别失败，请重试";
    } finally {
      transcribing.value = false;
    }
  },
);

async function refreshVoiceConfig() {
  try {
    const config = await api.voiceConfig();
    voiceEnabled.value = config.enabled;
    voiceMaxDuration.value = config.maxDurationSeconds;
  } catch {
    voiceEnabled.value = false;
  }
}

function refreshVoiceConfigWhenVisible() {
  if (document.visibilityState === "visible") void refreshVoiceConfig();
}

let voiceConfigTimer: number | undefined;

onMounted(() => {
  void refreshVoiceConfig();
  window.addEventListener("focus", refreshVoiceConfigWhenVisible);
  document.addEventListener("visibilitychange", refreshVoiceConfigWhenVisible);
  voiceConfigTimer = window.setInterval(refreshVoiceConfigWhenVisible, 30_000);
});

onUnmounted(() => {
  window.removeEventListener("focus", refreshVoiceConfigWhenVisible);
  document.removeEventListener("visibilitychange", refreshVoiceConfigWhenVisible);
  if (voiceConfigTimer !== undefined) window.clearInterval(voiceConfigTimer);
});

function resize() {
  const element = textarea.value;
  if (!element) return;
  element.style.height = "auto";
  element.style.height = `${Math.min(element.scrollHeight, 108)}px`;
  element.style.overflowY = element.scrollHeight > 108 ? "auto" : "hidden";
}

async function send() {
  const text = input.value.trim();
  if (!text) return;
  // Sending has been accepted by the composer. Clear immediately instead of
  // keeping the submitted text visible until the entire AI stream finishes.
  input.value = "";
  await nextTick();
  resize();
  emit("send", text, async (success) => {
    // Restore only when the parent rejected/failed the request and the user has
    // not entered replacement text in the meantime.
    if (!success && !input.value) {
      input.value = text;
      await nextTick();
      resize();
      textarea.value?.focus();
    }
  });
}

function onKeydown(event: KeyboardEvent) {
  if (event.key === "Enter" && !event.shiftKey && !event.isComposing) {
    event.preventDefault();
    send();
  }
}

async function beginVoiceInput() {
  voiceError.value = "";
  try {
    await startRecording(voiceMaxDuration.value);
  } catch (error) {
    const name = error instanceof DOMException ? error.name : "";
    voiceError.value = name === "NotAllowedError"
      ? "麦克风权限未开启，请在浏览器设置中允许后重试"
      : error instanceof Error ? error.message : "无法启动录音";
  }
}

async function insertTranscription(text: string) {
  const element = textarea.value;
  const start = element?.selectionStart ?? input.value.length;
  const end = element?.selectionEnd ?? start;
  const prefix = input.value.slice(0, start);
  const suffix = input.value.slice(end);
  const spacer = prefix && !/\s$/.test(prefix) ? " " : "";
  input.value = `${prefix}${spacer}${text}${suffix}`;
  await nextTick();
  resize();
  const cursor = prefix.length + spacer.length + text.length;
  element?.focus();
  element?.setSelectionRange(cursor, cursor);
}

function formatSeconds(seconds: number) {
  return `00:${String(seconds).padStart(2, "0")}`;
}
</script>

<template>
  <form class="chat-composer" @submit.prevent="send">
    <div v-if="recording" class="voice-recording-panel" role="status">
      <span class="voice-pulse" aria-hidden="true"></span>
      <strong>正在录音 {{ formatSeconds(elapsedSeconds) }}</strong>
      <span>最长 {{ voiceMaxDuration }} 秒</span>
    </div>
    <textarea
      v-else
      ref="textarea"
      v-model="input"
      rows="1"
      placeholder="请输入你的问题"
      aria-label="请输入你的问题"
      :disabled="disabled"
      @input="resize"
      @keydown="onKeydown"
    />
    <button v-if="recording" type="button" class="composer-voice cancel" aria-label="取消录音" @click="cancelRecording">
      <X :size="18" aria-hidden="true" />
    </button>
    <button v-else-if="voiceEnabled && !loading" type="button" class="composer-voice" :disabled="disabled || transcribing" :aria-label="transcribing ? '正在识别语音' : '语音输入'" @click="beginVoiceInput">
      <Mic :size="19" aria-hidden="true" />
      <span>{{ transcribing ? "识别中" : "语音" }}</span>
    </button>
    <button v-if="recording" type="button" class="composer-voice finish" aria-label="完成录音" @click="stopRecording">
      <Square :size="16" fill="currentColor" aria-hidden="true" />
      <span>完成</span>
    </button>
    <button v-if="loading" type="button" class="composer-stop" aria-label="停止生成" @click="emit('stop')">
      <Square :size="17" fill="currentColor" aria-hidden="true" />
      <span>停止</span>
    </button>
    <button v-else-if="!recording" type="submit" class="composer-send" :disabled="disabled || !input.trim()">
      <Send :size="18" aria-hidden="true" />
      <span>发送</span>
    </button>
    <p v-if="voiceError" class="voice-input-error" role="alert">{{ voiceError }}</p>
  </form>
</template>
