<template>
  <div class="fixed inset-0 z-50 flex">
    <!-- backdrop -->
    <div class="fixed inset-0 bg-black opacity-50" @click="$emit('close')"></div>

    <div
      class="relative ml-auto h-full w-full sm:w-80 md:w-96 lg:w-[32rem] bg-white shadow-lg flex flex-col"
    >
      <div class="flex w-full h-10 p-1 justify-end">
        <Button
          variant="ghost"
          size="icon"
          class="shrink-0"
          aria-label="Close panel"
          @click="$emit('close')"
        >
          <i class="dku-icon-dismiss-16"></i>
        </Button>
      </div>
      <!-- Tabs + close button -->
      <Tabs defaultValue="references" class="flex-1 flex flex-col min-h-0">
        <div class="sticky z-50 bg-white flex items-center py-1 gap-2 px-7">
          <TabsList class="flex space-x-2 flex-1 h-10">
            <TabsTrigger value="references">Sources</TabsTrigger>
            <TabsTrigger value="progress">Activities</TabsTrigger>
            <TabsTrigger value="artifacts">Downloads</TabsTrigger>
          </TabsList>
        </div>
        <!-- References -->
        <TabsContent value="references" class="flex-1 overflow-y-auto">
          <SourcesTab :eventLog="props.eventLog" />
        </TabsContent>
        <!-- Downloads -->
        <TabsContent value="artifacts" class="flex-1 overflow-y-auto py-2 px-7">
          <p v-if="!Object.values(artifacts).length" class="text-sm text-gray-500">
            No downloads are available.
          </p>
          <template v-for="meta in artifacts" key="artifacts">
            <div class="flex items-center space-x-2">
              <i :class="getAgentIcon(meta.agentId)"></i>
              <span class="text-foreground group-hover:underline">
                {{ meta.agentName || 'Used tool' }}
              </span>
            </div>
            <p class="text-sm text-gray-500 py-2 mb-2" v-if="meta.preview">
              The generated items are too large for a full display. You can download the complete
              files below.
            </p>
            <Accordion
              type="single"
              collapsible
              v-if="meta.artifacts"
              v-model="openArtifactsAccordion[meta.artifacts_id]"
              class="mb-2"
            >
              <template v-for="(data, dataIdx) in meta.artifacts" :key="idx">
                <AccordionItem :value="String(dataIdx)">
                  <div class="flex items-center py-2">
                    <div class="flex items-center flex-1">
                      <i
                        :class="[
                          getEventIcon({ eventKind: 'artifacts', eventData: {} }),
                          'text-foreground',
                          'my-auto',
                          '!rotate-0',
                        ]"
                      ></i>
                      <div class="text-sm font-medium group-hover:underline ml-2 inline-flex">
                        {{ data.name }}
                        <Badge class="ml-2 bg-gray-100 text-base-foreground" v-if="meta.preview">
                          sample
                        </Badge>
                      </div>
                    </div>
                    <TooltipProvider>
                      <Tooltip>
                        <TooltipTrigger as-child>
                          <AccordionTrigger class="!p-0 !flex-none w-8 h-8">
                            <template #icon>
                              <ChevronDown
                                class="text-muted-foreground size-4 shrink-0 transition-transform duration-200"
                              />
                            </template>
                          </AccordionTrigger>
                        </TooltipTrigger>
                        <TooltipContent>
                          <p>
                            {{
                              openArtifactsAccordion[meta.artifacts_id] === String(dataIdx)
                                ? 'Collapse panel'
                                : 'Expand panel'
                            }}
                          </p>
                        </TooltipContent>
                      </Tooltip>
                    </TooltipProvider>
                  </div>
                  <AccordionContent>
                    <div class="flex items-center pb-4">
                      <div>{{ data.description }}</div>
                      <!-- Download button (right-aligned) -->
                      <div class="ml-auto">
                        <Button
                          size="sm"
                          variant="outline"
                          @click.stop="
                            downloadArtifacts(props.messageId, meta.artifacts_id, dataIdx)
                          "
                        >
                          <i class="dku-icon-file-download-16"></i>
                          Download csv
                        </Button>
                      </div>
                    </div>
                    <Card class="p-4 space-y-2 rounded-[8px]" v-for="part in data.parts">
                      <div class="flex justify-between items-start">
                        <h5 class="text-sm font-semibold">
                          <span>{{ getEventInfo(part.type).label }}</span>
                        </h5>
                        <div v-if="part.type" class="flex items-center space-x-1">
                          <component
                            :is="getIconForType(part.type)"
                            class="w-4 h-4 text-gray-500"
                          />
                          <span class="text-xs text-gray-500">{{ part.type }}</span>
                        </div>
                      </div>
                      <div
                        v-if="part.records && part.records.columns"
                        class="source-content pl-2 pr-2 max-w-full overflow-hidden whitespace-normal"
                      >
                        <DataTable
                          :rows="getRecordsRows(part.records)"
                          :columns="getRecordsColumns(part.records)"
                        ></DataTable>
                      </div>
                      <div v-else>
                        <vue-json-pretty :data="part" :deep="2" theme="light" class="text-xs" />
                      </div>
                    </Card>
                  </AccordionContent>
                </AccordionItem>
              </template>
            </Accordion>
          </template>
        </TabsContent>
        <!-- Activity -->
        <TabsContent value="progress" class="flex-1 overflow-y-auto p-4 mx-5">
          <Accordion type="single" collapsible>
            <template v-for="(evt, idx) in progressEvents" :key="idx">
              <!-- has data: real AccordionItem -->
              <AccordionItem
                v-if="hasEventData(evt)"
                :value="String(idx)"
                class="relative border-b-0"
              >
                <!-- Left band with dot -->
                <LeftBand
                  :isStreaming="isStreaming"
                  :index="idx"
                  :lastIndex="progressEvents.length - 1"
                />

                <AccordionTrigger class="relative flex items-center py-4 pl-1 ml-3">
                  <div
                    class="absolute left-0 top-1/2 transform -translate-x-5 -translate-y-1.5 z-10"
                  >
                    <img
                      :src="
                        isStreaming && idx === progressEvents.length - 1
                          ? emptyActivityDotUrl
                          : activityDotUrl
                      "
                      alt="Activity dot"
                      class="w-3 h-3"
                    />
                  </div>
                  <div class="flex items-center space-x-1">
                    <i :class="[getEventIcon(evt), 'text-foreground', '!rotate-0']"></i>
                    <span
                      class="text-sm font-medium text-foreground group-hover:underline"
                      v-html="
                        getEventInfoAsHtml(evt.eventKind, [
                          evt.eventData?.agentName,
                          formatEvtValue(evt.eventData?.toolName, 'toolName'),
                          formatEvtValue(evt.eventData?.agentAsToolName, 'agentAsToolName'),
                        ]).label
                      "
                    >
                    </span>
                  </div>
                </AccordionTrigger>
                <AccordionContent>
                  <dl class="space-y-2 px-2 ml-3">
                    <template
                      v-for="([key, value], index) in Object.entries(
                        filteredEventData(evt.eventData) || {}
                      )"
                      :key="key"
                    >
                      <div
                        class="flex py-2 mb-0"
                        :class="{
                          'border-b-[2px] border-border/30':
                            index < Object.keys(filteredEventData(evt.eventData) || {}).length - 1,
                          'pb-0':
                            index >= Object.keys(filteredEventData(evt.eventData) || {}).length - 1,
                        }"
                      >
                        <!-- fixed width key -->
                        <dt class="w-32 shrink-0 text-sm font-medium text-foreground">
                          {{ formatKey(key) }}
                        </dt>
                        <dd
                          class="flex-1 text-sm text-foreground break-all"
                          v-if="typeof value === 'string' || typeof value === 'number'"
                        >
                          {{ formatEvtValue(value, key) }}
                        </dd>
                        <dd class="flex-1" v-else>
                          <vue-json-pretty
                            :data="value"
                            :deep="2"
                            theme="light"
                            class="text-xs pl-1"
                          />
                        </dd>
                      </div>
                    </template>
                  </dl>
                </AccordionContent>
                <div class="ml-4 w-full h-px bg-border"></div>
              </AccordionItem>

              <!-- no data: non-expandable AccordionItem without chevron -->
              <AccordionItem v-else :value="String(idx)" class="relative border-b-0">
                <!-- Left band with dot for non-accordion items -->
                <LeftBand
                  :isStreaming="isStreaming"
                  :index="idx"
                  :lastIndex="progressEvents.length - 1"
                />

                <!-- Header styled like AccordionTrigger but without chevron -->
                <div
                  class="relative flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none ml-3"
                >
                  <div
                    class="absolute left-0 top-1/2 transform -translate-x-5 -translate-y-1.5 z-10"
                  >
                    <img
                      :src="
                        isStreaming && idx === progressEvents.length - 1
                          ? emptyActivityDotUrl
                          : activityDotUrl
                      "
                      alt="Activity dot"
                      class="w-3 h-3"
                    />
                  </div>
                  <div class="flex items-center space-x-2 pl-1">
                    <i :class="[getEventIcon(evt), 'text-foreground', '!rotate-0']"></i>
                    <span
                      class="text-sm font-medium text-foreground"
                      v-html="
                        getEventInfoAsHtml(evt.eventKind, [
                          evt.eventData?.agentName,
                          formatEvtValue(evt.eventData?.toolName, 'toolName'),
                          formatEvtValue(evt.eventData?.agentAsToolName, 'agentAsToolName'),
                        ]).label
                      "
                    ></span>
                  </div>
                </div>
                <div class="ml-4 w-full h-px bg-border"></div>
              </AccordionItem>
            </template>
          </Accordion>

          <!-- Copy trace button at the bottom -->
          <div
            v-if="props.eventLog && props.eventLog.length > 0"
            class="pt-4 flex gap-2 justify-end"
          >
            <Button
              @click="copyTraceToClipboard"
              size="sm"
              variant="outline"
              :disabled="loadingTrace"
            >
              <template v-if="loadingTrace">
                <i
                  :class="[
                    getEventIcon({ eventKind: 'AGENT_SELECTION', eventData: {} }),
                    'animate-spin',
                    'mr-2',
                  ]"
                ></i>
                Loading trace...
              </template>
              <template v-else> Copy trace </template>
            </Button>
            <Button @click="openTraceExplorer" size="sm" variant="outline">
              View in Trace Explorer
            </Button>
          </div>
        </TabsContent>
      </Tabs>
    </div>
  </div>

  <AlertDialog v-model:open="showErrorModal">
    <AlertDialogContent>
      <AlertDialogHeader>
        <AlertDialogTitle>Error</AlertDialogTitle>
        <AlertDialogDescription>{{ errorMessage }}</AlertDialogDescription>
      </AlertDialogHeader>
      <AlertDialogFooter>
        <AlertDialogCancel @click="showErrorModal = false">Close</AlertDialogCancel>
      </AlertDialogFooter>
    </AlertDialogContent>
  </AlertDialog>
