<template>
  <div class="page">
    <header class="top-bar">
      <div class="brand">
        <img :src="iconWebLogo" alt="webicon" class="logo" />
        <img :src="iconTitle" alt="星程桑基图" class="title-logo" />
      </div>

        <div class="toolbar">
        <div class="tool-item theme-trigger">
          <span class="tool-label">选择主题</span>
          <button class="icon-btn" type="button" @click="toggleThemePicker">
            <img :src="iconChooseColor" alt="choose-color" />
          </button>

          <div v-if="showThemePicker" class="theme-popover">
            <div class="theme-header">选择配色主题</div>
            <div class="theme-list">
              <button
                v-for="theme in themes"
                :key="theme.id"
                class="theme-row"
                type="button"
                @click="pickTheme(theme.id)"
              >
                <img :src="selectedThemeId === theme.id ? iconRadioOn : iconRadioOff" alt="主题选择" />
                <div class="palette">
                  <span
                    v-for="color in theme.colors"
                    :key="`${theme.id}-${color}`"
                    class="palette-cell"
                    :style="{ backgroundColor: color }"
                  />
                </div>
              </button>
            </div>
          </div>
        </div>

        <div class="tool-item">
          <span class="tool-label">文件上传</span>
          <button class="icon-btn" type="button" @click="openFileDialog">
            <img :src="iconUpload" alt="upload" />
          </button>
        </div>

        <label
          class="upload-area"
          @dragover.prevent
          @drop.prevent="onDropFile"
          @keydown.enter.prevent="openFileDialog"
          @click="closeThemePicker"
        >
          <input
            ref="fileInputRef"
            class="hidden-input"
            type="file"
            accept=".csv,.xls,.xlsx"
            @change="onFileChange"
          />
          <span class="upload-text">{{ uploadMessage }}</span>
        </label>

        <div class="export-box">
          <img :src="iconExport" alt="export" class="export-main" />
          <button class="icon-btn export-item" type="button" @click="exportSvg">
            <img :src="iconExportSvg" alt="export-svg" />
          </button>
          <button class="icon-btn export-item" type="button" @click="exportPng">
            <img :src="iconExportPng" alt="export-png" />
          </button>
        </div>
      </div>

    </header>

    <main class="content">
      <section class="left-pane">
        <article class="panel block-panel">
          <h2>源数据</h2>

          <div class="field-block">
            <div class="field-title-wrap">
              <img :src="iconExpand" alt="展开" class="expand-icon" />
              <h3>数据列</h3>
            </div>

            <div
              v-for="(header, index) in columnHeaders"
              :key="`source-data-${index}`"
              class="column-row"
            >
              <img :src="iconData" alt="数据列" class="column-icon" />
              <span class="column-label">{{ header }}</span>
              <button class="select-btn" type="button" @click="mapping.sourceDataColumn = index">
                <img
                  :src="mapping.sourceDataColumn === index ? iconRadioOn : iconRadioOff"
                  alt="单选"
                />
              </button>
            </div>
          </div>

          <div class="field-block">
            <div class="field-title-wrap">
              <img :src="iconExpand" alt="展开" class="expand-icon" />
              <h3>描述列</h3>
            </div>

            <div
              v-for="(header, index) in columnHeaders"
              :key="`source-desc-${index}`"
              class="column-row"
            >
              <img :src="iconDescription" alt="描述列" class="column-icon" />
              <span class="column-label">{{ header }}</span>
              <button class="select-btn" type="button" @click="toggleSourceDescription(index)">
                <img
                  :src="
                    mapping.sourceDescriptionColumns.includes(index) ? iconCheckboxOn : iconCheckboxOff
                  "
                  alt="复选"
                />
              </button>
            </div>
          </div>
        </article>

        <article class="panel block-panel">
          <h2>目标数据</h2>
          <div class="field-block">
            <div class="field-title-wrap">
              <img :src="iconExpand" alt="展开" class="expand-icon" />
              <h3>描述列</h3>
            </div>

            <div
              v-for="(header, index) in columnHeaders"
              :key="`target-desc-${index}`"
              class="column-row"
            >
              <img :src="iconDescription" alt="描述列" class="column-icon" />
              <span class="column-label">{{ header }}</span>
              <button class="select-btn" type="button" @click="toggleTargetDescription(index)">
                <img
                  :src="
                    mapping.targetDescriptionColumns.includes(index) ? iconCheckboxOn : iconCheckboxOff
                  "
                  alt="复选"
                />
              </button>
            </div>
          </div>
        </article>
      </section>

      <section class="panel preview-panel">
        <div class="preview-head">
          <h2>桑基图预览</h2>

          <div class="preview-controls">
            <label>
              方向
              <select v-model="direction">
                <option value="source-to-target">source -&gt; target</option>
                <option value="target-to-source">target -&gt; source</option>
              </select>
            </label>
            <label>
              间距
              <input v-model.number="nodeGap" type="range" min="8" max="42" />
            </label>
            <label>
              边距
              <input v-model.number="chartPadding" type="range" min="10" max="70" />
            </label>
          </div>
        </div>

        <div v-if="buildError" class="error-text">{{ buildError }}</div>
        <div v-if="parseError" class="error-text">{{ parseError }}</div>

        <div class="example-line">示例：{{ previewExample }}</div>

        <div ref="chartRef" class="chart-area" />

        <div v-if="buildWarnings.length > 0" class="warning-area">
          <strong>解析告警（前 8 条）</strong>
          <ul>
            <li v-for="warning in buildWarnings" :key="warning">{{ warning }}</li>
          </ul>
        </div>
      </section>
    </main>

    <footer class="footer">@版权说明：星程社所有，反馈邮箱：douboer@gmail.com</footer>
  </div>
