<script setup lang="ts">
import { computed, watch, toRefs, ref } from 'vue'

import { Card, CardHeader, CardTitle, CardContent, CardFooter } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Separator } from '@/components/ui/separator'
import { Skeleton } from '@/components/ui/skeleton'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
import { getEventIcon } from '@/constants/events'
import MarkdownRenderer from './MarkdownRenderer.vue'
import {
  Carousel,
  CarouselContent,
  CarouselItem,
  // CarouselNext,
  // CarouselPrevious,
} from '@/components/ui/carousel'
import { Button } from '@/components/ui/button'
import type { CarouselApi } from './ui/carousel'
import { watchOnce } from '@vueuse/core'
type LiveEvent = { kind: string; data: any }

const props = withDefaults(
  defineProps<{
    liveEvents: Array<{ kind: string; data: any }>
    hasPendingLiveEvent: boolean
    maxAgents?: number
    showPlaceholders?: boolean
  }>(),
  {
    maxAgents: 3,
    showPlaceholders: true,
  }
)

const { liveEvents, hasPendingLiveEvent, maxAgents, showPlaceholders } = toRefs(props)

/** Which agents were actually called (from calling_agents). */
const calledAgentIds = computed<string[]>(() => {
  const last = [...liveEvents.value].reverse().find((e) => e.kind === 'calling_agents')
  const ids = (last?.data?.agents.map((el: any) => el['agentId']) ?? []) as string[]
  return Array.isArray(ids) ? ids : []
})

/** Fallback: infer agents seen in events if calling_agents wasn't received. */
const inferredAgentIds = computed<string[]>(() => {
  const set = new Set<string>()
  for (const e of liveEvents.value) {
    if (
      e.kind === 'agent_started' ||
      e.kind === 'agent_chunk' ||
      e.kind === 'agent_finished' ||
      e.kind === 'agent_error'
    ) {
      const id = e.data?.agentId
      if (id) set.add(id)
    }
  }
  return [...set]
})

/** Effective list of agents we should display, ordered by call order if available. */
const effectiveAgentIds = computed<string[]>(() => {
  if (calledAgentIds.value.length) return calledAgentIds.value
  return inferredAgentIds.value
})

/** Cap by actual called (or inferred) count and maxAgents. */
const visibleCap = computed(() =>
  Math.min(maxAgents.value, Math.max(0, effectiveAgentIds.value.length))
)

const PHASE_LABELS: Record<string, string> = {
  getting_ready: 'Getting ready',
  planning: 'Planning',
  thinking: 'Thinking',
  calling_tool: 'Calling tool',
  waiting_tool: 'Waiting on tool',
  writing: 'Writing',
  finalizing: 'Finalizing',
}

type AgentState = {
  agentId: string
  agentName?: string
  query: string
  text: string
  status: 'pending' | 'ok' | 'error' | 'timeout' | 'cancelled'
  startedAt?: number
  finishedAt?: number
  errorMsg?: string
  phases: string[]
  currentPhase?: string
}

function reduceAgentStates(events: LiveEvent[]): Record<string, AgentState> {
  const map: Record<string, AgentState> = {}

  const ensure = (agentId: string): AgentState => {
    if (!map[agentId]) {
      map[agentId] = {
        agentId,
        text: '',
        status: 'pending',
        phases: [],
        agentName: agentId,
        query: '',
      }
    }
    return map[agentId]
  }

  for (const ev of events) {
    const kind = ev.kind
    const data = ev.data || {}

    if (kind?.startsWith('synthesizing_')) continue

    if (kind === 'agent_started') {
      const a = ensure(data.agentId)
      a.startedAt = a.startedAt ?? Date.now()
      a.agentName = data.agentName || a.agentName || data.agentId
      a.query = data.query || ''
      a.status = 'pending'
    } else if (kind === 'agent_chunk') {
      const a = ensure(data.agentId)
      if (typeof data.text === 'string') a.text += data.text
    } else if (kind === 'agent_event') {
      const a = ensure(data.agentId)
      const ek = (data.eventKind || '').toString()
      if (ek) {
        if (!a.phases.includes(ek)) a.phases.push(ek)
        a.currentPhase = ek
      }
    } else if (kind === 'agent_error') {
      const a = ensure(data.agentId)
      a.status = 'error'
      a.errorMsg = data.message || 'Error while processing your request'
      a.finishedAt = a.finishedAt ?? Date.now()
    } else if (kind === 'agent_finished') {
      const a = ensure(data.agentId)
      a.status = (data.status as AgentState['status']) ?? 'ok'
      a.finishedAt = a.finishedAt ?? Date.now()
    }
  }
  return map
}