</template>

<script setup lang="ts">
import { defineProps, defineEmits, computed, ref, reactive } from 'vue'
import VueJsonPretty from 'vue-json-pretty'
import 'vue-json-pretty/lib/styles.css'
import { toast } from 'vue-sonner'
import { Button } from '@/components/ui/button'
import DataTable from '@/components/DataTable.vue'
import { Badge } from '@/components/ui/badge'

import {
  Accordion,
  AccordionItem,
  AccordionTrigger,
  AccordionContent,
} from '@/components/ui/accordion'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'
import { Card } from '@/components/ui/card'
import {
  FileText,
  File,
  Code,
  Image as ImageIcon,
  Link,
  Database,
  ChevronDown,
} from 'lucide-vue-next'
import { formatEvtValue, getEventIcon, getEventInfo, getEventInfoAsHtml } from '@/constants/events'
import { API } from '@/Api'
import { compareVersions, getAppConfig } from '@/lib/utils'
import {
  AlertDialog,
  AlertDialogContent,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogCancel,
} from '@/components/ui/alert-dialog'

import { getRecordsColumns, getRecordsRows } from '@/lib/utils'
import { downloadArtifacts } from '@/lib/downloadUtils'
import activityDotUrl from '@/assets/special-icon/activity-dot.svg?url'
import emptyActivityDotUrl from '@/assets/special-icon/empty-activity-dot.svg?url'
import { WT1iser } from '@/lib/wt1'
import type { EventLogEntry } from '@/types/messages'
import { useConversationStore } from '@/store/conversationStore'
import LeftBand from '@/components/LeftBand.vue'
import { useAgentCatalogStore } from '@/store/agentCatalogStore'
import { getAgentTypeIcon } from '@/lib/agentUtils'
import SourcesTab from '@/components/EventLogPanel/SourcesTab.vue'

