<script setup lang="ts">
import type {
  ChatDocumentAttachment,
  ChatDocumentLoader,
} from '@easyflow/common-ui';

import type { WorkflowFinalOutputSection } from './workflowFinalOutput';

import { computed, ref } from 'vue';

import {
  ChatDocumentAttachments,
  ChatImageAttachments,
  ChatTextBlock,
} from '@easyflow/common-ui';

import { ElButton } from 'element-plus';

import { copyTextWithFeedback } from '#/utils/clipboard-feedback';

import {
  buildWorkflowFinalOutputView,
  formatWorkflowOutputPreview,
  sanitizeWorkflowOutputTextForDisplay,
  serializeWorkflowOutput,
} from './workflowFinalOutput';

const props = defineProps<{
  output: unknown;
}>();

const view = computed(() => buildWorkflowFinalOutputView(props.output));
const hasMultipleSections = computed(() => view.value.sections.length > 1);
const expandedKeys = ref<Set<string>>(new Set());
const copyingKey = ref('');
const COLLAPSED_ITEM_LIMIT = 12;
const EXPANDED_ITEM_LIMIT = 100;
const COLLAPSED_TEXT_LIMIT = 4000;

function isExpanded(key: string) {
  return expandedKeys.value.has(key);
}

function toggleExpanded(key: string) {
  const next = new Set(expandedKeys.value);
  if (next.has(key)) {
    next.delete(key);
  } else {
    next.add(key);
  }
  expandedKeys.value = next;
}

function visibleLimit(section: WorkflowFinalOutputSection) {
  return isExpanded(section.key) ? EXPANDED_ITEM_LIMIT : COLLAPSED_ITEM_LIMIT;
}

function visibleListItems(section: WorkflowFinalOutputSection) {
  return section.listItems
    .slice(0, visibleLimit(section))
    .map((text, index) => ({ index, text }));
}

function visibleTableRows(section: WorkflowFinalOutputSection) {
  return section.tableRows.slice(0, visibleLimit(section));
}

function visibleObjectEntries(section: WorkflowFinalOutputSection) {
  if (
    !section.rawValue ||
    typeof section.rawValue !== 'object' ||
    Array.isArray(section.rawValue)
  ) {
    return [];
  }
  return Object.entries(section.rawValue).slice(0, visibleLimit(section));
}

function displayedScalar(section: WorkflowFinalOutputSection) {
  if (
    typeof section.rawValue !== 'string' ||
    isExpanded(section.key) ||
    section.scalarText.length <= COLLAPSED_TEXT_LIMIT
  ) {
    return section.scalarText;
  }
  return `${section.scalarText.slice(0, COLLAPSED_TEXT_LIMIT)}…`;
}

function hasMore(section: WorkflowFinalOutputSection) {
  if (section.kind === 'scalar') {
    return (
      typeof section.rawValue === 'string' &&
      section.scalarText.length > COLLAPSED_TEXT_LIMIT
    );
  }
  if (section.kind === 'list') {
    return section.listItems.length > COLLAPSED_ITEM_LIMIT;
  }
  if (section.kind === 'table') {
    return section.tableRows.length > COLLAPSED_ITEM_LIMIT;
  }
  if (section.kind === 'key-value') {
    return (
      section.rawValue !== null &&
      typeof section.rawValue === 'object' &&
      !Array.isArray(section.rawValue) &&
      Object.keys(section.rawValue).length > COLLAPSED_ITEM_LIMIT
    );
  }
  return (
    section.kind === 'json' &&
    formatWorkflowOutputPreview(section.rawValue, false) !==
      formatWorkflowOutputPreview(section.rawValue, true)
  );
}

function cellText(value: unknown) {
  if (value === null) {
    return '空值';
  }
  if (typeof value === 'string') {
    return sanitizeWorkflowOutputTextForDisplay(value);
  }
  return String(value);
}

function sectionMeta(section: WorkflowFinalOutputSection) {
  if (section.kind === 'images') {
    return `图片 · ${section.images.length} 项`;
  }
  if (section.kind === 'documents') {
    return `文件 · ${section.documents.length} 项`;
  }
  if (section.kind === 'media') {
    return `附件 · ${section.images.length + section.documents.length} 项`;
  }
  if (Array.isArray(section.rawValue)) {
    return `数组 · ${section.rawValue.length} 项`;
  }
  if (section.rawValue !== null && typeof section.rawValue === 'object') {
    return `对象 · ${Object.keys(section.rawValue).length} 项`;
  }
  return '';
}

async function copyOutput(key: string, value: unknown) {
  if (copyingKey.value) {
    return;
  }
  copyingKey.value = key;
  try {
    await copyTextWithFeedback(
      serializeWorkflowOutput(value),
      '已复制',
      '复制失败',
    );
  } finally {
    copyingKey.value = '';
  }
}

