<template>
  <div class="map-wrapper">
    <div ref="map" class="map-container"></div>
    <div class="map-overlay" ref="mapOverlay">
      <div
        v-for="(card, idx) in mapCards"
        :key="idx"
        class="map-card"
        :style="{ left: card.x + 'px', top: card.y + 'px' }"
      >
        <img class="map-card__bg" src="/icon/map-text-bg.png" alt="" />
        <img class="map-card__avatar" :src="card.avatar" alt="" />
        <div class="map-card__info">
          <div class="map-card__time">{{ card.time }}</div>
          <div class="map-card__text">{{ card.text }}</div>
        </div>
      </div>
    </div>
    <div class="timeline-panel">
      <div class="control-bar">
        <div class="control-left">
          <button class="ctrl-btn" @click="handlePause" :title="isPlaying ? '暂停' : '播放'">
            <svg v-if="isPlaying" width="10" height="14" viewBox="0 0 10 14" fill="none">
              <rect x="0" y="0" width="3" height="14" rx="0.5" fill="#04295A"/>
              <rect x="7" y="0" width="3" height="14" rx="0.5" fill="#04295A"/>
            </svg>
            <svg v-else width="10" height="14" viewBox="0 0 10 14" fill="none">
              <polygon points="0,0 10,7 0,14" fill="#04295A"/>
            </svg>
          </button>
          <button class="ctrl-btn" @click="handleStop" title="停止">
            <svg width="12" height="12" viewBox="0 0 12 12" fill="none">
              <rect x="0" y="0" width="12" height="12" rx="1" fill="#04295A"/>
            </svg>
          </button>
          <button class="ctrl-btn ctrl-btn--text" @click="handleReset">重置</button>
        </div>
        <div class="control-right">
          <span class="control-label">当前时间</span>
          <el-date-picker
            v-model="currentDate"
            type="date"
            format="YYYY-MM-DD"
            value-format="YYYY-MM-DD"
            placeholder="选择日期"
            class="timeline-date-picker"
            @change="handleDateChange"
          />
          <span class="control-label control-label--gap">刻度</span>
          <el-select v-model="scaleMode" class="timeline-select" @change="handleScaleChange">
            <el-option label="周制" value="week" />
            <el-option label="月制" value="month" />
            <el-option label="日制" value="day" />
          </el-select>
          <span class="control-label control-label--gap">速率</span>
          <el-select v-model="speedRate" class="timeline-select" @change="handleSpeedChange">
            <el-option label="1X" value="1" />
            <el-option label="2X" value="2" />
            <el-option label="4X" value="4" />
          </el-select>
        </div>
      </div>
      <div class="timeline-track" ref="timelineTrack"
        @mousedown="onTimelineMouseDown"
      >
        <div class="timeline-inner" ref="timelineInner">
          <template v-for="(tick, index) in visibleTicks" :key="tick.date">
            <div class="tick-cell" :style="{ width: tickWidth + 'px' }" @click="onTickClick(tick.globalIndex)">
              <div class="tick-line" :class="{ 'tick-line--major': tick.showLabel }"></div>
              <span class="tick-label" v-if="tick.showLabel">{{ tick.label }}</span>
            </div>
          </template> 
          <div class="timeline-cursor" v-if="cursorLeft >= 0" :style="{ left: cursorLeft + 'px' }"></div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import * as echarts from "echarts";
import worldJson from "@/assets/json/world.json";
import { getCharacterTrends } from "@/api/technologyFigures/technologyFigures";
import { ref } from "vue";

const CharacterTrends = ref([]);
const time = ref("");
const date = ref("");

const handlegetCharacterTrendsFn = async () => {
  const params = {
    startTime: "2025-01-01" || date.value,
  };
  try {
    const res = await getCharacterTrends(params);
    if (res.code === 200) {
      CharacterTrends.value = res.data.map((item) => {
        return {
          time: item.newsDate,
          text: item.newsContent.substring(0, 17),
          lon: item.lon || (Math.random() * 360 - 180).toFixed(6),
          lat: item.lat || (Math.random() * 180 - 90).toFixed(6),
          avatar: item.imageUrl,
        };
      });
    }
  } catch (error) {}
};

