<script setup lang="ts">
// SPEC: PJ2026-0106050514 Workbench实时运行面 draft-2026-06-30-p0-1297-spec-first; PJ2026-01060505 Workbench Performance draft-2026-06-17-p0; PJ2026-0104010803 唯一投影 draft-2026-06-19-p2-terminal-sealed-final-response; PJ2026-010401 Web工作台 draft-2026-06-20-p1-view-local-timing-ticker.
// Confirms Workbench message visibility after Vue render and keeps sealed final responses separate from diagnostics.

import { computed, nextTick, onMounted, ref, watch } from "vue";
import type { ChatMessage } from "@/types";
import ApiErrorDiagnostic from "@/components/common/ApiErrorDiagnostic.vue";
import LoadingState from "@/components/common/LoadingState.vue";
import CodeAgentStatusSummary from "@/components/workbench/CodeAgentStatusSummary.vue";
import MessageActions from "@/components/workbench/MessageActions.vue";
import MessageTraceDebugPanel from "@/components/workbench/MessageTraceDebugPanel.vue";
import WorkbenchMessageCard from "@/components/workbench/WorkbenchMessageCard.vue";
import { useWorkbenchBottomFollowScroll } from "@/composables/useWorkbenchScrollRuntime";
import { useClipboard } from "@/composables/useClipboard";
import { useWorkbenchStore } from "@/stores/workbench";
import { buildWorkbenchTimelineRows, workbenchTimelineSignature } from "@/stores/workbench-timeline-model";
import { messageDiagnosticView } from "@/utils/workbench-error-runtime";
import { acknowledgeWorkbenchVisibleAfterPaint } from "@/utils/workbench-performance";
import { traceIdentityText } from "./message-rendering";

const workbench = useWorkbenchStore();
const clipboard = useClipboard();
const panelRef = ref<HTMLElement | null>(null);
const scrollSessionKey = computed(() => workbench.activeSessionId ?? "workbench");
const { following, keepBottomAfterUpdate, onScroll: onPanelScroll, scrollToBottom } = useWorkbenchBottomFollowScroll(panelRef, { threshold: 56, sessionKey: scrollSessionKey, tabKey: "conversation" });
const detailMessageId = ref<string | null>(null);
const visibleMessages = computed(() => workbench.activeMessages);
const visibleTimelineRows = computed(() => buildWorkbenchTimelineRows(visibleMessages.value));
const detailMessage = computed(() => workbench.activeMessages.find((message) => message.id === detailMessageId.value) ?? null);
const scrollSignature = computed(() => workbenchTimelineSignature(visibleTimelineRows.value));

watch(scrollSignature, async () => {
  await keepBottomAfterUpdate();
  acknowledgeVisibleMessages();
});

onMounted(async () => {
  await nextTick();
  scrollToBottom();
  acknowledgeVisibleMessages();
});

watch(() => workbench.sessionDetailLoading, async () => {
  await nextTick();
  acknowledgeVisibleMessages();
});

function openDetails(message: ChatMessage): void {
  detailMessageId.value = message.id;
}

function messageHasDiagnostic(message: ChatMessage): boolean {
  return diagnosticFor(message).visible;
}

function diagnosticFor(message: ChatMessage) {
  return messageDiagnosticView(message);
}

function acknowledgeVisibleMessages(): void {
  acknowledgeWorkbenchVisibleAfterPaint({ messages: workbench.activeMessages, activeSessionId: workbench.activeSessionId, detailLoading: workbench.sessionDetailLoading });
}
</script>

<template>
  <section id="conversation-list" ref="panelRef" class="conversation-panel" :data-following="following ? 'true' : 'false'" @scroll="onPanelScroll">
    <LoadingState v-if="workbench.sessionDetailLoadingProgress.blocking" class="conversation-detail-loading" :label="workbench.sessionDetailLoadingProgress.label" />
    <div v-else-if="workbench.sessionDetailLoadingProgress.active" class="conversation-load-progress" :data-phase="workbench.sessionDetailLoadingProgress.phase">
      <LoadingState :label="workbench.sessionDetailLoadingProgress.label" compact />
    </div>
    <template v-for="row in visibleTimelineRows" :key="row.key">
      <div v-if="row.type === 'TurnGap'" class="timeline-row timeline-turn-gap" aria-hidden="true" />
      <div v-else-if="row.type === 'Thinking'" class="timeline-row timeline-thinking" :data-session-id="row.sessionId || undefined" :data-trace-id="row.traceId || undefined">
        <LoadingState class="message-loading" :label="row.reasoningHeading || '思考中...'" compact />
      </div>
      <div v-else-if="row.type === 'Retry'" class="timeline-row timeline-retry" :data-session-id="row.sessionId || undefined" :data-trace-id="row.traceId || undefined">等待重试</div>
      <div v-else-if="row.type === 'Error'" class="timeline-row timeline-error" :data-session-id="row.sessionId || undefined" :data-trace-id="row.traceId || undefined">{{ row.text }}</div>
      <WorkbenchMessageCard v-else-if="row.message" :message="row.message" @details="openDetails" />
    </template>
    <div v-if="!workbench.sessionDetailLoadingProgress.blocking && workbench.activeMessages.length === 0 && workbench.error" class="conversation-empty-hint conversation-error-hint">加载失败：{{ workbench.error }}</div>
    <div v-else-if="!workbench.sessionDetailLoadingProgress.active && workbench.activeMessages.length === 0" class="conversation-empty-hint">发起对话，或从左侧选择 session。</div>
    <div v-if="detailMessage" class="workbench-dialog-backdrop" role="presentation" @click.self="detailMessageId = null">
      <section id="message-detail-dialog" class="workbench-dialog wide" role="dialog" aria-modal="true" aria-labelledby="message-detail-title">
        <header>
          <div class="dialog-title-stack">
            <h2 id="message-detail-title">消息详情</h2>
            <p>{{ detailMessage.traceId || detailMessage.runnerTrace?.traceId || 'trace 未观测' }}</p>
          </div>
          <button type="button" class="dialog-close" aria-label="关闭" @click="detailMessageId = null">x</button>
        </header>
        <CodeAgentStatusSummary :message="detailMessage" />
        <ApiErrorDiagnostic v-if="messageHasDiagnostic(detailMessage)" class="message-detail-diagnostic" title="诊断" :error="diagnosticFor(detailMessage).text" :api-error="diagnosticFor(detailMessage).apiError" :diagnostic="diagnosticFor(detailMessage).diagnostic" :show-message="diagnosticFor(detailMessage).showMessage" compact />
        <MessageTraceDebugPanel :message="detailMessage" />
        <MessageActions
          :message="detailMessage"
          @cancel="workbench.cancelAgentMessage"
          @retry="workbench.retryAgentMessage"
          @copy-trace="(target: ChatMessage) => clipboard.copy(traceIdentityText(target))"
        />
      </section>
    </div>
  </section>
</template>

<style scoped>
.conversation-load-progress {
  position: sticky;
  z-index: 2;
  top: 0;
  align-self: flex-end;
  border: 1px solid #bae6fd;
  border-radius: 3px;
  background: #f8fafc;
  padding: 5px 8px;
  box-shadow: 0 1px 2px rgb(15 23 42 / 8%);
}
</style>
