<template>
  <ResizablePanelGroup :direction="props.isFullscreen ? 'horizontal' : 'vertical'">
    <ResizablePanel class="pl-0 flex flex-col">
      <!-- TODO: each panel section should be it's own component -->
      <div class="panel-header font-sans text-sm font-medium flex-none">
        Inputs
        <Tooltip :delay-duration="500">
          <TooltipTrigger as-child>
            <div class="expand-toggle" @click="toggleExpansion('inputs')">
              <ListCollapse :size="16" />
            </div>
          </TooltipTrigger>
          <TooltipContent side="bottom" class="z-[2000]">{{
            expansionDepth.inputs.length === 1 ? 'Collapse all' : 'Expand all'
          }}</TooltipContent>
        </Tooltip>
      </div>
      <NodeEmptyState v-if="isEmptyPanel(displayNode?.inputs, FormatPanelType.Inputs)" />
      <div v-else class="flex-1 overflow-auto">
        <Accordion
          type="multiple"
          collapsible
          className="w-full"
          v-model="expansionDepth.inputs"
          class="mt-1 mb-1"
        >
          <AccordionItem
            v-for="(inputItem, inputIndex) in Object.entries(displayNode?.inputs ?? {})"
            :key="inputIndex"
            :value="`input-item-${inputIndex + 1}`"
          >
            <AccordionTrigger
              class="ml-4 h-5 capitalize text-base p-0 mr-3"
              v-if="inputItem[0] === 'messages'"
              >{{ inputItem[0] }}</AccordionTrigger
            >
            <AccordionContent
              v-if="inputItem[0] === 'messages'"
              className="flex flex-col text-balance"
            >
              <Card
                v-for="(msg, msgIndex) in inputItem[1]"
                :key="msgIndex"
                class="format-card relative"
              >
                <div
                  class="cursor-pointer absolute top-2 right-2 inline-flex items-center justify-center p-1 rounded-md text-muted-foreground hover:text-foreground hover:bg-accent focus-visible:outline-none"
                >
                  <Tooltip :delay-duration="500">
                    <TooltipTrigger class="cursor-pointer" as-child>
                      <Copy
                        class="text-muted-foreground focus:outline-none"
                        :size="16"
                        @click="copyToClipboard(msg.text)"
                      />
                    </TooltipTrigger>
                    <TooltipContent side="bottom" class="z-[2000]">Click to copy</TooltipContent>
                  </Tooltip>
                </div>
                <div class="flex h-3">
                  <span
                    :data-role="msg.role"
                    class="capitalize data-[role=assistant]:text-assistant-avatar data-[role=user]:text-user-avatar data-[role=system]:text-llm data-[role=tool]:text-tool text-sm"
                    >{{ msg.role }}</span
                  >
                  <div
                    :data-role="msg.role"
                    class="message-toggle data-[role=assistant]:text-assistant-avatar data-[role=user]:text-user-avatar data-[role=system]:text-llm data-[role=tool]:text-tool text-sm relative bottom-[2px]"
                    @click="toggleMessage(msgIndex)"
                  >
                    <ChevronDown v-if="showMessages[msgIndex]" :size="16" />
                    <ChevronUp v-else :size="16" />
                  </div>
                </div>
                <div v-if="showMessages[msgIndex]">...</div>
                <div v-else>
                  <div
                    v-if="msg.text && !isJson(msg.text)"
                    v-md="{ content: msg.text, queryIndex: msgIndex }"
                  ></div>
                  <div
                    v-else-if="msg.text && isJson(msg.text)"
                    class="pt-2 flex-1 min-h-0 overflow-auto"
                  >
                    <vue-json-pretty
                      class="font-xs"
                      :data="parseDeep(parseJson(msg.text))"
                      :deep="9999"
                      show-icon
                      show-length
                    />
                  </div>
                  <div v-else-if="msg.toolCalls" class="text-sm">
                    <div>
                      <p class="py-1 pt-2"><span class="font-bold">Tool call(s)</span>:</p>
                      <div class="pl-2" v-for="(tc, tcIdx) in msg.toolCalls" :key="tcIdx">
                        <div><span class="font-bold">ID</span>: {{ tc.id }}</div>
                        <div><span class="font-bold">Name</span>: {{ tc.function.name }}</div>
                        <div>
                          <span class="font-bold">Arguments:</span> {{ tc.function.arguments }}
                        </div>
                        <Separator
                          v-if="msg.toolCalls.length > 1 && tcIdx !== msg.toolCalls.length - 1"
                          orientation="horizontal"
                          class="my-2"
                        />
                      </div>
                    </div>
                  </div>
                  <div
                    v-else-if="msg.toolOutputs"
                    v-for="(to, toIdx) in msg.toolOutputs"
                    :key="toIdx"
                    class="text-sm"
                  >
                    <p><span class="font-bold">Call ID: </span>{{ to.callId }}</p>
                    <p><span class="font-bold">Output: </span>{{ to.output }}</p>
                    <Separator
                      v-if="msg.toolOutputs.length > 1"
                      orientation="horizontal"
                      class="m-1"
                    />
                  </div>
                  <div v-else-if="msg.content">{{ msg.content }}</div>
                </div>
              </Card>
            </AccordionContent>
          </AccordionItem>
        </Accordion>
      </div>
    </ResizablePanel>
    <ResizableHandle withHandle />
    <ResizablePanel class="pl-0 flex flex-col">
      <div class="panel-header font-sans text-sm font-medium flex-none">
        Outputs
        <Tooltip :delay-duration="500">
          <TooltipTrigger as-child>
            <div class="expand-toggle" @click="toggleExpansion('outputs')">
              <ListCollapse :size="16" />
            </div>
          </TooltipTrigger>
          <TooltipContent side="bottom" class="z-[2000]">{{
            expansionDepth.outputs.length === 3 ? 'Collapse all' : 'Expand all'
          }}</TooltipContent>
        </Tooltip>
      </div>
      <NodeEmptyState v-if="isEmptyPanel(displayNode?.outputs, FormatPanelType.Outputs)" />
      <div v-else class="flex-1 overflow-auto">
        <Accordion
          type="multiple"
          collapsible
          className="w-full"
          :default-value="['output-item-1', 'output-item-2', 'output-item-3']"
          v-model="expansionDepth.outputs"
          class="mt-1 pb-0"
        >
          <AccordionItem
            v-for="(outputItem, outputIndex) in Object.entries(displayNode?.outputs ?? {})"
            :key="outputIndex"
            :value="`output-item-${outputIndex + 1}`"
            class="pb-0"
          >
            <!-- Text -->
            <AccordionTrigger
              class="ml-4 h-5 capitalize text-base p-0 mr-3"
              v-if="outputItem[0] === 'text'"
              >{{ outputItem[0] }}</AccordionTrigger
            >
            <AccordionContent
              v-if="outputItem[0] === 'text'"
              className="flex flex-col text-balance"
            >
              <Card class="format-card relative">
                <div
                  class="cursor-pointer absolute top-2 right-2 inline-flex items-center justify-center p-1 rounded-md text-muted-foreground hover:text-foreground hover:bg-accent focus-visible:outline-none"
                >
                  <Tooltip :delay-duration="500">
                    <TooltipTrigger class="cursor-pointer" as-child>
                      <Copy
                        class="text-muted-foreground focus:outline-none"
                        :size="16"
                        @click="copyToClipboard(outputItem[1])"
                      />
                    </TooltipTrigger>
                    <TooltipContent side="bottom" class="z-[2000]">Click to copy</TooltipContent>
                  </Tooltip>
                </div>
                <div class="h-3">
                  <span class="capitalize text-assistant-avatar text-sm">Assistant</span>
                </div>
                <div
                  v-if="!isJson(outputItem[1])"
                  v-md="{ content: outputItem[1], queryIndex: outputIndex }"
                ></div>
                <div v-else class="pt-2 flex-1 min-h-0 overflow-auto">
                  <vue-json-pretty
                    class="font-xs"
                    :data="parseDeep(parseJson(outputItem[1]))"
                    :deep="9999"
                    show-icon
                    show-length
                  />
                </div>
              </Card>
            </AccordionContent>
            <!-- Tool calls  -->
            <AccordionTrigger
              class="ml-4 h-5 capitalize text-base p-0 mr-3"
              v-if="outputItem[0] === 'toolCalls'"
              >{{ outputItem[0] }}</AccordionTrigger
            >
            <AccordionContent
              v-if="outputItem[0] === 'toolCalls'"
              className="flex flex-col text-balance"
            >
              <Card class="format-card">
                <span v-if="outputItem[0]" class="capitalize text-tool"> Name </span>
                {{ formatToolCallName(outputItem[1]) }}
              </Card>
              <Card class="format-card">
                <span v-if="outputItem[0]" class="capitalize text-tool"> Arguments </span>
                <div
                  v-if="!isJson(formatToolCallArgs(outputItem[1]))"
                  v-md="{ content: formatToolCallArgs(outputItem[1]), queryIndex: outputIndex }"
                ></div>
                <div v-else class="pt-2 flex-1 min-h-0 overflow-auto">
                  <vue-json-pretty
                    class="font-xs"
                    :data="parseDeep(parseJson(formatToolCallArgs(outputItem[1])))"
                    :deep="9999"
                    show-icon
                    show-length
                  />
                </div>
              </Card>
            </AccordionContent>
            <!-- Tool Output -->
            <AccordionTrigger
              class="ml-4 h-5 capitalize text-base p-0 mr-3"
              v-if="outputItem[0] === 'output'"
              >{{ outputItem[0] }}</AccordionTrigger
            >
            <AccordionContent
              v-if="outputItem[0] === 'output'"
              className="flex flex-col text-balance"
            >
              <div v-for="(outputKey, outputKeyIdx) in outputKeys" :key="outputKeyIdx">
                <Card v-if="outputItem[1][outputKey]" class="format-card">
                  <span class="capitalize text-tool">
                    {{ outputKey }}
                  </span>
                  <p v-if="outputKey !== 'artifact'">{{ outputItem[1][outputKey] }}</p>
                  <div
                    v-else-if="
                      outputKey === 'artifact' &&
                      outputItem[1][outputKey] &&
                      outputItem[1][outputKey].sources
                    "
                    class="pt-2 flex-1 min-h-0 overflow-auto"
                  >
                    <vue-json-pretty
                      class="font-xs"
                      :data="parseDeep(extractSources(outputItem[1][outputKey].sources))"
                      :deep="9999"
                      show-icon
                      show-length
                    />
                  </div>
                </Card>
              </div>
            </AccordionContent>
            <!-- Sources -->
            <AccordionTrigger
              class="ml-4 h-5 capitalize text-base p-0 mr-3"
              v-if="outputItem[0] === 'artifact'"
              >{{ outputItem[0] }}</AccordionTrigger
            >
            <AccordionContent
              v-if="outputItem[0] === 'artifact'"
              className="flex flex-col text-balance"
            >
              <Card class="format-card">
                <span v-if="outputItem[1].artifact" class="capitalize text-tool">
                  {{ outputItem[1] }}
                </span>
                <div v-if="outputItem[1].sources" class="pt-2 flex-1 min-h-0 overflow-auto">
                  <vue-json-pretty
                    class="font-xs"
                    :data="parseDeep(outputItem[1].sources)"
                    :deep="9999"
                    show-icon
                    show-length
                  />
                </div>
              </Card>
            </AccordionContent>
          </AccordionItem>
        </Accordion>
      </div>
    </ResizablePanel>
  </ResizablePanelGroup>