const allAgentsMap = computed(() => reduceAgentStates(liveEvents.value))

/** Visible agents in the effective order, capped. */
const visibleAgents = computed<AgentState[]>(() => {
  const ids = effectiveAgentIds.value.slice(0, visibleCap.value)
  return ids.map((id) => allAgentsMap.value[id]).filter(Boolean)
})

/** Placeholders only when streaming & fewer visible than cap. */
const placeholderCount = computed(() => {
  if (!showPlaceholders.value) return 0
  if (!hasPendingLiveEvent.value) return 0
  return Math.max(0, visibleCap.value - visibleAgents.value.length)
})

function statusInfo(s: AgentState['status']) {
  switch (s) {
    case 'pending':
      return { label: 'Streaming…', variant: 'secondary' as const }
    case 'ok':
      return { label: 'Finished', variant: 'default' as const }
    case 'timeout':
      return { label: 'Timeout', variant: 'destructive' as const }
    case 'cancelled':
      return { label: 'Cancelled', variant: 'outline' as const }
    case 'error':
      return { label: 'Error', variant: 'destructive' as const }
    default:
      return { label: s, variant: 'secondary' as const }
  }
}
function phaseLabel(ek: string) {
  return PHASE_LABELS[ek] || ek.replace(/_/g, ' ').replace(/\b\w/g, (m) => m.toUpperCase())
}

const isBusy = computed(
  () => hasPendingLiveEvent.value || visibleAgents.value.some((a) => a?.status === 'pending')
)
const allDone = computed(
  () => visibleAgents.value.length > 0 && visibleAgents.value.every((a) => a?.status !== 'pending')
)

const emit = defineEmits<{ (e: 'all-done-changed', value: boolean): void }>()
watch(allDone, (v, prev) => {
  if (v !== prev) emit('all-done-changed', v)
})
const api = ref<CarouselApi>()
const totalCount = ref(0)
const current = ref(0)
function setApi(val: CarouselApi) {
  api.value = val
}

watchOnce(api, (api) => {
  if (!api) return

  // initial values
  totalCount.value = api.scrollSnapList().length
  current.value = api.selectedScrollSnap() + 1

  api.on('select', () => {
    current.value = api.selectedScrollSnap() + 1
  })

  api.on('reInit', () => {
    totalCount.value = api.scrollSnapList().length
    current.value = api.selectedScrollSnap() + 1
  })
})

function goTo(i: number) {
  api.value?.scrollTo(i - 1)
}
</script>

