import { useState, useEffect, useRef } from 'react'
import { Link, Routes, Route, useNavigate } from 'react-router-dom'
import AdminPage from './AdminPage'
import './App.css'

const SPOTIFY_SHOW_URL = 'https://open.spotify.com/show/0Gq1TzoJOdReSZ1gYQi8Xl'
const SPOTIFY_EMBED_URL =
  'https://open.spotify.com/embed/show/0Gq1TzoJOdReSZ1gYQi8Xl?utm_source=generator&theme=0'
const SPOTIFY_CREATOR_URL = 'https://creators.spotify.com/pod/profile/nmemmert/'
const APPLE_PODCASTS_URL =
  'https://podcasts.apple.com/search?term=Verse+by+Verse+with+Nate'
const YOUTUBE_URL = 'https://www.youtube.com/@blackzebraem5558'
const AMAZON_MUSIC_URL =
  'https://music.amazon.com/podcasts/202322bf-db86-4e7d-9a6b-4db7cbccbccf/verse-by-verse-with-nate'
const FACEBOOK_URL = 'https://facebook.com/versebyversewithnate'
const CONSENT_KEY = 'vbn_analytics_consent_choice'

function FacebookIcon() {
  return (
    <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
      <path d="M24 12.073C24 5.405 18.627 0 12 0S0 5.405 0 12.073C0 18.1 4.388 23.094 10.125 24v-8.437H7.078v-3.49h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.234 2.686.234v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.49h-2.796V24C19.612 23.094 24 18.1 24 12.073z" />
    </svg>
  )
}

export interface CustomLink {
  id: string
  label: string
  url: string
  placement: 'platforms' | 'footer' | 'resources'
}

export interface CustomBlock {
  id: string
  heading: string
  body: string
}

export interface SiteContent {
  eyebrow: string
  heroTagline: string
  aboutShowHeading: string
  aboutShowP1: string
  aboutShowP2: string
  aboutNate: string
  seriesLabel: string
  seriesTitle: string
  seriesDescription: string
  studyGuideTitle: string
  studyGuideDescription: string
  studyGuideUrl: string
  shareHeading: string
  shareP: string
  customLinks: CustomLink[]
  customBlocks: CustomBlock[]
}

export const DEFAULTS: SiteContent = {
  eyebrow: 'A Journey Through Scripture',
  heroTagline: "Exploring God's Word one verse at a time",
  aboutShowHeading: 'Depth. Clarity. Application.',
  aboutShowP1:
    'Verse by Verse with Nate walks through Scripture passage by passage — unpacking the original context, drawing out the meaning, and connecting each verse to how we live today.',
  aboutShowP2:
    "Whether you're in the car, at the gym, or just looking for something to anchor your day, each episode is designed to feed your faith with solid, practical teaching.",
  aboutNate:
    'Nate Emmert is a husband, dad, and lifelong student of the Bible from Lynchburg, Va. He created Verse by Verse to share the joy of deep Scripture study in a format anyone can follow along with — no seminary required.',
  seriesLabel: 'Now Playing',
  seriesTitle: 'Study of Titus: Sound Doctrine',
  seriesDescription:
    "A deep-dive into Paul's letter to Titus — unpacking what it means to build a church and a life on sound doctrine.",
  studyGuideTitle: 'Companion Study Guide',
  studyGuideDescription:
    'Go deeper in your study with the official Verse by Verse companion guide — now available on Amazon.',
  studyGuideUrl: 'https://a.co/d/01sG2tOJ',
  shareHeading: 'Help one more person hear the Word this week.',
  shareP: 'Scan the QR code or text the show link to a friend who needs encouragement today.',
  customLinks: [],
  customBlocks: [],
}

function SpotifyIcon() {
  return (
    <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
      <path d="M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.66 0 12 0zm5.521 17.34c-.24.359-.66.48-1.021.24-2.82-1.74-6.36-2.101-10.561-1.141-.418.122-.779-.179-.899-.539-.12-.421.18-.78.54-.9 4.56-1.021 8.52-.6 11.64 1.32.42.18.479.659.301 1.02zm1.44-3.3c-.301.42-.841.6-1.262.3-3.239-1.98-8.159-2.58-11.939-1.38-.479.12-1.02-.12-1.14-.6-.12-.48.12-1.021.6-1.141C9.6 9.9 15 10.561 18.72 12.84c.361.181.54.78.241 1.2zm.12-3.36C15.24 8.4 8.82 8.16 5.16 9.301c-.6.179-1.2-.181-1.38-.721-.18-.601.18-1.2.72-1.381 4.26-1.26 11.28-1.02 15.721 1.621.539.3.719 1.02.419 1.56-.299.421-1.02.599-1.559.3z" />
    </svg>
  )
}

function YouTubeIcon() {
  return (
    <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
      <path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" />
    </svg>
  )
}

function AmazonMusicIcon() {
  return (
    <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
      <path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z" />
    </svg>
  )
}

interface PublicQuestion {
  id: string
  firstName: string
  question: string
  answer: string
}

// ── Chatbot ────────────────────────────────────────────────────────────────

interface ChatbotEntry {
  id: string
  type: 'qa' | 'topic' | 'episode'
  title: string
  content: string
  keywords: string[]
  sourceLabel?: string
  priority?: boolean
  createdAt?: string
  updatedAt?: string
}

interface ChatMessage {
  role: 'bot' | 'user'
  text: string
  suggestions?: ChatSuggestion[]
  sources?: string[]
}

interface ChatSuggestion {
  label: string
  prompt?: string
  response?: ChatResponse
}

interface ChatResponse {
  text: string
  suggestions: ChatSuggestion[]
  sources?: string[]
}

type ResponseStyle = 'brief' | 'balanced' | 'deep'

const STOP_WORDS = new Set([
  'a','an','the','is','are','was','were','be','been','being','have','has','had','do','does','did',
  'will','would','could','should','may','might','shall','can','i','you','he','she','it','we','they',
  'me','him','her','us','them','my','your','his','its','our','their','this','that','these','those',
  'and','but','or','nor','so','yet','for','of','in','on','at','to','from','with','by','about',
  'what','how','why','when','where','who','which','if','then','than','as','do','just','not',
])

const TOKEN_ALIASES: Record<string, string[]> = {
  bible: ['translation', 'version', 'scripture', 'bsb', 'berean'],
  translation: ['version', 'bsb', 'berean', 'bible'],
  version: ['translation', 'bsb', 'berean', 'bible'],
  elders: ['elder', 'elders', 'leadership', 'leaders', 'overseer', 'overseers', 'pastor'],
  elder: ['elders', 'leadership', 'overseer', 'pastor'],
  leadership: ['elders', 'elder', 'overseer', 'overseers', 'leaders'],
  grace: ['salvation', 'saved', 'godliness', 'mercy'],
  salvation: ['saved', 'grace', 'mercy', 'gospel'],
  saved: ['salvation', 'grace', 'mercy', 'gospel'],
  gospel: ['grace', 'salvation', 'jesus', 'faith', 'good news'],
  hope: ['blessed hope', 'appearing', 'return', 'coming'],
  appearing: ['return', 'coming', 'hope'],
  return: ['appearing', 'coming', 'hope'],
  paul: ['saul', 'apostle paul'],
  titus: ['titus 1', 'titus 2', 'titus 3'],
  quiet: ['prayer', 'devotional', 'reading'],
  devotional: ['quiet', 'prayer', 'reading'],
  family: ['skeptic', 'skeptical', 'gospel', 'faith'],
  politics: ['public life', 'authorities', 'rulers', 'government'],
  public: ['politics', 'government', 'authorities'],
}

interface ParsedVerseRef {
  book: string
  chapter: number
  startVerse: number
  endVerse: number
}

function tokenize(text: string): string[] {
  return text.toLowerCase().replace(/[^a-z0-9\s]/g, ' ').split(/\s+/).filter(w => w.length > 2 && !STOP_WORDS.has(w))
}

function extractVerseRefs(text: string): string[] {
  const matches = text.match(/\b(?:[1-3]\s*)?[a-z]+\s+\d+:\d+(?:\s*[-–]\s*\d+)?\b/gi) ?? []
  return [...new Set(matches.map(normalizeVerseRef))]
}

function normalizeVerseRef(ref: string): string {
  return ref
    .toLowerCase()
    .replace(/[–—]/g, '-')
    .replace(/\s+/g, ' ')
    .replace(/\s*-\s*/g, '-')
    .trim()
}

function parseVerseRef(ref: string): ParsedVerseRef | null {
  const match = normalizeVerseRef(ref).match(/^((?:[1-3]\s*)?[a-z]+)\s+(\d+):(\d+)(?:-(\d+))?$/)
  if (!match) return null

  const [, book, chapter, startVerse, endVerse] = match
  return {
    book: book.replace(/\s+/g, ' ').trim(),
    chapter: Number(chapter),
    startVerse: Number(startVerse),
    endVerse: Number(endVerse ?? startVerse),
  }
}

function expandTokens(tokens: string[]): string[] {
  const expanded = new Set(tokens)
  for (const token of tokens) {
    const aliases = TOKEN_ALIASES[token] ?? []
    for (const alias of aliases) expanded.add(alias)
  }
  return [...expanded]
}

function getSourceLabel(entry: ChatbotEntry): string {
  if (entry.sourceLabel && entry.sourceLabel.trim()) return entry.sourceLabel.trim()
  return entry.title
}

function isBibleVersionQuery(query: string, queryTokens: string[]): boolean {
  const q = query.toLowerCase()
  const hasBibleWord = ['bible', 'translation', 'version'].some(k => q.includes(k) || queryTokens.includes(k))
  const hasIntentWord = ['use', 'recommend', 'podcast', 'read', 'study'].some(k => q.includes(k) || queryTokens.includes(k))
  return hasBibleWord && hasIntentWord
}

function hasBsbSignal(entry: ChatbotEntry): boolean {
  const haystack = `${entry.title} ${entry.content} ${entry.keywords.join(' ')}`.toLowerCase()
  return haystack.includes('berean standard bible') || haystack.includes(' bsb ')
}

function normalizeExcerpt(text: string): string {
  return text.toLowerCase().replace(/\s+/g, ' ').trim()
}

function hasMeaningfulExtraDetail(shortAnswer: string, fullNotes: string): boolean {
  const normalizedShort = normalizeExcerpt(shortAnswer)
  const normalizedNotes = normalizeExcerpt(fullNotes)

  if (!normalizedNotes) return false
  if (normalizedNotes === normalizedShort) return false
  if (normalizedNotes.includes(normalizedShort) && normalizedNotes.length - normalizedShort.length < 120) return false

  return true
}

type ChatIntent = 'verse-summary' | 'episode-lookup' | 'application' | 'definition' | 'practice' | 'overview' | 'identity' | 'follow-up' | 'general'

interface SearchChunk {
  id: string
  entryId: string
  entryType: ChatbotEntry['type']
  title: string
  sourceLabel: string
  content: string
  keywords: string[]
  verseRefs: string[]
  episodeNumber: number | null
  topicTags: string[]
  kind: 'summary' | 'detail'
}

interface ChatContextState {
  turnCount: number
  lastPrompt: string
  lastIntent: ChatIntent | null
  lastSubject: string
  lastVerseRefs: string[]
  lastEntryIds: string[]
  lastSourceLabels: string[]
  lastTopics: string[]
  lastChunks: SearchChunk[]
  recentPrompts: string[]
  recentSubjects: string[]
  recentEntryIds: string[]
}

