<script setup lang="ts">
import { computed, onMounted, onUnmounted, shallowRef, useTemplateRef } from 'vue'

import type { TraderAction } from '../types'
import { formatSignedWanAmount, formatWanAmount, numberFromText } from '../utils/format'

type AggregatedActionRow = {
  trade_date: string
  buyTotalWan: number
  sellTotalWan: number
  netTotalWan: number
  cumulativeNetWan: number
  traderCount: number
}

const props = defineProps<{
  actions: TraderAction[]
}>()

const chartContainerRef = useTemplateRef<HTMLDivElement>('chartContainerRef')
const hoveredIndex = shallowRef<number | null>(null)
const containerWidth = shallowRef(0)

const aggregatedRows = computed<AggregatedActionRow[]>(() => {
  const grouped = new Map<
    string,
    {
      trade_date: string
      buyTotalWan: number
      sellTotalWan: number
      netTotalWan: number
      traders: Set<string>
    }
  >()

  for (const action of props.actions) {
    const buyTotalWan = numberFromText(action.buy_amount_wan) ?? 0
    const sellTotalWan = numberFromText(action.sell_amount_wan) ?? 0
    const netTotalWan = numberFromText(action.net_amount_wan) ?? buyTotalWan - sellTotalWan
    const current = grouped.get(action.trade_date) ?? {
      trade_date: action.trade_date,
      buyTotalWan: 0,
      sellTotalWan: 0,
      netTotalWan: 0,
      traders: new Set<string>(),
    }

    current.buyTotalWan += buyTotalWan
    current.sellTotalWan += sellTotalWan
    current.netTotalWan += netTotalWan
    if (action.matched_trader_name) {
      current.traders.add(action.matched_trader_name)
    }
    grouped.set(action.trade_date, current)
  }

  let cumulativeNetWan = 0
  return [...grouped.values()]
    .sort((left, right) => left.trade_date.localeCompare(right.trade_date))
    .map((item) => {
      cumulativeNetWan += item.netTotalWan
      return {
        trade_date: item.trade_date,
        buyTotalWan: item.buyTotalWan,
        sellTotalWan: item.sellTotalWan,
        netTotalWan: item.netTotalWan,
        cumulativeNetWan,
        traderCount: item.traders.size,
      }
    })
})

