<script>
import { getGlobalSocket } from "@/utils/socket";
import { emitAppReturnFromBackground } from "@/utils/backgroundRefreshBus.js";
import { registerDeviceApi, unregisterDeviceApi } from "@/utils/api.js";
import { getDeviceFingerprint } from "@/utils/deviceFingerprint.js";
import { initPreloadApi, setPreloadApi, getPreloadApi } from '@/utils/preloadApi/index'
import { preloadConfig } from '@/config/preload.config'
// 静态导入(而非动态):Vite 警告"scanLaunch.js 同时被静态和动态导入"
// 既然 Header.vue 已经静态引入,这里也用静态避免冲突,模块直接进主 bundle
import { onDeepLinkTo } from '@/utils/scanLaunch.js'
import { clearAllSandboxTmpFiles } from '@/utils/ioPath.js'

// 记录上次隐藏时间的 storage key
const HIDE_TIME_KEY = "app_last_hide_time";

// 推送事件去抖:同 (type, notification_id) 在 PUSH_DEBOUNCE_MS 毫秒内只处理一次
// (避免系统重投/重复抵达)—— P1-4 修复:之前 keyed-by-type 导致
// "不同 nid 的同 type 推送"被丢弃,现在按 (type, nid) 区分。
const PUSH_DEBOUNCE_MS = 10000;
const recentByType = {};

// #ifdef APP-PLUS
/**
 * 解析 plus.runtime 传入的 deep link,匹配 /verify/ 时转发给 scanLaunch.onDeepLinkTo。
 * - iOS UniversalLinks: options.path = '/verify/12345'
 * - Android App Links : options.url   = 'https://topfans.online/verify/12345'
 * 调用方:onLaunch#handleLaunchOptions(plus.runtime.launchOptions) 冷启动,
 *        onLaunch#newintent listener  运行期 Android 唤起,
 *        onShow#handleLaunchOptions(plus.runtime.arguments) 运行期 iOS 唤起。
 *
 * ★ P1.1 修复:plus.runtime.arguments 在 app 生命周期内是 sticky 的,
 *   Universal Link 触发一次后,URL 会保留,后续每次 onShow 都会重处理,
 *   把用户从当前页拽回验真页。这里用 lastConsumedDeepLink 做"已消费"标记,
 *   只在 URL 变化时才重新派发,避免误跳。
 */
let lastConsumedDeepLink = ''
function handleLaunchOptions(options) {
  if (!options) return
  // 兼容多种形态:Universal Links(iOS object {path})、App Links(Android object {url})、
  // 自定义 URL scheme(可能是 string "topfans://verify/12345" 或 object {url: "..."})
  let raw = ''
  if (typeof options === 'string') {
    raw = options
  } else if (typeof options === 'object') {
    raw = options.url || (options.path ? `https://topfans.online${options.path}` : '')
  }
  if (!raw || !raw.includes('/verify/')) return
  if (raw === lastConsumedDeepLink) return  // 跳过重复
  lastConsumedDeepLink = raw
  onDeepLinkTo(raw)
}
// #endif