</template>

<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch, watchEffect } from 'vue';
import * as echarts from 'echarts/core';
import type { EChartsOption } from 'echarts';
import { SankeyChart } from 'echarts/charts';
import { TooltipComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import {
  applyDirection,
  buildSankeyData,
  parseDataFile,
  type MappingConfig,
  type RawTable,
  type SankeyBuildResult
} from './core';
import iconWebLogo from '../assets/icons/webicon.png';
import iconTitle from '../assets/icons/星程字体转换.svg';
import iconChooseColor from '../assets/icons/choose-color.svg';
import iconUpload from '../assets/icons/upload.svg';
import iconExport from '../assets/icons/export.svg';
import iconExportSvg from '../assets/icons/export-svg.svg';
import iconExportPng from '../assets/icons/export-png.svg';
import iconData from '../assets/icons/data.svg';
import iconDescription from '../assets/icons/description.svg';
import iconRadioOn from '../assets/icons/radiobutton.svg';
import iconRadioOff from '../assets/icons/radiobutton-no.svg';
import iconCheckboxOn from '../assets/icons/checkbox.svg';
import iconCheckboxOff from '../assets/icons/checkbox-no.svg';
import iconExpand from '../assets/icons/expand.svg';

echarts.use([SankeyChart, TooltipComponent, CanvasRenderer]);

/**
 * 主题色板列表。
 * 颜色来自 Figma 示例，保证与设计稿观感一致。
 */
const themes = [
  {
    id: 'morandi',
    colors: ['#F4F1DE', '#EAB69F', '#E07A5F', '#8F5D5D', '#3D405B', '#5F797B', '#81B29A', '#9EB998']
  },
  {
    id: 'purple',
    colors: ['#F72585', '#B5179E', '#7209B7', '#560BAD', '#480CA8', '#3A0CA3', '#3F37C9', '#4895EF', '#4CC9F0']
  },
  {
    id: 'fog',
    colors: ['#E8EDDF', '#CFDBD5', '#B7B7A4', '#A5A58D', '#6B705C', '#4F5D75', '#5D576B', '#6D597A']
  },
  {
    id: 'sunset',
    colors: ['#355070', '#515575', '#6D597A', '#915F78', '#B56576', '#CD6873', '#E56B6F', '#E88C7D', '#EAAC8B']
  }
] as const;

const selectedThemeId = ref<(typeof themes)[number]['id']>('purple');
const showThemePicker = ref(false);
const uploadMessage = ref('点击上传或将csv/xls文件拖到这里上传');
const parseError = ref('');
const buildError = ref('');

const chartRef = ref<HTMLDivElement | null>(null);
const fileInputRef = ref<HTMLInputElement | null>(null);
let chartInstance: echarts.EChartsType | null = null;

const rawTable = ref<RawTable | null>(null);
const buildResult = ref<SankeyBuildResult | null>(null);

const mapping = reactive<MappingConfig>({
  sourceDataColumn: 1,
  sourceDescriptionColumns: [0, 1],
  targetDescriptionColumns: [2],
  delimiter: '-'
});

const direction = ref<'source-to-target' | 'target-to-source'>('source-to-target');
const nodeGap = ref(24);
const chartPadding = ref(30);

const selectedTheme = computed(() => themes.find((item) => item.id === selectedThemeId.value) ?? themes[0]);

const columnHeaders = computed(() => {
  const headers = rawTable.value?.headers ?? [];
  if (headers.length > 0) {
    return headers.map((item, index) => item || `列${index + 1}`);
  }

  return ['列1', '列2', '列3', '列4'];
});

const buildWarnings = computed(() => buildResult.value?.meta.warnings.slice(0, 8) ?? []);

const previewExample = computed(() => {
  const table = rawTable.value;
  if (!table || table.rows.length === 0) {
    return '宁波北欧10-2582 -> 嘉兴四级算力池-11623-小模型';
  }

  const firstRow = table.rows[0];
  const sourceParts =
    mapping.sourceDescriptionColumns.length > 0
      ? mapping.sourceDescriptionColumns.map((column) => firstRow[column] ?? '').filter(Boolean)
      : [firstRow[mapping.sourceDataColumn ?? 0] ?? ''];

  const targetParts = mapping.targetDescriptionColumns
    .map((column) => firstRow[column] ?? '')
    .filter(Boolean);

  return `${sourceParts.join(mapping.delimiter)} -> ${targetParts.join(mapping.delimiter)}`;
});

const chartNodes = computed(() => {
  const links = buildResult.value ? applyDirection(buildResult.value.links, direction.value) : [];
  const names = new Set<string>();

  links.forEach((link) => {
    names.add(link.source);
    names.add(link.target);
  });

  const palette = selectedTheme.value.colors;
  return Array.from(names).map((name, index) => ({
    name,
    itemStyle: {
      color: palette[index % palette.length]
    }
  }));
});

const chartLinks = computed(() => {
  if (!buildResult.value) {
    return [];
  }
  return applyDirection(buildResult.value.links, direction.value);
});

const chartOption = computed<EChartsOption>(() => {
  return {
    backgroundColor: '#f7f8fa',
    tooltip: {
      trigger: 'item'
    },
    series: [
      {
        type: 'sankey',
        left: chartPadding.value,
        top: chartPadding.value,
        right: chartPadding.value,
        bottom: chartPadding.value,
        nodeAlign: 'justify',
        nodeGap: nodeGap.value,
        nodeWidth: 14,
        roam: true,
        label: {
          color: '#4e5969',
          fontSize: 12
        },
        lineStyle: {
          color: 'source',
          curveness: 0.45,
          opacity: 0.45
        },
        data: chartNodes.value,
        links: chartLinks.value
      }
    ]
  };
});

/**
 * 每次映射配置变化都实时重新聚合，保持“输入即预览”的交互。
 */
watchEffect(() => {
  const table = rawTable.value;
  if (!table) {
    buildResult.value = null;
    buildError.value = '';
    return;
  }

  if (mapping.sourceDataColumn === null) {
    buildResult.value = null;
    buildError.value = '请选择源数据列';
    return;
  }

  if (mapping.targetDescriptionColumns.length === 0) {
    buildResult.value = null;
    buildError.value = '请至少选择一个目标描述列';
    return;
  }

  try {
    buildResult.value = buildSankeyData(table, {
      sourceDataColumn: mapping.sourceDataColumn,
      sourceDescriptionColumns: [...mapping.sourceDescriptionColumns],
      targetDescriptionColumns: [...mapping.targetDescriptionColumns],
      delimiter: mapping.delimiter
    });
    buildError.value = '';
  } catch (error) {
    buildResult.value = null;
    buildError.value = error instanceof Error ? error.message : '构建桑基图失败';
  }
});

watch(
  chartOption,
  () => {
    if (!chartInstance) {
      return;
    }
    chartInstance.setOption(chartOption.value, true);
  },
  { deep: true }
);

function syncChartSize(): void {
  chartInstance?.resize();
}

function closeThemePicker(): void {
  showThemePicker.value = false;
}

function toggleThemePicker(): void {
  showThemePicker.value = !showThemePicker.value;
}

function pickTheme(themeId: (typeof themes)[number]['id']): void {
  selectedThemeId.value = themeId;
  showThemePicker.value = false;
}

function toggleSourceDescription(column: number): void {
  if (mapping.sourceDescriptionColumns.includes(column)) {
    mapping.sourceDescriptionColumns = mapping.sourceDescriptionColumns.filter((item) => item !== column);
    return;
  }

  mapping.sourceDescriptionColumns = [...mapping.sourceDescriptionColumns, column].sort((a, b) => a - b);
}

function toggleTargetDescription(column: number): void {
  if (mapping.targetDescriptionColumns.includes(column)) {
    mapping.targetDescriptionColumns = mapping.targetDescriptionColumns.filter((item) => item !== column);
    return;
  }

  mapping.targetDescriptionColumns = [...mapping.targetDescriptionColumns, column].sort((a, b) => a - b);
}

function openFileDialog(): void {
  fileInputRef.value?.click();
}

function setDefaultMappingByColumns(columnSize: number): void {
  const safeSize = Math.max(columnSize, 1);
  const safeColumn = (index: number): number => Math.min(index, safeSize - 1);

  mapping.sourceDataColumn = safeColumn(1);
  mapping.sourceDescriptionColumns = [safeColumn(0), safeColumn(1)].filter(
    (item, index, list) => list.indexOf(item) === index
  );
  mapping.targetDescriptionColumns = [safeColumn(2), safeColumn(3)].filter(
    (item, index, list) => list.indexOf(item) === index
  );
}

/**
 * 统一处理上传文件，支持点击上传和拖拽上传两种入口。
 */
async function loadDataFile(file: File): Promise<void> {
  parseError.value = '';

  try {
    const parsed = await parseDataFile(file);
    rawTable.value = parsed;
    setDefaultMappingByColumns(parsed.headers.length);
    uploadMessage.value = `已加载: ${file.name}（${parsed.rows.length} 行）`;
  } catch (error) {
    parseError.value = error instanceof Error ? error.message : '文件解析失败';
  }
}

async function onFileChange(event: Event): Promise<void> {
  const input = event.target as HTMLInputElement;
  const file = input.files?.[0];
  if (!file) {
    return;
  }

  await loadDataFile(file);
  input.value = '';
}

async function onDropFile(event: DragEvent): Promise<void> {
  const file = event.dataTransfer?.files?.[0];
  if (!file) {
    return;
  }

  await loadDataFile(file);
}

function formatFileTimestamp(): string {
  const now = new Date();
  const pad = (value: number) => String(value).padStart(2, '0');
  return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}_${pad(
    now.getHours()
  )}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
}

