<script setup lang="ts">
// Responsibility: admin-only, local-state view of the YAML-gated hwlab.event.debug.v1 SSE stream.

import { computed, onBeforeUnmount, ref, watch } from "vue";
import { connectWorkbenchKafkaSseDebug, type WorkbenchKafkaDebugOffsetBarrier, type WorkbenchKafkaDebugReplayCounts, type WorkbenchKafkaDebugReplayResult, type WorkbenchKafkaSseDebugEvent, type WorkbenchKafkaSseDebugStream } from "@/api/workbench-debug";
import StatusBadge from "@/components/common/StatusBadge.vue";
import { applyWorkbenchIsolatedKafkaDebugEvent, createWorkbenchIsolatedKafkaDebugState } from "@/stores/workbench-isolated-kafka-debug";
import { classifyWorkbenchKafkaDebugLocalResult } from "@/stores/workbench-kafka-debug-local-result";
import { createRawHwlabIngressState, type RawHwlabIngressState } from "@/stores/workbench-raw-hwlab-ingress";
import WorkbenchMessageCard from "./WorkbenchMessageCard.vue";

const props = withDefaults(defineProps<{
  traceId: string | null;
  replayEnabled?: boolean;
  rawEnabled?: boolean;
  rawIngress?: RawHwlabIngressState;
  onClearRawIngress?: () => void;
  replayId?: string | null;
  outputBarrier?: WorkbenchKafkaDebugOffsetBarrier | null;
}>(), {
  replayEnabled: true,
  rawEnabled: false,
  rawIngress: () => createRawHwlabIngressState(),
  onClearRawIngress: () => undefined,
  replayId: null,
  outputBarrier: null
});
type InspectorTab = "trace" | "raw";
const state = ref(createWorkbenchIsolatedKafkaDebugState());
const connection = ref<WorkbenchKafkaSseDebugEvent | null>(null);
const status = ref<"idle" | "connecting" | "replaying" | "completed" | "incomplete" | "error" | "closed">("idle");
const transportError = ref<string | null>(null);
const activeTab = ref<InspectorTab>(props.replayEnabled ? "trace" : "raw");
const clientReceived = ref(0);
const clientDecoded = ref(0);
const replayResult = ref<WorkbenchKafkaDebugReplayResult | null>(null);
let stream: WorkbenchKafkaSseDebugStream | null = null;

const badgeStatus = computed(() => status.value === "completed" ? "completed" : ["incomplete", "error"].includes(status.value) ? "failed" : "running");
const statusLabel = computed(() => status.value === "completed" ? "重放完成" : status.value === "incomplete" ? "重放不完整" : status.value === "replaying" ? "重放中" : status.value === "idle" ? "等待重放" : `debug ${status.value}`);
const visibleError = computed(() => transportError.value ?? state.value.error);
const replayBusy = computed(() => ["connecting", "replaying"].includes(status.value));
const rawText = computed(() => props.rawIngress.rows.map((row) => row.rawText).join("\n"));
const replayIdLabel = computed(() => replayResult.value?.replayId ?? connection.value?.replayId ?? props.replayId ?? "-");
const outputBarrierLabel = computed(() => props.outputBarrier
  ? `${props.outputBarrier.partition}:${props.outputBarrier.firstOffset}-${props.outputBarrier.lastOffset}`
  : "-");
const serverCounts = computed<WorkbenchKafkaDebugReplayCounts>(() => replayResult.value?.counts ?? {});

watch(() => props.traceId, resetForTrace);
watch(() => [props.replayEnabled, props.rawEnabled] as const, ([replayEnabled, rawEnabled]) => {
  if (activeTab.value === "trace" && !replayEnabled && rawEnabled) activeTab.value = "raw";
  if (activeTab.value === "raw" && !rawEnabled && replayEnabled) activeTab.value = "trace";
}, { immediate: true });
onBeforeUnmount(closeStream);

