<script setup>
import {ref, onMounted, onBeforeUnmount, nextTick, watch} from "vue";
import {onLoad, onShow, onHide} from "@dcloudio/uni-app";
import Container from "@/components/Container.vue";
import BattleHeader from "@/components/BattleHeader.vue";
import BowTarget from "@/components/BowTarget.vue";
import BattleFooter from "@/components/BattleFooter.vue";
import ScreenHint from "@/components/ScreenHint.vue";
import SButton from "@/components/SButton.vue";
import RoundEndTip from "@/components/RoundEndTip.vue";
import TestDistance from "@/components/TestDistance.vue";
import TeamAvatars from "@/components/TeamAvatars.vue";
import ShootProgress2 from "@/components/ShootProgress2.vue";
import SModal from "@/components/SModal.vue";
import {laserCloseAPI, getBattleAPI} from "@/apis";
import {MESSAGETYPESV2} from "@/constants";
import audioManager from "@/audioManager";
import useStore from "@/store";
import {storeToRefs} from "pinia";

const store = useStore();
const {user, online} = storeToRefs(store);
const start = ref(null);
const tips = ref("");
const battleId = ref("");
const currentRound = ref(0);
/** 专用于 RoundEndTip 展示的轮次，与进度条的 currentRound 解耦，避免 ToSomeoneShoot 提前更新 currentRound 导致 Tip 显示错误轮次 */
const roundTipRound = ref(0);
const goldenRound = ref(0);
const currentRedPoint = ref(0);
const currentBluePoint = ref(0);
const scores = ref([]);
const blueScores = ref([]);
const redTeam = ref([]);
const blueTeam = ref([]);
const currentShooterId = ref(0);
const roundResults = ref([]);
const redPoints = ref(0);
const bluePoints = ref(0);
const showRoundTip = ref(false);
const isFinalShoot = ref(false);
const matchStatus = ref(undefined);
const updateRemainSecond = ref(0);
/** 当前对局每人射击时间（秒），从后端 shootTime 字段动态读取，默认 15 */
const shootTimeTotal = ref(15);
/** 对战来源类型（1=好友约战，2=匹配对战），用于结算页分流 */
const battleWay = ref(0);
/** 上一次处理的 ToSomeoneShoot 关键字段，用于去重过滤重复消息 */
const lastToSomeoneShootKey = ref("");
/** 控制设备离线提示弹窗的显示状态 */
const showOfflineModal = ref(false);

/**
 * 监听设备在线状态，比赛进行中设备离线时弹窗提示用户
 */
watch(online, (newVal, oldVal) => {
  if (!newVal && oldVal && start.value === true) {
    showOfflineModal.value = true;
  }
});