const chartModel = computed(() => {
  const rows = aggregatedRows.value
  const measuredWidth = containerWidth.value || 0
  const width = Math.max(measuredWidth, 320, rows.length * 54)
  const height = 260
  const left = 56
  const right = 64
  const top = 18
  const bottom = 34
  const innerWidth = width - left - right
  const innerHeight = height - top - bottom

  const emptyModel = {
    width,
    height,
    left,
    right,
    top,
    bottom,
    zeroY: top + innerHeight / 2,
    stepX: 0,
    buyBars: [] as Array<{ x: number; y: number; height: number; title: string }>,
    sellBars: [] as Array<{ x: number; y: number; height: number; title: string }>,
    netPoints: [] as Array<{ x: number; y: number }>,
    cumulativePoints: [] as Array<{ x: number; y: number }>,
    labels: [] as Array<{ x: number; label: string; visible: boolean }>,
    leftAxis: [] as Array<{ y: number; label: string }>,
    rightAxis: [] as Array<{ y: number; label: string }>,
    hoverColumns: [] as Array<{ x: number; width: number }>,
  }

  if (!rows.length) {
    return emptyModel
  }

  const leftMax = Math.max(
    ...rows.flatMap((row) => [Math.abs(row.buyTotalWan), Math.abs(row.sellTotalWan), Math.abs(row.netTotalWan)]),
    1,
  )
  const zeroY = top + innerHeight / 2
  const yOfLeft = (value: number) => zeroY - (value / leftMax) * (innerHeight / 2)

  const cumulativeValues = rows.map((row) => row.cumulativeNetWan)
  const rightMin = Math.min(0, ...cumulativeValues)
  const rightMax = Math.max(0, ...cumulativeValues)
  const rightRange = rightMax - rightMin || 1
  const yOfRight = (value: number) => top + ((rightMax - value) / rightRange) * innerHeight

  const stepX = rows.length === 1 ? innerWidth / 2 : innerWidth / (rows.length - 1)
  const groupWidth = Math.max(20, Math.min(28, stepX * 0.58))
  const barWidth = Math.max(7, groupWidth / 2 - 2)
  const labelStep = Math.max(1, Math.ceil(rows.length / 6))

  return {
    width,
    height,
    left,
    right,
    top,
    bottom,
    zeroY,
    stepX,
    buyBars: rows.map((row, index) => {
      const x = left + (rows.length === 1 ? innerWidth / 2 : index * stepX)
      const y = yOfLeft(row.buyTotalWan)
      return {
        x: x - barWidth - 2,
        y,
        height: Math.max(2, zeroY - y),
        title: `${row.trade_date}\n买入 ${formatWanAmount(row.buyTotalWan)}\n卖出 ${formatWanAmount(row.sellTotalWan)}\n净额 ${formatSignedWanAmount(row.netTotalWan)}`,
      }
    }),
    sellBars: rows.map((row, index) => {
      const x = left + (rows.length === 1 ? innerWidth / 2 : index * stepX)
      const y = zeroY
      const sellY = yOfLeft(-row.sellTotalWan)
      return {
        x: x + 2,
        y,
        height: Math.max(2, sellY - zeroY),
        title: `${row.trade_date}\n买入 ${formatWanAmount(row.buyTotalWan)}\n卖出 ${formatWanAmount(row.sellTotalWan)}\n净额 ${formatSignedWanAmount(row.netTotalWan)}`,
      }
    }),
    netPoints: rows.map((row, index) => ({
      x: left + (rows.length === 1 ? innerWidth / 2 : index * stepX),
      y: yOfLeft(row.netTotalWan),
    })),
    cumulativePoints: rows.map((row, index) => ({
      x: left + (rows.length === 1 ? innerWidth / 2 : index * stepX),
      y: yOfRight(row.cumulativeNetWan),
    })),
    labels: rows.map((row, index) => ({
      x: left + (rows.length === 1 ? innerWidth / 2 : index * stepX),
      label: row.trade_date.slice(5),
      visible: index % labelStep === 0 || index === rows.length - 1,
    })),
    leftAxis: Array.from({ length: 5 }, (_, index) => {
      const value = leftMax - (leftMax * 2 * index) / 4
      return { y: yOfLeft(value), label: formatSignedWanAmount(value) }
    }),
    rightAxis: Array.from({ length: 5 }, (_, index) => {
      const value = rightMax - (rightRange * index) / 4
      return { y: yOfRight(value), label: formatSignedWanAmount(value) }
    }),
    hoverColumns: rows.map((_row, index) => ({
      x: left + (rows.length === 1 ? innerWidth / 2 : index * stepX) - Math.max(stepX, 20) / 2,
      width: Math.max(stepX, 20),
    })),
  }
})

const netLinePoints = computed(() => chartModel.value.netPoints.map((point) => `${point.x},${point.y}`).join(' '))
const cumulativeLinePoints = computed(() =>
  chartModel.value.cumulativePoints.map((point) => `${point.x},${point.y}`).join(' '),
)

const activeRow = computed(() => {
  if (!aggregatedRows.value.length) return null
  if (hoveredIndex.value === null) return null
  return aggregatedRows.value[hoveredIndex.value] ?? null
})

const activeTooltip = computed(() => {
  if (hoveredIndex.value === null || !activeRow.value) return null
  const point = chartModel.value.netPoints[hoveredIndex.value]
  if (!point) return null

  const tooltipWidth = 168
  const tooltipHeight = 90
  const x = Math.min(
    Math.max(point.x - tooltipWidth / 2, chartModel.value.left + 6),
    chartModel.value.width - chartModel.value.right - tooltipWidth - 6,
  )
  const prefersBelow = point.y < chartModel.value.top + tooltipHeight + 24
  const y = prefersBelow
    ? Math.min(point.y + 12, chartModel.value.height - chartModel.value.bottom - tooltipHeight - 6)
    : Math.max(point.y - tooltipHeight - 12, chartModel.value.top + 6)

  return {
    x,
    y,
    width: tooltipWidth,
    height: tooltipHeight,
  }
})