const conversationStore = useConversationStore()
const agentCatalogStore = useAgentCatalogStore()

const isStreaming = computed(() => conversationStore.activeConversation?.isStreaming || false)

defineEmits(['close'])

const props = defineProps<{
  eventLog: Array<EventLogEntry>
  artifacts: any
  messageId: string
  draftMode?: boolean
  agentIds?: string[]
}>()

const showErrorModal = ref(false)
const errorMessage = ref('')
const excludedKeys = ['agentName', 'toolName', 'agentAsToolName', 'agentId']
const openArtifactsAccordion = reactive<Record<string, string | undefined>>({})

const filteredEventData = (data: Record<string, any>) => {
  if (!data) return null
  const filteredEntries = Object.entries(data).filter(
    ([key, value]) => !excludedKeys.includes(key) && value != null
  )
  return filteredEntries.length > 0 ? Object.fromEntries(filteredEntries) : null
}
/* ------------------------------------------------------------------ */
/* Transform eventLog → progress / trace ----------------------------- */
function formatKey(key: string | number): any {
  return typeof key === 'string'
    ? key
        .replace(/([A-Z])/g, ' $1') // add space before caps
        .replace(/^./, (s) => s.toUpperCase()) // capitalize first letter
    : key
}
const traceData = ref<any>(null)
const loadingTrace = ref<boolean>(false)