const recoverData = (battleInfo, {force = false, arrowOnly = false} = {}) => {
  try {
    battleId.value = battleInfo.matchId;
    // 存储对战来源，供结算跳转分流使用
    if (battleInfo.way !== undefined) battleWay.value = battleInfo.way;

    // 优先使用接口返回的队伍数据，如果没有则尝试从缓存读取（应对匹配刚完成接口未就绪的情况）
    const t1 = battleInfo.teams?.[1] || {};
    const t2 = battleInfo.teams?.[2] || {};

    if (t1.players) blueTeam.value = [...t1.players];
    else {
      const cached = uni.getStorageSync("blue-team");
      if (cached && cached.length) blueTeam.value = cached;
    }

    if (t2.players) redTeam.value = [...t2.players];
    else {
      const cached = uni.getStorageSync("red-team");
      if (cached && cached.length) redTeam.value = cached;
    }

    start.value = battleInfo.status !== 0;

    if (battleInfo.status === 0) {
      const readyRemain = (Date.now() - (battleInfo.serverTime || Date.now())) / 1000;
      if (readyRemain > 0 && readyRemain < 15) {
        setTimeout(() => {
          uni.$emit("update-timer", 15 - readyRemain - 0.2);
        }, 200);
      }
      return;
    }
    if (!arrowOnly) {
      // 在 ToSomeoneShoot 时同步更新当前轮次，使进度条轮次图片及时切换
      currentRound.value = battleInfo.current.round || 1;
      currentShooterId.value = battleInfo.current.playerId;
      const redPlayer = battleInfo.teams[2].players.find(
          (item) => item.id === battleInfo.current.playerId
      );
      let nextTips = redPlayer ? "请红队射箭" : "请蓝队射箭";
      if (force) nextTips += "重回";
      if (
          battleInfo.current.playerId === user.value.id
      ) {
        nextTips += "你";
      }
      // 同队连续出手时 tips 值不变，HeaderProgress watch 不触发音频
      // 需在赋值前比较旧值，手动播放音频确保始终触发 onAudioEnded 启动计时器
      // 注意：使用 interrupt=false 避免打断上一支箭的环数播报音频
      if (tips.value === nextTips && !nextTips.includes("重回")) {
        const audioKey = redPlayer ? "请红方射箭" : "请蓝方射箭";
        audioManager.play(audioKey, false);
      }
      tips.value = nextTips;
      // 同步写入 Pinia store，供 HeaderProgress 通过 immediate watch 在 onMounted 时立即读取
      // 规避 WeChat 小程序 onShow 在组件 onMounted 之前触发导致 uni.$emit 事件丢失的时序问题
      store.updateTips(nextTips);
      // force 模式下改为在 nextTick 内发送 update-tips，规避 WeChat 小程序
      // onShow 与 onMounted 时序问题导致 HeaderProgress 监听器尚未注册的边界情况
      if (!force) {
        uni.$emit("update-tips", nextTips);
      }
      // redPlayer 已在上方 find() 确认：不为 null 则当前射手在红队
      const targetTeam = redPlayer ? 'red' : 'blue';
      // 从后端消息读取本局射击时间，动态设置进度条满值与倒计时秒数（降级兜底 15s）
      shootTimeTotal.value = battleInfo.shootTime ?? 15;
      if (force) {
        // force 模式下 start.value 刚由 null 变 true，Vue 异步调度渲染，
        // ShootProgress2（v-if="start"）尚未挂载，update-remain 事件会被丢弃。
        // 同时 HeaderProgress 对含"重回"的 tips 拦截音频，倒计时无法依赖
        // onAudioEnded 驱动，需在 nextTick 后直接启动。

        // playerId=0 表示后端处于过渡状态（上一位射手已完成、下一位尚未分配）
        // 此时 startTime/targetTeam 均不可靠，停止进度条并等待 WS ToSomeoneShoot 驱动
        if (!battleInfo.current.playerId) {
          nextTick(() => {
            uni.$emit("update-tips", nextTips);
            uni.$emit("update-remain", {stop: true});
          });
          return;
        }

        const elapsed = (Date.now() - battleInfo.current.startTime) / 1000;
        if (elapsed > 0 && elapsed < shootTimeTotal.value) {
          updateRemainSecond.value = shootTimeTotal.value - elapsed - 0.2;
        }
        const actualRemain = updateRemainSecond.value;
        nextTick(() => {
          // 在 nextTick 内统一发送，确保 ShootProgress2 和 HeaderProgress 均已挂载
          uni.$emit("update-tips", nextTips);
          uni.$emit("update-remain", {reset: true, value: shootTimeTotal.value, team: targetTeam});
          // 重置动画完成后，直接启动倒计时（无需依赖音频结束回调）
          setTimeout(() => {
            uni.$emit("update-remain", {stop: false, value: actualRemain, team: targetTeam});
          }, 100);
        });
      } else {
        // ToSomeoneShoot 到达时：若进度条仍在倒计时则瞬间清零再切换满值，确保视觉连贯
        uni.$emit("update-remain", {zeroThenReset: true, value: shootTimeTotal.value, team: targetTeam});
        updateRemainSecond.value = shootTimeTotal.value;
      }
    } else {
      currentRound.value = battleInfo.current.round || 1;
      const latestRound = battleInfo.rounds[currentRound.value - 1];
      if (latestRound) {
        blueScores.value = latestRound.shoots[1];
        scores.value = latestRound.shoots[2];
      }
    }
    roundResults.value = battleInfo.rounds || [];
    isFinalShoot.value = battleInfo.current.goldRound;
    bluePoints.value = battleInfo.teams[1].score;
    redPoints.value = battleInfo.teams[2].score;
  } catch (err) {
    console.log(err);
  }
};