interface IntentAnalysis {
  type: ChatIntent
  rawQuery: string
  searchQuery: string
  queryTokens: string[]
  literalTerms: string[]
  subject: string
  excludedSubject: string
  isCorrection: boolean
  needsClarification: boolean
  clarificationPrompt: string
  verseRefs: string[]
  episodeNumber: number | null
  isFollowUp: boolean
}

type ScoredChunk = { chunk: SearchChunk; score: number }

const EMPTY_CHAT_CONTEXT: ChatContextState = {
  turnCount: 0,
  lastPrompt: '',
  lastIntent: null,
  lastSubject: '',
  lastVerseRefs: [],
  lastEntryIds: [],
  lastSourceLabels: [],
  lastTopics: [],
  lastChunks: [],
  recentPrompts: [],
  recentSubjects: [],
  recentEntryIds: [],
}

function toTitleCaseLabel(text: string): string {
  return text
    .split(' ')
    .map(part => part ? part.charAt(0).toUpperCase() + part.slice(1) : part)
    .join(' ')
}

function buildSourceCitations(chunks: SearchChunk[]): string[] {
  return [...new Set(chunks.slice(0, 3).map(chunk => {
    const verse = chunk.verseRefs[0] ? ` (${formatVerseRef(chunk.verseRefs[0])})` : ''
    return `${chunk.sourceLabel}${verse}`
  }))]
}

function formatResponseText(response: ChatResponse): string {
  if (!response.sources || response.sources.length === 0) return response.text
  return `${response.text}\n\nSources: ${response.sources.join(' | ')}`
}