function replayCurrentTrace(): void {
  if (!props.replayEnabled) return;
  closeStream(false);
  state.value = createWorkbenchIsolatedKafkaDebugState();
  connection.value = null;
  transportError.value = null;
  clientReceived.value = 0;
  clientDecoded.value = 0;
  replayResult.value = null;
  const traceId = cleanTraceId(props.traceId);
  if (!traceId) {
    status.value = "idle";
    return;
  }
  status.value = "connecting";
  stream = connectWorkbenchKafkaSseDebug({
    stream: "hwlab-debug",
    traceId,
    fromBeginning: true,
    replayId: props.replayId,
    outputBarrier: props.outputBarrier,
    onIngress: (frame) => {
      if (frame.eventName !== "hwlab.kafka.event") return;
      clientReceived.value += 1;
      if (frame.decodeStatus === "accepted") clientDecoded.value += 1;
    },
    onOpen: () => { status.value = "replaying"; },
    onError: () => {
      if (["completed", "incomplete"].includes(status.value)) return;
      status.value = "error";
      transportError.value ||= "隔离 Kafka debug 重放连接失败";
    },
    onEvent: (event, eventName) => {
      if (eventName === "hwlab.kafka.connected") {
        if (event.debugIsolation !== true || event.deliverySemantics !== "debug-replay" || event.liveOnly !== false || event.replay !== true) {
          status.value = "error";
          transportError.value = "后端未声明 debug-replay 隔离契约";
          return;
        }
        connection.value = event;
        transportError.value = null;
        return;
      }
      if (eventName === "hwlab.kafka.error") {
        status.value = "error";
        transportError.value = event.error?.message || "隔离 Kafka debug SSE 返回错误";
        return;
      }
      if (eventName === "hwlab.kafka.replay-complete") {
        stream?.close();
        stream = null;
        const finalResult = event.result
          ? classifyWorkbenchKafkaDebugLocalResult(event.result, {
              clientReceived: clientReceived.value,
              decoded: clientDecoded.value,
              applied: state.value.appliedCount
            })
          : null;
        replayResult.value = finalResult;
        const complete = finalResult
          ? finalResult.code === "terminal_complete" && finalResult.terminalObserved === true && finalResult.ok !== false
          : event.reason === "terminal" && event.terminalObserved === true;
        status.value = complete ? "completed" : "incomplete";
        transportError.value = complete
          ? null
          : finalResult?.code
            ? `${finalResult.code}: ${finalResult.message || "隔离重放未完成"}`
            : `重放未观测到 terminal（${event.reason || "unknown"}）`;
        return;
      }
      if (eventName !== "hwlab.kafka.event") return;
      state.value = applyWorkbenchIsolatedKafkaDebugEvent(state.value, event, traceId);
    }
  });
  if (!stream) {
    status.value = "error";
    transportError.value = "当前浏览器不支持 EventSource";
  }
}

function closeStream(updateStatus = true): void {
  stream?.close();
  stream = null;
  if (updateStatus && status.value !== "idle") status.value = "closed";
}

function clearPanel(): void {
  closeStream(false);
  state.value = createWorkbenchIsolatedKafkaDebugState();
  connection.value = null;
  transportError.value = null;
  clientReceived.value = 0;
  clientDecoded.value = 0;
  replayResult.value = null;
  status.value = "idle";
}

function resetForTrace(): void {
  clearPanel();
}

function cleanTraceId(value: string | null): string | null {
  const text = typeof value === "string" ? value.trim() : "";
  return text || null;
}
</script>

