<template>
  <div class="enrollment-chart" :class="{ 'enrollment-chart--compact': compact }">
    <StateLoading v-if="loading" :rows="5" />
    <div v-else-if="items.length === 0" class="chart-empty-shell">
      <div class="chart-empty-body">
        <div class="chart-empty-title">暂无入组进度数据</div>
        <div class="chart-empty-desc">{{ emptyText }}</div>
      </div>
    </div>
    <div v-else class="chart-body">
      <div class="chart-scroll">
        <div class="chart-plot" :style="chartPlotStyle">
          <svg class="chart-svg" :viewBox="`0 0 ${chartWidth} ${chartHeight}`" preserveAspectRatio="xMidYMid meet">
            <defs>
              <linearGradient :id="gradientTargetId" x1="0" y1="0" x2="0" y2="1">
                <stop offset="0%" stop-color="#e8edf3" />
                <stop offset="100%" stop-color="#f1f5f9" />
              </linearGradient>
              <linearGradient :id="gradientActualId" x1="0" y1="0" x2="0" y2="1">
                <stop offset="0%" stop-color="#4a7c9b" />
                <stop offset="100%" stop-color="#3f5d75" />
              </linearGradient>
            </defs>
            <!-- Horizontal grid lines -->
            <g class="chart-grid">
              <line
                v-for="tick in yTicks"
                :key="`grid-${tick.value}`"
                class="grid-line"
                :x1="axisLeft"
                :y1="tickY(tick.value)"
                :x2="axisRight"
                :y2="tickY(tick.value)"
              />
            </g>
            <!-- Y axis labels -->
            <g class="chart-axis">
              <g v-for="tick in yTicks" :key="`tick-${tick.value}`" class="axis-tick">
                <text :x="axisLeft - 14" :y="tickY(tick.value)" class="tick-label">
                  {{ formatNumber(tick.value) }}
                </text>
              </g>
            </g>
            <!-- Bars -->
            <g class="chart-bars">
              <g v-for="(item, index) in items" :key="item.key" class="bar-group">
                <title>{{ item.label }}: {{ item.actual }}{{ showTarget ? ` / ${item.target}` : '' }}</title>
                <!-- Target bar (background) -->
                <rect
                  v-if="showTarget && (item.target || 0) > 0"
                  class="bar-target"
                  :x="barLeft(index)"
                  :y="barTop(item.target || 0)"
                  :width="barWidth"
                  :height="barHeight(item.target || 0)"
                  :rx="barRadius"
                  :fill="`url(#${gradientTargetId})`"
                />
                <!-- Actual bar -->
                <rect
                  v-if="item.actual > 0"
                  class="bar-actual"
                  :x="barLeft(index)"
                  :y="barTop(item.actual)"
                  :width="barWidth"
                  :height="barHeight(item.actual)"
                  :rx="barRadius"
                  :fill="`url(#${gradientActualId})`"
                />
                <!-- Value label -->
                <text :x="barCenter(index)" :y="valueY(item)" class="bar-value">
                  <tspan class="bar-value-actual">{{ formatNumber(item.actual) }}</tspan>
                  <tspan v-if="showTarget" class="bar-value-divider" dx="3">/</tspan>
                  <tspan v-if="showTarget" class="bar-value-target" dx="3">{{ formatNumber(item.target || 0) }}</tspan>
                </text>
                <!-- X axis label -->
                <text :x="barCenter(index)" :y="labelY" class="bar-label">
                  {{ truncateLabel(item.label) }}
                </text>
              </g>
            </g>
          </svg>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed } from "vue";
import StateLoading from "../../../components/StateLoading.vue";

export type ChartMode = "center" | "month";

export interface EnrollmentBarItem {
  key: string;
  label: string;
  actual: number;
  target?: number;
}

const props = withDefaults(
  defineProps<{
    mode: ChartMode;
    items: EnrollmentBarItem[];
    loading?: boolean;
    emptyText?: string;
    compact?: boolean;
  }>(),
  {
    loading: false,
    emptyText: "暂无入组数据",
    compact: false,
  }
);

const compact = computed(() => props.compact);
const showTarget = computed(() => props.mode === "center");

const baseChartWidth = computed(() => (compact.value ? 560 : 960));
const chartHeight = computed(() => (compact.value ? 240 : 260));
const axisPadding = computed(() => ({
  top: compact.value ? 24 : 28,
  right: compact.value ? 24 : 32,
  bottom: compact.value ? 42 : 48,
  left: compact.value ? 46 : 56,
}));

const chartWidth = computed(() => {
  if (!compact.value) return baseChartWidth.value;
  const itemWidth = showTarget.value ? 72 : 64;
  return Math.max(
    baseChartWidth.value,
    props.items.length * itemWidth + axisPadding.value.left + axisPadding.value.right,
  );
});

const chartPlotStyle = computed(() => ({
  "--chart-min-width": `${chartWidth.value}px`,
  "--chart-aspect": `${chartWidth.value} / ${chartHeight.value}`,
}));

const gradientTargetId = `enroll-target-${Math.random().toString(36).slice(2, 8)}`;
const gradientActualId = `enroll-actual-${Math.random().toString(36).slice(2, 8)}`;

const maxValue = computed(() => {
  if (!props.items.length) return 1;
  return props.items.reduce((max, item) => {
    const candidate = showTarget.value ? Math.max(item.actual, item.target || 0) : item.actual;
    return Math.max(max, candidate);
  }, 1);
});