function getDateDaysAgo(days) {
  const currentDate = new Date();
  const pastDate = new Date(currentDate);
  pastDate.setDate(currentDate.getDate() - days);
  const year = pastDate.getFullYear();
  const month = String(pastDate.getMonth() + 1).padStart(2, "0");
  const day = String(pastDate.getDate()).padStart(2, "0");
  return `${year}-${month}-${day}`;
}

function generateTicks(startDate, endDate, mode) {
  const ticks = [];
  const start = new Date(startDate);
  const end = new Date(endDate);
  const current = new Date(start);
  while (current <= end) {
    const m = String(current.getMonth() + 1).padStart(2, "0");
    const d = String(current.getDate()).padStart(2, "0");
    ticks.push({
      date: `${current.getFullYear()}-${m}-${d}`,
      label: `${m}.${d}`,
      showLabel: ticks.length % 7 === 0,
    });
    if (mode === "week") {
      current.setDate(current.getDate() + 7);
    } else if (mode === "month") {
      current.setMonth(current.getMonth() + 1);
    } else {
      current.setDate(current.getDate() + 1);
    }
  }
  return ticks;
}

export default {
  name: "MapAnimation",
  props: {
    peoDate: {
      type: String,
      default: "本周",
    },
  },
  data() {
    const today = new Date();
    const todayStr = today.getFullYear() + "-" +
      String(today.getMonth() + 1).padStart(2, "0") + "-" +
      String(today.getDate()).padStart(2, "0");
    return {
      isPlaying: false,
      playTimer: null,
      currentDate: todayStr,
      scaleMode: "day",
      speedRate: "2",
      currentTickIndex: 0,
      timelineTicks: [],
      startDate: "",
      endDate: "",
      chartInstance: null,
      mapCards: [],
      tickWidth: 10,
      viewStartIndex: 0,
      viewDays: 120,
    };
  },
  computed: {
    visibleTicks() {
      const start = Math.max(0, this.viewStartIndex);
      const end = Math.min(this.timelineTicks.length, start + this.viewDays);
      return this.timelineTicks.slice(start, end).map((tick, i) => ({
        ...tick,
        globalIndex: start + i,
      }));
    },
    cursorLeft() {
      const offset = this.currentTickIndex - this.viewStartIndex;
      if (offset < 0 || offset >= this.viewDays) return -9999;
      return offset * this.tickWidth + this.tickWidth / 2;
    },
  },
  watch: {
    async peoDate(Val) {
      time.value = Val;
      const days = ref();
      if (time.value === "本周") days.value = 7;
      else if (time.value === "今天") days.value = 0;
      else if (time.value === "昨天") days.value = 1;
      else if (time.value === "近三天") days.value = 3;
      else if (time.value === "本月") days.value = new Date().getDate() - 1;
      date.value = getDateDaysAgo(days.value);
      await handlegetCharacterTrendsFn();
      this.initTimelineFromData();
      this.initMap();
    },

  },
  async mounted() {
    time.value = this.peoDate;
    let days = 7;
    if (time.value === "本周") days = 7;
    else if (time.value === "今天") days = 0;
    else if (time.value === "昨天") days = 1;
    else if (time.value === "近三天") days = 3;
    else if (time.value === "本月") days = new Date().getDate() - 1;
    date.value = getDateDaysAgo(days);

    await handlegetCharacterTrendsFn();
    this.initTimelineFromData();
    this.initMap();

    this._tlWheelHandler = (e) => {
      e.preventDefault();
      this.onTimelineWheel(e);
    };
    const tlEl = this.$refs.timelineTrack;
    if (tlEl) {
      tlEl.addEventListener("wheel", this._tlWheelHandler, { passive: false });
    }
  },
  beforeUnmount() {
    this.clearPlayTimer();
    const tlEl = this.$refs.timelineTrack;
    if (tlEl && this._tlWheelHandler) {
      tlEl.removeEventListener("wheel", this._tlWheelHandler);
    }
    if (this.chartInstance) {
      this.chartInstance.dispose();
      this.chartInstance = null;
    }
  },
  methods: {
    /**
     * 鼠标滚轮左右滚动时间轴
     */
    onTimelineWheel(e) {
      const delta = e.deltaY || e.deltaX;
      const step = delta > 0 ? 7 : -7;
      this.viewStartIndex = Math.max(0, Math.min(
        this.timelineTicks.length - this.viewDays,
        this.viewStartIndex + step
      ));
    },
    /**
     * 鼠标拖拽滚动时间轴
     */
    onTimelineMouseDown(e) {
      const startX = e.clientX;
      const startViewIndex = this.viewStartIndex;
      const onMove = (ev) => {
        const dx = ev.clientX - startX;
        const daysMoved = Math.round(dx / this.tickWidth);
        this.viewStartIndex = Math.max(0, Math.min(
          this.timelineTicks.length - this.viewDays,
          startViewIndex - daysMoved
        ));
      };
      const onUp = () => {
        document.removeEventListener("mousemove", onMove);
        document.removeEventListener("mouseup", onUp);
      };
      document.addEventListener("mousemove", onMove);
      document.addEventListener("mouseup", onUp);
    },
    /**
     * 点击某个刻度跳转
     */
    onTickClick(globalIndex) {
      if (this.isPlaying) this.pausePlay();
      this.currentTickIndex = globalIndex;
      this.syncDateFromTick();
      this.updateMapByDate(this.currentDate);
      this.$emit("timeline-change", this.currentDate);
    },
    /**
     * 将可视窗口居中到指定 tick
     */
    scrollTimelineTo(tickIdx) {
      const half = Math.floor(this.viewDays / 2);
      this.viewStartIndex = Math.max(0, Math.min(
        this.timelineTicks.length - this.viewDays,
        tickIdx - half
      ));
    },
    rebuildTimeline() {
      if (!this.startDate || !this.endDate) return;
      this.timelineTicks = generateTicks(this.startDate, this.endDate, "day");
      this.initTimelinePosition();
      this.scrollTimelineTo(this.currentTickIndex);
    },
    handleDateChange(val) {
      this.currentDate = val;
      if (val < this.startDate || val > this.endDate) {
        const y = parseInt(val.substring(0, 4));
        if (val < this.startDate) {
          this.startDate = y + "-01-01";
        }
        if (val > this.endDate) {
          this.endDate = y + "-12-31";
        }
        this.timelineTicks = generateTicks(this.startDate, this.endDate, "day");
      }
      const idx = this.timelineTicks.findIndex((t) => t.date >= val);
      this.currentTickIndex = idx >= 0 ? idx : this.timelineTicks.length - 1;
      this.currentDate = this.timelineTicks[this.currentTickIndex].date;
      this.updateMapByDate(this.currentDate);
      this.scrollTimelineTo(this.currentTickIndex);
      this.$emit("timeline-change", this.currentDate);
    },
    handleScaleChange(val) {
      this.$emit("scale-change", val);
    },
    handleSpeedChange(val) {
      this.$emit("speed-change", val);
      if (this.isPlaying) {
        this.clearPlayTimer();
        this.scheduleNextTick();
      }
    },

    handlePause() {
      if (this.isPlaying) {
        this.pausePlay();
      } else {
        if (this.currentTickIndex >= this.timelineTicks.length - 1) {
          return;
        }
        this.startPlay();
      }
      this.$emit("play-toggle", this.isPlaying);
    },
    handleStop() {
      this.clearPlayTimer();
      this.isPlaying = false;
      this.$emit("stop", this.currentDate);
    },
    handleReset() {
      this.clearPlayTimer();
      this.isPlaying = false;
      const now = new Date();
      this.currentDate = now.getFullYear() + "-" +
        String(now.getMonth() + 1).padStart(2, "0") + "-" +
        String(now.getDate()).padStart(2, "0");
      this.initTimelinePosition();
      this.scrollTimelineTo(this.currentTickIndex);
      this.updateMapByDate(this.currentDate);
      this.$emit("reset");
      this.$emit("timeline-change", this.currentDate);
    },
    startPlay() {
      this.clearPlayTimer();
      this.isPlaying = true;
      this.scheduleNextTick();
    },
    pausePlay() {
      this.clearPlayTimer();
      this.isPlaying = false;
    },
    scheduleNextTick() {
      const interval = 1000 / Number(this.speedRate);
      this.playTimer = setTimeout(() => {
        if (this.currentTickIndex < this.timelineTicks.length - 1) {
          this.currentTickIndex++;
          this.syncDateFromTick();
          this.updateMapByDate(this.currentDate);
          this.scrollTimelineTo(this.currentTickIndex);
          this.$emit("timeline-change", this.currentDate);
          this.scheduleNextTick();
        } else {
          this.isPlaying = false;
          this.playTimer = null;
          this.$emit("play-end");
        }
      }, interval);
    },
    clearPlayTimer() {
      if (this.playTimer) {
        clearTimeout(this.playTimer);
        this.playTimer = null;
      }
    },
    syncDateFromTick() {
      const tick = this.timelineTicks[this.currentTickIndex];
      if (tick) {
        this.currentDate = tick.date;
      }
    },

    initMap() {
      echarts.registerMap("China", worldJson);
      if (this.chartInstance) {
        this.chartInstance.dispose();
      }
      const chart = echarts.init(this.$refs.map);
      this.chartInstance = chart;

      const option = {
        grid: {
          left: "10%",
          right: "10%",
          bottom: "10%",
          top: "10%",
          containLabel: true,
        },
        geo: {
          map: "China",
          roam: 'move',
          label: {
            emphasis: { show: false, color: "#fff" },
          },
          silent: true,
          itemStyle: {
            areaColor: "#F6FAFF",
            borderColor: "#B9DCFF",
          },
        },
        series: [
          {
            name: "行程",
            type: "effectScatter",
            coordinateSystem: "geo",
            data: [],
            symbolSize: 10,
            showEffectOn: "render",
            rippleEffect: { brushType: "stroke" },
            itemStyle: {
              color: "#ddb926",
              shadowBlur: 10,
              shadowColor: "#333",
            },
          },
        ],
      };
      chart.setOption(option);

      // 地图拖拽/缩放时重新计算 DOM 卡片位置（加防抖避免频繁���发）
      let georoamTimer = null;
      chart.on("georoam", () => {
        if (georoamTimer) clearTimeout(georoamTimer);
        georoamTimer = setTimeout(() => {
          const events = this._currentDisplayData || [];
          this.updateMapCards(events);
        }, 100);
      });

      // 初始化：默认定位到当前日期
      this.initTimelinePosition();
      this.scrollTimelineTo(this.currentTickIndex);
      this.updateMapByDate(this.currentDate);
    },

    /**
     * 根据数据范围 + 当前日期，构建覆盖完整年份的每日时间轴
     * 取数据最小年份1月1日 ~ 当前年份12月31日
     */
    initTimelineFromData() {
      const allData = CharacterTrends.value || [];
      const now = new Date();
      const currentYear = now.getFullYear();
      let minYear = currentYear;
      let maxYear = currentYear;
      // 从数据中找最早和最晚的年份
      allData.forEach((item) => {
        if (item.time) {
          const y = parseInt(item.time.substring(0, 4));
          if (y < minYear) minYear = y;
          if (y > maxYear) maxYear = y;
        }
      });
      this.startDate = minYear + "-01-01";
      this.endDate = maxYear + "-12-31";
      this.timelineTicks = generateTicks(this.startDate, this.endDate, "day");
      this.initTimelinePosition();
    },

    /**
     * 将时间轴指针定位到当前日期（today），如果 today 超出范围则取最近端
     */
    initTimelinePosition() {
      if (!this.timelineTicks.length) return;
      const idx = this.timelineTicks.findIndex((t) => t.date >= this.currentDate);
      if (idx >= 0) {
        this.currentTickIndex = idx;
        this.currentDate = this.timelineTicks[idx].date;
      } else {
        // currentDate 超出时间轴末尾，定位到最后
        this.currentTickIndex = this.timelineTicks.length - 1;
        this.currentDate = this.timelineTicks[this.currentTickIndex].date;
      }
    },

    /**
     * 按日期筛选数据并更新地图
     * 窗口模式：仅显示 [currentDate - 7天, currentDate + 7天] 内的数据
     */
    updateMapByDate(dateStr) {
      const chart = this.chartInstance;
      if (!chart) return;

      const allData = CharacterTrends.value || [];
      // 计算前后一周范围
      const cur = new Date(dateStr);
      const weekBefore = new Date(cur);
      weekBefore.setDate(cur.getDate() - 7);
      const weekAfter = new Date(cur);
      weekAfter.setDate(cur.getDate() + 7);
      const fmt = (d) => d.getFullYear() + "-" +
        String(d.getMonth() + 1).padStart(2, "0") + "-" +
        String(d.getDate()).padStart(2, "0");
      const rangeStart = fmt(weekBefore);
      const rangeEnd = fmt(weekAfter);

      const filteredData = allData.filter((item) => item.time >= rangeStart && item.time <= rangeEnd);

      // 保存当前显示的数据，供 georoam 时刷新位置用
      this._currentDisplayData = filteredData;

      // 更新散点
      chart.setOption({
        series: [
          {
            data: filteredData.map((item) => ({
              value: [item.lon, item.lat],
              time: item.time,
              text: item.text,
              avatar: item.avatar,
              itemStyle: { color: "#ffcc00" },
            })),
          },
        ],
      });

      // 更新 DOM 浮窗卡片位置
      this.updateMapCards(filteredData);
    },

    /**
     * 根据事件数据更新 DOM 浮窗卡片（绝对定位在地图上方）
     */
    updateMapCards(events) {
      const chart = this.chartInstance;
      if (!chart) {
        this.mapCards = [];
        return;
      }
      this.mapCards = events.map((event) => {
        const pos = chart.convertToPixel({ geoIndex: 0 }, [event.lon, event.lat]);
        if (!pos) return null;
        return {
          x: pos[0] - 170,
          y: pos[1] - 72,
          avatar: event.avatar,
          time: event.time,
          text: event.text,
        };
      }).filter(Boolean);
    },

    /**
     * 地图拖拽/缩放后，重新计算 DOM 卡片位置
     */
    refreshGraphicPositions() {
      const events = this._currentDisplayData || [];
      this.updateMapCards(events);
    },

  },
};
</script>

