<script setup lang="ts">
import { ref, computed, nextTick } from 'vue'
import { apiGet, transcribeAudio } from '@/api/client'
import { useVoiceRecorder } from '@/composables/useVoiceRecorder'
import { useSilenceDetector } from '@/composables/useSilenceDetector'
import { useChatStore } from '@/stores/chat'
import { useSettingsStore } from '@/stores/settings'
import { useToastStore } from '@/stores/toast'
import type { Note } from '@/types/note'

const props = withDefaults(defineProps<{
  /** Textarea placeholder */
  placeholder?: string
  /** When true, hides the note picker (briefing mode) */
  briefingMode?: boolean
  /** Pill shape — compact rounded style for widget */
  pill?: boolean
}>(), {
  placeholder: 'Type a message… (Enter to send, Shift+Enter for new line)',
  briefingMode: false,
  pill: false,
})

const emit = defineEmits<{
  submit: [payload: { content: string; contextNoteId?: number }]
  abort: []
}>()

const store = useChatStore()
const settingsStore = useSettingsStore()
const voiceEnabled = computed(() => settingsStore.voiceSttReady)

// ── Core input ────────────────────────────────────────────────────────────────
const messageInput = ref('')
const inputEl = ref<HTMLTextAreaElement | null>(null)
const wrapperEl = ref<HTMLElement | null>(null)

function autoResize() {
  const el = inputEl.value
  if (!el) return
  el.style.height = 'auto'
  el.style.height = Math.min(el.scrollHeight, 150) + 'px'
}

function resetTextareaHeight() {
  const el = inputEl.value
  if (!el) return
  el.style.height = 'auto'
}

function onInputKeydown(e: KeyboardEvent) {
  if (e.key === 'Enter' && !e.shiftKey) {
    e.preventDefault()
    onSubmit()
  }
}

function onSubmit() {
  const content = messageInput.value.trim()
  if (!content) return
  emit('submit', { content, contextNoteId: attachedNote.value?.id })
  messageInput.value = ''
  attachedNote.value = null
  resetTextareaHeight()
}

// ── Note picker ───────────────────────────────────────────────────────────────
const attachedNote = ref<{ id: number; title: string } | null>(null)
const showNotePicker = ref(false)
const noteSearchQuery = ref('')
const noteSearchResults = ref<{ id: number; title: string }[]>([])
const noteSearchLoading = ref(false)
let noteSearchTimer: ReturnType<typeof setTimeout> | null = null

function toggleNotePicker() {
  showNotePicker.value = !showNotePicker.value
  if (showNotePicker.value) {
    noteSearchQuery.value = ''
    noteSearchResults.value = []
    nextTick(() => {
      (wrapperEl.value?.querySelector('.note-picker-search') as HTMLInputElement)?.focus()
    })
  }
}

function onNoteSearchInput() {
  if (noteSearchTimer) clearTimeout(noteSearchTimer)
  noteSearchTimer = setTimeout(async () => {
    const q = noteSearchQuery.value.trim()
    if (!q) { noteSearchResults.value = []; return }
    noteSearchLoading.value = true
    try {
      const data = await apiGet<{ notes: Note[] }>(`/api/notes?q=${encodeURIComponent(q)}&all=true&limit=5`)
      noteSearchResults.value = data.notes.map((n) => ({ id: n.id, title: n.title }))
    } catch {
      noteSearchResults.value = []
    } finally {
      noteSearchLoading.value = false
    }
  }, 250)
}

function selectNote(note: { id: number; title: string }) {
  attachedNote.value = note
  showNotePicker.value = false
}

function removeAttachedNote() {
  attachedNote.value = null
}

// ── Voice (click-to-toggle + silence detection) ─────────────────────────────
const transcribingVoice = ref(false)
const recorder = useVoiceRecorder()
const silenceDetector = useSilenceDetector()

// Live mic pulse. `silenceDetector.amplitude` is 0..1 RMS; we floor at 0.1
// so the button still breathes on silence and cap the scale growth so loud
// input doesn't punch through the input bar.
const micStyle = computed(() => {
  if (!recorder.recording.value) return {}
  const pulse = 0.1 + Math.min(silenceDetector.amplitude.value, 1) * 0.9
  return {
    transform: `scale(${1 + pulse * 0.18})`,
    boxShadow: `0 0 ${6 + pulse * 14}px ${2 + pulse * 4}px rgba(239, 68, 68, ${0.2 + pulse * 0.3})`,
  }
})