function sanitizeSubject(value: string): string {
  return value
    .toLowerCase()
    .replace(/[^a-z\s'-]/g, ' ')
    .replace(/\s+/g, ' ')
    .trim()
}

function extractIdentitySubject(normalizedQuery: string): string {
  const match = normalizedQuery.match(/\b(?:who\s+(?:is|was)|tell me about)\s+([a-z][a-z\s'-]{1,40})\b/i)
  if (!match) return ''
  const cleaned = sanitizeSubject(match[1])
  const withoutArticles = cleaned.replace(/^(a|an|the)\s+/, '').trim()
  return withoutArticles.split(' ').slice(0, 3).join(' ').trim()
}

function extractCorrectionSubjects(normalizedQuery: string): { rejected: string; expected: string } | null {
  const match = normalizedQuery.match(/\bthat(?:'s| is)\s+([a-z][a-z\s'-]{0,24})\s+not\s+([a-z][a-z\s'-]{0,24})\b/i)
  if (!match) return null

  const rejected = sanitizeSubject(match[1]).split(' ').slice(0, 3).join(' ').trim()
  const expected = sanitizeSubject(match[2]).split(' ').slice(0, 3).join(' ').trim()
  if (!rejected || !expected) return null

  return { rejected, expected }
}

function escapeRegex(text: string): string {
  return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}

function extractEpisodeNumber(text: string): number | null {
  const match = text.match(/\bepisode\s+(\d+)\b/i)
  if (!match) return null
  return Number(match[1])
}

function formatVerseRef(ref: string): string {
  const parsed = parseVerseRef(ref)
  if (!parsed) return ref

  const book = parsed.book
    .split(' ')
    .map(part => /^\d+$/.test(part) ? part : part.charAt(0).toUpperCase() + part.slice(1))
    .join(' ')

  return parsed.startVerse === parsed.endVerse
    ? `${book} ${parsed.chapter}:${parsed.startVerse}`
    : `${book} ${parsed.chapter}:${parsed.startVerse}-${parsed.endVerse}`
}

function extractSentences(text: string): string[] {
  return text
    .split(/(?<=[.!?])\s+(?=[A-Z])/)
    .map(sentence => sentence.trim())
    .filter(Boolean)
}

function buildRelevantExcerpt(text: string, queryTokens: string[], maxLength: number): string {
  const expandedTokens = expandTokens(queryTokens)
  const sentences = extractSentences(text).filter(sentence => sentence.length > 25)

  if (sentences.length === 0) return text.trim().slice(0, maxLength)

  const rankedSentences = sentences.map(sentence => ({
    sentence,
    score: tokenize(sentence).filter(token => expandedTokens.some(queryToken => token.includes(queryToken) || queryToken.includes(token))).length,
  }))

  const selected = rankedSentences.some(item => item.score > 0)
    ? rankedSentences.sort((a, b) => b.score - a.score).slice(0, 2).map(item => item.sentence)
    : rankedSentences.slice(0, 1).map(item => item.sentence)

  return selected.join(' ').slice(0, maxLength)
}

function buildTopicTags(entry: ChatbotEntry): string[] {
  return [...new Set([
    ...tokenize(entry.title),
    ...entry.keywords.flatMap(tokenize),
  ])].slice(0, 10)
}

function splitLargeBlock(block: string): string[] {
  if (block.length <= 550) return [block]

  const sentences = extractSentences(block)
  if (sentences.length <= 1) return [block]

  const chunks: string[] = []
  let current = ''

  for (const sentence of sentences) {
    const next = current ? `${current} ${sentence}` : sentence
    if (next.length > 520 && current) {
      chunks.push(current.trim())
      current = sentence
      continue
    }
    current = next
  }

  if (current.trim()) chunks.push(current.trim())
  return chunks
}

function injectSemanticBreaks(content: string): string {
  return content
    .replace(/([.!?])\s+(?=(WHO|WHAT|WHY|WHERE|WHEN|HOW)\s+[A-Z][A-Z\s'’:-]{4,}\?)/g, '$1\n\n')
    .replace(/([.!?])\s+(?=(DISCUSSION QUESTIONS|CLOSING|THE \w+|VERSE \d+))/g, '$1\n\n')
    .replace(/\s+(?=(DISCUSSION QUESTIONS|CLOSING)\b)/g, '\n\n')
}

function splitContentIntoSections(content: string): string[] {
  const normalizedContent = injectSemanticBreaks(content)

  const rawBlocks = normalizedContent
    .split(/\n\s*\n/)
    .map(block => block.replace(/\s+/g, ' ').trim())
    .filter(Boolean)

  const mergedBlocks: string[] = []

  for (let index = 0; index < rawBlocks.length; index += 1) {
    const block = rawBlocks[index]
    const isHeading = /^[A-Z0-9'’:()\-\s]{5,90}$/.test(block)
    if (isHeading && rawBlocks[index + 1]) {
      mergedBlocks.push(`${block}. ${rawBlocks[index + 1]}`)
      index += 1
      continue
    }
    mergedBlocks.push(block)
  }

  return mergedBlocks.flatMap(splitLargeBlock)
}

function buildSearchChunks(entries: ChatbotEntry[]): SearchChunk[] {
  return entries.flatMap(entry => {
    const sourceLabel = getSourceLabel(entry)
    const verseRefs = extractVerseRefs(`${entry.title} ${entry.content} ${entry.keywords.join(' ')}`)
    const topicTags = buildTopicTags(entry)
    const episodeNumber = extractEpisodeNumber(`${entry.title} ${sourceLabel}`)
    const sections = splitContentIntoSections(entry.content)
    const chunks: SearchChunk[] = []

    const summaryContent = buildRelevantExcerpt(entry.content, topicTags, entry.type === 'episode' ? 340 : 260)
    chunks.push({
      id: `${entry.id}-summary`,
      entryId: entry.id,
      entryType: entry.type,
      title: entry.title,
      sourceLabel,
      content: summaryContent,
      keywords: entry.keywords,
      verseRefs,
      episodeNumber,
      topicTags,
      kind: 'summary',
    })

    if (sections.length > 1 || entry.content.length > summaryContent.length + 150) {
      sections.forEach((section, index) => {
        if (normalizeExcerpt(section) === normalizeExcerpt(summaryContent)) return
        chunks.push({
          id: `${entry.id}-detail-${index}`,
          entryId: entry.id,
          entryType: entry.type,
          title: entry.title,
          sourceLabel,
          content: section,
          keywords: entry.keywords,
          verseRefs: extractVerseRefs(`${entry.title} ${section} ${entry.keywords.join(' ')}`),
          episodeNumber,
          topicTags,
          kind: 'detail',
        })
      })
    }

    return chunks
  })
}

function buildCharacterTrigrams(text: string): Set<string> {
  const normalized = normalizeExcerpt(text)
  const trigrams = new Set<string>()
  if (normalized.length < 3) return trigrams

  for (let index = 0; index <= normalized.length - 3; index += 1) {
    trigrams.add(normalized.slice(index, index + 3))
  }

  return trigrams
}

function getSemanticSimilarity(left: string, right: string): number {
  const leftSet = buildCharacterTrigrams(left)
  const rightSet = buildCharacterTrigrams(right)

  if (leftSet.size === 0 || rightSet.size === 0) return 0

  let intersection = 0
  for (const gram of leftSet) {
    if (rightSet.has(gram)) intersection += 1
  }

  const union = leftSet.size + rightSet.size - intersection
  return union === 0 ? 0 : intersection / union
}

function isContextualFollowUp(query: string): boolean {
  const normalized = query.trim().toLowerCase()
  return /^(what about|how about|and\b|also\b|that\b|this\b|those\b|these\b|more\b|go deeper\b|expand\b|show me\b|next\b|same\b|what else\b|tell me more\b|his\b|him\b|he\b)/.test(normalized)
}

function normalizeQuestionShape(text: string): string {
  return text
    .toLowerCase()
    .replace(/\bwho\s+(is|was)\b/g, 'who')
    .replace(/\bwhat\s+(is|does)\b/g, 'what')
    .replace(/\s+/g, ' ')
    .trim()
}

function extractLiteralTerms(text: string): string[] {
  const cleaned = text
    .toLowerCase()
    .replace(/[–—]/g, '-')
    .replace(/[^a-z0-9:\-\s']/g, ' ')

  return [...new Set(
    cleaned
      .split(/\s+/)
      .map(term => term.trim())
      .filter(term => term.length >= 2 && !STOP_WORDS.has(term)),
  )].slice(0, 20)
}

function normalizeSearchText(text: string): string {
  return text
    .toLowerCase()
    .replace(/[–—]/g, '-')
    .replace(/[^a-z0-9:\-\s']/g, ' ')
    .replace(/\s+/g, ' ')
    .trim()
}

function cleanSearchPhrase(text: string): string {
  return normalizeSearchText(text)
    .replace(/^(can you|please|show me|tell me|what is|what does|who is|who was)\s+/, '')
    .trim()
}

function getLiteralSearchScore(indexText: string, titleText: string, analysis: IntentAnalysis): number {
  const normalizedIndex = normalizeSearchText(indexText)
  const normalizedTitle = normalizeSearchText(titleText)
  let score = 0

  const phrase = cleanSearchPhrase(analysis.searchQuery)
  if (phrase.length >= 6) {
    if (normalizedTitle.includes(phrase)) score += 20
    else if (normalizedIndex.includes(phrase)) score += 14
  }

  for (const term of analysis.literalTerms) {
    if (normalizedTitle.includes(term)) score += 4
    else if (normalizedIndex.includes(term)) score += 2.25
  }

  return score
}

type ConversationCue = 'none' | 'greeting' | 'gratitude' | 'affirmation' | 'deepen'

function detectConversationCue(query: string): ConversationCue {
  const normalized = query.trim().toLowerCase()
  if (!normalized) return 'none'

  if (/^(hi|hey|hello|yo|good morning|good afternoon|good evening)\b/.test(normalized)) return 'greeting'
  if (/\b(thank you|thanks|appreciate it|that helps)\b/.test(normalized)) return 'gratitude'
  if (/^(yes|yep|yeah|ok|okay|sounds good|right|exactly)\b/.test(normalized)) return 'affirmation'
  if (/\b(go deeper|deeper|more detail|expand on that|tell me more|go on)\b/.test(normalized)) return 'deepen'

  return 'none'
}

function enrichQueryWithContext(query: string, context: ChatContextState): string {
  const normalized = query.trim().toLowerCase()
  if (!normalized || !context.lastSubject) return query
  if (normalized.includes(context.lastSubject)) return query

  const hasPronounReference = /\b(he|him|his)\b/.test(normalized)
  const isBridgeFollowUp = /^(what about|how about|and|also)\b/.test(normalized)

  if (hasPronounReference || isBridgeFollowUp) {
    return `${query} ${context.lastSubject}`.trim()
  }

  return query
}

function getExcerptMaxChars(style: ResponseStyle, kind: 'normal' | 'deep'): number {
  if (style === 'brief') return kind === 'deep' ? 260 : 170
  if (style === 'deep') return kind === 'deep' ? 520 : 330
  return kind === 'deep' ? 380 : 240
}

function buildDeeperDiveReply(context: ChatContextState, style: ResponseStyle): ChatResponse | null {
  if (context.lastChunks.length === 0) return null

  const primary = context.lastChunks[0]
  const deeperChunk = context.lastChunks.find(chunk => chunk.kind === 'detail') ?? primary
  const tokens = tokenize(`${context.lastPrompt} ${context.lastSubject}`)
  const deeperExcerpt = buildRelevantExcerpt(deeperChunk.content, tokens, getExcerptMaxChars(style, 'deep'))
  const lead = primary.verseRefs[0]
    ? `Going deeper in ${formatVerseRef(primary.verseRefs[0])}:`
    : `Going deeper in ${primary.sourceLabel}:`

  const suggestions = buildSmartFollowUpPrompts(
    context.lastChunks,
    analyzeIntent(context.lastPrompt || primary.title, context),
    deeperExcerpt,
  )

  return {
    text: `${lead}\n${deeperExcerpt}`,
    suggestions,
    sources: buildSourceCitations(context.lastChunks),
  }
}

function buildConversationOnlyReply(cue: ConversationCue, context: ChatContextState, style: ResponseStyle): ChatResponse | null {
  if (cue === 'none') return null

  if (cue === 'greeting') {
    return {
      text: `Glad you're here. Ask me about a person, passage, or episode and I'll walk it through with you step by step.`,
      suggestions: INLINE_CHAT_PROMPTS.slice(0, 3).map(prompt => ({ label: prompt, prompt })),
    }
  }

  if (cue === 'gratitude') {
    const suggestions = context.lastChunks.length > 0
      ? buildSmartFollowUpPrompts(context.lastChunks, analyzeIntent(context.lastPrompt || 'go deeper', context), context.lastChunks[0].content)
      : [
          { label: 'Give me a summary of Titus 3:4-7', prompt: 'Give me a summary of Titus 3:4-7' },
          { label: 'What does Titus 1 teach about church leadership?', prompt: 'What does Titus 1 teach about church leadership?' },
        ]

    return {
      text: `Anytime. Want to keep going on this thread or jump to another question?`,
      suggestions: suggestions.slice(0, 3),
      sources: context.lastChunks.length > 0 ? buildSourceCitations(context.lastChunks) : undefined,
    }
  }

  if (cue === 'affirmation' || cue === 'deepen') {
    return buildDeeperDiveReply(context, style)
  }

  return null
}

function buildClarificationReply(analysis: IntentAnalysis, context: ChatContextState): ChatResponse {
  const followupSuggestions = context.lastChunks.length > 0
    ? buildSmartFollowUpPrompts(
        context.lastChunks,
        analyzeIntent(context.lastPrompt || 'go deeper', context),
        context.lastChunks[0]?.content ?? '',
      )
    : [
        { label: 'Who was Titus?', prompt: 'Who was Titus?' },
        { label: 'Give me a summary of Titus 3:4-7', prompt: 'Give me a summary of Titus 3:4-7' },
      ]

  return {
    text: `${analysis.clarificationPrompt}\n\nI want to answer from Nate's notes accurately, so one more detail will help me match the right content.`,
    suggestions: followupSuggestions.slice(0, 3),
    sources: context.lastChunks.length > 0 ? buildSourceCitations(context.lastChunks) : undefined,
  }
}

function analyzeIntent(query: string, context: ChatContextState): IntentAnalysis {
  const rawQuery = query.trim()
  const normalized = rawQuery.toLowerCase()
  const verseRefs = extractVerseRefs(rawQuery)
  const episodeNumber = extractEpisodeNumber(rawQuery)
  const wantsSummary = /\b(summary|summarize|overview|explain)\b/.test(normalized)
  const wantsEpisode = /\bepisode\b|\bpodcast\b|\blisten\b/.test(normalized) || episodeNumber !== null
  const wantsApplication = /\bapply\b|\bapplication\b|\btoday\b|\blive this out\b|\bpractically\b/.test(normalized)
  const wantsDefinition = /\bmean\b|\bmeaning\b|\bdefine\b|\bwhat is\b|\bwhat does\b/.test(normalized)
  const wantsIdentity = /\bwho is\b|\bwho was\b|\btell me about\b/.test(normalized)
  const wantsPractice = /\bhow can i\b|\bhow do i\b|\bhow should i\b|\bpray\b|\bpractice\b/.test(normalized)
  const wantsComparison = /\bdifference\b|\bcompare\b|\bvs\.?\b|\bversus\b/.test(normalized)
  const correctionSubjects = extractCorrectionSubjects(normalized)
  const isFollowUp = isContextualFollowUp(rawQuery) || correctionSubjects !== null
  const explicitSubject = extractIdentitySubject(normalized)
  const subject = correctionSubjects?.expected || explicitSubject || (isFollowUp ? context.lastSubject : '')
  const excludedSubject = correctionSubjects?.rejected ?? ''
  const isCorrection = correctionSubjects !== null
  const tokenCount = tokenize(rawQuery).length
  const hasStandalonePronoun = /\b(this|that|it|he|she|they|them|those|these|him|her)\b/.test(normalized)

  let needsClarification = false
  let clarificationPrompt = ''

  if (!isFollowUp && hasStandalonePronoun && context.turnCount === 0) {
    needsClarification = true
    clarificationPrompt = 'Can you name the specific person, verse, or episode you mean?'
  } else if (!isFollowUp && tokenCount < 2 && verseRefs.length === 0 && episodeNumber === null && !wantsIdentity) {
    needsClarification = true
    clarificationPrompt = 'Can you make that a bit more specific so I can match Nate\'s notes?'
  } else if (/\bwho\s+(is|was)\b/.test(normalized) && !subject) {
    needsClarification = true
    clarificationPrompt = 'Who would you like to ask about specifically?'
  } else if (wantsComparison && !/\band\b|\bbetween\b/.test(normalized)) {
    needsClarification = true
    clarificationPrompt = 'What two things would you like me to compare?'
  }

  let type: ChatIntent = 'general'
  if (wantsEpisode) type = 'episode-lookup'
  else if (verseRefs.length > 0 && wantsSummary) type = 'verse-summary'
  else if (wantsIdentity || subject) type = 'identity'
  else if (wantsApplication) type = 'application'
  else if (wantsDefinition) type = 'definition'
  else if (wantsPractice) type = 'practice'
  else if (wantsSummary) type = 'overview'
  else if (isFollowUp) type = 'follow-up'
  else if (wantsComparison) type = 'overview'

  let searchQuery = rawQuery
  if (type === 'identity' && subject) {
    searchQuery = `${searchQuery} ${subject}`
  }
  if (verseRefs.length === 0 && context.lastVerseRefs.length > 0 && isFollowUp) {
    searchQuery += ` ${context.lastVerseRefs[0]}`
  }
  if (episodeNumber === null && context.lastSourceLabels.length > 0 && isFollowUp) {
    searchQuery += ` ${context.lastSourceLabels[0]}`
  }
  if (context.lastTopics.length > 0 && isFollowUp) {
    searchQuery += ` ${context.lastTopics.slice(0, 2).join(' ')}`
  }
  if (context.recentSubjects.length > 0 && isFollowUp) {
    searchQuery += ` ${context.recentSubjects.slice(-2).join(' ')}`
  }
  if (context.recentPrompts.length > 0 && isFollowUp) {
    searchQuery += ` ${context.recentPrompts.slice(-1)[0]}`
  }

  return {
    type,
    rawQuery,
    searchQuery,
    queryTokens: tokenize(searchQuery),
    literalTerms: extractLiteralTerms(searchQuery),
    subject,
    excludedSubject,
    isCorrection,
    needsClarification,
    clarificationPrompt,
    verseRefs,
    episodeNumber,
    isFollowUp,
  }
}

function getVerseMatchScoreForRefs(candidateRefs: string[], queryRefs: string[]): number {
  const parsedQueries = queryRefs.map(parseVerseRef).filter((ref): ref is ParsedVerseRef => ref !== null)
  if (parsedQueries.length === 0) return 0

  const parsedCandidates = candidateRefs.map(parseVerseRef).filter((ref): ref is ParsedVerseRef => ref !== null)
  let score = 0

  for (const queryRef of parsedQueries) {
    for (const candidateRef of parsedCandidates) {
      if (candidateRef.book !== queryRef.book) continue

      if (
        candidateRef.chapter === queryRef.chapter
        && candidateRef.startVerse === queryRef.startVerse
        && candidateRef.endVerse === queryRef.endVerse
      ) {
        score = Math.max(score, 32)
        continue
      }

      if (
        candidateRef.chapter === queryRef.chapter
        && candidateRef.startVerse <= queryRef.startVerse
        && candidateRef.endVerse >= queryRef.endVerse
      ) {
        score = Math.max(score, 24)
        continue
      }

      if (
        candidateRef.chapter === queryRef.chapter
        && queryRef.startVerse <= candidateRef.startVerse
        && queryRef.endVerse >= candidateRef.endVerse
      ) {
        score = Math.max(score, 18)
        continue
      }

      if (candidateRef.chapter === queryRef.chapter) {
        score = Math.max(score, 9)
        continue
      }

      score = Math.max(score, 2)
    }
  }

  return score
}

function hasSharedVerseContext(candidateRefs: string[], contextRefs: string[]): boolean {
  return getVerseMatchScoreForRefs(candidateRefs, contextRefs) > 0
}

function scoreChunkForAnalysis(chunk: SearchChunk, analysis: IntentAnalysis, context: ChatContextState): number {
  const expandedTokens = expandTokens(analysis.queryTokens)
  const titleTokens = tokenize(chunk.title)
  const indexTokens = [
    ...tokenize(chunk.title),
    ...tokenize(chunk.content),
    ...chunk.keywords.flatMap(tokenize),
    ...chunk.topicTags,
    ...chunk.verseRefs.flatMap(tokenize),
  ]

  let score = 0
  const normalizedQuery = normalizeExcerpt(analysis.searchQuery)
  const normalizedChunk = normalizeExcerpt(`${chunk.title} ${chunk.sourceLabel} ${chunk.content} ${chunk.keywords.join(' ')}`)
  const normalizedQuestionQuery = normalizeQuestionShape(analysis.searchQuery)
  const normalizedQuestionTitle = normalizeQuestionShape(chunk.title)
  const normalizedTitle = chunk.title.toLowerCase()
  const normalizedContent = chunk.content.toLowerCase()
  const fullTextIndex = `${chunk.title} ${chunk.sourceLabel} ${chunk.content} ${chunk.keywords.join(' ')} ${chunk.topicTags.join(' ')} ${chunk.verseRefs.join(' ')}`

  for (const token of expandedTokens) {
    if (titleTokens.some(titleToken => titleToken === token)) score += 5
    else if (titleTokens.some(titleToken => titleToken.includes(token) || token.includes(titleToken))) score += 3

    if (indexTokens.some(indexToken => indexToken === token)) score += 1.5
    else if (indexTokens.some(indexToken => indexToken.includes(token) || token.includes(indexToken))) score += 0.75
  }

  if (normalizedQuery.length > 8 && normalizedChunk.includes(normalizedQuery)) score += 18
  if (normalizedQuestionQuery.length > 5 && normalizedQuestionTitle === normalizedQuestionQuery) score += 28
  if (normalizedQuestionQuery.length > 5 && normalizedChunk.includes(normalizedQuestionQuery)) score += 12
  score += getLiteralSearchScore(fullTextIndex, chunk.title, analysis)

  score += getVerseMatchScoreForRefs(chunk.verseRefs, analysis.verseRefs)
  score += getSemanticSimilarity(analysis.searchQuery, `${chunk.title} ${chunk.content}`) * 14

  if (chunk.title.toLowerCase().trim() === analysis.rawQuery.toLowerCase().trim()) score += 40
  if (analysis.episodeNumber !== null && chunk.episodeNumber === analysis.episodeNumber) score += 22

  if (analysis.type === 'verse-summary') {
    if (chunk.kind === 'summary') score += 8
    if (chunk.verseRefs.length > 0) score += 7
  }

  if (analysis.type === 'episode-lookup') {
    if (chunk.entryType === 'episode') score += 12
    if (chunk.episodeNumber !== null) score += 4
  }

  if (analysis.type === 'application' && /\bapply\b|\bapplication\b|\btoday\b|\bdaily\b|\bcarry this week\b/i.test(chunk.content)) {
    score += 8
  }

  if (analysis.type === 'definition' && /\bmeans\b|\bgreek\b|\bliterally\b|\bword\b/i.test(chunk.content)) {
    score += 8
  }

  if (analysis.type === 'identity') {
    if (/^who\s+(is|was)\b/i.test(chunk.title)) score += 18
    if (/\bclosest and most trusted co-workers\b|\btrue child in our common faith\b|\bcame to faith through paul's ministry\b/i.test(chunk.content)) {
      score += 14
    }
    if (chunk.entryType === 'qa' || chunk.entryType === 'topic') score += 8

    if (analysis.subject) {
      const subjectPattern = new RegExp(`\\b${escapeRegex(analysis.subject)}\\b`, 'i')
      const identityPattern = new RegExp(`\\b${escapeRegex(analysis.subject)}\\b.{0,30}\\b(is|was)\\b|\\b(is|was)\\b.{0,30}\\b${escapeRegex(analysis.subject)}\\b`, 'i')
      const titleIsIdentityQuestion = /^who\s+(is|was)\b/i.test(normalizedTitle)

      if (subjectPattern.test(normalizedTitle)) score += 22
      if (subjectPattern.test(normalizedContent)) score += 12
      if (identityPattern.test(normalizedContent) || identityPattern.test(normalizedTitle)) score += 14

      if (!subjectPattern.test(normalizedTitle) && !subjectPattern.test(normalizedContent)) score -= 20
      if (titleIsIdentityQuestion && !subjectPattern.test(normalizedTitle)) score -= 28
    }

    if (analysis.excludedSubject) {
      const excludedPattern = new RegExp(`\\b${escapeRegex(analysis.excludedSubject)}\\b`, 'i')
      if (excludedPattern.test(normalizedTitle)) score -= 26
      if (excludedPattern.test(normalizedContent)) score -= 12
    }
  }

  if (analysis.type === 'practice' && /\bpray\b|\bdaily\b|\bconsistent\b|\bpractice\b|\bhow\b/i.test(chunk.content)) {
    score += 6
  }

  if (analysis.isFollowUp) {
    if (context.lastEntryIds.includes(chunk.entryId)) score += 7
    if (context.recentEntryIds.includes(chunk.entryId)) score += 4
    if (hasSharedVerseContext(chunk.verseRefs, context.lastVerseRefs)) score += 6
    if (chunk.topicTags.some(tag => context.lastTopics.includes(tag))) score += 4
  }

  if (analysis.verseRefs.length > 0 && chunk.title.toLowerCase().startsWith('give me a summary of') && getVerseMatchScoreForRefs(chunk.verseRefs, analysis.verseRefs) === 0) {
    score -= 10
  }

  return score
}

function getTopDistinctChunks(scored: ScoredChunk[], limit: number): SearchChunk[] {
  const seen = new Set<string>()
  const chunks: SearchChunk[] = []

  for (const item of scored) {
    if (seen.has(item.chunk.id)) continue
    seen.add(item.chunk.id)
    chunks.push(item.chunk)
    if (chunks.length >= limit) break
  }

  return chunks
}

function buildPromptForChunk(chunk: SearchChunk, analysis: IntentAnalysis): string {
  if (analysis.type === 'episode-lookup' || chunk.entryType === 'episode') {
    return `What does ${chunk.sourceLabel} teach?`
  }

  if (chunk.verseRefs[0]) {
    return `Give me a summary of ${formatVerseRef(chunk.verseRefs[0])}`
  }

  return chunk.title
}

function determineConfidence(scored: ScoredChunk[], analysis: IntentAnalysis): 'high' | 'medium' | 'low' {
  const top = scored[0]?.score ?? 0
  const second = scored[1]?.score ?? 0
  const margin = top - second
  const hasExactVerse = scored[0] ? getVerseMatchScoreForRefs(scored[0].chunk.verseRefs, analysis.verseRefs) >= 24 : false

  if (top >= 42 || (hasExactVerse && margin >= 7)) return 'high'
  if (top >= 24 && margin >= 4) return 'medium'
  return 'low'
}

function buildDetailedNotesReplyFromChunks(chunks: SearchChunk[]): string {
  const notes = chunks
    .slice(0, 3)
    .map(chunk => `From ${chunk.sourceLabel}:\n${chunk.content.trim()}`)
    .filter(Boolean)

  return `From Nate's notes:\n${notes.join('\n\n')}`
}

function buildLowConfidenceReply(scored: ScoredChunk[], analysis: IntentAnalysis): ChatResponse {
  const choices = getTopDistinctChunks(scored, 3)
  const topLabels = choices.slice(0, 2).map(chunk => chunk.sourceLabel)
  const text = topLabels.length === 2
    ? `I don't want to make up an answer. I couldn't find a confident match in Nate's notes yet. Did you mean "${topLabels[0]}" or "${topLabels[1]}"?`
    : `I don't want to make up an answer. I couldn't find this clearly in Nate's current notes yet. You can submit this question directly to Nate below.`

  const choiceSuggestions = choices.map(chunk => ({
    label: chunk.sourceLabel,
    prompt: buildPromptForChunk(chunk, analysis),
  }))

  return {
    text,
    suggestions: [
      ...choiceSuggestions,
      {
        label: 'How do I submit a question to Nate?',
        prompt: 'How do I submit a question to Nate?',
      },
    ].slice(0, 3),
    sources: buildSourceCitations(choices),
  }
}

function buildSmartFollowUpPrompts(chunks: SearchChunk[], analysis: IntentAnalysis, shortAnswer: string): ChatSuggestion[] {
  const prompts = new Set<string>()
  const primaryChunk = chunks[0]
  const primaryVerse = primaryChunk?.verseRefs[0]

  if (primaryVerse && analysis.type !== 'verse-summary') {
    prompts.add(`Give me a summary of ${formatVerseRef(primaryVerse)}`)
  }

  if (primaryChunk?.episodeNumber !== null) {
    prompts.add(`What are the main takeaways from Episode ${primaryChunk.episodeNumber}?`)
  }

  if (primaryVerse) {
    const parsed = parseVerseRef(primaryVerse)
    if (parsed) {
      prompts.add(`How does ${parsed.book.charAt(0).toUpperCase() + parsed.book.slice(1)} ${parsed.chapter} connect to the rest of the chapter?`)
    }
  }

  if (analysis.type !== 'application') prompts.add('How should I apply this today?')
  if (analysis.type !== 'episode-lookup') prompts.add('Which episode should I listen to next on this topic?')

  const promptSuggestions = [...prompts]
    .filter(prompt => prompt.toLowerCase() !== analysis.rawQuery.toLowerCase())
    .slice(0, 3)
    .map(prompt => ({ label: prompt, prompt }))

  // Add context-specific follow-ups based on what was discussed
  const subjectContext = analysis.subject || extractIdentitySubject(analysis.rawQuery.toLowerCase())
  if (subjectContext && !analysis.rawQuery.toLowerCase().includes('apply')) {
    promptSuggestions.unshift({
      label: `How does this apply to my life?`,
      prompt: `Based on what you just said about ${subjectContext}, how should I apply this today?`
    })
  }

  if (primaryVerse && analysis.type === 'identity') {
    promptSuggestions.unshift({
      label: `What's the historical context?`,
      prompt: `What was the historical and cultural context of ${formatVerseRef(primaryVerse)}?`
    })
  }

  const detailedNotesReply = buildDetailedNotesReplyFromChunks(chunks)
  const canOfferDetailedNotes = hasMeaningfulExtraDetail(shortAnswer, detailedNotesReply)

  if (!canOfferDetailedNotes) return promptSuggestions

  return [
    {
      label: "Show me Nate's notes on this",
      response: {
        text: detailedNotesReply,
        suggestions: promptSuggestions,
      },
    },
    ...promptSuggestions,
  ].slice(0, 3)
}

function buildSmartFallbackReply(analysis: IntentAnalysis, context: ChatContextState): ChatResponse {
  const suggestions = new Set<string>()

  if (analysis.verseRefs.length > 0) {
    suggestions.add(`Give me a summary of ${formatVerseRef(analysis.verseRefs[0])}`)
  }
  if (context.lastVerseRefs[0]) suggestions.add(`How does ${formatVerseRef(context.lastVerseRefs[0])} connect to the rest of the chapter?`)
  suggestions.add('What does Titus 1 teach about church leadership?')
  suggestions.add('What does grace train us to do?')
  suggestions.add('How do I submit a question to Nate?')

  return {
    text: `I don't want to make up an answer. I couldn't find this in Nate's current notes. Please submit this question to Nate using the contact form below.`,
    suggestions: [...suggestions].slice(0, 3).map(prompt => ({ label: prompt, prompt })),
  }
}

function buildShortAnswerFromChunks(chunks: SearchChunk[], analysis: IntentAnalysis, style: ResponseStyle): string {
  const primaryChunk = chunks[0]
  if (!primaryChunk) return ''

  const baseMax = primaryChunk.entryType === 'episode'
    ? getExcerptMaxChars(style, 'deep')
    : getExcerptMaxChars(style, 'normal')
  const excerpt = buildRelevantExcerpt(primaryChunk.content, analysis.queryTokens, baseMax)
  const primaryVerse = primaryChunk.verseRefs[0]

  if (analysis.type === 'episode-lookup' && primaryChunk.episodeNumber !== null) {
    return `Episode ${primaryChunk.episodeNumber} is the closest match. ${excerpt}`
  }

  if (analysis.type === 'verse-summary' && primaryVerse) {
    return `${formatVerseRef(primaryVerse)} focuses on ${excerpt.charAt(0).toLowerCase() + excerpt.slice(1)}`
  }

  if (analysis.type === 'application') {
    return `Nate's main application is ${excerpt.charAt(0).toLowerCase() + excerpt.slice(1)}`
  }

  if (analysis.type === 'definition') {
    return `Nate explains it this way: ${excerpt}`
  }

  return excerpt
}

function synthesizeSmartReply(scored: ScoredChunk[], analysis: IntentAnalysis, context: ChatContextState, style: ResponseStyle): ChatResponse {
  if (scored.length === 0) return buildSmartFallbackReply(analysis, context)

  const confidence = determineConfidence(scored, analysis)
  if (confidence === 'low') return buildLowConfidenceReply(scored, analysis)

  const topChunks = getTopDistinctChunks(scored, confidence === 'high' ? 3 : 2)
  const shortAnswer = buildShortAnswerFromChunks(topChunks, analysis, style)
  const followUps = buildSmartFollowUpPrompts(topChunks, analysis, shortAnswer)

  const intro = analysis.isCorrection
    ? `Thanks for the correction.`
    : analysis.isFollowUp || context.turnCount > 0
      ? `Staying with your thread:`
      : confidence === 'medium'
        ? `Closest match I found:`
        : `Short answer:`

  const text = `${intro}\n${shortAnswer}`

  return {
    text,
    suggestions: followUps,
    sources: buildSourceCitations(topChunks),
  }
}

function addConfidenceLabel(response: ChatResponse, confidence: 'high' | 'medium' | 'low'): ChatResponse {
  const label = confidence === 'high' 
    ? '✓ High confidence'
    : confidence === 'medium' 
      ? '~ Medium confidence' 
      : '? Low confidence'
  return {
    ...response,
    text: `${response.text}\n\n[${label}]`
  }
  }

function buildNextChatContext(query: string, analysis: IntentAnalysis, scored: ScoredChunk[], previousContext: ChatContextState): ChatContextState {
  const topChunks = getTopDistinctChunks(scored, 3)
  if (topChunks.length === 0) return EMPTY_CHAT_CONTEXT

  const inferredSubject = analysis.subject
    || extractIdentitySubject(query.toLowerCase())
    || contextSubjectFromTitle(topChunks[0]?.title ?? '')

  return {
    turnCount: previousContext.turnCount + 1,
    lastPrompt: query,
    lastIntent: analysis.type,
    lastSubject: inferredSubject,
    lastVerseRefs: [...new Set(topChunks.flatMap(chunk => chunk.verseRefs))].slice(0, 3),
    lastEntryIds: [...new Set(topChunks.map(chunk => chunk.entryId))],
    lastSourceLabels: [...new Set(topChunks.map(chunk => chunk.sourceLabel))].slice(0, 3),
    lastTopics: [...new Set(topChunks.flatMap(chunk => chunk.topicTags))].slice(0, 6),
    lastChunks: topChunks,
    recentPrompts: [...previousContext.recentPrompts, query].slice(-5),
    recentSubjects: [...new Set([...previousContext.recentSubjects, inferredSubject].filter(Boolean))].slice(-5),
    recentEntryIds: [...new Set([...previousContext.recentEntryIds, ...topChunks.map(chunk => chunk.entryId)])].slice(-10),
  }
}

function contextSubjectFromTitle(title: string): string {
  const normalized = title.toLowerCase().trim()
  const subject = extractIdentitySubject(normalized)
  return subject || ''
}

function isNotesRequest(query: string): boolean {
  return /\bshow\b.*\bnotes\b|\bnate'?s notes\b|\bfull notes\b/i.test(query)
}

function isEpisodeFollowUpRequest(query: string): boolean {
  return /\bwhat episode\b|\bwhich episode\b|\bwhat was that episode\b/i.test(query)
}

function buildContextualEpisodeReply(context: ChatContextState): ChatResponse | null {
  const primaryChunk = context.lastChunks[0]
  if (!primaryChunk || primaryChunk.episodeNumber === null) return null

  return {
    text: `This comes from Episode ${primaryChunk.episodeNumber}: ${primaryChunk.sourceLabel}.`,
    suggestions: buildSmartFollowUpPrompts(context.lastChunks, analyzeIntent(primaryChunk.sourceLabel, context), primaryChunk.content),
    sources: buildSourceCitations(context.lastChunks),
  }
}
const SUGGESTED_PROMPTS = [
  'What Bible translation do you use in the podcast?',
  'What does Titus 1 teach about church leadership?',
  'How can I stay consistent with daily Bible reading?',
  'How do I handle a difficult or confusing passage?',
  'How can I share my faith with skeptical family?',
  'Give me a summary of Titus 3:4-7',
  'What episode talks about grace training us?',
  'How do I submit a question to Nate?',
]

const INLINE_CHAT_PROMPTS = SUGGESTED_PROMPTS.slice(0, 6)
const BOT_NAME = 'The Mine'

function openMinePopoutWindow() {
  const features = [
    'popup=yes',
    'width=460',
    'height=760',
    'resizable=yes',
    'scrollbars=yes',
  ].join(',')

  window.open('/the-mine', 'the-mine-window', features)
}

function closeMineWindow() {
  if (window.opener) {
    window.close()
    return
  }

  window.location.href = '/'
}

function ChatBot({ mode = 'embedded' }: { mode?: 'embedded' | 'standalone' }) {
  const isStandalone = mode === 'standalone'
  const [open, setOpen] = useState(false)
  const [messages, setMessages] = useState<ChatMessage[]>([
    { role: 'bot', text: `Welcome to ${BOT_NAME}. Dig into the Word, nugget by nugget. Ask about the podcast, Bible study, or a passage Nate has covered — or pick a prompt below.` },
  ])
  const [input, setInput] = useState('')
  const [entries, setEntries] = useState<ChatbotEntry[]>([])
  const [loaded, setLoaded] = useState(false)
  const [loading, setLoading] = useState(false)
  const [responseStyle, setResponseStyle] = useState<ResponseStyle>('balanced')
  const [pendingPrompt, setPendingPrompt] = useState<string | null>(null)
  const bottomRef = useRef<HTMLDivElement>(null)
  const lastEntriesSyncRef = useRef(0)
  const chunkIndexRef = useRef<SearchChunk[]>([])
  const chatContextRef = useRef<ChatContextState>(EMPTY_CHAT_CONTEXT)
  const chatVisible = isStandalone || open

  const loadEntries = async (force = false) => {
    const now = Date.now()
    if (!force && loaded && now - lastEntriesSyncRef.current < 15000) return entries

    try {
      const response = await fetch('/api/chatbot-content', { cache: 'no-store' })
      if (!response.ok) return entries
      const data = await response.json()
      const nextEntries = Array.isArray(data) ? data : []
      setEntries(nextEntries)
      chunkIndexRef.current = buildSearchChunks(nextEntries)
      setLoaded(true)
      lastEntriesSyncRef.current = now
      return nextEntries
    } catch {
      return entries
    }
  }

  const openChat = () => {
    setOpen(true)
    void loadEntries(true)
  }

  useEffect(() => {
    if (!isStandalone) return
    setOpen(true)
    void loadEntries(true)
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isStandalone])

  const askPrompt = (prompt: string) => {
    openChat()
    if (entries.length > 0) {
      void respond(prompt)
    } else {
      setPendingPrompt(prompt)
    }
  }

  useEffect(() => {
    if (pendingPrompt !== null && entries.length > 0) {
      const p = pendingPrompt
      setPendingPrompt(null)
      void respond(p)
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [entries, pendingPrompt])

  useEffect(() => {
    if (chatVisible) bottomRef.current?.scrollIntoView({ behavior: 'smooth' })
  }, [messages, chatVisible])

  useEffect(() => {
    if (!chatVisible) return undefined

    const intervalId = window.setInterval(() => {
      void loadEntries(true)
    }, 30000)

    return () => window.clearInterval(intervalId)
  }, [chatVisible])

  const handleSuggestion = (suggestion: ChatSuggestion) => {
    if (suggestion.response) {
      const response = suggestion.response
      setMessages(m => [
        ...m,
        { role: 'user', text: suggestion.label },
        { role: 'bot', text: formatResponseText(response), suggestions: response.suggestions, sources: response.sources },
      ])
      return
    }

    if (suggestion.prompt) {
      askPrompt(suggestion.prompt)
    }
  }

  const maybeRewriteWithLlm = async (
    question: string,
    draft: ChatResponse,
    contextChunks: SearchChunk[],
  ): Promise<ChatResponse> => {
    try {
      const response = await fetch('/api/chatbot-grounded-rewrite', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          question,
          draftAnswer: draft.text,
          sources: draft.sources ?? [],
          contextChunks: contextChunks.slice(0, 4).map(chunk => ({
            title: chunk.title,
            sourceLabel: chunk.sourceLabel,
            content: chunk.content,
          })),
        }),
      })

      if (!response.ok) return draft
      const payload = await response.json()
      const rewritten = typeof payload?.text === 'string' ? payload.text.trim() : ''
      if (!rewritten) return draft

      return {
        ...draft,
        text: rewritten,
      }
    } catch {
      return draft
    }
  }

  const respond = async (query: string) => {
    const userMsg: ChatMessage = { role: 'user', text: query }
    setMessages(m => [...m, userMsg])
    setInput('')
    setLoading(true)

    const latestEntries = await loadEntries()
    const latestChunks = chunkIndexRef.current.length > 0 ? chunkIndexRef.current : buildSearchChunks(latestEntries)
    const activeContext = chatContextRef.current
    const trimmedQuery = query.trim()
    const contextualQuery = enrichQueryWithContext(trimmedQuery, activeContext)

    setTimeout(() => {
      void (async () => {
      const cue = detectConversationCue(trimmedQuery)
      const cueReply = buildConversationOnlyReply(cue, activeContext, responseStyle)
      if (cueReply) {
        setMessages(m => [...m, { role: 'bot', text: formatResponseText(cueReply), suggestions: cueReply.suggestions, sources: cueReply.sources }])
        setLoading(false)
        return
      }

      if (isNotesRequest(trimmedQuery) && activeContext.lastChunks.length > 0) {
        const notesReply: ChatResponse = {
          text: buildDetailedNotesReplyFromChunks(activeContext.lastChunks),
          suggestions: buildSmartFollowUpPrompts(
            activeContext.lastChunks,
            analyzeIntent(activeContext.lastPrompt || trimmedQuery, activeContext),
            activeContext.lastChunks[0]?.content ?? '',
          ),
          sources: buildSourceCitations(activeContext.lastChunks),
        }

        setMessages(m => [...m, { role: 'bot', text: formatResponseText(notesReply), suggestions: notesReply.suggestions, sources: notesReply.sources }])
        setLoading(false)
        return
      }

      if (isEpisodeFollowUpRequest(trimmedQuery) && activeContext.lastChunks.length > 0) {
        const episodeReply = buildContextualEpisodeReply(activeContext)
        if (episodeReply) {
          setMessages(m => [...m, { role: 'bot', text: formatResponseText(episodeReply), suggestions: episodeReply.suggestions, sources: episodeReply.sources }])
          setLoading(false)
          return
        }
      }

      const analysis = analyzeIntent(contextualQuery, activeContext)
      if (analysis.needsClarification) {
        const clarificationReply = buildClarificationReply(analysis, activeContext)
        setMessages(m => [...m, {
          role: 'bot',
          text: formatResponseText(clarificationReply),
          suggestions: clarificationReply.suggestions,
          sources: clarificationReply.sources,
        }])
        setLoading(false)
        return
      }

      const bibleVersionIntent = isBibleVersionQuery(analysis.searchQuery, analysis.queryTokens)
      const scored = latestChunks
        .map(chunk => {
          let score = scoreChunkForAnalysis(chunk, analysis, activeContext)
          const matchingEntry = latestEntries.find(entry => entry.id === chunk.entryId)
          if (matchingEntry && bibleVersionIntent && hasBsbSignal(matchingEntry)) score += 12
          if (bibleVersionIntent && chunk.entryType === 'episode') score += 2
          return { chunk, score }
        })
        .filter(x => x.score > 0)
        .sort((a, b) => b.score - a.score)

      let botReply: ChatResponse
      let topChunksForRewrite: SearchChunk[] = []
      let shouldTryLlmRewrite = false
      if (scored.length > 0) {
        const confidence = determineConfidence(scored, analysis)
        topChunksForRewrite = getTopDistinctChunks(scored, 3)
        botReply = synthesizeSmartReply(scored, analysis, activeContext, responseStyle)
        if (confidence !== 'low') {
          shouldTryLlmRewrite = true
          chatContextRef.current = buildNextChatContext(contextualQuery, analysis, scored, activeContext)
          botReply = addConfidenceLabel(botReply, confidence)
        }
      } else {
        botReply = buildSmartFallbackReply(analyzeIntent(contextualQuery, activeContext), activeContext)
      }

      if (shouldTryLlmRewrite) {
        botReply = await maybeRewriteWithLlm(trimmedQuery, botReply, topChunksForRewrite)
      }

      setMessages(m => [...m, { role: 'bot', text: formatResponseText(botReply), suggestions: botReply.suggestions, sources: botReply.sources }])
      setLoading(false)
      })()
    }, 400)
  }

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault()
    const q = input.trim()
    if (!q) return
    void respond(q)
  }

  return (
    <>
      {!isStandalone && (
      <section id="the-mine" className="section-chatbot-feature" aria-label={`Ask ${BOT_NAME}`}>
        <div className="section-inner chatbot-feature-inner">
          <div className="chatbot-feature-copy">
            <p className="eyebrow">Ask {BOT_NAME}</p>
            <h2 className="section-heading">
              <span className="ornament">✦</span> {BOT_NAME} <span className="ornament">✦</span>
            </h2>
            <p className="chatbot-feature-lead">
              Your study companion for Verse by Verse with Nate. Ask about Titus, Bible study, prayer, daily reading, or anything Nate has already covered in his notes and episodes.
            </p>
            <p className="chatbot-feature-sub">
              Have a question about the episode? {BOT_NAME} is here to help you dig deeper.
            </p>
            <div className="chatbot-feature-actions">
              <button type="button" className="btn-primary" onClick={openChat}>
                Open {BOT_NAME}
              </button>
              <button type="button" className="btn-secondary" onClick={openMinePopoutWindow}>
                Pop Out {BOT_NAME}
              </button>
              <a href="#contact" className="btn-secondary">
                Ask Nate Directly ↓
              </a>
            </div>
          </div>

          <div className="chatbot-feature-card">
            <p className="chatbot-feature-kicker">Try asking:</p>
            <div className="chatbot-feature-prompts">
              {INLINE_CHAT_PROMPTS.map(prompt => (
                <button key={prompt} className="chatbot-prompt-btn chatbot-prompt-btn--feature" onClick={() => askPrompt(prompt)}>
                  {prompt}
                </button>
              ))}
            </div>
          </div>
        </div>
      </section>
      )}

      {/* Floating bubble */}
      {!isStandalone && (
      <button
        className={`chatbot-bubble ${open ? 'chatbot-bubble--open' : ''}`}
        onClick={() => open ? setOpen(false) : openChat()}
        aria-label={open ? `Close ${BOT_NAME}` : `Open ${BOT_NAME}`}
      >
        {open ? (
          <svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
        ) : (
          <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z"/></svg>
        )}
      </button>
      )}

      {/* Chat panel */}
      {chatVisible && (
        <div className={isStandalone ? 'chatbot-page' : ''}>
        <div className={`chatbot-panel ${isStandalone ? 'chatbot-panel--standalone' : ''}`} role="dialog" aria-label={BOT_NAME}>
          <div className="chatbot-header">
            <span>⛏ {BOT_NAME}</span>
            <div className="chatbot-header-actions">
              {!isStandalone && (
                <button type="button" className="chatbot-header-btn" onClick={openMinePopoutWindow} aria-label={`Open ${BOT_NAME} in a new window`}>
                  Pop Out
                </button>
              )}
              {isStandalone ? (
                <>
                  <Link to="/" className="chatbot-header-btn chatbot-header-btn--link">Back to Site</Link>
                  <button type="button" className="chatbot-close" onClick={closeMineWindow} aria-label="Close window">✕</button>
                </>
              ) : (
                <button className="chatbot-close" onClick={() => setOpen(false)} aria-label="Close">✕</button>
              )}
            </div>
          </div>

          <div className="chatbot-messages">
            {messages.map((msg, i) => (
              <div key={i} className={`chatbot-msg chatbot-msg--${msg.role}`}>
                {msg.text.split('\n').map((line, j) => <p key={j}>{line}</p>)}
                {msg.role === 'bot' && msg.suggestions && msg.suggestions.length > 0 && (
                  <div className="chatbot-msg-suggestions" aria-label="Related follow-up questions">
                    {msg.suggestions.map(suggestion => (
                      <button
                        key={`${i}-${suggestion.label}`}
                        type="button"
                        className="chatbot-prompt-btn chatbot-prompt-btn--inline"
                        onClick={() => handleSuggestion(suggestion)}
                      >
                        {suggestion.label}
                      </button>
                    ))}
                  </div>
                )}
              </div>
            ))}
            {loading && (
              <div className="chatbot-msg chatbot-msg--bot chatbot-msg--typing">
                <span /><span /><span />
              </div>
            )}
            <div ref={bottomRef} />
          </div>

          {messages.length === 1 && (
            <div className="chatbot-prompts">
              {SUGGESTED_PROMPTS.map(p => (
                <button key={p} className="chatbot-prompt-btn" onClick={() => askPrompt(p)}>{p}</button>
              ))}
            </div>
          )}

          <form className="chatbot-form" onSubmit={handleSubmit}>
            <select
              className="chatbot-style-select"
              value={responseStyle}
              onChange={e => setResponseStyle(e.target.value as ResponseStyle)}
              aria-label="Response style"
            >
              <option value="brief">Brief</option>
              <option value="balanced">Balanced</option>
              <option value="deep">Deep</option>
            </select>
            <input
              type="text"
              className="chatbot-input"
              placeholder={`Ask a question (${toTitleCaseLabel(responseStyle)} mode)…`}
              value={input}
              onChange={e => setInput(e.target.value)}
              maxLength={300}
              aria-label="Your question"
            />
            <button type="submit" className="chatbot-send" disabled={!input.trim() || loading} aria-label="Send">
              <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
            </button>
          </form>
        </div>
        </div>
      )}
    </>
  )
}

function TheMineWindowPage() {
  return <ChatBot mode="standalone" />
}

const QA_PAGE_SIZE = 6

function QASection() {
  const [questions, setQuestions] = useState<PublicQuestion[]>([])
  const [searchQuery, setSearchQuery] = useState('')
  const [expanded, setExpanded] = useState<{ [key: string]: boolean }>({})
  const [page, setPage] = useState(0)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    fetch('/api/questions')
      .then(r => (r.ok ? r.json() : Promise.reject(new Error('Failed to load questions'))))
      .then(data => {
        setQuestions((data as { questions: PublicQuestion[] }).questions ?? [])
        setLoading(false)
      })
      .catch(() => {
        setLoading(false)
      })
  }, [])

  const filteredQuestions = questions.filter(q =>
    q.question.toLowerCase().includes(searchQuery.toLowerCase()) ||
    q.answer.toLowerCase().includes(searchQuery.toLowerCase())
  )

  const totalPages = Math.ceil(filteredQuestions.length / QA_PAGE_SIZE)
  const pagedQuestions = filteredQuestions.slice(page * QA_PAGE_SIZE, (page + 1) * QA_PAGE_SIZE)

  const toggleExpanded = (id: string) => {
    setExpanded(e => ({ ...e, [id]: !e[id] }))
  }

  // Reset to page 0 when search changes
  const handleSearch = (value: string) => {
    setSearchQuery(value)
    setPage(0)
  }

  if (loading || questions.length === 0) return null

  return (
    <section className="section-qa" aria-label="Questions and answers">
      <div className="section-inner">
        <h2 className="section-heading">
          <span className="ornament">✦</span> Questions & Answers <span className="ornament">✦</span>
        </h2>

        <div className="qa-search">
          <label>
            <span>Search Questions</span>
            <input
              type="text"
              placeholder="Search by topic..."
              value={searchQuery}
              onChange={e => handleSearch(e.target.value)}
            />
          </label>
        </div>

        {filteredQuestions.length === 0 ? (
          <div className="qa-no-results">
            <p>No matching questions found. <a href="#contact">Submit your question</a></p>
          </div>
        ) : (
          <>
            <div className="qa-cards">
              {pagedQuestions.map(question => (
                <div key={question.id} className="qa-card-scene">
                  <div
                    className={`qa-card-inner ${expanded[question.id] ? 'flipped' : ''}`}
                    onClick={() => toggleExpanded(question.id)}
                    role="button"
                    tabIndex={0}
                    aria-label={expanded[question.id] ? 'Show question' : 'Show answer'}
                    onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && toggleExpanded(question.id)}
                  >
                    <div className="qa-card-face qa-card-front">
                      <span className="qa-face-label">Q</span>
                      <p className="qa-question-text">{question.question}</p>
                      <span className="qa-flip-hint">tap to reveal answer</span>
                    </div>
                    <div className="qa-card-face qa-card-back">
                      <span className="qa-face-label">A</span>
                      <p className="qa-answer-text">{question.answer}</p>
                      <span className="qa-flip-hint">— Answered by Nate</span>
                    </div>
                  </div>
                </div>
              ))}
            </div>

            {totalPages > 1 && (
              <div className="qa-pagination">
                <button
                  className="qa-page-btn"
                  onClick={() => setPage(p => p - 1)}
                  disabled={page === 0}
                  aria-label="Previous page"
                >
                  ‹ Prev
                </button>
                <span className="qa-page-info">
                  {page + 1} / {totalPages}
                </span>
                <button
                  className="qa-page-btn"
                  onClick={() => setPage(p => p + 1)}
                  disabled={page >= totalPages - 1}
                  aria-label="Next page"
                >
                  Next ›
                </button>
              </div>
            )}
          </>
        )}
      </div>
    </section>
  )
}

function ContactForm() {
  const navigate = useNavigate()
  const [fields, setFields] = useState({ name: '', email: '', message: '', messageType: 'question' })
  const [subscribe, setSubscribe] = useState(true)
  const [honey, setHoney] = useState('')
  const [status, setStatus] = useState<'idle' | 'submitting' | 'error'>('idle')
  const [errorMsg, setErrorMsg] = useState('')

  function handleChange(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) {
    setFields(f => ({ ...f, [e.target.name]: e.target.value }))
  }

  function handleSelectChange(e: React.ChangeEvent<HTMLSelectElement>) {
    setFields(f => ({ ...f, [e.target.name]: e.target.value }))
  }

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    setStatus('submitting')
    setErrorMsg('')
    try {
      const res = await fetch('/api/contact', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ ...fields, subscribe, _honey: honey }),
      })
      if (!res.ok) {
        const data = await res.json().catch(() => ({}))
        setErrorMsg((data as { message?: string }).message ?? 'Something went wrong. Please try again.')
        setStatus('error')
        return
      }
      navigate('/thanks')
    } catch {
      setErrorMsg('Could not connect. Please try again later.')
      setStatus('error')
    }
  }

  return (
    <form className="contact-form" onSubmit={handleSubmit} noValidate>
      <input
        type="text"
        className="contact-honeypot"
        tabIndex={-1}
        autoComplete="off"
        aria-hidden="true"
        value={honey}
        onChange={e => setHoney(e.target.value)}
      />
      <label>
        Name
        <input type="text" name="name" required autoComplete="name" value={fields.name} onChange={handleChange} />
      </label>
      <label>
        Email
        <input type="email" name="email" required autoComplete="email" value={fields.email} onChange={handleChange} />
      </label>
      <label>
        Message Type
        <select name="messageType" value={fields.messageType} onChange={handleSelectChange}>
          <option value="question">Bible Question</option>
          <option value="testimony">Testimony</option>
          <option value="topic">Topic Request</option>
          <option value="general">General Message</option>
        </select>
      </label>
      <label>
        Message
        <textarea name="message" rows={6} required value={fields.message} onChange={handleChange} />
      </label>
      <label className="contact-consent">
        <input
          type="checkbox"
          checked={subscribe}
          onChange={e => setSubscribe(e.target.checked)}
        />
        <span>Send me updates from Verse by Verse with Nate. I can unsubscribe anytime.</span>
      </label>
      {status === 'error' && <p className="contact-error">{errorMsg}</p>}
      <button type="submit" className="btn-primary" disabled={status === 'submitting'}>
        {status === 'submitting' ? 'Sending…' : 'Send Message'}
      </button>
    </form>
  )
}

function AnalyticsConsentBanner() {
  const [choice, setChoice] = useState<'unknown' | 'accepted' | 'declined'>(() => {
    const saved = localStorage.getItem(CONSENT_KEY)
    if (saved === 'accepted' || saved === 'declined') return saved
    return 'unknown'
  })

  async function sendChoice(consent: boolean) {
    await fetch('/api/analytics-consent', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ consent }),
    })
  }

  async function accept() {
    setChoice('accepted')
    localStorage.setItem(CONSENT_KEY, 'accepted')
    try {
      await sendChoice(true)
    } catch {
      // Keep local preference even if network fails.
    }
  }

  async function decline() {
    setChoice('declined')
    localStorage.setItem(CONSENT_KEY, 'declined')
    try {
      await sendChoice(false)
    } catch {
      // Keep local preference even if network fails.
    }
  }

  if (choice !== 'unknown') return null

  return (
    <div className="consent-banner" role="region" aria-label="Analytics consent">
      <p>
        We use optional analytics cookies to measure visits and location trends for site improvement.
      </p>
      <div className="consent-actions">
        <button type="button" className="btn-primary" onClick={accept}>Accept</button>
        <button type="button" className="btn-secondary" onClick={decline}>Decline</button>
      </div>
    </div>
  )
}

function ShareShowButton() {
  const [feedback, setFeedback] = useState('')

  async function handleShare() {
    const shareUrl = window.location.origin
    const shareTitle = 'Verse by Verse with Nate'
    const shareText = 'Check out Verse by Verse with Nate.'

    try {
      if (navigator.share) {
        await navigator.share({ title: shareTitle, text: shareText, url: shareUrl })
        setFeedback('Shared successfully.')
        return
      }

      if (navigator.clipboard?.writeText) {
        await navigator.clipboard.writeText(shareUrl)
        setFeedback('Link copied. Share it with a friend.')
        return
      }

      setFeedback(`Copy this link: ${shareUrl}`)
    } catch {
      setFeedback('Share canceled.')
    }
  }

  return (
    <div className="share-show-wrap">
      <button type="button" className="btn-primary btn-share-show" onClick={handleShare}>
        Share the Show
      </button>
      {feedback && <p className="share-feedback">{feedback}</p>}
    </div>
  )
}

function LandingPage({ content }: { content: SiteContent }) {
  return (
    <div className="site">
      {/* ── HEADER ── */}
      <header className="site-header">
        <div className="header-inner">
          <span className="header-ornament">✦ &nbsp; ✦ &nbsp; ✦</span>
          <nav className="header-nav">
            <a href="#listen">Listen</a>
            <a href="#about">About</a>
            <a href="#the-mine">The Mine</a>
            <a href="#series">Series</a>
            <a href="#contact">Contact</a>
            <a
              href={SPOTIFY_SHOW_URL}
              target="_blank"
              rel="noreferrer"
              className="header-cta"
            >
              Subscribe Free
            </a>
          </nav>
        </div>
      </header>

      {/* ── HERO ── */}
      <section className="hero" aria-label="Show introduction">
        <div className="hero-art">
          <div className="art-glow" aria-hidden="true" />
          <img
            src="/images/podcast-art.jpeg"
            alt="Verse by Verse with Nate podcast artwork"
            className="podcast-art"
          />
        </div>
        <div className="hero-text">
          <p className="eyebrow">{content.eyebrow}</p>
          <h1 className="hero-title">
            <span className="verse-word">VERSE</span>
            <span className="by-label">· by ·</span>
            <span className="verse-word">VERSE</span>
          </h1>
          <p className="with-nate">
            <span className="word-with">with </span>Nate
          </p>
          <div className="rule-divider" aria-hidden="true" />
          <p className="tagline">{content.heroTagline}</p>
          <div className="hero-ctas">
            <a
              href={SPOTIFY_SHOW_URL}
              target="_blank"
              rel="noreferrer"
              className="btn-primary"
            >
              <SpotifyIcon />
              Listen on Spotify
            </a>
            <a href="#listen" className="btn-secondary">
              All Platforms ↓
            </a>
          </div>
        </div>
      </section>

      {/* ── PLAYER ── */}
      <section className="section-player" id="listen" aria-label="Podcast player">
        <div className="section-inner">
          <h2 className="section-heading">
            <span className="ornament">✦</span> Latest Episodes{' '}
            <span className="ornament">✦</span>
          </h2>
          <div className="embed-wrap">
            <iframe
              title="Verse by Verse with Nate"
              src={SPOTIFY_EMBED_URL}
              width="100%"
              height="352"
              frameBorder="0"
              allowFullScreen
              allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
              loading="lazy"
              style={{ borderRadius: '14px' }}
            />
          </div>
          <div className="platform-buttons">
            <a href={SPOTIFY_SHOW_URL} target="_blank" rel="noreferrer" className="platform-btn spotify-btn">
              <SpotifyIcon />
              Listen on Spotify
            </a>
            <a href={YOUTUBE_URL} target="_blank" rel="noreferrer" className="platform-btn youtube-btn">
              <YouTubeIcon />
              YouTube
            </a>
            <a href={AMAZON_MUSIC_URL} target="_blank" rel="noreferrer" className="platform-btn amazon-btn">
              <AmazonMusicIcon />
              Amazon Music
            </a>
            <a href={FACEBOOK_URL} target="_blank" rel="noreferrer" className="platform-btn facebook-btn">
              <FacebookIcon />
              Facebook
            </a>
            <a href={APPLE_PODCASTS_URL} target="_blank" rel="noreferrer" className="platform-btn apple-btn">
              <img src="/images/apple-podcasts-badge.svg" alt="Listen on Apple Podcasts" className="apple-badge" />
            </a>
            {(content.customLinks ?? []).filter(l => l.placement === 'platforms').map(l => (
              <a key={l.id} href={l.url} target="_blank" rel="noreferrer" className="platform-btn custom-btn">
                {l.label}
              </a>
            ))}
          </div>
        </div>
      </section>

      {/* ── ABOUT ── */}
      <section className="section-about" id="about" aria-label="About the show">
        <div className="section-inner about-inner">
          <div className="about-photo">
            <img src="/images/nate-photo.jpeg" alt="Nate Emmert" />
          </div>
          <div className="about-text">
            <p className="eyebrow">About the Show</p>
            <h2>{content.aboutShowHeading}</h2>
            <p>{content.aboutShowP1}</p>
            <p>{content.aboutShowP2}</p>
            <div className="rule-divider" aria-hidden="true" />
            <p className="eyebrow">About Nate</p>
            <p>{content.aboutNate}</p>
          </div>
        </div>
      </section>

      {/* ── SERIES ── */}
      <section className="section-series" id="series" aria-label="Current series">
        <div className="section-inner">
          <h2 className="section-heading">
            <span className="ornament">✦</span> Current Series{' '}
            <span className="ornament">✦</span>
          </h2>
          <div className="series-grid">
            <article className="series-card">
              <img
                src="/images/titus-cover.png"
                alt={content.seriesTitle}
                className="series-art"
              />
              <div className="series-info">
                <p className="series-label">{content.seriesLabel}</p>
                <h3>{content.seriesTitle}</h3>
                <p>{content.seriesDescription}</p>
                <a
                  href={SPOTIFY_SHOW_URL}
                  target="_blank"
                  rel="noreferrer"
                  className="btn-primary"
                >
                  <SpotifyIcon />
                  Listen to Series
                </a>
              </div>
            </article>
          </div>
        </div>
      </section>

      {/* ── STUDY GUIDE ── */}
      <section className="section-guide" aria-label="Companion study guide">
        <div className="section-inner guide-inner">
          <div className="guide-icon" aria-hidden="true">
            <svg viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
              <rect x="8" y="6" width="36" height="48" rx="3" fill="#c8860a" opacity="0.15" stroke="#c8860a" strokeWidth="1.5"/>
              <rect x="14" y="6" width="36" height="48" rx="3" fill="#0f0f0f" stroke="#c8860a" strokeWidth="1.5"/>
              <line x1="22" y1="22" x2="42" y2="22" stroke="#c8860a" strokeWidth="1.5" strokeLinecap="round"/>
              <line x1="22" y1="30" x2="42" y2="30" stroke="#c8860a" strokeWidth="1.5" strokeLinecap="round" opacity="0.6"/>
              <line x1="22" y1="38" x2="34" y2="38" stroke="#c8860a" strokeWidth="1.5" strokeLinecap="round" opacity="0.4"/>
            </svg>
          </div>
          <div className="guide-text">
            <p className="eyebrow">Available on Amazon</p>
            <h2>{content.studyGuideTitle}</h2>
            <p>{content.studyGuideDescription}</p>
            <a
              href={content.studyGuideUrl}
              target="_blank"
              rel="noreferrer"
              className="btn-primary"
            >
              Get the Book →
            </a>
          </div>
        </div>
      </section>

      {/* ── SHARE / QR ── */}
      <section className="section-qr" aria-label="Share the show">
        <div className="section-inner qr-inner">
          <div className="qr-text">
            <p className="eyebrow">Share the Show</p>
            <h2>{content.shareHeading}</h2>
            <p>{content.shareP}</p>
            <a
              href={SPOTIFY_CREATOR_URL}
              target="_blank"
              rel="noreferrer"
              className="btn-secondary"
            >
              creators.spotify.com/pod/profile/nmemmert
            </a>
            <ShareShowButton />
          </div>
          <div className="qr-code-wrap">
            <div className="qr-frame">
              <img
                src="/images/qr-code.jpg"
                alt="Scan to listen to Verse by Verse with Nate"
                className="qr-code"
              />
            </div>
            <p className="scan-label">SCAN TO LISTEN</p>
          </div>
        </div>
      </section>

  {/* ── Q&A ── */}
  <QASection />
      <ChatBot />

      {/* ── CONTACT ── */}
      <section className="section-contact" id="contact" aria-label="Contact form">
        <div className="section-inner contact-inner">
          <div className="contact-copy">
            <p className="eyebrow">Get in Touch</p>
            <h2 className="section-heading">
              <span className="ornament">✦</span> Contact Nate <span className="ornament">✦</span>
            </h2>
            <p className="contact-highlight">
              Ask a Bible question, share a testimony, or request a topic for a future episode.
            </p>
            <p>We usually respond within 48 hours.</p>
          </div>
          <ContactForm />
        </div>
      </section>

      {/* ── CUSTOM RESOURCES ── */}
      {(content.customLinks ?? []).filter(l => l.placement === 'resources').length > 0 && (
        <section className="section-resources" aria-label="More resources">
          <div className="section-inner">
            <h2 className="section-heading">
              <span className="ornament">✦</span> More Resources{' '}
              <span className="ornament">✦</span>
            </h2>
            <div className="resources-list">
              {(content.customLinks ?? []).filter(l => l.placement === 'resources').map(l => (
                <a key={l.id} href={l.url} target="_blank" rel="noreferrer" className="resource-link">
                  {l.label} →
                </a>
              ))}
            </div>
          </div>
        </section>
      )}

      {/* ── CUSTOM BLOCKS ── */}
      {(content.customBlocks ?? []).map(block => (
        <section key={block.id} className="section-custom-block" aria-label={block.heading}>
          <div className="section-inner custom-block-inner">
            <h2>{block.heading}</h2>
            <p>{block.body}</p>
          </div>
        </section>
      ))}

      {/* ── FOOTER ── */}
      <footer className="site-footer">
        <p className="footer-ornament">✦ &nbsp; ✦ &nbsp; ✦</p>
        <p className="footer-title">Verse by Verse with Nate</p>
        <p className="footer-sub">A Journey Through Scripture</p>
        <p className="footer-contact">
            Contact:{' '}
            <a href="mailto:hello@versebyversewithnate.us">hello@versebyversewithnate.us</a>
        </p>
        <p className="footer-response">We usually reply within 48 hours.</p>
        <nav className="footer-links" aria-label="Footer links">
          <a href={SPOTIFY_SHOW_URL} target="_blank" rel="noreferrer">Spotify</a>
          <span aria-hidden="true">·</span>
          <a href={APPLE_PODCASTS_URL} target="_blank" rel="noreferrer">Apple Podcasts</a>
          <span aria-hidden="true">·</span>
          <a href={YOUTUBE_URL} target="_blank" rel="noreferrer">YouTube</a>
          <span aria-hidden="true">·</span>
          <a href={AMAZON_MUSIC_URL} target="_blank" rel="noreferrer">Amazon Music</a>
          <span aria-hidden="true">·</span>
          <a href={SPOTIFY_CREATOR_URL} target="_blank" rel="noreferrer">Creator Profile</a>
          <span aria-hidden="true">·</span>
          <a href={FACEBOOK_URL} target="_blank" rel="noreferrer">Facebook</a>
          {(content.customLinks ?? []).filter(l => l.placement === 'footer').flatMap(l => [
            <span key={`sep-${l.id}`} aria-hidden="true">·</span>,
            <a key={l.id} href={l.url} target="_blank" rel="noreferrer">{l.label}</a>,
          ])}
        </nav>
        <nav className="footer-links footer-links--legal" aria-label="Legal links">
          <Link to="/privacy">Privacy Policy</Link>
          <span aria-hidden="true">·</span>
          <Link to="/terms">Terms</Link>
        </nav>
        <p className="footer-copy">© 2026 Nate Emmert · Made with faith.</p>
        <p className="footer-privacy">
          Privacy: with consent, analytics may store masked IP-based location data (country/state/county/city) and returning visitor activity.
        </p>
      </footer>

      <AnalyticsConsentBanner />
    </div>
  )
}

function ThankYouPage() {
  const navigate = useNavigate()
  const [secondsLeft, setSecondsLeft] = useState(15)

  useEffect(() => {
    const timeoutId = window.setTimeout(() => {
      navigate('/')
    }, 15000)

    const intervalId = window.setInterval(() => {
      setSecondsLeft(s => (s > 0 ? s - 1 : 0))
    }, 1000)

    return () => {
      window.clearTimeout(timeoutId)
      window.clearInterval(intervalId)
    }
  }, [navigate])

  return (
    <main className="thanks-page" aria-label="Thank you">
      <div className="thanks-card">
        <p className="eyebrow">Message Received</p>
        <h1>Thank You</h1>
        <p>
          Your message was sent successfully. We appreciate you reaching out and will get back to you soon.
        </p>
        <p className="thanks-countdown">Returning to the main site in {secondsLeft} seconds...</p>
        <button type="button" className="btn-primary" onClick={() => navigate('/')}>
          Go Back Now
        </button>
      </div>
    </main>
  )
}

function SubscribePage() {
  const navigate = useNavigate()
  const [name, setName] = useState('')
  const [email, setEmail] = useState('')
  const [honey, setHoney] = useState('')
  const [status, setStatus] = useState<'idle' | 'submitting' | 'error'>('idle')
  const [errorMsg, setErrorMsg] = useState('')

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    setStatus('submitting')
    setErrorMsg('')

    try {
      const res = await fetch('/api/contact', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          name,
          email,
          messageType: 'general',
          message: 'Newsletter signup from subscribe page',
          subscribe: true,
          _honey: honey,
        }),
      })

      if (!res.ok) {
        const data = await res.json().catch(() => ({}))
        setErrorMsg((data as { message?: string }).message ?? 'Something went wrong. Please try again.')
        setStatus('error')
        return
      }

      navigate('/subscribe/thanks')
    } catch {
      setErrorMsg('Could not connect. Please try again later.')
      setStatus('error')
    }
  }

  return (
    <main className="thanks-page" aria-label="Subscribe">
      <div className="thanks-card subscribe-card">
        <p className="eyebrow">Verse by Verse with Nate</p>
        <h1>Subscribe for Updates</h1>
        <p>
          Get ministry updates and new episode announcements by email.
        </p>

        <form className="contact-form subscribe-form" onSubmit={handleSubmit} noValidate>
          <input
            type="text"
            className="contact-honeypot"
            tabIndex={-1}
            autoComplete="off"
            aria-hidden="true"
            value={honey}
            onChange={e => setHoney(e.target.value)}
          />
          <label>
            Name
            <input
              type="text"
              name="name"
              required
              autoComplete="name"
              value={name}
              onChange={e => setName(e.target.value)}
            />
          </label>
          <label>
            Email
            <input
              type="email"
              name="email"
              required
              autoComplete="email"
              value={email}
              onChange={e => setEmail(e.target.value)}
            />
          </label>

          {status === 'error' && <p className="contact-error">{errorMsg}</p>}

          <button type="submit" className="btn-primary" disabled={status === 'submitting'}>
            {status === 'submitting' ? 'Subscribing…' : 'Subscribe'}
          </button>
          <Link to="/" className="btn-secondary">Back to Site</Link>
        </form>
      </div>
    </main>
  )
}