<style lang="scss" scoped>
.map-wrapper {
  width: 1600px;
  position: relative;
  font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
}

.map-container {
  width: 100%;
  height: 551px;
  position: relative;
  z-index: 1;
}

/* ========== 地图浮窗卡片 ========== */
.map-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 551px;
  pointer-events: none;
  z-index: 2;
  overflow: hidden;
}

.map-card {
  position: absolute;
  width: 339px;
  height: 72px;
  pointer-events: auto;
}

.map-card__bg {
  position: absolute;
  top: 0;
  left: 0;
  width: 339px;
  height: 72px;
  object-fit: fill;
}

.map-card__avatar {
  position: absolute;
  top: 9px;
  left: 10px;
  width: 42px;
  height: 42px;
  border: 2px solid #1890ff;
  border-radius: 2px;
  object-fit: cover;
  z-index: 1;
}

.map-card__info {
  position: absolute;
  top: 9px;
  left: 64px;
  z-index: 1;
}

.map-card__time {
  font-size: 16px;
  font-weight: 800;
  color: rgba(5, 95, 194, 1);
  line-height: 1.2;
}

.map-card__text {
  font-size: 16px;
  font-weight: 400;
  color: rgba(59, 65, 75, 1);
  line-height: 1.4;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 260px;
}