function onAudioEnded(s) {
  // "请红方射箭"/"请蓝方射箭"：队友或对手轮次；"轮到你了"：用户本人轮次（HeaderProgress特殊分支）
  // 三者音频结束后均需启动倒计时进度条
  if (s.indexOf('请红方射箭') >= 0 || s.indexOf('请蓝方射箭') >= 0 || s.indexOf('轮到你了') >= 0) {
    let team;
    if (s.indexOf('请红方射箭') >= 0) team = 'red';
    else if (s.indexOf('请蓝方射箭') >= 0) team = 'blue';
    else team = tips.value.includes('红队') ? 'red' : 'blue'; // 轮到你了：从当前tips判断用户所在队伍
    uni.$emit("update-remain", {stop: false, value: updateRemainSecond.value, team: team});
  }
  if (s.indexOf("比赛结束") >= 0) {
    console.log("比赛结束");
    onBattleEnd()
  }
}

function onBattleEnd() {
  if (matchStatus.value === 2) {
    // 全部跳转到新结算页
    uni.redirectTo({
      url: `/pages/friend-battle-result?battleId=${battleId.value}`,
    });
  }
}

/**
 * @param {object} msg - NewRound 消息
 * @param {number} prevRound - 在 NewRound 消息到达时即时捕获的旧轮次，用于 Tip 展示，避免异步延迟内 currentRound 已被更新
 */
function onNewRound(msg, prevRound) {
  showRoundTip.value = true;
  isFinalShoot.value = msg.current.goldRound;
  // 新轮次开始时立即清空靶纸箭迹，避免残留上一轮落点至新轮次第一箭到来
  scores.value = [];
  blueScores.value = [];
  // 决金轮箭数不确定（平局后可能再加一箭），清零 totalShot 隐藏箭数显示
  if (msg.current.goldRound) {
    store.updateShotInfo(0, 0);
  }
  // 用传入的 prevRound（捕获时刻的旧轮次）展示结算 Tip，与进度条 currentRound 解耦
  roundTipRound.value = prevRound;
  const latestRound = msg.rounds[prevRound - 1];
  if (latestRound) {
    if (isFinalShoot.value) {
      currentBluePoint.value = msg.teams[1].score;
      currentRedPoint.value = msg.teams[2].score;
    } else {
      currentBluePoint.value = latestRound.scores[1].score;
      currentRedPoint.value = latestRound.scores[2].score;
    }
  }
}

async function onReceiveMessage(msg) {
  if (Array.isArray(msg)) return;
  if (msg.type === MESSAGETYPESV2.BattleStart) {
    start.value = true;
  } else if (msg.type === MESSAGETYPESV2.ToSomeoneShoot) {
    // 去重防护：如果 playerId + round 与上一次处理完全相同，则忽略重复推送的消息
    // （防止 onBeforeUnmount 未及时 off 和页面重创建导致同一事件被处理多次）
    const key = `${msg?.current?.playerId}-${msg?.current?.round}`;
    if (key === lastToSomeoneShootKey.value) return;
    lastToSomeoneShootKey.value = key;
    recoverData(msg);
  } else if (msg.type === MESSAGETYPESV2.ShootResult) {
    showRoundTip.value = false;
    recoverData(msg, {arrowOnly: true});
    if (msg.shootData?.threeConsecutive10Rings === true) {
      nextTick(() => audioManager.play("tententen", false));
    }
  } else if (msg.type === MESSAGETYPESV2.NewRound) {
    // 在进入延迟前先捕获当前轮次，供 onNewRound 使用，防止 800ms 内 ToSomeoneShoot 提前更新 currentRound 造成 Tip 展示错轮
    const prevRound = currentRound.value;
    setTimeout(() => {
      onNewRound(msg, prevRound)
    }, 800)
  } else if (msg.type === MESSAGETYPESV2.BattleEnd) {
    matchStatus.value = msg.status;
    if (msg.status === 4) {
      showRoundTip.value = true;
      currentBluePoint.value = 0;
      currentRedPoint.value = 0;
      setTimeout(() => {
        uni.navigateBack();
      }, 2000);
    }
  }
}