function SubscribeThankYouPage() {
  return (
    <main className="thanks-page" aria-label="Subscription confirmed">
      <div className="thanks-card">
        <p className="eyebrow">Verse by Verse with Nate</p>
        <h1>You are subscribed</h1>
        <p>
          Thank you for subscribing. You will receive future ministry updates and episode announcements.
        </p>
        <Link to="/" className="btn-primary">Back to Site</Link>
      </div>
    </main>
  )
}

function LegalPage({ title, body }: { title: string; body: string[] }) {
  return (
    <main className="thanks-page" aria-label={title}>
      <div className="thanks-card">
        <p className="eyebrow">Verse by Verse with Nate</p>
        <h1>{title}</h1>
        {body.map((line, idx) => (
          <p key={`${title}-${idx}`}>{line}</p>
        ))}
        <Link to="/" className="btn-primary">Back to Site</Link>
      </div>
    </main>
  )
}

function AdminShell({ content, onSave }: { content: SiteContent; onSave: (c: SiteContent) => void }) {
  const [status, setStatus] = useState<'checking' | 'authenticated' | 'unauthenticated' | 'misconfigured'>('checking')
  const [password, setPassword] = useState('')
  const [errorMsg, setErrorMsg] = useState('')
  const [submitting, setSubmitting] = useState(false)

  useEffect(() => {
    fetch('/api/admin-auth/status')
      .then(r => (r.ok ? r.json() : Promise.reject(new Error('Status failed'))))
      .then(data => {
        const next = data as { authenticated?: boolean; configured?: boolean }
        if (next.configured === false) {
          setStatus('misconfigured')
          return
        }
        setStatus(next.authenticated ? 'authenticated' : 'unauthenticated')
      })
      .catch(() => {
        setStatus('unauthenticated')
      })
  }, [])

  async function handleLogin(e: React.FormEvent) {
    e.preventDefault()
    setSubmitting(true)
    setErrorMsg('')
    try {
      const res = await fetch('/api/admin-auth/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ password }),
      })
      if (!res.ok) {
        const data = await res.json().catch(() => ({}))
        setErrorMsg((data as { message?: string }).message ?? 'Login failed.')
        setSubmitting(false)
        return
      }
      setStatus('authenticated')
      setPassword('')
    } catch {
      setErrorMsg('Login failed.')
    } finally {
      setSubmitting(false)
    }
  }

  async function handleLogout() {
    try {
      await fetch('/api/admin-auth/logout', { method: 'POST' })
    } finally {
      setStatus('unauthenticated')
    }
  }

  if (status === 'authenticated') {
    return <AdminPage content={content} onSave={onSave} onLogout={handleLogout} />
  }

  return (
    <main className="admin-auth-page" aria-label="Admin sign in">
      <div className="admin-auth-card">
        <p className="eyebrow">Admin Access</p>
        <h1>{status === 'misconfigured' ? 'Admin Not Configured' : 'Sign in to Admin'}</h1>
        {status === 'checking' && <p className="admin-auth-note">Checking session...</p>}
        {status === 'misconfigured' && (
          <p className="admin-auth-note">Set the ADMIN_PASSWORD environment variable on the server to enable admin login.</p>
        )}
        {status === 'unauthenticated' && (
          <form className="admin-auth-form" onSubmit={handleLogin}>
            <label>
              Password
              <input
                type="password"
                value={password}
                onChange={e => setPassword(e.target.value)}
                autoComplete="current-password"
                required
              />
            </label>
            {errorMsg && <p className="admin-auth-error">{errorMsg}</p>}
            <button type="submit" className="btn-primary" disabled={submitting}>
              {submitting ? 'Signing In…' : 'Sign In'}
            </button>
            <Link to="/" className="btn-secondary">Back to Site</Link>
          </form>
        )}
      </div>
    </main>
  )
}