function setHoveredIndex(index: number) {
  hoveredIndex.value = index
}

function clearHoveredIndex() {
  hoveredIndex.value = null
}

let resizeObserver: ResizeObserver | null = null

function syncContainerWidth() {
  containerWidth.value = chartContainerRef.value?.clientWidth ?? 0
}

onMounted(() => {
  syncContainerWidth()
  if (chartContainerRef.value) {
    resizeObserver = new ResizeObserver(() => {
      syncContainerWidth()
    })
    resizeObserver.observe(chartContainerRef.value)
  }
})

onUnmounted(() => {
  resizeObserver?.disconnect()
  resizeObserver = null
})
</script>

<template>
  <div class="action-chart-panel">
    <div class="action-chart-head">
      <div>
        <h4 class="action-chart-title">买卖力度趋势</h4>
        <p class="action-chart-note">柱形图显示买卖，折线显示单日净额和累计净额。</p>
      </div>
      <div class="action-chart-legend">
        <span class="legend-chip">
          <span class="legend-swatch bar buy" />
          买入
        </span>
        <span class="legend-chip">
          <span class="legend-swatch bar sell" />
          卖出
        </span>
        <span class="legend-chip">
          <span class="legend-swatch line net" />
          当日净额
        </span>
        <span class="legend-chip">
          <span class="legend-swatch line cumulative" />
          累计净额
        </span>
      </div>
    </div>

    <div v-if="aggregatedRows.length" class="action-chart-body">
      <div ref="chartContainerRef" class="action-chart-scroll">
        <svg
          class="action-chart-svg"
          :viewBox="`0 0 ${chartModel.width} ${chartModel.height}`"
          :style="{ width: `${chartModel.width}px`, height: `${chartModel.height}px` }"
          preserveAspectRatio="none"
          @mouseleave="clearHoveredIndex"
        >
          <g opacity="0.08" stroke="#ffffff">
            <line
              v-for="tick in chartModel.leftAxis"
              :key="`left-grid-${tick.label}`"
              :x1="chartModel.left"
              :y1="tick.y"
              :x2="chartModel.width - chartModel.right"
              :y2="tick.y"
            />
          </g>

          <line
            :x1="chartModel.left"
            :y1="chartModel.zeroY"
            :x2="chartModel.width - chartModel.right"
            :y2="chartModel.zeroY"
            stroke="rgba(255,255,255,0.22)"
            stroke-width="1.2"
          />

          <g v-for="(tick, index) in chartModel.leftAxis" :key="`left-axis-${index}`">
            <text x="0" :y="tick.y + 4" fill="#93a2b5" font-size="10">{{ tick.label }}</text>
          </g>

          <g v-for="(tick, index) in chartModel.rightAxis" :key="`right-axis-${index}`">
            <text :x="chartModel.width - chartModel.right + 8" :y="tick.y + 4" fill="#c9ad68" font-size="10">
              {{ tick.label }}
            </text>
          </g>

          <g v-for="(column, index) in chartModel.hoverColumns" :key="`hover-${index}`">
            <rect
              :x="column.x"
              y="0"
              :width="column.width"
              :height="chartModel.height"
              :fill="hoveredIndex === index ? 'rgba(255,255,255,0.04)' : 'transparent'"
              @mouseenter="setHoveredIndex(index)"
            />
          </g>

          <g v-for="(bar, index) in chartModel.buyBars" :key="`buy-${index}`">
            <rect
              :x="bar.x"
              :y="bar.y"
              width="10"
              :height="bar.height"
              rx="2"
              fill="#ff7b7b"
            >
              <title>{{ bar.title }}</title>
            </rect>
          </g>

          <g v-for="(bar, index) in chartModel.sellBars" :key="`sell-${index}`">
            <rect
              :x="bar.x"
              :y="bar.y"
              width="10"
              :height="bar.height"
              rx="2"
              fill="#4ca8ff"
            >
              <title>{{ bar.title }}</title>
            </rect>
          </g>

          <polyline
            fill="none"
            stroke="#f0c96a"
            stroke-width="2.4"
            :points="cumulativeLinePoints"
          />
          <polyline
            fill="none"
            stroke="#7de1b2"
            stroke-width="2.2"
            :points="netLinePoints"
          />

          <g v-for="(point, index) in chartModel.netPoints" :key="`net-point-${index}`">
            <circle :cx="point.x" :cy="point.y" r="3.4" fill="#7de1b2" />
          </g>
          <g v-for="(point, index) in chartModel.cumulativePoints" :key="`cumulative-point-${index}`">
            <circle :cx="point.x" :cy="point.y" r="3.2" fill="#f0c96a" />
          </g>

          <g v-if="activeTooltip && activeRow">
            <rect
              :x="activeTooltip.x"
              :y="activeTooltip.y"
              :width="activeTooltip.width"
              :height="activeTooltip.height"
              rx="10"
              fill="rgba(8, 12, 18, 0.96)"
              stroke="rgba(255,255,255,0.08)"
            />
            <text :x="activeTooltip.x + 12" :y="activeTooltip.y + 18" fill="#ffffff" font-size="11" font-weight="700">
              {{ activeRow.trade_date }}
            </text>
            <text :x="activeTooltip.x + 12" :y="activeTooltip.y + 36" fill="#ff7b7b" font-size="11">
              买入 {{ formatWanAmount(activeRow.buyTotalWan) }}
            </text>
            <text :x="activeTooltip.x + 12" :y="activeTooltip.y + 52" fill="#4ca8ff" font-size="11">
              卖出 {{ formatWanAmount(activeRow.sellTotalWan) }}
            </text>
            <text :x="activeTooltip.x + 12" :y="activeTooltip.y + 68" fill="#7de1b2" font-size="11">
              当日净额 {{ formatSignedWanAmount(activeRow.netTotalWan) }}
            </text>
            <text :x="activeTooltip.x + 12" :y="activeTooltip.y + 84" fill="#f0c96a" font-size="11">
              累计净额 {{ formatSignedWanAmount(activeRow.cumulativeNetWan) }}
            </text>
          </g>

          <g v-for="(label, index) in chartModel.labels" :key="`label-${index}`">
            <text
              v-if="label.visible"
              :x="label.x"
              :y="chartModel.height - 10"
              text-anchor="middle"
              fill="#93a2b5"
              font-size="10"
            >
              {{ label.label }}
            </text>
          </g>
        </svg>
      </div>
    </div>

    <p v-else class="action-chart-empty">暂无可展示的买卖明细。</p>
  </div>