</template>
<script setup lang="ts">
import { onMounted, type Ref, ref, watch } from 'vue'
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable'
import type { TraceNode } from '@/common/interface/trace'
import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger
} from '@/components/ui/accordion'
import { ListCollapse, ChevronUp, ChevronDown, Copy } from 'lucide-vue-next'
import type { ToolCall } from '@/common/interface/trace'
import { Card } from '@/components/ui/card'
import VueJsonPretty from 'vue-json-pretty'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import type { Source } from '@/common/interface/trace'
import { parseDeep } from '@/utils/nodeJson.ts'
import NodeEmptyState from './NodeEmptyState.vue'
import { usePanelSelectionStore } from '@/stores/panelSelection'
import type { FormattedPanelExpansions } from '@/common/interface/panelSelection'
import { Separator } from '@/components/ui/separator'
import { isEmptyPanel, FormatPanelType } from '@/utils/emptyNode'
import { copyToClipboard } from '@/utils/clipboard'

const panelSelectionStore = usePanelSelectionStore()

const props = defineProps<{
  isFullscreen: boolean
  displayNode: TraceNode | undefined
}>()

const maxInputsSelect = ['input-item-1']
const maxOutputsSelect = ['output-item-1', 'output-item-2', 'output-item-3']

const outputKeys: string[] = ['content', 'status', 'name', 'tool_call_id', 'artifact']
const showMessages = ref<boolean[]>([])