export default function App() {
  const [content, setContent] = useState<SiteContent>(DEFAULTS)

  useEffect(() => {
    fetch('/api/admin-content')
      .then(r => (r.ok ? r.json() : null))
      .then(data => {
        if (data?.siteContent) {
          setContent(c => ({ ...c, ...data.siteContent }))
        }
      })
      .catch(() => {})
  }, [])

  return (
    <Routes>
      <Route path="/" element={<LandingPage content={content} />} />
      <Route path="/the-mine" element={<TheMineWindowPage />} />
      <Route path="/thanks" element={<ThankYouPage />} />
      <Route path="/subscribe" element={<SubscribePage />} />
      <Route path="/subscribe/thanks" element={<SubscribeThankYouPage />} />
      <Route path="/admin" element={<AdminShell content={content} onSave={setContent} />} />
      <Route
        path="/privacy"
        element={(
          <LegalPage
            title="Privacy Policy"
            body={[
              'We respect your privacy and collect limited data to operate and improve this site.',
              'If you consent to analytics cookies, we may store masked IP-based location signals and returning visitor activity.',
              'Contact form details are used only to respond to your message and ministry communication requests.',
            ]}
          />
        )}
      />
      <Route
        path="/terms"
        element={(
          <LegalPage
            title="Terms"
            body={[
              'Content on this site is for informational and ministry purposes.',
              'External links are provided for convenience and are subject to third-party policies.',
              'By using this site, you agree to lawful use and respectful communication.',
            ]}
          />
        )}
      />
    </Routes>
  )
}