/* ========== 时间线面板 ========== */
.timeline-panel {
  width: 1231px;
  margin: -50px auto 0 182px;
  background: #fff;
  border: 1px solid #c8d8e8;
  border-radius: 2px;
  overflow: hidden;
  position: relative;
  z-index: 10;
}

/* ========== 控制栏 ========== */
.control-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 42px;
  padding: 0 12px;
}

/* -- 左侧按钮组 -- */
.control-left {
  display: flex;
  align-items: center;
  gap: 8px;
}

.ctrl-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 34px;
  height: 28px;
  padding: 0 8px;
  border: 1px solid #04295a;
  border-radius: 2px;
  background-color: #fff;
  color: #04295a;
  cursor: pointer;
  transition: all 0.15s;
  position: relative;
  z-index: 11;
  pointer-events: auto;

  &:hover {
    background-color: #eaf2fd;
  }
  &:active {
    background-color: #d4e3f5;
  }
}

.ctrl-btn--text {
  font-size: 14px;
  font-weight: 400;
  padding: 0 14px;
  color: #04295a;
  letter-spacing: 2px;
}

/* -- 右侧控制区 -- */
.control-right {
  display: flex;
  align-items: center;
  gap: 8px;
}

.control-label {
  font-size: 14px;
  color: #04295a;
  white-space: nowrap;
  user-select: none;
}