async function toggleVoice() {
  if (transcribingVoice.value) return
  if (recorder.recording.value) {
    await stopRecording()
  } else {
    await startRecording()
  }
}

async function startRecording() {
  if (!voiceEnabled.value || recorder.recording.value) return
  if (!recorder.isSupported) {
    useToastStore().show('Microphone requires HTTPS or localhost', 'error')
    return
  }
  await recorder.startRecording()
  if (recorder.error.value) {
    useToastStore().show(recorder.error.value, 'error')
    return
  }
  if (recorder.stream.value) {
    silenceDetector.start(recorder.stream.value, () => stopRecording())
  }
}

async function stopRecording() {
  silenceDetector.stop()
  if (!recorder.recording.value) return
  transcribingVoice.value = true
  try {
    const blob = await recorder.stopRecording()
    // Pass last assistant message as context to reduce STT mishearings
    const msgs = store.currentConversation?.messages ?? []
    const lastAssistant = [...msgs].reverse().find(m => m.role === 'assistant')?.content
    const { transcript } = await transcribeAudio(blob, lastAssistant)
    if (transcript.trim()) {
      messageInput.value = transcript.trim()
      await nextTick()
      autoResize()
      onSubmit()
    }
  } catch { /* transcription failed silently */ }
  finally { transcribingVoice.value = false }
}

// ── Exposed interface ─────────────────────────────────────────────────────────
function focus() {
  inputEl.value?.focus()
}

function prefill(text: string) {
  messageInput.value = text
  nextTick(() => {
    autoResize()
    inputEl.value?.focus()
  })
}

defineExpose({ focus, prefill })
</script>

<template>
  <div ref="wrapperEl" class="chat-input-bar" :class="{ 'chat-input-bar--pill': pill }">
    <!-- Attached note pill -->
    <div v-if="attachedNote" class="attached-note">
      <span class="attached-note-pill">
        {{ attachedNote.title }}
        <button class="attached-note-remove" aria-label="Remove" @click="removeAttachedNote">&times;</button>
      </span>
    </div>

    <div class="input-row">
      <!-- Note picker -->
      <div class="note-picker-wrapper">
        <button
          class="btn-icon"
          @click="toggleNotePicker"
          :disabled="!store.chatReady"
          title="Attach a note"
        >
          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66l-9.2 9.19a2 2 0 01-2.83-2.83l8.49-8.48"/>
          </svg>
        </button>
        <div v-if="showNotePicker" class="note-picker-dropdown">
          <input
            class="note-picker-search"
            v-model="noteSearchQuery"
            @input="onNoteSearchInput"
            placeholder="Search notes..."
          />
          <div class="note-picker-results">
            <div
              v-for="note in noteSearchResults"
              :key="note.id"
              class="note-picker-item"
              @click="selectNote(note)"
            >{{ note.title || 'Untitled' }}</div>
            <div v-if="noteSearchLoading" class="note-picker-empty">Searching...</div>
            <div v-else-if="noteSearchQuery && !noteSearchResults.length" class="note-picker-empty">No notes found</div>
          </div>
        </div>
      </div>

      <!-- Textarea -->
      <textarea
        ref="inputEl"
        v-model="messageInput"
        @keydown="onInputKeydown"
        @input="autoResize"
        :placeholder="!store.chatReady ? 'Chat unavailable' : store.streaming ? 'Type to queue… (Enter to queue)' : placeholder"
        :disabled="!store.chatReady"
        rows="1"
        class="input-textarea"
      ></textarea>

      <!-- PTT mic -->
      <button
        v-if="voiceEnabled"
        class="btn-icon btn-mic"
        :class="{ 'mic-recording': recorder.recording.value, 'mic-transcribing': transcribingVoice }"
        :style="micStyle"
        @click.prevent="toggleVoice"
        :disabled="transcribingVoice || !store.chatReady"
        :title="recorder.recording.value ? 'Click to stop (or wait for silence)' : 'Click to speak'"
        :aria-label="recorder.recording.value ? 'Stop recording' : 'Start recording'"
      >
        <svg v-if="!transcribingVoice" width="17" height="17" viewBox="0 0 24 24" fill="currentColor">
          <path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm-1-9c0-.55.45-1 1-1s1 .45 1 1v6c0 .55-.45 1-1 1s-1-.45-1-1V5zm6 6c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z"/>
        </svg>
        <svg v-else width="17" height="17" viewBox="0 0 24 24" fill="currentColor">
          <circle cx="12" cy="12" r="8" opacity="0.35"/><circle cx="12" cy="12" r="4"/>
        </svg>
      </button>

      <!-- Abort (streaming) or Send -->
      <button
        v-if="store.streaming"
        class="btn-abort-inline"
        @click="emit('abort')"
        title="Stop generation"
      >
        <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor"><rect width="14" height="14" rx="2"/></svg>
      </button>
      <button
        v-else
        class="btn-send"
        @click="onSubmit"
        :disabled="!messageInput.trim() || !store.chatReady"
      >&uarr;</button>
    </div>
  </div>