const loadDocument: ChatDocumentLoader = async (
  document: ChatDocumentAttachment,
) => {
  const downloadUrl = String(document.downloadUrl || '').trim();
  if (!downloadUrl) {
    throw new Error('文件缺少下载地址');
  }
  const opened = window.open(downloadUrl, '_blank', 'noopener,noreferrer');
  if (!opened) {
    throw new Error('浏览器阻止了文件下载，请允许打开新窗口后重试');
  }
};
</script>

<template>
  <article class="workflow-final-output" aria-label="工作流运行结果">
    <header class="workflow-final-output__header">
      <h2>运行结果</h2>
      <ElButton
        v-if="!view.empty"
        text
        size="small"
        :loading="copyingKey === '__all__'"
        :disabled="Boolean(copyingKey)"
        @click="copyOutput('__all__', output)"
      >
        {{ hasMultipleSections ? '复制全部' : '复制结果' }}
      </ElButton>
    </header>

    <p v-if="view.empty" class="workflow-final-output__empty">
      运行完成，暂无输出
    </p>

    <section
      v-for="section in view.sections"
      v-else
      :key="section.key"
      class="workflow-final-output__section"
    >
      <div class="workflow-final-output__section-heading">
        <div class="workflow-final-output__section-title">
          <h3>{{ section.label }}</h3>
          <span
            v-if="sectionMeta(section)"
            class="workflow-final-output__section-meta"
          >
            {{ sectionMeta(section) }}
          </span>
        </div>
        <ElButton
          v-if="hasMultipleSections"
          text
          size="small"
          :aria-label="`复制 ${section.label}`"
          :loading="copyingKey === section.key"
          :disabled="Boolean(copyingKey)"
          @click="copyOutput(section.key, section.rawValue)"
        >
          复制
        </ElButton>
      </div>

      <ChatImageAttachments
        v-if="section.images.length > 0"
        :items="section.images"
        compact
      />
      <ChatDocumentAttachments
        v-if="section.documents.length > 0"
        :items="section.documents"
        :document-loader="loadDocument"
        compact
      />

      <ChatTextBlock
        v-if="section.kind === 'scalar'"
        class="workflow-final-output__text"
        :content="displayedScalar(section)"
      />

      <ol
        v-else-if="section.kind === 'list'"
        class="workflow-final-output__list"
        :aria-label="`${section.label} 数组，共 ${section.listItems.length} 项`"
      >
        <li
          v-for="item in visibleListItems(section)"
          :key="`${section.key}-${item.index}`"
          class="workflow-final-output__list-item"
        >
          <span class="workflow-final-output__list-index" aria-hidden="true">
            {{ String(item.index + 1).padStart(2, '0') }}
          </span>
          <span class="workflow-final-output__list-content">
            {{ item.text }}
          </span>
        </li>
        <li
          v-if="section.listItems.length === 0"
          class="workflow-final-output__list-empty"
        >
          空数组
        </li>
      </ol>

      <div
        v-else-if="section.kind === 'table'"
        class="workflow-final-output__table-wrap"
      >
        <table class="workflow-final-output__table">
          <thead>
            <tr>
              <th v-for="column in section.tableColumns" :key="column">
                {{ column }}
              </th>
            </tr>
          </thead>
          <tbody>
            <tr
              v-for="(row, rowIndex) in visibleTableRows(section)"
              :key="`${section.key}-row-${rowIndex}`"
            >
              <td v-for="column in section.tableColumns" :key="column">
                {{ cellText(row[column]) }}
              </td>
            </tr>
          </tbody>
        </table>
      </div>

      <dl
        v-else-if="section.kind === 'key-value'"
        class="workflow-final-output__key-values"
      >
        <template
          v-for="entry in visibleObjectEntries(section)"
          :key="entry[0]"
        >
          <dt>{{ entry[0] }}</dt>
          <dd>{{ cellText(entry[1]) }}</dd>
        </template>
      </dl>

      <pre
        v-else-if="section.kind === 'json'"
        class="workflow-final-output__json"
      ><code>{{ formatWorkflowOutputPreview(section.rawValue, isExpanded(section.key)) }}</code></pre>

      <div v-if="hasMore(section)" class="workflow-final-output__expand">
        <ElButton text type="primary" @click="toggleExpanded(section.key)">
          {{ isExpanded(section.key) ? '收起' : '展开更多' }}
        </ElButton>
      </div>
    </section>
  </article>
</template>

<style scoped>
.workflow-final-output {
  width: min(100%, 760px);
  padding: var(--space-4) 0;
}

.workflow-final-output__header,
.workflow-final-output__section-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.workflow-final-output__header {
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--el-border-color-lighter);
}