export default {
  // 全局内存级标记，配合 uni.storage 中的 needs_welcome 一起判定
  // TopfansWelcome 是否展示。onLaunch 触发时重置为 false（冷启动 = 新会话），
  // 进入 square 后由 handleEnterTopfans 设为 true（本次会话内不再展示）。
  globalData: {
    welcomeShownThisSession: false,
    // 冷启动时是否需要跳转一键登录（onLaunch 设置，onShow 消费后立即清除）
    shouldShowQuickLogin: false,
  },
  onLaunch: function () {
    console.log("App Launch");

    // 冷启动时检查本地 token，标记是否需要跳转到一键登录页
    // 仅 onLaunch 触发（冷启动/app 销毁后重新打开），后台切回前台不触发
    // 通过 HIDE_TIME_KEY 判断 App 是否短时间内被系统重启（卡顿/网络切换/内存回收）：
    // 若切后台 5 分钟内又触发 onLaunch，说明是系统被动重启，跳过 quickLogin 保持原页面
    const token = uni.getStorageSync("access_token");
    const lastHideTime = uni.getStorageSync(HIDE_TIME_KEY) || 0;
    const RECENT_RESTART_THRESHOLD = 5 * 60 * 1000; // 5 分钟
    const isRecentRestart = Date.now() - lastHideTime < RECENT_RESTART_THRESHOLD;
    this.globalData.shouldShowQuickLogin = !!token && !isRecentRestart;

    // 系统被动重启时，清除 needs_welcome，防止回到 square 时误触发 TopfansWelcome
    // （welcomeShownThisSession 会被重置为 false，若 needs_welcome 还在 storage 就会弹欢迎页）
    if (isRecentRestart) {
      uni.removeStorageSync("needs_welcome");
    }
    // 【TopfansWelcome 会话判定】
    // 触发场景（即用户期望展示欢迎页的入口）：
    //   1) "后台关闭才会在打开"：用户在后台杀掉进程后重新打开 → onLaunch 触发 → 新会话
    //   2) "每次第一次启动"：冷启动 / 全新安装后首次启动 → onLaunch 触发 → 新会话
    // 不触发的场景：
    //   - 仅按 home 键前后台切换（onShow 而非 onLaunch）→ 标记保持原值 → 不展示
    //   - 用户已经点过"进入 TOPFANS 世界" → 标记为 true → 不展示
    // 注：storage 中的 needs_welcome 仍保留，由登录/注册流程负责写入，
    // 只有 app 销毁（卸载/清缓存）才会随本地存储一起丢失。
    this.globalData.welcomeShownThisSession = false;
    // 启动早期 bootstrap 设备指纹（spec §9.1 v2.3 — X-Device-Fingerprint）
    // getDeviceFingerprint 内部自包含 setStorageSync，但显式调一次可避免首次网络请求走临时生成
    getDeviceFingerprint();
    // 不在这里初始化 AI Chat 连接，由各页面自行管理

    // ★ 新增：初始化预加载 API
    try {
      const api = initPreloadApi(preloadConfig)
      setPreloadApi(api)
      api.warmStartup()
    } catch (e) {
      console.warn('[preload] init failed:', e.message)
    }

    // ★ 启动清理:清掉旧版本产生的 doc/<业务>/tmp/ 历史残留(升级新包后第一次启动生效)
    // fire-and-forget,不阻塞 onLaunch 其它初始化
    // #ifdef APP-PLUS
    clearAllSandboxTmpFiles().catch((e) => {
      console.warn('[App] clearAllSandboxTmpFiles failed:', e?.message)
    })
    // #endif

    // ★ 新增：清理历史下载的更新包（见 upgrade-popup.vue:_downloads/upgrade_*.wgt 累积）
    this.cleanupUpgradePackages();

    this.setPermissions();

    // #ifdef APP-PLUS
    // 冷启动 deep link:iOS UniversalLinks / Android App Links 通过 launchOptions 传入
    handleLaunchOptions(plus.runtime.launchOptions || {})
    // 运行期 deep link:Android 通过 newintent 事件接收(冷启动完成后,用户从浏览器/分享唤起 App)
    plus.globalEvent.addEventListener('newintent', e => {
      handleLaunchOptions(e.intent?.data || {})
    })
    // #endif
  },
  onShow: function () {
    console.log("App Show");

    // 冷启动时如果有本地 token，跳转到一键登录页（仅首次 onShow 执行一次）
    // 后台切回前台时 shouldShowQuickLogin 为 false，不跳转，直接回到挂载时的页面
    if (this.globalData.shouldShowQuickLogin) {
      this.globalData.shouldShowQuickLogin = false;
      // 清除 needs_welcome，避免 App 重启后 quickLogin → square 误触发 TopfansWelcome
      // 首次登录/注册的欢迎页由 login.vue / selectRole.vue 各自负责写入
      uni.removeStorageSync("needs_welcome");
      // 即使跳转到一键登录，仍需初始化推送服务和清除角标
      this.getAllNotice();
      this.clearBadgeAndNotifications();
      uni.reLaunch({ url: "/pages/login/quickLogin" });
      return;
    }

    this.handleBackgroundReturn();

    // ★ 新增：idle 预拉
    try {
      const api = getPreloadApi()
      if (api) api.warmIdle()
    } catch (e) {
      // silent
    }

    // 从后台切回前台时重新初始化 WebSocket（onHide 中已关闭）
    this.initWebSocket();

    this.getAllNotice();

    // 打开 App 时清除图标角标和通知栏
    this.clearBadgeAndNotifications();

    // #ifdef APP-PLUS
    // iOS UniversalLinks 在 App 已运行时被唤起,参数通过 plus.runtime.arguments 传入;
    // Android 走 onLaunch 注册的 newintent listener。
    handleLaunchOptions(plus.runtime.arguments || {})
    // #endif
  },
  onHide: function () {
    console.log("App Hide");
    // 关闭所有 WebSocket 连接
    this.closeWebSocket();
    // 记录切到后台的时间，用于 onShow 时判断是否"从后台返回"
    uni.setStorageSync(HIDE_TIME_KEY, Date.now());

    // 应用进入后台时创建本地通知
    this.getAllNotice();
  },
  methods: {
    initWebSocket() {
      const token = uni.getStorageSync("access_token");
      if (token) {
        console.log("初始化全局 WebSocket 连接");
        const globalSocket = getGlobalSocket();
        globalSocket.init(token);
      }
    },
    closeWebSocket() {
      console.log("关闭全局 WebSocket 连接");
      const globalSocket = getGlobalSocket();
      globalSocket.closeAll();
    },
    /**
     * 清理历史下载的更新包（_downloads/upgrade_*.{wgt,apk,ipa}）
     * - uni-upgrade-center-app 插件的 plus.downloader 默认把包放在 _downloads/upgrade_${ts}.{wgt|apk}
     * - 升级弹窗关闭/下载中断/未点击安装时,这些临时包不会自动清理,会持续累积
     * - 已通过 saveFile() 持久化的包在 _doc/ 目录下(路径不同),由升级弹窗 checkLocalStoragePackage 控制
     *
     * 各平台实际会产生哪些文件:
     *   - Android 整包更新 → .apk
     *   - Android wgt 热更新 → .wgt
     *   - iOS     wgt 热更新 → .wgt (iOS 整包 (ipa) 走 App Store,不下载本地)
     *   - ipa 加入正则仅作防御:防止后端配置错误/silent update 误推到本地
     *
     * 启动期清理一次即可(App 正常运行期间只要不离 _downloads/ 目录就不会有新遗留)
     * 仅 APP-PLUS 有效
     */
    cleanupUpgradePackages() {
      // #ifdef APP-PLUS
      try {
        const dirUrl = plus.io.convertLocalFileSystemURL("_downloads/");
        plus.io.resolveLocalFileSystemURL(
          dirUrl,
          (dirEntry) => {
            const reader = dirEntry.createReader();
            reader.readEntries(
              (entries) => {
                entries.forEach((entry) => {
                  // 命名规则:见 uni-upgrade-center-app/pages/upgrade-popup.vue:_downloads/upgrade_${Date.now()}.{wgt|apk}
                  // ipa 在标准流程不会出现,但加入正则作防御
                  if (!/^upgrade_\d+\.(wgt|apk|ipa)$/i.test(entry.name)) return;
                  entry.remove(
                    () => {},
                    (e) =>
                      console.warn(
                        "[UPGRADE-CLEANUP] 删包失败:",
                        entry.fullPath,
                        e?.message,
                      ),
                  );
                });
              },
              (e) =>
                console.warn(
                  "[UPGRADE-CLEANUP] 枚举 _downloads 失败:",
                  e?.message,
                ),
            );
          },
          () => {
            // _downloads 目录不存在是正常情况(从未下过更新包),静默忽略
          },
        );
      } catch (e) {
        console.warn("[UPGRADE-CLEANUP] 初始化失败:", e?.message);
      }
      // #endif
    },
    /**
     * 处理"从后台切回前台"事件
     * 通过 storage 中的上次隐藏时间判断本次 onShow 是否由后台返回触发
     * 若是，则通知所有订阅了 useBackgroundRefresh 的页面执行刷新
     */
    handleBackgroundReturn() {
      const lastHide = uni.getStorageSync(HIDE_TIME_KEY) || 0;
      if (!lastHide) return;
      // 清理标记，避免下次普通 onShow（如首次启动）误触发
      uni.removeStorageSync(HIDE_TIME_KEY);
      // 时间异常保护
      if (Date.now() - lastHide < 0) return;
      emitAppReturnFromBackground();
    },
    // 获取消息列表，刚进页面时，在钩子内触发
    getAllNotice() {
      // 1 获取客户端推送标识信息 cid , 必须要获取到cid后才能接收推送信息
      uni.getPushClientId({
        success: (res) => {
          // 将获取到的cid存起来，方便其它页面从缓存中获取
          uni.setStorageSync("cid", res.cid);
          // console.log("客户端推送标识:", res.cid);

          // 1.1 把 cid + 设备信息上报给后端,后端写入 user_devices 表;
          //     后续后端推送时按 user_id 查这张表拿到 cid 列表。
          // 静默失败即可:即使后端没收到,App 仍能正常接收推送,只是通知中心数据对不齐。
          this.reportCidToServer(res.cid);
        },
        fail: (err) => {
          console.warn("getPushClientId failed", err);
        },
      });

      // 2 启动监听推送消息事件
      uni.onPushMessage((res) => {
        const { type, data } = res;
        if (type == "click") {
          // console.log('"click"-从系统推送服务点击消息启动应用事件；', res);
          // V1.2.7 优先走 mailbox 站内信聚合页(新功能);focus+nid 命中则跳到具体消息
          const focus =
            res.data?.payload?.focus ||
            res.data?.focus ||
            data?.payload?.focus;
          const nid =
            res.data?.payload?.nid ||
            res.data?.nid ||
            data?.payload?.nid;
          if (focus && nid) {
            setTimeout(() => {
              uni.navigateTo({
                url: `/pages/mailbox/index?focus=${encodeURIComponent(focus)}&nid=${encodeURIComponent(nid)}`,
              });
            }, 1000);
          } else if (data?.payload?.url) {
            setTimeout(() => {
              uni.navigateTo({
                url: data.payload.url,
              });
            }, 1000);
          } else {
            console.log(data);
            uni.reLaunch({
              //   url: "/pagesA/index/index",
            });
          }
        }
        if (type == "receive") {
          // V1.2.5 修正确认:msg.payload 是 stringified JSON,内含 .title/.content/.data
          console.log('"receive"-应用从推送服务器接收到推送消息事件', res);
          try {
            // uni.onPushMessage 的 res 形态:{ type, data: { payload: '...string', title, content, ... } }
            // 不同渠道(UniPush/厂商通道)payload 字段位置可能不同,做兼容读取
            const payloadStr =
              res.data?.payload || res.payload || data?.payload || "";
            const envelope = payloadStr ? JSON.parse(payloadStr) : {};
            const payload = envelope?.data || data || {};
            const t = payload.type;
            const nid = payload.notification_id || "";
            const debounceKey = `${t}:${nid}`;
            const now = Date.now();
            // 10s 同 (type, notification_id) 去抖,避免系统重投导致重复 dispatch
            // (P1-4:原 key=type 会丢不同的 nid,改为组合 key)
            if (debounceKey && recentByType[debounceKey] && now - recentByType[debounceKey] < PUSH_DEBOUNCE_MS) {
              console.log("[push] debounce skip", debounceKey);
              return;
            }
            if (debounceKey) recentByType[debounceKey] = now;
            // 派发到 mailbox store:更新未读计数 + (若在站内信页)PREPEND_ITEM
            this.$store.dispatch("mailbox/applyPushPayload", {
              data: payload,
              title: envelope.title || res.data?.title || data?.title || "",
              content:
                envelope.content || res.data?.content || data?.content || "",
            });
          } catch (e) {
            console.error("[push] receive parse fail", e);
          }
        }
      });
    },

    /**
     * 上报 cid 给后端,失败不抛出(纯 best-effort)。
     * 仅在已登录时才上报(JWT 在 storage);未登录时跳过,登录后再 onShow 触发一次。
     */
    async reportCidToServer(cid) {
      if (!cid) return;
      const token = uni.getStorageSync("access_token");
      if (!token) return;
      try {
        const sys = uni.getSystemInfoSync();
        await registerDeviceApi({
          cid,
          // plus.os.name 在 APP-PLUS 下存在;其他平台兜底取 sys.platform
          platform: (sys.osName || sys.platform || "").toLowerCase(),
          appVersion: sys.appVersion || "",
          deviceModel: sys.model || ""
        });
        // console.log("cid reported to server ok");
      } catch (err) {
        console.warn("cid reported to server failed:", err);
      }
    },

    /**
     * 登出时注销当前用户所有推送设备(传空 cid)。
     * 调用方示例:store/modules/user.js LogoutAction 完成后 await this.unregisterAllDevices()
     */
    async unregisterAllDevices() {
      try {
        await unregisterDeviceApi("");
      } catch (err) {
        console.warn("unregisterAllDevices failed:", err);
      }
    },

    /**
     * 清除 App 图标角标 + 系统通知栏中本应用的通知。
     * - iOS:plus.runtime.setBadgeNumber(0) 即可清除角标。
     * - Android:setBadgeNumber(0) 对部分厂商(华为/小米/OPPO/vivo/荣耀)有效;
     *           不生效的机型由 UniPush SDK 推送时按未读数自行维护;
     *           额外调用 NotificationManager.cancelAll() 清掉通知栏遗留的通知。
     * 调用时机:onShow(用户进入 App 时)。
     */
    clearBadgeAndNotifications() {
      // #ifdef APP-PLUS
      try {
        // 1. 清除桌面图标角标
        plus.runtime.setBadgeNumber(0);
      } catch (e) {
        console.warn("setBadgeNumber(0) failed:", e);
      }

      // 2. 清除系统通知栏中本应用的所有通知(Android)
      if (plus.os.name === "Android") {
        try {
          const main = plus.android.runtimeMainActivity();
          const Context = plus.android.importClass("android.content.Context");
          const notificationManager = main.getSystemService(
            Context.NOTIFICATION_SERVICE
          );
          if (notificationManager) {
            plus.android.importClass("android.app.NotificationManager");
            if (typeof notificationManager.cancelAll === "function") {
              notificationManager.cancelAll();
            }
          }
        } catch (e) {
          // 部分 Android 运行时未暴露 cancelAll，跳过即可，避免 onShow 持续报错。
        }
      }
      // #endif
    },

    setPermissions() {
      // #ifdef APP-PLUS
      if (plus.os.name == "Android") {
        // 判断是Android
        var main = plus.android.runtimeMainActivity();
        var pkName = main.getPackageName();
        var uid = main.getApplicationInfo().plusGetAttribute("uid");
        var NotificationManagerCompat = plus.android.importClass(
          "android.support.v4.app.NotificationManagerCompat",
        );
        //android.support.v4升级为androidx
        if (NotificationManagerCompat == null) {
          NotificationManagerCompat = plus.android.importClass(
            "androidx.core.app.NotificationManagerCompat",
          );
        }
        var areNotificationsEnabled =
          NotificationManagerCompat.from(main).areNotificationsEnabled();
        // 未开通‘允许通知’权限，则弹窗提醒开通，并点击确认后，跳转到系统设置页面进行设置
        if (!areNotificationsEnabled) {
          uni.showModal({
            title: "通知权限开启提醒",
            content: "您还没有开启通知权限，无法接受到消息通知，请前往设置！",
            showCancel: false,
            confirmText: "去设置",
            success: function (res) {
              if (res.confirm) {
                var Intent = plus.android.importClass("android.content.Intent");
                var Build = plus.android.importClass("android.os.Build");
                //android 8.0引导
                if (Build.VERSION.SDK_INT >= 26) {
                  var intent = new Intent(
                    "android.settings.APP_NOTIFICATION_SETTINGS",
                  );
                  intent.putExtra("android.provider.extra.APP_PACKAGE", pkName);
                } else if (Build.VERSION.SDK_INT >= 21) {
                  //android 5.0-7.0
                  var intent = new Intent(
                    "android.settings.APP_NOTIFICATION_SETTINGS",
                  );
                  intent.putExtra("app_package", pkName);
                  intent.putExtra("app_uid", uid);
                } else {
                  //(<21)其他--跳转到该应用管理的详情页
                  intent.setAction(
                    Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                  );
                  var uri = Uri.fromParts(
                    "package",
                    mainActivity.getPackageName(),
                    null,
                  );
                  intent.setData(uri);
                }
                // 跳转到该应用的系统通知设置页
                main.startActivity(intent);
              }
            },
          });
        }
      } else if (plus.os.name == "iOS") {
        // 判断是ISO
        var isOn = undefined;
        var types = 0;
        var app = plus.ios.invoke("UIApplication", "sharedApplication");
        var settings = plus.ios.invoke(app, "currentUserNotificationSettings");
        if (settings) {
          types = settings.plusGetAttribute("types");
          plus.ios.deleteObject(settings);
        } else {
          types = plus.ios.invoke(app, "enabledRemoteNotificationTypes");
        }
        plus.ios.deleteObject(app);
        isOn = 0 != types;
        if (isOn == false) {
          uni.showModal({
            title: "通知权限开启提醒",
            content: "您还没有开启通知权限，无法接受到消息通知，请前往设置！",
            showCancel: false,
            confirmText: "去设置",
            success: function (res) {
              if (res.confirm) {
                var app = plus.ios.invoke("UIApplication", "sharedApplication");
                var setting = plus.ios.invoke(
                  "NSURL",
                  "URLWithString:",
                  "app-settings:",
                );
                plus.ios.invoke(app, "openURL:", setting);
                plus.ios.deleteObject(setting);
                plus.ios.deleteObject(app);
              }
            },
          });
        }
      }
      // #endif
    },
  },
};
</script>