<template>
  <section class="isolated-kafka-debug" data-debug-isolated="true" data-testid="workbench-kafka-debug-panel" :data-replay-status="status" aria-label="Workbench Kafka 隔离调试面板">
    <header class="isolated-debug-header">
      <div class="isolated-debug-title">
        <strong>Kafka 卡片隔离调试</strong>
        <span>隔离重放与产品 SSE 原始事件共用一个只读调试卡片</span>
      </div>
      <div class="isolated-debug-actions">
        <span v-if="activeTab === 'trace'" data-testid="workbench-kafka-debug-status"><StatusBadge :status="badgeStatus" :label="statusLabel" /></span>
        <span v-else class="raw-ingress-status" data-testid="workbench-raw-hwlab-status">{{ rawIngress.receivedCount }} frames</span>
        <button v-if="activeTab === 'trace' && replayEnabled" class="btn btn-sm btn-primary" data-testid="workbench-kafka-debug-replay" type="button" :disabled="!traceId || replayBusy" @click="replayCurrentTrace">重放</button>
        <button v-if="activeTab === 'trace'" class="btn btn-sm btn-secondary" data-testid="workbench-kafka-debug-clear" type="button" @click="clearPanel">清空隔离面板</button>
        <button v-else class="btn btn-sm btn-secondary" data-testid="workbench-raw-hwlab-clear" type="button" @click="onClearRawIngress">清空观察窗</button>
      </div>
    </header>

    <nav class="isolated-debug-tabs" aria-label="Kafka 调试观察模式">
      <button v-if="replayEnabled" type="button" data-testid="workbench-kafka-debug-tab-trace" :aria-selected="activeTab === 'trace'" @click="activeTab = 'trace'">Trace 卡片</button>
      <button v-if="rawEnabled" type="button" data-testid="workbench-kafka-debug-tab-raw" :aria-selected="activeTab === 'raw'" @click="activeTab = 'raw'">原始 HWLAB Event</button>
    </nav>

    <div v-if="activeTab === 'trace'" class="isolated-debug-contract" data-testid="workbench-isolated-debug-contract">
      <span><b>traceId</b> <code>{{ traceId || "等待当前 turn" }}</code></span>
      <span><b>topic</b> <code>{{ connection?.topic || "等待 consumer-ready" }}</code></span>
      <span><b>group</b> <code>{{ connection?.groupId || "-" }}</code></span>
      <span><b>replayId</b> <code>{{ replayIdLabel }}</code></span>
      <span><b>barrier</b> <code>{{ outputBarrierLabel }}</code></span>
      <span><b>delivery</b> debug-replay · fromBeginning=true</span>
    </div>
    <div v-else class="isolated-debug-contract" data-testid="workbench-raw-hwlab-contract">
      <span><b>source</b> <code>现有产品 EventSource</code></span>
      <span><b>scope</b> <code>{{ rawIngress.scopeKey || "等待产品 SSE scope" }}</code></span>
      <span><b>delivery</b> hwlab.event.v1 · memory-only · no client event filter</span>
    </div>

    <p v-if="activeTab === 'trace' && visibleError" class="isolated-debug-error">{{ visibleError }}</p>

    <div v-if="activeTab === 'trace'" class="isolated-debug-contract" data-testid="workbench-kafka-debug-layer-counts">
      <span><b>server</b> scanned={{ serverCounts.scanned ?? "-" }} · matched={{ serverCounts.matched ?? "-" }} · delivered={{ serverCounts.delivered ?? "-" }}</span>
      <span><b>client local overlay</b> received={{ clientReceived }} · decoded={{ clientDecoded }} · applied={{ state.appliedCount }}</span>
    </div>

    <div v-if="activeTab === 'trace'" class="isolated-debug-body">
      <div class="isolated-debug-card">
        <WorkbenchMessageCard
          v-if="state.message"
          :message="state.message"
          :detail-enabled="false"
          :trace-auto-expanded="true"
          storage-key-prefix="hwlab.workbench.isolated-kafka-debug.trace-open"
        />
        <div v-else class="isolated-debug-empty">
          {{ traceId ? "点击“重放”读取此 trace 已存在的隔离 debug 事件。" : "提交或选择含 traceId 的 turn 后再重放。" }}
        </div>
      </div>

      <aside class="isolated-debug-log" aria-label="隔离 reducer 日志">
        <header>
          <strong>Production reducer</strong>
          <span data-testid="workbench-kafka-debug-counts">{{ state.appliedCount }}/{{ state.receivedCount }} applied</span>
        </header>
        <ol v-if="state.logs.length > 0">
          <li v-for="entry in state.logs" :key="entry.id" :data-accepted="entry.accepted ? 'true' : 'false'">
            <code>{{ entry.eventType }}</code>
            <span>{{ entry.actionType }}</span>
            <small>{{ entry.stepTypes.join(", ") || entry.reason || "no-op" }}</small>
          </li>
        </ol>
        <div v-else class="isolated-debug-empty compact">尚未收到 debug event。</div>
      </aside>
    </div>
    <div v-else class="raw-hwlab-event-window">
      <header>
        <strong>产品 SSE 原始帧</strong>
        <span data-testid="workbench-raw-hwlab-counts">
          received={{ rawIngress.receivedCount }} · retained={{ rawIngress.rows.length }} · decoded={{ rawIngress.decodedCount }} · rejected={{ rawIngress.rejectedCount }} · evicted={{ rawIngress.evictedCount }} · bytes={{ rawIngress.retainedBytes }}
        </span>
      </header>
      <textarea
        data-testid="workbench-raw-hwlab-text"
        readonly
        spellcheck="false"
        :value="rawText"
        :placeholder="rawEnabled ? '等待现有产品 SSE 收到 hwlab.event.v1' : '原始 HWLAB Event capability 已关闭'"
      />
    </div>
  </section>