.workflow-final-output__header h2,
.workflow-final-output__section-heading h3 {
  margin: 0;
  color: var(--el-text-color-primary);
}

.workflow-final-output__header h2 {
  font-size: 16px;
  font-weight: 600;
  line-height: 24px;
}

.workflow-final-output__section {
  padding-top: var(--space-4);
}

.workflow-final-output__section + .workflow-final-output__section {
  margin-top: var(--space-2);
  border-top: 1px solid var(--el-border-color-lighter);
}

.workflow-final-output__section-heading {
  min-height: 32px;
  margin-bottom: var(--space-2);
}

.workflow-final-output__section-title {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  min-width: 0;
}

.workflow-final-output__section-heading h3 {
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 14px;
  font-weight: 600;
  line-height: 22px;
  white-space: nowrap;
}

.workflow-final-output__section-meta {
  flex: none;
  padding: 0 var(--space-2);
  font-size: 12px;
  font-weight: 500;
  line-height: 24px;
  color: var(--el-text-color-secondary);
  background: var(--el-fill-color-light);
  border-radius: var(--radius-control);
}

.workflow-final-output__empty {
  margin: var(--space-4) 0 0;
  font-size: 14px;
  line-height: 22px;
  color: var(--el-text-color-secondary);
}

.workflow-final-output__text {
  color: var(--el-text-color-primary);
}

.workflow-final-output__list {
  padding: 0;
  margin: 0;
  overflow: hidden;
  list-style: none;
  background: var(--el-bg-color);
  border: 1px solid var(--el-border-color-lighter);
  border-radius: var(--radius-control);
}

.workflow-final-output__list-item {
  display: grid;
  grid-template-columns: var(--space-8) minmax(0, 1fr);
  gap: var(--space-3);
  align-items: start;
  padding: var(--space-3) var(--space-4);
}

.workflow-final-output__list-item + .workflow-final-output__list-item {
  border-top: 1px solid var(--el-border-color-lighter);
}

.workflow-final-output__list-index {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--space-8);
  height: var(--space-6);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: var(--el-text-color-secondary);
  background: var(--el-fill-color-light);
  border-radius: var(--radius-control);
}

.workflow-final-output__list-content {
  min-width: 0;
  font-size: 14px;
  line-height: 24px;
  color: var(--el-text-color-primary);
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}

.workflow-final-output__list-empty {
  padding: var(--space-4);
  font-size: 13px;
  line-height: 20px;
  color: var(--el-text-color-secondary);
  text-align: center;
}

.workflow-final-output__table-wrap,
.workflow-final-output__json {
  max-height: 360px;
  overflow: auto;
  border: 1px solid var(--el-border-color-lighter);
  border-radius: var(--radius-control);
}

.workflow-final-output__table {
  width: 100%;
  min-width: 480px;
  font-size: 13px;
  line-height: 20px;
  border-collapse: collapse;
}

.workflow-final-output__table th,
.workflow-final-output__table td {
  padding: var(--space-2) var(--space-3);
  vertical-align: top;
  text-align: left;
  border-bottom: 1px solid var(--el-border-color-lighter);
}

.workflow-final-output__table th {
  position: sticky;
  top: 0;
  z-index: 1;
  font-weight: 600;
  color: var(--el-text-color-secondary);
  background: var(--el-fill-color-light);
}

.workflow-final-output__table tbody tr:last-child td {
  border-bottom: 0;
}

.workflow-final-output__key-values {
  display: grid;
  grid-template-columns: minmax(96px, 0.32fr) minmax(0, 1fr);
  margin: 0;
  font-size: 13px;
  line-height: 20px;
}

.workflow-final-output__key-values dt,
.workflow-final-output__key-values dd {
  padding: var(--space-2) 0;
  margin: 0;
  border-bottom: 1px solid var(--el-border-color-lighter);
}

.workflow-final-output__key-values dt {
  padding-right: var(--space-4);
  color: var(--el-text-color-secondary);
}

.workflow-final-output__key-values dd {
  color: var(--el-text-color-primary);
  overflow-wrap: anywhere;
}

.workflow-final-output__json {
  padding: var(--space-3);
  margin: 0;
  font-family: var(--el-font-family-monospace, monospace);
  font-size: 12px;
  line-height: 20px;
  color: var(--el-text-color-primary);
  white-space: pre-wrap;
  background: var(--el-fill-color-light);
}

.workflow-final-output__expand {
  margin-top: var(--space-1);
}

@media (max-width: 720px) {
  .workflow-final-output__key-values {
    grid-template-columns: 1fr;
  }

  .workflow-final-output__key-values dt {
    padding-bottom: 0;
    border-bottom: 0;
  }
}
</style>