<template>
  <div class="w-full">
    <div class="mb-2 flex items-center justify-between">
      <Badge v-if="isBusy" variant="secondary">Receiving…</Badge>
      <Badge v-else variant="outline">Idle</Badge>
    </div>

    <Carousel class="w-full" :opts="{ align: 'start', loop: false }" @init-api="setApi">
      <CarouselContent>
        <!-- Agent slides -->
        <CarouselItem
          v-for="agent in visibleAgents"
          :key="agent.agentId"
          class="snap-start basis-[48%] sm:basis-[45%] md:basis-[42%] lg:basis-[48%] xl:basis-[48%] flex-none"
        >
          <Card class="rounded-2xl h-full flex flex-col py-2 gap-1">
            <CardHeader class="py-1">
              <div class="flex items-center justify-between gap-3">
                <div class="text-base font-semibold truncate text-xs">
                  {{ agent.agentName || agent.agentId }}
                </div>
                <Badge :variant="statusInfo(agent.status).variant">
                  {{ statusInfo(agent.status).label }}
                </Badge>
              </div>
              <div class="text-base truncate text-xs" v-if="agent.query">
                <span class="font-semibold">Query:</span> {{ agent.query }}
              </div>
              <div
                v-if="agent.currentPhase && !agent.finishedAt"
                class="flex flex-wrap items-center gap-1.5 text-xs"
              >
                <i
                  :class="[
                    getEventIcon({ eventKind: agent.currentPhase, eventData: {} }),
                    'mr-1',
                    'text-primary',
                    'animate-spin',
                  ]"
                ></i>
                <span>{{ phaseLabel(agent.currentPhase) }}</span>
              </div>
            </CardHeader>

            <Separator />

            <CardContent class="p-0 flex-1 min-h-0">
              <ScrollArea class="h-full max-h-72 overflow-hidden">
                <MarkdownRenderer
                  v-if="agent.text"
                  :content="agent.text"
                  class="px-6 py-2 full-h"
                />
                <div v-else class="p-4 space-y-2">
                  <Skeleton class="h-4 w-5/6" />
                  <Skeleton class="h-4 w-4/6" />
                  <Skeleton class="h-4 w-3/6" />
                </div>
              </ScrollArea>
            </CardContent>

            <CardFooter class="flex items-center justify-between py-2">
              <TooltipProvider>
                <Tooltip>
                  <TooltipTrigger as-child>
                    <div class="text-xs text-muted-foreground">
                      <template v-if="agent.startedAt">
                        Started at {{ new Date(agent.startedAt).toLocaleTimeString() }}
                      </template>
                      <template v-else> Waiting to start… </template>
                      <template v-if="agent.finishedAt">
                        · Finished at {{ new Date(agent.finishedAt).toLocaleTimeString() }}
                      </template>
                    </div>
                  </TooltipTrigger>
                  <TooltipContent
                    ><p class="text-xs">Per-agent timestamps (local time)</p></TooltipContent
                  >
                </Tooltip>
              </TooltipProvider>
            </CardFooter>
          </Card>
        </CarouselItem>

        <!-- Placeholder slides (only while streaming) -->
        <CarouselItem
          v-for="n in placeholderCount"
          :key="'placeholder-' + n"
          class="snap-start basis-[48%] sm:basis-[45%] md:basis-[42%] lg:basis-[40%] xl:basis-[33%] flex-none"
        >
          <Card class="rounded-2xl h-full flex flex-col opacity-60">
            <CardHeader class="py-1">
              <div class="flex items-center justify-between gap-3">
                <CardTitle class="text-base font-semibold truncate">Waiting for agent…</CardTitle>
                <Badge variant="secondary">Pending</Badge>
              </div>
            </CardHeader>
            <Separator />
            <CardContent class="p-0 flex-1 min-h-0">
              <div class="p-4 space-y-2">
                <Skeleton class="h-4 w-5/6" />
                <Skeleton class="h-4 w-4/6" />
                <Skeleton class="h-4 w-3/6" />
              </div>
            </CardContent>
            <CardFooter class="py-2">
              <div class="text-xs text-muted-foreground">Awaiting chunks…</div>
            </CardFooter>
          </Card>
        </CarouselItem>
      </CarouselContent>

      <!-- Built-in arrows -->
      <!-- <CarouselPrevious /> -->
      <!-- <CarouselNext /> -->
    </Carousel>
    <!-- Dots (simple, using carouselApi) -->
    <div class="mt-3 flex items-center justify-center gap-1.5">
      <Button
        v-for="i in totalCount"
        :key="'dot-' + i"
        type="button"
        class="h-2 w-2 rounded-full transition-opacity p-0"
        :class="i === current ? 'bg-primary opacity-100' : 'bg-muted-foreground/40 opacity-50'"
        @click="goTo(i)"
        aria-label="Go to slide"
      />
    </div>
  </div>
</template>

<style scoped>
.no-scrollbar {
  -ms-overflow-style: none; /* IE/Edge */
  scrollbar-width: none; /* Firefox */
}
.no-scrollbar::-webkit-scrollbar {
  display: none; /* Chrome/Safari */
}
</style>