</template>

<style scoped>
.action-chart-panel {
  display: grid;
  gap: 10px;
  height: 100%;
  min-height: 0;
}

.action-chart-head {
  display: grid;
  gap: 10px;
}

.action-chart-title {
  margin: 0;
  font-size: 15px;
}

.action-chart-note {
  margin: 6px 0 0;
  color: var(--color-muted);
  font-size: 12px;
  line-height: 1.55;
}

.action-chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.legend-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.08);
  color: var(--color-muted);
  font-size: 11px;
  background: rgba(255, 255, 255, 0.03);
}

.legend-swatch {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative;
  width: 18px;
  height: 10px;
}

.legend-swatch.bar {
  border-radius: 999px;
}

.legend-swatch.buy {
  background: #ff7b7b;
}

.legend-swatch.sell {
  background: #4ca8ff;
}

.legend-swatch.line::before {
  content: '';
  width: 18px;
  height: 2px;
  border-radius: 999px;
}

.legend-swatch.line.net::before {
  background: #7de1b2;
}

.legend-swatch.line.cumulative::before {
  background: #f0c96a;
}

.action-chart-body {
  display: grid;
  gap: 8px;
  min-height: 0;
}

.action-chart-scroll {
  overflow-x: auto;
  overflow-y: hidden;
  padding-bottom: 2px;
  min-height: 228px;
}

.action-chart-svg {
  display: block;
  min-height: 228px;
}

.action-chart-empty {
  margin: 0;
  color: var(--color-muted);
  font-size: 12px;
  line-height: 1.65;
}
</style>