onLoad(async (options) => {
  if (options.battleId) battleId.value = options.battleId;
  // 重置箭数和提示文案，防止因 Pinia 保留上一场比赛的旧值而错误展示
  store.updateShotInfo(0, 0);
  store.updateTips("");
  // uni.enableAlertBeforeUnload({
  //   message: "离开比赛可能导致比赛失败，是否继续？",
  //   success: (res) => {
  //     console.log("已启用离开提示");
  //   },
  // });
});
onMounted(async () => {
  uni.setKeepScreenOn({
    keepScreenOn: true,
  });
  uni.$on("socket-inbox", onReceiveMessage);
  uni.$on("audioEnded", onAudioEnded);
  await laserCloseAPI();
});
onBeforeUnmount(() => {
  uni.setKeepScreenOn({
    keepScreenOn: false,
  });
  // 注销所有第三方事件盓听，防止页面重创建时监听叠加导致消息重复处理
  uni.$off("socket-inbox", onReceiveMessage);
  uni.$off("audioEnded", onAudioEnded);
  audioManager.stopAll();
});
const refreshTimer = ref(null);
onShow(async () => {
  if (battleId.value) {
    // 延迟 300ms 等待网络恢复，避免小程序从后台切回时网络尚未就绪导致请求失败报错
    await new Promise(resolve => setTimeout(resolve, 300));
    const result = await getBattleAPI(battleId.value);
    if (!result) return;
    if (result.status === 2) {
      // 比赛已结束（如切后台再回来）：跳新结算页
      uni.redirectTo({
        url: `/pages/friend-battle-result?battleId=${result.matchId}`,
      });
    } else {
      if (result.status !== 0) {
        // 比赛进行中，从后端恢复箭数（测距阶段不展示）
        // 决金轮不展示箭数；其余轮次从 indexMap 按用户 id 取已射箭数
        if (result.shootNumber && !result.current?.goldRound) {
          store.updateShotInfo(result.current?.indexMap?.[user.value.id] ?? 0, result.shootNumber);
        }
      } else {
        // 测距阶段重置箭数和提示文案，防止 ImmediateWatcher 读取 Pinia 保留的旧值
        store.updateShotInfo(0, 0);
        // 同步清空 Pinia tips 与本地 tips，避免重新开赛时 HeaderProgress 展示上一场旧文案
        store.updateTips("");
        tips.value = "";
      }
      recoverData(result, {force: true});
    }
  }
});
</script>

<template>
  <Container :bgType="start ? 3 : 1">
    <view class="container">
      <!-- winner=0 表示对局尚未开始，不显示胜者徽章 -->
      <BattleHeader
          v-if="start === false"
          :redTeam="redTeam"
          :blueTeam="blueTeam"
          :winner="0"
      />
      <TestDistance v-if="start === false" :guide="false" :isBattle="true"/>
      <view v-if="start" class="players-row">
        <TeamAvatars
            :team="blueTeam"
            :isRed="false"
            :currentShooterId="currentShooterId"
        />
        <ShootProgress2
            :tips="tips"
            :total="shootTimeTotal"
            :currentRound="
            goldenRound > 0 ? 'gold' + goldenRound : 'round' + currentRound
          "
        />
        <TeamAvatars :team="redTeam" :currentShooterId="currentShooterId"/>
      </view>
      <BowTarget
          v-if="start"
          mode="team"
          :scores="scores"
          :blueScores="blueScores"
      />
      <BattleFooter
          v-if="start"
          :roundResults="roundResults"
          :redPoints="redPoints"
          :bluePoints="bluePoints"
          :goldenRound="goldenRound"
      />
      <ScreenHint
          :show="showRoundTip"
          :mode="isFinalShoot ? 'tall' : 'normal'"
      >
        <RoundEndTip
            v-if="showRoundTip"
            :isFinal="isFinalShoot"
            :round="roundTipRound"
            :bluePoint="currentBluePoint"
            :redPoint="currentRedPoint"
            :roundData="
            roundResults[roundTipRound - 1] ? roundResults[roundTipRound - 1] : []
          "
            :onAutoClose="()=>{ showRoundTip = false}"
        />
      </ScreenHint>
      <!-- 设备离线提示弹窗 -->
      <SModal
          :show="showOfflineModal"
          :noBg="true"
          height="360rpx"
          :onClose="() => (showOfflineModal = false)"
      >
        <view class="offline-modal">
          <text class="offline-title">设备已离线</text>
          <text class="offline-desc">检测到设备已断开连接，请检查设备后继续比赛</text>
          <SButton @click="showOfflineModal = false">我知道了</SButton>
        </view>
      </SModal>
    </view>
  </Container>
</template>

<style scoped>
.container {
  width: 100%;
  height: 100%;
}

.players-row {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: -2%;
  margin-bottom: 6%;
}

.offline-modal {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 60rpx 40rpx 40rpx;
  gap: 24rpx;
}

.offline-title {
  font-size: 36rpx;
  font-weight: bold;
  color: #FED847;
}

.offline-desc {
  font-size: 28rpx;
  color: #CCCCCC;
  text-align: center;
  line-height: 1.6;
}
</style>