</template>

<style scoped>
.chat-input-bar {
  width: 100%;
}

.attached-note {
  padding: 0.25rem 0.5rem;
}
.attached-note-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.2rem;
  background: var(--color-primary);
  color: #fff;
  border-radius: 10px;
  padding: 0.15rem 0.4rem;
  font-size: 0.75rem;
}
.attached-note-remove {
  background: none;
  border: none;
  color: rgba(255, 255, 255, 0.7);
  cursor: pointer;
  font-size: 0.9rem;
  line-height: 1;
  padding: 0 0.1rem;
}
.attached-note-remove:hover { color: #fff; }

.input-row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.5rem 0.5rem 0.5rem 0.75rem;
  background: var(--color-input-bar-bg);
  border-radius: 12px;
  box-shadow: 0 2px 8px var(--color-shadow);
}

.chat-input-bar--pill .input-row {
  border-radius: 24px;
  padding: 0.6rem 0.6rem 0.6rem 1rem;
}

.input-textarea {
  flex: 1;
  resize: none;
  padding: 0.35rem 0.5rem;
  border: none;
  background: transparent;
  color: var(--color-input-bar-text);
  outline: none;
  font-family: inherit;
  font-size: 0.9rem;
  max-height: 150px;
  overflow-y: auto;
}
.input-textarea::placeholder { color: var(--color-input-bar-placeholder); }
.input-textarea:disabled { opacity: 0.5; }

.btn-icon {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--color-input-bar-text);
  opacity: 0.6;
  padding: 0.2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  flex-shrink: 0;
}
.btn-icon:hover { opacity: 1; }
.btn-icon:disabled { opacity: 0.3; cursor: default; }

.btn-mic.mic-recording {
  opacity: 1;
  color: #ef4444;
  border-radius: 50%;
  /* Smooth between amplitude samples (~100ms) so the pulse feels continuous
     rather than stepping. */
  transition: transform 0.12s ease-out, box-shadow 0.12s ease-out;
}
.btn-mic.mic-transcribing { opacity: 0.5; }

.note-picker-wrapper { position: relative; }
.note-picker-dropdown {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 0;
  width: 260px;
  background: var(--color-bg-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: 0 4px 16px var(--color-shadow);
  z-index: 20;
  overflow: hidden;
}
.note-picker-search {
  width: 100%;
  padding: 0.45rem 0.65rem;
  border: none;
  border-bottom: 1px solid var(--color-border);
  background: transparent;
  color: var(--color-text);
  font-size: 0.85rem;
  outline: none;
  font-family: inherit;
  box-sizing: border-box;
}
.note-picker-results { max-height: 180px; overflow-y: auto; }
.note-picker-item {
  padding: 0.4rem 0.65rem;
  cursor: pointer;
  font-size: 0.85rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.note-picker-item:hover { background: var(--color-bg-secondary); }
.note-picker-empty { padding: 0.4rem 0.65rem; color: var(--color-text-muted); font-size: 0.8rem; }

.btn-send {
  width: 30px;
  min-width: 30px;
  height: 30px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--gradient-cta);
  color: #fff;
  border: none;
  border-radius: 50%;
  cursor: pointer;
  font-size: 1rem;
  flex-shrink: 0;
  transition: box-shadow 0.15s;
}
.btn-send:hover { box-shadow: 0 0 16px rgba(124, 58, 237, 0.35); }
.btn-send:disabled { opacity: 0.35; cursor: default; box-shadow: none; }

.btn-abort-inline {
  width: 28px;
  min-width: 28px;
  height: 28px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-bg-secondary);
  border: 1px solid var(--color-border);
  border-radius: 50%;
  cursor: pointer;
  flex-shrink: 0;
}
.btn-abort-inline:hover { border-color: #ef4444; color: #ef4444; }
</style>