</template>

<style scoped>
.isolated-kafka-debug {
  display: grid;
  min-width: 0;
  min-height: 0;
  grid-template-rows: auto auto auto minmax(0, 1fr);
  gap: 8px;
  overflow: hidden;
  border-top: 2px solid #7c3aed;
  background: #f7f5ff;
  padding: 10px;
}

.isolated-debug-header,
.isolated-debug-actions,
.isolated-debug-contract,
.isolated-debug-log header,
.raw-hwlab-event-window header {
  display: flex;
  align-items: center;
}

.isolated-debug-header {
  justify-content: space-between;
  gap: 12px;
}

.isolated-debug-title {
  display: grid;
  gap: 2px;
  color: #2e1065;
}

.isolated-debug-title span,
.isolated-debug-log header span {
  color: #6d28d9;
  font-size: 11px;
}

.isolated-debug-actions {
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 6px;
}

.raw-ingress-status {
  color: #6d28d9;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 11px;
}

.isolated-debug-tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid #ddd6fe;
}

.isolated-debug-tabs button {
  border: 0;
  border-bottom: 2px solid transparent;
  background: transparent;
  padding: 5px 9px;
  color: #6d28d9;
  cursor: pointer;
  font-size: 12px;
  font-weight: 700;
}

.isolated-debug-tabs button[aria-selected="true"] {
  border-bottom-color: #7c3aed;
  color: #2e1065;
}

.isolated-debug-contract {
  min-width: 0;
  flex-wrap: wrap;
  gap: 6px 14px;
  color: #5b21b6;
  font-size: 11px;
}

.isolated-debug-contract span {
  min-width: 0;
}

.isolated-debug-contract code {
  overflow-wrap: anywhere;
}

.isolated-debug-error,
.isolated-debug-empty {
  margin: 0;
  border: 1px solid #ddd6fe;
  border-radius: 7px;
  background: white;
  padding: 9px;
  color: #6b7280;
  font-size: 12px;
}

.isolated-debug-error {
  border-color: #fecaca;
  background: #fff1f2;
  color: #9f1239;
}

.isolated-debug-body {
  display: grid;
  min-width: 0;
  min-height: 0;
  grid-template-columns: minmax(0, 1fr) minmax(240px, 0.4fr);
  gap: 8px;
}

.isolated-debug-card,
.isolated-debug-log {
  min-width: 0;
  min-height: 0;
  overflow: auto;
}

.isolated-debug-log {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  gap: 6px;
  border: 1px solid #ddd6fe;
  border-radius: 7px;
  background: white;
  padding: 8px;
}

.isolated-debug-log header {
  justify-content: space-between;
  gap: 8px;
  color: #4c1d95;
  font-size: 12px;
}

.isolated-debug-log ol {
  display: grid;
  align-content: start;
  gap: 5px;
  min-height: 0;
  margin: 0;
  overflow: auto;
  padding: 0;
  list-style: none;
}

.isolated-debug-log li {
  display: grid;
  grid-template-columns: minmax(78px, 0.65fr) minmax(78px, 0.65fr) minmax(0, 1fr);
  gap: 6px;
  border-left: 3px solid #7c3aed;
  background: #fafafa;
  padding: 6px;
  color: #374151;
  font-size: 10px;
}

.isolated-debug-log li[data-accepted="false"] {
  border-left-color: #e11d48;
}

.isolated-debug-log small {
  overflow: hidden;
  color: #6b7280;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.isolated-debug-empty.compact {
  padding: 7px;
}

.raw-hwlab-event-window {
  display: grid;
  min-width: 0;
  min-height: 0;
  grid-template-rows: auto minmax(0, 1fr);
  gap: 6px;
}

.raw-hwlab-event-window header {
  justify-content: space-between;
  gap: 8px;
  color: #4c1d95;
  font-size: 12px;
}

.raw-hwlab-event-window header span {
  color: #6d28d9;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 10px;
}

.raw-hwlab-event-window textarea {
  width: 100%;
  min-height: 0;
  resize: none;
  border: 1px solid #ddd6fe;
  border-radius: 7px;
  background: #111827;
  padding: 9px;
  color: #e5e7eb;
  font: 11px/1.5 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  white-space: pre;
}

@media (max-width: 900px) {
  .isolated-debug-body {
    grid-template-columns: minmax(0, 1fr);
  }

  .isolated-debug-log {
    max-height: 160px;
  }
}
</style>