.control-label--gap {
  margin-left: 24px;
}

/* Element Plus 日期选择器 */
.timeline-date-picker {
  width: 160px !important;

  :deep(.el-input__wrapper) {
    height: 28px;
    border-radius: 2px;
    box-shadow: 0 0 0 1px #b8cde5 inset !important;
    background: #fff;
    padding: 0 8px;
  }
  :deep(.el-input__inner) {
    font-size: 14px;
    color: #04295a;
    font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
  }
  :deep(.el-input__prefix) {
    display: none;
  }
}

/* Element Plus 下拉选择器 */
.timeline-select {
  width: 80px !important;

  :deep(.el-input__wrapper) {
    height: 28px;
    border-radius: 2px;
    box-shadow: 0 0 0 1px #b8cde5 inset !important;
    background: #fff;
    padding: 0 8px;
  }
  :deep(.el-input__inner) {
    font-size: 14px;
    color: #04295a;
    font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
  }
}

/* ========== 可滚动时间轴 ========== */
.timeline-track {
  width: 100%;
  height: 32px;
  background-color: #6593d7;
  position: relative;
  overflow: hidden;
  user-select: none;
  cursor: grab;

  &:active {
    cursor: grabbing;
  }

  /* 顶部 1px 白色横线 */
  &::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 1px;
    background: rgba(255, 255, 255, 0.85);
    z-index: 5;
    pointer-events: none;
  }
}

.timeline-inner {
  height: 100%;
  position: relative;
  display: flex;
  align-items: flex-start;
}

.tick-cell {
  flex-shrink: 0;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  cursor: pointer;
  position: relative;

  &:hover {
    background-color: rgba(255, 255, 255, 0.08);
  }
}

.tick-line {
  width: 1px;
  height: 5px;
  background: rgba(255, 255, 255, 0.35);
  margin-top: 2px;
}

.tick-line--major {
  height: 8px;
  background: rgba(255, 255, 255, 0.6);
}

.tick-label {
  position: absolute;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 11px;
  color: #fff;
  white-space: nowrap;
  pointer-events: none;
  font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
  line-height: 1;
}

.timeline-cursor {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  background: #fff;
  box-shadow: 0 0 4px rgba(255, 255, 255, 0.8);
  z-index: 6;
  transform: translateX(-50%);
  pointer-events: none;
}
</style>