const expansionDepth: Ref<Record<string, string[]>> = ref({
  // We don't use v-model directly from the store because we only want to
  // maintain if something is fully expanded or minimally collapsed not individual item expansion
  inputs: maxInputsSelect,
  outputs: maxOutputsSelect
})

onMounted(() => {
  expansionDepth.value['inputs'] = panelSelectionStore.formattedExpansions['inputs']
    ? maxInputsSelect
    : []
  expansionDepth.value['outputs'] = panelSelectionStore.formattedExpansions['outputs']
    ? maxOutputsSelect
    : []
})

function toggleExpansion(key: keyof FormattedPanelExpansions & string) {
  const maxExpansionsValue =
    key === 'inputs' ? maxInputsSelect : key === 'outputs' ? maxOutputsSelect : []
  expansionDepth.value[key] =
    expansionDepth.value[key].length === maxExpansionsValue.length ? [] : maxExpansionsValue
  panelSelectionStore.toggleFormattedSelection(key)
}

function formatToolCallArgs(toolCalls: ToolCall[]) {
  // Joins streamed message chunks
  const args = new Set(
    (toolCalls ?? []).map((c) => c?.function?.arguments).filter((v): v is string => !!v)
  )
  return [...args].join('')
}

function formatToolCallName(toolCalls: ToolCall[] | null | undefined): string {
  // Only one of the toolCalls messages tends to have the name. We find it here
  const names = new Set(
    (toolCalls ?? []).map((c) => c?.function?.name).filter((v): v is string => !!v)
  )
  return [...names].join('')
}

