<template>
  <div class="flex-1 min-h-0 flex flex-col bg-gray-100">
    <div class="shrink-0 flex items-center gap-2 text-xs text-gray-400 px-3 py-2 border-b border-gray-100" data-testid="sidebar-role-header">
      <span v-if="sessionRoleIcon" class="material-icons text-xs leading-none">{{ sessionRoleIcon }}</span>
      <span v-if="sessionRoleName" class="truncate">{{ sessionRoleName }}</span>
      <div class="ml-auto flex items-center gap-0.5 shrink-0">
        <CopyChatButton :results="results" :result-timestamps="resultTimestamps" :session-role-name="sessionRoleName" />
        <button
          type="button"
          class="h-8 w-8 flex items-center justify-center rounded text-gray-400 hover:text-gray-700 hover:bg-gray-100 transition-colors"
          :class="{ '!text-blue-500': showRightSidebar }"
          :title="t('sidebarHeader.toolCallHistory')"
          :aria-label="t('sidebarHeader.toolCallHistory')"
          :aria-pressed="showRightSidebar"
          @click="emit('toggle-right-sidebar')"
        >
          <span class="material-icons text-lg" aria-hidden="true">build</span>
        </button>
        <CanvasViewToggle :model-value="layoutMode" @update:model-value="(mode) => emit('update:layoutMode', mode)" />
      </div>
    </div>
    <!-- `relative` so the "new messages" affordance can sit over the scroll
         area without joining its scrolling content. -->
    <div class="relative flex-1 min-h-0 flex flex-col">
      <button
        v-if="hasNewMessages"
        type="button"
        class="absolute bottom-3 left-1/2 -translate-x-1/2 z-10 flex items-center gap-1 rounded-full bg-blue-600 px-3 py-1.5 text-xs font-medium text-white shadow-lg hover:bg-blue-700 transition-colors"
        data-testid="chat-new-messages"
        @click="emit('jump-to-latest')"
      >
        {{ t("sidebarHeader.newMessages") }}
        <span class="material-icons text-sm leading-none" aria-hidden="true">arrow_downward</span>
      </button>
      <div
        ref="root"
        class="flex-1 min-h-0 overflow-y-auto p-2 space-y-2 outline-none"
        tabindex="0"
        data-testid="tool-results-scroll"
        @mousedown="emit('activate')"
      >
        <div
          v-for="result in results"
          :key="result.uuid"
          class="relative cursor-pointer rounded border border-gray-300 text-sm text-gray-900 hover:opacity-75 transition-opacity"
          :class="result.uuid === selectedUuid ? 'ring-2 ring-blue-500' : ''"
          @click="emit('select', result.uuid)"
        >
          <span class="absolute top-0 left-2 -translate-y-1/2 bg-gray-100 px-1 text-[10px] text-gray-400 leading-none pointer-events-none">
            {{ sourceLabel(result) }}
          </span>
          <span
            v-if="resultTimestamps.get(result.uuid)"
            class="absolute top-0 right-2 -translate-y-1/2 bg-gray-100 px-1 text-[10px] text-gray-400 leading-none pointer-events-none"
          >
            {{ formatSmartTime(resultTimestamps.get(result.uuid)!) }}
          </span>
          <component :is="getPlugin(result.toolName)?.previewComponent" v-if="getPlugin(result.toolName)?.previewComponent" :result="result" />
          <span v-else class="block truncate p-2">{{ result.title || result.toolName }}</span>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import type { ToolResultComplete } from "gui-chat-protocol/vue";
import { getPlugin } from "../tools";
import { formatSmartTime } from "../utils/format/date";
import { isRecord } from "../utils/types";
import CanvasViewToggle from "./CanvasViewToggle.vue";
import CopyChatButton from "./CopyChatButton.vue";
import type { LayoutMode } from "../utils/canvas/layoutMode";

const { t } = useI18n();

defineProps<{
  // Already filtered to "what the user should see" by the parent's
  // `sidebarResults` computed (see useSessionDerived.ts) — keyboard
  // navigation, selection, and this render all consume the same
  // visible-only list so a hidden result can't slip into selected
  // state and leave the sidebar showing no highlight.
  results: ToolResultComplete[];
  selectedUuid: string | null;
  resultTimestamps: Map<string, number>;
  sessionRoleName?: string | undefined;
  sessionRoleIcon?: string | undefined;
  layoutMode: LayoutMode;
  showRightSidebar: boolean;
  /** Output arrived while the reader was scrolled away from the bottom.
   *  Drives the "new messages" affordance — deliberately NOT just "scrolled
   *  up", which would claim new output when the reader is only re-reading. */
  hasNewMessages?: boolean;
}>();

function sourceLabel(result: ToolResultComplete): string {
  if (result.toolName === "text-response") return result.title ?? "Assistant";
  // `action` lives on the persisted tool-result (see #670b40a5
  // `feat(sidebar): use ToolResult.action for multi-feature labels`)
  // but is not yet declared on `ToolResultComplete` in
  // `gui-chat-protocol`, so it is read off the record surface.
  const action = isRecord(result) ? result.action : undefined;
  return typeof action === "string" && action.length > 0 ? `${result.toolName}(${action})` : result.toolName;
}

const emit = defineEmits<{
  select: [uuid: string];
  activate: [];
  "update:layoutMode": [mode: LayoutMode];
  "toggle-right-sidebar": [];
  "jump-to-latest": [];
}>();

const root = ref<HTMLDivElement | null>(null);
defineExpose({ root });
</script>

<style scoped>
/* Card click selects the result; rendered markdown links inside the preview must not navigate. */
:deep(a) {
  pointer-events: none;
  color: inherit;
  text-decoration: none;
}
</style>