<template>
  <view class="app-container"> </view>
</template>

<style>
/*每个页面公共css */

/* 引入 TheMiladiatorRegular 字体 */
@font-face {
  font-family: "TheMiladiatorRegular";
  src: url("/static/fonts/The Miladiator Regular.ttf") format("truetype");
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

/* 引入 ZaoZiGongFangJianHei-1 字体 */
@font-face {
  font-family: "ZaoZiGongFangJianHei-1";
  src: url("/static/fonts/ZaoZiGongFangJianHei-1.ttf") format("truetype");
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

/* 引入 ZaoZiGongFangJianHei-1 字体 */
@font-face {
  font-family: "JDLTYuanTiJian";
  src: url("/static/fonts/JDLTYuanTiJian.ttf") format("truetype");
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

/* 圆体 JDLTYuanTiJian.ttf 在部分 Android WebView 上报 OTS/cmap 解析失败，暂不 @font-face 加载，避免控制台告警与渲染异常 */

/* 全局字体设置 */
body {
  font-family:
    "JDLTYuanTiJian",
    -apple-system,
    BlinkMacSystemFont,
    "PingFang SC",
    "Hiragino Sans GB",
    "Microsoft YaHei",
    "Noto Sans SC",
    sans-serif;
}

/* App 容器 */
.app-container {
  width: 100%;
  min-height: 100vh;
  position: relative;
}

.page-content {
  width: 100%;
  min-height: 100vh;
}
</style>