function isJson(value: string): boolean {
  try {
    JSON.parse(value)
    return true
  } catch {
    return false
  }
}

function parseJson(value: string): object | string {
  try {
    return JSON.parse(value)
  } catch {
    return value
  }
}

function extractSources(sources: Source[] | undefined) {
  if (!sources) return []
  return sources.map((source) => source.toolCallDescription)
}
function toggleMessage(index: number) {
  showMessages.value[index] = !showMessages.value[index]
}

// TODO: this resets the collapsed message when we search
// it would be better if this were not done with index but key
watch(
  () => props.displayNode,
  () => {
    showMessages.value = []
  },
  { deep: true }
)
</script>
<style scoped>
.panel-header {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  padding: 0.25rem;
  border-top-width: 1px;
  border-top-style: solid;
  border-top-color: var(--color-border);
  background-color: var(--color-muted);

  font-family: var(--font-sans, sans-serif);
  font-size: 1rem;
  font-weight: 500;
  line-height: 1.5rem;
}
.expand-toggle {
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 0.375rem;
  background-color: #ffffff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  margin-left: 0.5rem;
}
.message-toggle {
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 0.375rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  margin-left: 0.5rem;
}
.format-avatar {
  width: 6rem;
  height: 1.5rem;
  font-weight: 500;
}
/* some values taken from Answers resource/frontend/src/components/QuestionCard.vue */
.format-card {
  max-width: 95%;
  align-self: flex-end;
  word-wrap: break-word;
  font-family: Verdana;
  margin: 16px;
  margin-bottom: 0px;
  margin-top: 8px;
  padding: 8px;
  gap: 8px;
}
.format-card:hover {
  background-color: hsl(var(--accent));
}
</style>