function downloadByDataUrl(dataUrl: string, filename: string): void {
  const anchor = document.createElement('a');
  anchor.href = dataUrl;
  anchor.download = filename;
  document.body.append(anchor);
  anchor.click();
  anchor.remove();
}

function exportSvg(): void {
  if (!chartInstance) {
    return;
  }

  const svgEl = chartRef.value?.querySelector('svg');
  if (svgEl) {
    const serialized = new XMLSerializer().serializeToString(svgEl);
    const blob = new Blob([serialized], { type: 'image/svg+xml;charset=utf-8' });
    const url = URL.createObjectURL(blob);
    downloadByDataUrl(url, `sankey_${formatFileTimestamp()}.svg`);
    URL.revokeObjectURL(url);
    return;
  }

  const dataUrl = chartInstance.getDataURL({
    type: 'svg',
    backgroundColor: '#ffffff',
    pixelRatio: 2
  });
  downloadByDataUrl(dataUrl, `sankey_${formatFileTimestamp()}.svg`);
}

function exportPng(): void {
  if (!chartInstance) {
    return;
  }

  const dataUrl = chartInstance.getDataURL({
    type: 'png',
    backgroundColor: '#ffffff',
    pixelRatio: 2
  });
  downloadByDataUrl(dataUrl, `sankey_${formatFileTimestamp()}.png`);
}

onMounted(() => {
  const container = chartRef.value;
  if (!container) {
    return;
  }

  chartInstance = echarts.init(container, undefined, { renderer: 'canvas' });
  chartInstance.setOption(chartOption.value);
  window.addEventListener('resize', syncChartSize);
});

onBeforeUnmount(() => {
  window.removeEventListener('resize', syncChartSize);
  chartInstance?.dispose();
  chartInstance = null;
});
</script>