async function loadTraceData() {
  if (!traceData.value && !loadingTrace.value) {
    loadingTrace.value = true
    const params: Record<string, string> = {}
    if (props.draftMode) {
      params.draft = 'true'
      // Check if agentIds exists and has at least one element before accessing index 0
      if (props.agentIds && props.agentIds.length > 0) {
        params.agent_id = props.agentIds[0]
      } else {
        console.warn('Draft mode enabled but no agent ID available')
      }
    }

    try {
      const response = await API.getMessageTrace(props.messageId, params)
      traceData.value = response.data
    } catch (e) {
      console.error('Failed to load trace data', e)
    } finally {
      loadingTrace.value = false
    }
  }
}

const progressEvents = computed(() =>
  props.eventLog.filter(
    (evt) =>
      evt.eventKind !== 'references' &&
      evt.eventKind !== 'chart_plan' &&
      evt.eventKind !== 'artifacts' &&
      evt.eventKind !== 'agent_event' &&
      evt.eventKind !== 'agent_chunk' &&
      evt.eventKind !== 'synthesizing_chunk' &&
      (evt.eventKind !== 'AGENT_SELECTION' || evt.eventData.justification !== 'User selected')
  )
)
/* ------------------------------------------------------------------ */
/* Trace functionality ---------------------------------------------- */
// Copy trace to clipboard
async function copyTraceToClipboard() {
  WT1iser.copyTrace()
  try {
    await loadTraceData()
    if (!traceData.value) {
      toast.error('No trace available to copy')
      return
    }

    const traceString =
      typeof traceData.value === 'string'
        ? traceData.value
        : JSON.stringify(traceData.value, null, 2)

    await navigator.clipboard.writeText(traceString)
    toast.success('Trace copied to clipboard')
  } catch (error) {
    console.error('Failed to copy trace:', error)
    toast.error('Failed to copy trace to clipboard')
  }
}

// Open trace in Trace Explorer
async function openTraceExplorer() {
  WT1iser.viewTraceExplorer()
  try {
    // Load trace data if not already loaded
    await loadTraceData()

    if (!traceData.value) {
      toast.error('No trace available to open')
      return
    }

    // Check if the "traces-explorer" plugin is installed
    API.getPluginInfo('traces-explorer')
      .then((pluginInfo) => {
        if (pluginInfo.installed) {
          // Handle the case where the plugin is installed
          handlePluginInstalled(pluginInfo.version)
        } else {
          // Handle the case where the plugin is not installed
          handlePluginNotInstalled()
        }
      })
      .catch((error) => {
        // Handle errors during the plugin check
        console.error('Error checking plugin status:', error)
        toast.error(error.message || 'Failed to check plugin status')
      })
  } catch (error) {
    console.error('Failed to open trace explorer:', error)
    toast.error('Failed to open trace explorer')
  }
}