const yTicks = computed(() => {
  const max = maxValue.value;
  const step = max <= 5 ? 1 : max <= 12 ? 2 : Math.max(1, Math.ceil(max / 5));
  const top = Math.max(1, Math.ceil(max / step) * step);
  const ticks: Array<{ value: number; minor: boolean }> = [];
  for (let value = top; value >= 0; value -= step) {
    ticks.push({ value, minor: false });
  }
  return ticks;
});

const axisMax = computed(() => Math.max(1, yTicks.value[0]?.value ?? 1));
const plotWidth = computed(() => chartWidth.value - axisPadding.value.left - axisPadding.value.right);
const plotHeight = computed(() => chartHeight.value - axisPadding.value.top - axisPadding.value.bottom);
const axisLeft = computed(() => axisPadding.value.left);
const axisRight = computed(() => chartWidth.value - axisPadding.value.right);
const axisTop = computed(() => axisPadding.value.top);
const axisBottom = computed(() => chartHeight.value - axisPadding.value.bottom);
const labelY = computed(() => axisBottom.value + (compact.value ? 18 : 20));
const valueGap = computed(() => (compact.value ? 8 : 10));
const barRadius = computed(() => (compact.value ? 5 : 6));

const bandWidth = computed(() => (props.items.length ? plotWidth.value / props.items.length : plotWidth.value));
const barWidth = computed(() => Math.min(compact.value ? 44 : 52, bandWidth.value * 0.5));

const barCenter = (index: number) => axisLeft.value + bandWidth.value * index + bandWidth.value / 2;
const barLeft = (index: number) => barCenter(index) - barWidth.value / 2;
const barHeight = (value: number) => {
  if (value <= 0) return 0;
  return (value / axisMax.value) * plotHeight.value;
};
const barTop = (value: number) => axisTop.value + plotHeight.value - barHeight(value);
const tickY = (value: number) => axisTop.value + ((axisMax.value - value) / axisMax.value) * plotHeight.value;

const valueY = (item: EnrollmentBarItem) => {
  const anchor = showTarget.value ? Math.max(item.actual, item.target || 0) : item.actual;
  return barTop(anchor) - valueGap.value;
};

const truncateLabel = (label: string) => {
  const limit = compact.value ? 7 : 8;
  if (label.length <= limit) return label;
  return `${label.slice(0, limit - 1)}...`;
};

const formatNumber = (value: number) => new Intl.NumberFormat("zh-CN").format(value || 0);
</script>

<style scoped>
.enrollment-chart {
  width: 100%;
}

.chart-empty-shell {
  min-height: 160px;
  border-radius: 12px;
  padding: 22px;
  background: linear-gradient(180deg, #f8fafc, #f1f5f9);
}

.chart-empty-body {
  min-height: 100px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
}

.chart-empty-title {
  font-size: 16px;
  font-weight: 600;
  color: var(--ctms-text-regular);
}

.chart-empty-desc {
  margin-top: 8px;
  font-size: 13px;
  line-height: 1.7;
  color: var(--ctms-text-secondary);
}

.chart-body {
  display: flex;
  flex-direction: column;
}

.chart-scroll {
  overflow: hidden;
}

.chart-plot {
  border-radius: 10px;
  background: linear-gradient(180deg, #fafbfd 0%, #f6f8fb 100%);
  position: relative;
  width: 100%;
  aspect-ratio: var(--chart-aspect);
  min-height: 200px;
}

.chart-svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

.grid-line {
  stroke: #e8ecf1;
  stroke-width: 1px;
  stroke-dasharray: 4 3;
}

.tick-label {
  font-size: 11px;
  fill: var(--ctms-text-disabled);
  dominant-baseline: middle;
  text-anchor: end;
  font-weight: 500;
}

.bar-target {
  stroke: #dce3ec;
  stroke-width: 1px;
}

.bar-actual {
  transition: opacity 0.2s ease;
}

.bar-group:hover .bar-actual {
  opacity: 0.85;
}

.bar-value {
  font-size: 12px;
  fill: var(--ctms-text-main);
  font-weight: 600;
  text-anchor: middle;
}

.bar-value-actual {
  fill: var(--ctms-primary);
  font-weight: 700;
  font-size: 13px;
}

.bar-value-divider {
  fill: var(--ctms-text-disabled);
  font-weight: 400;
}

.bar-value-target {
  fill: var(--ctms-text-secondary);
  font-weight: 500;
}

.bar-label {
  font-size: 12px;
  fill: var(--ctms-text-regular);
  text-anchor: middle;
  dominant-baseline: hanging;
  font-weight: 500;
}

.enrollment-chart--compact .chart-plot {
  width: max(100%, var(--chart-min-width));
  min-height: 0;
  border-radius: 6px;
}

.enrollment-chart--compact .chart-scroll {
  overflow-x: auto;
  overflow-y: hidden;
}

.enrollment-chart--compact .tick-label {
  font-size: 10px;
}

.enrollment-chart--compact .bar-value {
  font-size: 11px;
}

.enrollment-chart--compact .bar-value-actual {
  font-size: 12px;
}

.enrollment-chart--compact .bar-label {
  font-size: 10px;
}

@media (max-width: 768px) {
  .bar-label {
    font-size: 11px;
  }
}
</style>