// Handle the case where the plugin is installed
const handlePluginInstalled = async (pluginVersion?: string) => {
  try {
    // Get the app config directly
    const appConfig = getAppConfig()

    if (!appConfig) {
      throw new Error('App config is not available')
    }

    // Check if the plugin version is supported
    if (pluginVersion && compareVersions(pluginVersion, '1.0.4') < 0) {
      const errorMessage = `The currently installed Trace Explorer plugin (version ${pluginVersion}) does not support this feature. To proceed, the plugin must be updated to version 1.0.4 or newer. Please contact your administrator.`
      throw new Error(errorMessage)
    }

    // Retrieve the Trace Explorer default web app configuration from appConfig
    const traceExplorerDefaultWebApp = appConfig?.llmSettings?.traceExplorerDefaultWebApp

    if (!traceExplorerDefaultWebApp?.projectKey || !traceExplorerDefaultWebApp?.webAppId) {
      errorMessage.value =
        'No Trace Explorer instance has been selected in the Administration settings > LLM Mesh > Configuration > Trace. Please contact your administrator.'
      showErrorModal.value = true
      return
    }

    // Build the Trace Explorer URL
    if (!traceExplorerDefaultWebApp.projectKey || !traceExplorerDefaultWebApp.webAppId) {
      throw new Error('Trace Explorer projectKey or webAppId is missing.')
    }
    const traceExplorerUrl = await buildTraceExplorerUrl({
      projectKey: traceExplorerDefaultWebApp.projectKey,
      webAppId: traceExplorerDefaultWebApp.webAppId,
    })

    // Open the Trace Explorer in a new tab
    window.open(`${traceExplorerUrl}?readTraceFromLS=true`, '_blank')
  } catch (error: any) {
    console.error('Error handling plugin installation:', error)
    toast.error(error.message || 'An error occurred while opening the Trace Explorer.')
  }
}

// Handle the case where the plugin is not installed
function handlePluginNotInstalled() {
  toast.error('The "traces-explorer" plugin is not installed. Please install it to proceed.')
}

const buildTraceExplorerUrl = async (traceExplorerWebApp: {
  projectKey: string
  webAppId: string
}): Promise<string> => {
  try {
    const publicInfo = await API.getPublicInfo(
      traceExplorerWebApp.projectKey,
      traceExplorerWebApp.webAppId
    )

    // Store the trace data in localStorage with proper type handling
    const traceString =
      typeof traceData.value === 'string'
        ? traceData.value
        : JSON.stringify(traceData.value, null, 2)

    localStorage.setItem('ls.llm.traceExplorer.trace', traceString)

    // Build the base URL
    const appConfig = getAppConfig()
    let baseUrl = ''
    if (appConfig?.dssExternalURL) {
      baseUrl += appConfig.dssExternalURL
    }
    baseUrl += '/webapps/'

    // Construct the final URL
    const vanityOrId =
      publicInfo.securityInfo.vanityURL || `${publicInfo.webapp.projectKey}/${publicInfo.webapp.id}`
    return `${baseUrl}${vanityOrId}/`
  } catch (error) {
    const errorMessage = `The Trace Explorer Web App instance ${traceExplorerWebApp.webAppId} in the project ${traceExplorerWebApp.projectKey} does not exist or you don't have read access to the project. Please contact your administrator.`
    console.error(errorMessage, error)
    throw new Error(errorMessage)
  }
}
/* ------------------------------------------------------------------ */
/* Utils ------------------------------------------------------------- */
const typeIconMap: Record<string, any> = {
  SIMPLE_DOCUMENT: FileText,
  CODE_SNIPPET: Code,
  HTML_PAGE: FileText,
  IMAGE: ImageIcon,
  URL: Link,
  DATABASE_RECORD: Database,
}

function getIconForType(type?: string) {
  if (!type) return File
  return typeIconMap[type] ?? File
}

function hasEventData(evt: EventLogEntry) {
  return (
    evt.eventData &&
    Object.keys(evt.eventData)?.filter((key) => excludedKeys.indexOf(key) === -1).length > 0
  )
}

function getAgentIcon(agentId: string): string {
  const agent = agentCatalogStore.getAgentById(agentId)
  if (agent) {
    return `${getAgentTypeIcon(agent)} !rotate-0`
  }
  return 'dku-icon-ai-agent-16 !rotate-0'
}
</script>

<style scoped>
.vue-json-pretty {
  overflow-x: hidden;
  word-break: break-word;
}

.vjs-value-string {
  color: oklch(54.6% 0.245 262.881);
}

.vjs-tree {
  text-align: left;
  font-family: 'Source Sans Pro', sans-serif;
  font-size: 12px;
}
</style>
