<template>
	<view class="detail-container">
		<!-- 背景图片 -->
		<image class="background-image" src="/static/square/squearbj.png" mode="aspectFill"></image>

		<!-- Header -->
		<view class="header-bar">
			<view class="back-btn" @tap="handleBack">
				<text class="back-icon">←</text>
			</view>
			<view class="header-actions">
				<ShareReportButtons :ownerNickname="assetData.owner_nickname || ''" :assetId="assetIdParam"
					:coverUrl="coverUrl" :coverKey="assetData.cover_url || assetData.image_url || ''"
					:displayTxHash="displayTxHash" externalCanvasId="shareCanvas" />
			</view>
		</view>

		<!-- 2026-07-28: 提示文案上移到 detail-container 顶层,fixed 定位,
		     渲染在所有 v-if/v-else-if 之外 → expanded 与 normal 两态全程可见。
		     配合 hintText computed 同时识别 isPullActive 与 isCollapsePullActive,
		     同一段文案同时服务下拉放大与上拉收起两套手势 -->
		<text v-if="(isPullActive || isCollapsePullActive) && hintText" class="pull-hint">{{ hintText }}</text>

		<!-- 加载中 -->
		<view v-if="loading" class="loading-wrapper">
			<!-- 旋转光环 -->
			<view class="loading-ring-outer">
				<view class="loading-ring"></view>
			</view>
			<!-- 三颗脉冲粒子 -->
			<view class="loading-dots">
				<view class="loading-dot dot-1"></view>
				<view class="loading-dot dot-2"></view>
				<view class="loading-dot dot-3"></view>
			</view>
			<!-- 文字淡入淡出 -->
			<text class="loading-text">正在加载藏品...</text>
		</view>

		<!-- 错误状态 -->
		<view v-else-if="loadError && !craftConfirmMode" class="error-wrapper">
			<text class="error-text">{{ loadError }}</text>
			<button class="retry-btn" @tap="loadData">重试</button>
		</view>

		<!-- 放大态:完全脱离 scroll-view,父级无滚动,组件手势不被抢占 -->
		<view v-else-if="viewMode === 'expanded'" class="pull-expanded-view">
			<!-- 2026-07-28: 上拉收起手势 surface + transform 镜像容器
			     (symmetric mirror of .card-pull-surface in normal branch)。
			     仅负责触摸事件 + 镜像 transform,内层 AssetCardPullExpand 渲染不变。
			     :is-expanded 固定为 false:让 wrapper 的 collapseCardTransform 提供完整视觉
			     scale(1.6 at rest → 1.0 at max pull),内层始终 scale(1)。原设计
			     (isExpanded && !isCollapsePullActive) 在释放瞬间会让内层从 scale(1) 跳到
			     scale(1.6),叠加 wrapper 的 scale(1) → 出现"先放大再塌缩"双重跳变。
			     新方案下,内层固定 scale(1),wrapper 平滑过渡 scale(1.6) ↔ scale(s) translateY(-pull) -->
			<view class="pull-collapse-surface" :style="{ transform: collapseCardTransform }"
				@touchstart="onCollapseTouchStart" @touchmove="onCollapseTouchMove"
				@touchend="onCollapseTouchEnd" @touchcancel="onCollapseTouchEnd">
				<AssetCardPullExpand
					:cover-url="coverUrl"
					:is-lenticular="isLenticularAsset"
					:lenticular-layers="lenticularLayers"
					:layer-transforms="layerTransforms"
					:simulate="simulate"
					:grade-badge-url="gradeBadgeUrl"
					:stickers="activeStickers"
					:tilt-hint-text="'倾斜手机查看光栅效果'"
					:shimmer-mid-opacity="0.16"
					:card-anim-class="cardWrapperAnimClass"
					:is-expanded="false"
					:show-mask="false"
					@mode-change="onCardModeChange"
					@pull-expand-change="onPullExpandChange"
				/>
			</view>
		</view>

		<!-- 详情内容 -->
		<scroll-view v-else scroll-y class="content-scroll" :show-scrollbar="false" @scroll="onScroll">
			<!-- 2026-07-28 fix: 提示文案必须放在 .card-pull-surface(带 pull transform)之外,
			     否则会跟随卡片一起 scale/translateY,被缩放到屏幕外或藏到卡片后面。
			     2026-07-28 refactor (continued): 此处 <text> 已上移到 detail-container 顶层
			     (位于 header-bar 之后、v-if loading 之前),渲染在所有 v-if/v-else-if 之外,
			     同时服务 normal 态下拉与 expanded 态上拉两套手势。下方保留注释占位以记录迁移。 -->
			<view class="content-wrapper">
				<!-- 藏品卡片区域 -->
				<view class="card-section">
					<!-- 2026-07-27 refactor (Finding A): 触摸事件 + 跟手 transform 改挂在 .card-pull-surface
					     上,避免 .card-wrapper 上 card-3d-flip 动画的 transform 覆盖父级 :style,
					     使下拉跟手反馈可见 -->
					<view class="card-pull-surface" :style="{ transform: cardTransform }"
						@touchstart="onTouchStart" @touchmove="onTouchMove"
						@touchend="onTouchEnd" @touchcancel="onTouchEnd">
						<view class="card-wrapper" :class="[
								{ 'card-wrapper--lenticular': isLenticularAsset },
								cardWrapperAnimClass,
							]">
							<image class="card-frame" src="/static/square/gerenzhongxincangpinkuang.png" mode="aspectFill">
							</image>
							<view v-if="isLenticularAsset" class="detail-lenticular-slot">
								<LenticularCard class="detail-lenticular-card" :layers="lenticularLayers"
									:transforms="layerTransforms" gyro-source="simulation"
									tilt-hint-text="倾斜手机查看光栅效果" :shimmer-mid-opacity="0.16"
									:simulate-tilt-from-normalized="simulate"
									@mode-change="onCardModeChange" />
							</view>
							<image v-else class="card-image" :src="coverUrl" mode="aspectFill"></image>
							<image class="card-badge" :src="gradeBadgeUrl" mode="aspectFit"></image>
							<!-- 贴纸叠加层 -->
							<image v-for="sticker in activeStickers" :key="sticker.id" class="card-sticker"
								:src="sticker.src" mode="aspectFit" :style="getStickerStyle(sticker)" />
						</view>
					</view>

					<!-- 贴纸合成导出隐藏画布 -->
					<canvas v-if="activeStickers.length" canvas-id="stickerCompositCanvas" class="export-canvas"
						:style="{ position: 'fixed', left: '-9999px', top: '-9999px', width: '450px', height: '600px' }" />
				</view>

				<!-- 2026-07-27: 卡片下方的四个 sibling 模块统一包在 .card-siblings 里,
				     通过 opacity 同步 pullDistance 做淡出,接住放大态切换时的视觉接缝 -->
				<view class="card-siblings"
					:style="{ opacity: siblingFadeOpacity, transition: 'opacity .12s linear' }">

					<!-- 点赞 + 收益 + 倒计时行 -->
					<view class="card-meta-row">
						<!-- 点赞 -->
						<view class="like-area" @tap="handleLike">
							<image :src="isLiked ? '/static/icon/like-after.png' : '/static/icon/like-before.png'"
								class="like-icon" mode="aspectFit"></image>
							<text class="like-num">{{ likeCount || 0 }}</text>
						</view>

						<!-- 收益 -->
						<view class="earnings-area">
							<image class="crystal-icon" src="/static/icon/crystal.png" mode="aspectFit"></image>
							<text class="earnings-text">{{ assetData.hourly_earnings || 5 }}/时</text>
						</view>

						<!-- 倒计时（如有） -->
						<view v-if="assetData.display_status === 1 && countdownText" class="countdown-area">
							<view class="countdown-pill">
								<image class="crystal-icon" src="/static/assetDetail/time.png" mode="aspectFit"></image>
								<text class="countdown-val">{{ countdownText }}</text>
							</view>
						</view>
					</view>

					<!-- 信息行（创作者 + 铸造时间 + 来源） -->
					<view class="info-row">
						<view class="info-col">
							<view class="info-item">
								<text class="info-label">来源</text>
								<text class="info-value">{{ assetData.source || '铸造' }}</text>
							</view>
						</view>
						<view class="info-col">
							<view class="info-item">
								<text class="info-label">创作者</text>
								<view class="info-nickname">
									<image v-if="assetData.owner?.avatar" class="info-avatar" :src="assetData.owner.avatar"
										mode="aspectFill">
									</image>
									<text class="info-value">{{ assetData.owner_nickname || '未知' }}</text>
								</view>
							</view>
						</view>
						<view class="info-col">
							<view class="info-item">
								<text class="info-label">铸造时间</text>
								<text class="info-value">{{ formattedAcquireTime }}</text>
							</view>
						</view>
					</view>

					<!-- 创作者信息模块 -->
					<view v-if="likeCount > 0" class="creator-section" @tap="showLikeUsersModal = true">
						<!-- 点赞用户头像区域 -->
						<view class="liked-users-area">
							<view v-for="(user, index) in likedUsers" :key="index" class="liked-user-avatar" :style="{
								transform: `translate(${user.ellipseX || 0}rpx, ${user.ellipseY || 0}rpx) scale(${user.size || 1})`,
								zIndex: index < 2 ? index + 1 : (index < 4 ? index + 3 : index + 1)
							}">
								<image class="liked-user-image" :src="user.avatar" mode="aspectFill"></image>
							</view>
						</view>
						<view class="creator-info">
							<text class="creator-title">TA们最近为你点过赞</text>
							<view class="creator-card">
								<text class="creator-tip">点击查看</text>
								<image class="creator-preview" src="/static/rank/activity-support-icon/tubiao.png"
									mode="aspectFill"></image>
							</view>
						</view>
					</view>

					<!-- 链上数据 -->
					<view class="chain-section">
						<image class="chain-logo" src="/static/logo/APPLOGO.png" mode="aspectFit"></image>
						<view class="chain-left">
							<view class="chain-row">
								<text class="chain-label">数根名称</text>
								<view class="chain-value-wrap">
									<text class="chain-value">{{ assetData.name || '未知' }}</text>
								</view>
							</view>
							<view class="chain-row">
								<text class="chain-label">数根发行方</text>
								<view class="chain-value-wrap">
									<text class="chain-value">TOPFANS</text>
								</view>
							</view>
							<view class="chain-row">
								<text class="chain-label">区块链编号</text>
								<view class="chain-value-wrap">
									<text class="chain-value">{{ showBlockNumber ? (assetData.block_number || '未知') :
										hiddenBlockNumber }}</text>
									<view class="toggle-btn" @tap="showBlockNumber = !showBlockNumber">
										<image class="toggle-icon"
											:src="showBlockNumber ? '/static/icon/show.png' : '/static/icon/hide.png'"
											mode="aspectFit"></image>
									</view>
								</view>
							</view>
							<view class="chain-row">
								<text class="chain-label">链上哈希</text>
								<view class="chain-value-wrap">
									<text class="chain-value chain-hash" @longpress="copyHash">{{ showTxHash ? displayTxHash
										: hiddenTxHash }}</text>
									<view class="toggle-btn" @tap="showTxHash = !showTxHash">
										<image class="toggle-icon"
											:src="showTxHash ? '/static/icon/show.png' : '/static/icon/hide.png'"
											mode="aspectFit"></image>
									</view>
								</view>
							</view>
						</view>
					</view>
				</view>

				<!-- 完整链上哈希显示遮罩层 -->
				<view v-if="showTxHash" class="txhash-mask" @tap="showTxHash = false">
					<view class="txhash-popup" @tap.stop>
						<view class="txhash-popup-header">
							<text class="txhash-popup-title">链上哈希</text>
							<view class="txhash-popup-close" @tap="showTxHash = false">
								<image class="close-icon" src="/static/icon/hide.png" mode="aspectFit"></image>
							</view>
						</view>
						<text class="txhash-popup-content" selectable @longpress="copyHash">{{ displayTxHash }}</text>
					</view>
				</view>
			</view>
		</scroll-view>

		<!-- 点赞用户弹窗 -->
		<LikeUsersModal :visible="showLikeUsersModal" :tabs="[]" @close="showLikeUsersModal = false">
			<template #content>
				<view class="like-users-list">
					<view v-for="(user, index) in allLikedUsers" :key="index" class="like-user-item">
						<image class="like-user-avatar" :src="user.avatar" mode="aspectFill"></image>
						<view class="like-user-info">
							<text class="like-user-name">{{ user.nickname }}</text>
						</view>
						<!-- 拜访按钮 -->
						<view class="visit-button" @tap="handleVisit(user)">
							<image class="visit-icon" src="/static/square/dianjibaifang.png" mode="aspectFit" lazy-load>
							</image>
							<text class="visit-text">点击拜访</text>
						</view>
					</view>
				</view>
			</template>
		</LikeUsersModal>

		<!-- 举报弹窗（也由 ShareReportButtons 触发，asset-detail 内显式声明以便直接控制） -->
		<ReportModal
			:visible="showReportModal"
			:assetId="assetIdParam"
			@close="showReportModal = false"
			@submit="handleReportSubmit"
		/>

		<!-- 离屏 canvas(放 page 级,uni-app 组件内 canvas 不被 createCanvasContext 识别)
		     ⚠️ width/height 必须设 HTML 属性(决定 native canvas surface 尺寸),
		     CSS display:none + rpx 单位会让 surface = 0 → drawImage 报 'width or height of 0' -->
		<canvas
			canvas-id="shareCanvas"
			id="shareCanvas"
			:width="750"
			:height="1334"
			class="share-canvas"
		/>
	</view>
</template>

<script setup>
import { ref, computed, onUnmounted } from 'vue';
import { onLoad, onShow, onHide, onUnload } from '@dcloudio/uni-app';
import { getAssetDetailApi, getMintOrderDetailApi, likeAssetApi, unlikeAssetApi, getAssetMaterialsApi, getAssetLikersApi } from '@/utils/api.js';
import { reportEvent } from '@/utils/task-api.js';
import { getPreloadApi } from '@/utils/preloadApi/index';
import { getAssetCoverRealUrl } from '@/utils/assetImageHelper.js';
import LikeUsersModal from '@/pages/components/LikeUsersModal.vue';
import ShareReportButtons from '@/pages/components/ShareReportButtons.vue';
import ReportModal from '@/pages/components/ReportModal.vue';
import LenticularCard from '@/components/lenticular/LenticularCard.vue';
import AssetCardPullExpand from '@/components/AssetCardPullExpand/AssetCardPullExpand.vue';
import { useLenticularCraftTiltPreview } from '@/composables/useLenticularCraftTiltPreview.js';
import {
	buildLenticularLayersTwo,
	buildLenticularLayers,
	LENTICULAR_STUDIO_STORAGE_KEY,
	CRAFT_LENTICULAR_CN,
	CRAFT_LASER_CARD_CN,
	CRAFT_TAG_LENTICULAR,
} from '@/utils/castloveMintForm.js';
import {
	CASTLOVE_FORM_KEY,
	CRAFT_SELECTED_IMAGE_KEY,
	STUDIO_LENTICULAR,
	STUDIO_LASER,
} from '@/utils/castloveGenerationFlow.js';
import { submitCraftMintFromPath } from '@/utils/craftMintSubmit.js';
import { composeStickers, relationsToStickers } from '@/utils/sticker-compositor.js';
import { getStoredUser } from '@/utils/getStoredUser.js';
// 页面参数
const assetIdParam = ref('');
const orderIdParam = ref('');
const fromParam = ref('');
const studioKindParam = ref('');
const lastLoadKey = ref('');

const craftConfirmMode = ref(false);
const craftFormData = ref({});
const craftCoverUrl = ref('');
const craftMinting = ref(false);
const lenticularLayers = ref([]);
const {
	layerTransforms,
	simulate,
} = useLenticularCraftTiltPreview(lenticularLayers);

// 卡片当前控制模式：'auto' = 自动摆动中；'manual' = 用户接管中
// 用来决定外层 card-wrapper 是否启用 card-3d-flip 动画（manual 时关闭避免和手动控制冲突）
const cardMode = ref('auto')
function onCardModeChange(mode) {
	cardMode.value = mode
}
const cardWrapperAnimClass = computed(() => cardMode.value === 'auto' ? 'card-wrapper--auto-flip' : '')

// 视图模式: 'normal' = 列表滚动卡片 / 'expanded' = 全屏放大态
const viewMode = ref('normal');

function onPullExpandChange({ state }) {
	viewMode.value = state;
}

// 2026-07-27 refactor: 下拉手势相关状态从 AssetCardPullExpand 组件上移到本页面,
// 关键原因见 Block 1:normal 态下 .card-wrapper 必须能接收触摸才能触发放大态,
// 因此手势 state 必须住在手势表面所在的父级上,而不能藏在组件内部。

// === 手势状态(由父级持有,作为 props 注入 AssetCardPullExpand) ===
const pullStartY = ref(null);
const pullDistance = ref(0);
const isPullActive = ref(false);
const isExpanded = ref(false);
// scrollTop 跟踪(避免与列表滚动冲突,仅在顶部进入激活态)
const scrollTopRef = ref(0);

// 2026-07-28: 放大态下的上拉收起手势 state(symmetric mirror of down-pull above)。
// 与 down-pull 解耦的独立 ref 集合:direction / startY / distance / active。
// 设计动机:上拉只可能在 expanded 分支挂载时触发,而 down-pull 只可能在 normal 分支
// 触发,二者 v-if/v-else-if 互斥,自然隔离;共用 ref 反而要写额外判断。
const collapseStartY = ref(null);
const collapsePullDistance = ref(0);
const isCollapsePullActive = ref(false);

// === 手势常量(与组件 props 默认值对齐) ===
const PULL_EXPAND_THRESHOLD = 80;
const PULL_MAX_DISTANCE = 240;
const PULL_MAX_SCALE = 1.6;

// #ifdef APP-PLUS
const isAppPlus = true;
// #else
const isAppPlus = false;
// #endif

const shouldHandleGesture = computed(() => {
	return isAppPlus && !craftConfirmMode.value && !loading.value && !loadError.value;
});

const hintText = computed(() => {
	// 2026-07-28: 上拉收起手势 hint(在 expanded 状态下激活)
	if (isCollapsePullActive.value) {
		if (collapsePullDistance.value >= PULL_EXPAND_THRESHOLD) return '松手收起';
		if (collapsePullDistance.value > 20) return '上拉收起';
		return '';
	}
	if (pullDistance.value >= PULL_EXPAND_THRESHOLD) return '松手查看大图';
	if (pullDistance.value > 20) return '下拉查看大图';
	return '';
});

// 父级持有 cardTransform 公式(对齐 spec §1.5):正常态卡片实时跟手放大;切换到放大态时
// AssetCardPullExpand 自己用同一公式再算一遍(pullDistance=0 / isExpanded=true → scale(maxScale))
const cardTransform = computed(() => {
	if (isExpanded.value) {
		return `scale(${PULL_MAX_SCALE})`;
	}
	if (isPullActive.value) {
		const t = Math.min(1, Math.max(0, pullDistance.value) / PULL_MAX_DISTANCE);
		const s = 1 + t * (PULL_MAX_SCALE - 1);
		return `scale(${s.toFixed(3)}) translateY(${pullDistance.value}px)`;
	}
	return 'scale(1)';
});

// 2026-07-27: sibling content 同步淡出。pullDistance 从 0 → threshold*0.6 (默认 48rpx),
// opacity 从 1.0 → 0.0;超过 48rpx 仍维持 0,直到 onTouchEnd 切到 expanded(viewMode 切换
// 会销毁整个 <scroll-view>,siblings 随节点一起消失,所以接缝处无缝)
const siblingFadeOpacity = computed(() => {
	// 2026-07-28: 上拉收起过程中 sibling 从 0 → 1 渐显,正好与下拉淡出对称。
	// limit 与下拉共用(48rpx),即拉到 48rpx 时 siblings 已完全可见,松开 < 80rpx
	// 时回弹放大态不会留下"残影"
	if (isCollapsePullActive.value) {
		const limit = PULL_EXPAND_THRESHOLD * 0.6;
		const t = Math.min(1, Math.max(0, collapsePullDistance.value / limit));
		return t;
	}
	if (!isPullActive.value) return 1;
	const limit = PULL_EXPAND_THRESHOLD * 0.6; // 48rpx with default 80rpx threshold
	const t = Math.min(1, Math.max(0, pullDistance.value / limit));
	return 1 - t;
});

// === 手势回调 ===
// 2026-07-28 fix: touchstart 只清理残留状态,不 eager 记录 startY。
// 纯点击(无 move)时 touchend 会因 isPullActive=false 提前 return,
// eager 写入的 startY 会残留到下一次手势,导致首帧 transform 跳变
// ("点一下卡片先缩小再弹回")。startY 改为在首个 touchmove 里懒记录。
function onTouchStart(e) {
	if (!shouldHandleGesture.value) return;
	if (isExpanded.value) return;
	if (scrollTopRef.value !== 0) return; // 仅在列表顶部进入
	if (!e.touches || e.touches.length !== 1) return;
	pullStartY.value = null;
	pullDistance.value = 0;
	isPullActive.value = false;
}

function onTouchMove(e) {
	if (!shouldHandleGesture.value) return;
	if (isExpanded.value) return;
	if (scrollTopRef.value !== 0) return; // 仅在列表顶部进入
	if (!e.touches || e.touches.length !== 1) return;
	const currentY = e.touches[0].pageY;
	if (pullStartY.value == null) {
		// 手势真正开始移动时才记录起点
		pullStartY.value = currentY;
	}
	const delta = currentY - pullStartY.value;
	if (delta <= 0) return; // 向上/未真正下拉,不进入激活态
	const next = Math.min(delta * 0.6, PULL_MAX_DISTANCE);
	pullDistance.value = next;
	if (next > 0) {
		isPullActive.value = true;
		// 阻止默认滚动(避免与列表滚动手势冲突)
		if (typeof e.preventDefault === 'function') {
			try { e.preventDefault(); } catch (_) {}
		}
	}
}

function onTouchEnd() {
	if (!isPullActive.value) {
		pullStartY.value = null;
		return;
	}
	// 2026-07-28 fix(revert): 去掉延迟切换定时器。transition 已移除,
	// 释放后立即翻 viewMode,分支销毁重建即完成视觉切换;延迟 260ms 反而
	// 让释放动作有"等一下才响应"的迟滞感。
	isPullActive.value = false;
	if (pullDistance.value >= PULL_EXPAND_THRESHOLD) {
		isExpanded.value = true;
		viewMode.value = 'expanded'; // 同时通知 v-else-if 切到放大分支
	}
	pullDistance.value = 0;
	pullStartY.value = null;
}

// 2026-07-28: 上拉收起的 wrapper transform(scale 1.6 → 1.0,translateY 0 → -pullDistance)。
// 2026-07-28 fix(jump): 三段语义 ——
// 1) viewMode !== 'expanded':wrapper 整体回到 scale(1)(no-op),
//    此时 scroll-view 的卡片接管视觉(本身 scale(1) transform),无冲突
// 2) viewMode === 'expanded' && !isCollapsePullActive:wrapper 提供 scale(1.6)(锁定态),
//    内层 AssetCardPullExpand :is-expanded="false" 永远 scale(1),总视觉 = 1.6 × 1 = 1.6 ✓
// 3) viewMode === 'expanded' && isCollapsePullActive:wrapper 实时跟手 scale(s) translateY(-pull),
//    s 从 1.6 线性降到 1.0,内层 scale(1),总视觉 = s × 1 = s(同 1.6→1.0 平滑收缩) ✓
// 关键差异:旧实现 !isCollapsePullActive → 'scale(1)' 会让视觉从 s 跳到 1.6(因为内层此时是 scale(1.6)),
// 释放瞬间 v-else-if 销毁又跳到 scale(1),双重跳变即用户报的"上拉跳一下"。
// 新实现把锁定态 scale(1.6) 显式交给 wrapper,内层永远 scale(1),释放时 wrapper 平滑回 scale(1.6) translateY(0),
// 配合 .pull-collapse-surface 的 CSS transition,跨分支销毁前视觉已经"回到锁定态"接缝更小。
const collapseCardTransform = computed(() => {
	if (viewMode.value !== 'expanded') return 'scale(1)';
	if (!isCollapsePullActive.value) return 'scale(1.6)';
	const t = Math.min(1, Math.max(0, collapsePullDistance.value / PULL_MAX_DISTANCE));
	const s = PULL_MAX_SCALE - t * (PULL_MAX_SCALE - 1); // 1.6 → 1.0
	return `scale(${s.toFixed(3)}) translateY(${-collapsePullDistance.value}px)`;
});

// 2026-07-28: 上拉收起手势回调(对称镜像 onTouchStart/Move/End)。
// 仅在 expanded 分支挂载时触发(viewMode.value === 'expanded'),
// 节点销毁/创建的天然隔离使 down-pull 与 up-pull 不会同时处于激活态
// 2026-07-28 fix: 同 onTouchStart —— 不 eager 记录 startY,避免纯点击时
// 残留的 startY 在下一次 touchstart/touchmove 之间让 collapseCardTransform
// 重算,出现"点一下先缩小再弹回"。startY 在首个 touchmove 里懒记录。
function onCollapseTouchStart(e) {
	if (!shouldHandleGesture.value) return;
	if (viewMode.value !== 'expanded') return;
	if (!e.touches || e.touches.length !== 1) return;
	collapseStartY.value = null;
	collapsePullDistance.value = 0;
	isCollapsePullActive.value = false;
}

function onCollapseTouchMove(e) {
	if (!shouldHandleGesture.value) return;
	if (viewMode.value !== 'expanded') return;
	if (!e.touches || e.touches.length !== 1) return;
	const currentY = e.touches[0].pageY;
	if (collapseStartY.value == null) {
		collapseStartY.value = currentY;
	}
	const delta = currentY - collapseStartY.value;
	if (delta >= 0) return; // 未真正上拉(向下/静止),不进入激活态
	const next = Math.min(-delta * 0.6, PULL_MAX_DISTANCE);
	collapsePullDistance.value = next;
	if (next > 0) {
		isCollapsePullActive.value = true;
		// 阻止默认行为,避免与父级 mask/其他手势冲突
		if (typeof e.preventDefault === 'function') {
			try { e.preventDefault(); } catch (_) {}
		}
	}
}

function onCollapseTouchEnd() {
	if (!isCollapsePullActive.value) {
		collapseStartY.value = null;
		return;
	}
	// 2026-07-28 fix(revert): 去掉延迟切换定时器,释放后立即切分支。
	isCollapsePullActive.value = false;
	if (collapsePullDistance.value >= PULL_EXPAND_THRESHOLD) {
		isExpanded.value = false;
		viewMode.value = 'normal'; // 切回 normal 分支,scroll-view 重新挂载
	}
	collapsePullDistance.value = 0;
	collapseStartY.value = null;
}

function onScroll(e) {
	scrollTopRef.value = (e && e.detail && typeof e.detail.scrollTop === 'number')
		? e.detail.scrollTop
		: 0;
}

const isLenticularAsset = computed(() => {
	if (craftConfirmMode.value) return false
	const tags = assetData.value?.tags
	if (Array.isArray(tags) && tags.includes(CRAFT_TAG_LENTICULAR)) return true
	return false
})

const isLenticularDetail = ref(false)

const isCraftLenticular = computed(() => studioKindParam.value === STUDIO_LENTICULAR);
const craftCategoryLabel = computed(() => {
	if (isCraftLenticular.value) return CRAFT_LENTICULAR_CN;
	if (studioKindParam.value === STUDIO_LASER) return CRAFT_LASER_CARD_CN;
	return craftFormData.value?.typeName || '星卡';
});
const craftAssetName = computed(() => craftFormData.value?.name || craftCategoryLabel.value);
const craftCreatorName = computed(() => '我');
const craftEarningsHint = computed(() => '21/时');
const craftMintDate = computed(() => {
	const d = new Date();
	const mm = String(d.getMonth() + 1).padStart(2, '0');
	const dd = String(d.getDate()).padStart(2, '0');
	return `${d.getFullYear()}.${mm}.${dd}`;
});
const craftBlockPlaceholder = computed(() => '铸造后生成');
const craftHashPlaceholder = computed(() => '铸造后生成');

// 数据状态
const loading = ref(true);
const loadError = ref('');
const assetData = ref({});
const coverUrl = ref('');
const isLiked = ref(false);
const likeCount = ref(0);
const liking = ref(false);

// 当前用户信息
const userAvatarUrl = ref('');
const currentUserId = ref('');
const currentUserNickname = ref('');

// 加载当前用户信息
const loadCurrentUser = () => {
	try {
		// ★ 2026-07-27 refactor:委托 @/utils/getStoredUser,统一 JSON.parse 守卫
		const userInfo = getStoredUser() || {};
		userAvatarUrl.value = userInfo.avatar_url || '';
		currentUserId.value = String(userInfo.uid || userInfo.user_id || '');
		currentUserNickname.value = userInfo.nickname || '';
	} catch (e) {
		console.error('解析用户信息失败:', e);
	}
};

// 点赞用户头像数据（最多6个，按椭圆排列，每个头像有独立的偏移量配置）
// 椭圆排列配置：从真实API加载后，按此配置映射
const ellipseConfig = [
	{ ellipseX: -40, ellipseY: -40, size: 0.85 },
	{ ellipseX: 40, ellipseY: -40, size: 0.85 },
	{ ellipseX: -96, ellipseY: 0, size: 0.9 },
	{ ellipseX: 120, ellipseY: -40, size: 0.9 },
	{ ellipseX: 64, ellipseY: 32, size: 1 },
	{ ellipseX: -16, ellipseY: 32, size: 1.15 }
];

// 点赞用户数据（从API加载）
const likedUsers = ref([]);

// 把 API 返回的原始点赞用户映射成展示用的头像数组（最多 6 个，按椭圆排列）
const mapLikersToDisplay = (users) => {
	if (!Array.isArray(users) || !users.length) return [];
	return users.slice(0, 6).map((user, index) => {
		const config = ellipseConfig[index] || { ellipseX: 0, ellipseY: 0, size: 1 };
		return {
			avatar: user.avatar || '/static/sucai/default-avatar.png',
			ellipseX: config.ellipseX,
			ellipseY: config.ellipseY,
			size: config.size
		};
	});
};

// 把 API 返回的原始点赞用户映射成弹窗用的完整列表
const mapLikersToModal = (users) => {
	if (!Array.isArray(users)) return [];
	return users.map(user => ({
		userId: user.user_id,
		nickname: user.nickname || '匿名用户',
		avatar: user.avatar || '/static/sucai/default-avatar.png',
		liked_at: user.liked_at || 0
	}));
};

// 加载点赞用户头像
const loadLikedUsers = async (assetId) => {
	if (!assetId) return;
	try {
		likedUsersLoading.value = true;
		// 加载点赞用户
		const res = await getAssetLikersApi(assetId, 20, 0);
		if (res.code === 0 && res.data?.users) {
			likedUsers.value = mapLikersToDisplay(res.data.users);
			allLikedUsers.value = mapLikersToModal(res.data.users);
		}
	} catch (e) {
		console.error('加载点赞用户失败:', e);
		// 失败时使用空数组，让 v-if="likeCount > 0" 条件隐藏整个模块
		likedUsers.value = [];
		allLikedUsers.value = [];
	} finally {
		likedUsersLoading.value = false;
	}
};

// ---- 贴纸相关 ----
const activeStickers = ref([]);
const compositingStickers = ref(false);

function getStickerStyle(sticker) {
	const ox = (sticker.pos_x != null ? sticker.pos_x : 0.5) * 100
	const oy = (sticker.pos_y != null ? sticker.pos_y : 0.5) * 100
	const rot = sticker.rotation != null ? sticker.rotation : 0
	const scX = sticker.scale_x != null ? sticker.scale_x : 1
	const scY = sticker.scale_y != null ? sticker.scale_y : 1
	const op = sticker.opacity != null ? sticker.opacity : 1
	return {
		left: `${ox}%`,
		top: `${oy}%`,
		transform: `translate(-50%, -50%) translateZ(0) rotate(${rot}deg) scale(${scX}, ${scY})`,
		opacity: op,
	}
}

async function loadStickersForAsset(materialRelations) {
	if (!materialRelations || !materialRelations.length) {
		activeStickers.value = []
		return
	}
	const stickers = relationsToStickers(materialRelations)
	const withIds = stickers.map((s, i) => ({ ...s, id: `sticker_${i}` }))
	activeStickers.value = withIds
}

async function exportCompositeImage() {
	if (!coverUrl.value || !activeStickers.value.length) return null
	compositingStickers.value = true
	try {
		const result = await composeStickers({
			baseImageSrc: coverUrl.value,
			stickers: activeStickers.value,
			exportW: 450,
			exportH: 600,
			canvasId: 'stickerCompositCanvas',
		})
		return result
	} catch (e) {
		console.error('[asset-detail] sticker compose failed:', e)
		return null
	} finally {
		compositingStickers.value = false
	}
}

// 弹窗相关
const showLikeUsersModal = ref(false);
const likeUsersActiveTab = ref(0);
// 举报弹窗（asset-detail 内显式持有，与 ShareReportButtons 内的 modal 互不影响，可由本页面其他位置触发）
const showReportModal = ref(false);

// 举报提交回调（埋点/统计/事件总线用）
const handleReportSubmit = (payload) => {
	console.log('[asset-detail] report submitted', payload);
};

// 全部点赞用户数据（从API加载）
const allLikedUsers = ref([]);
const likedUsersLoading = ref(false);

const handleLikeUsersTabChange = ({ tabIndex }) => {
	likeUsersActiveTab.value = tabIndex;
};

// 拜访按钮（预拉他人作品数据）
const handleVisit = (user) => {
	const api = getPreloadApi();
	(api || uni).navigateTo({
		url: `/pages/profile/hisWorks?userId=${user.userId}&nickname=${encodeURIComponent(user.nickname)}`
	});
};

// 区块链编号和交易哈希的显示切换
const showBlockNumber = ref(false);
const showTxHash = ref(false);

// 倒计时
let countdownTimer = null;

// 倒计时状态
const countdowns = ref({});

// 计算剩余时间
const calculateRemainingTime = (item) => {
	if (item.remainSeconds !== undefined) {
		if (item.remainSeconds <= 0) {
			return { hours: 0, minutes: 0, seconds: 0, expired: true };
		}
		const hours = Math.floor(item.remainSeconds / 3600);
		const minutes = Math.floor((item.remainSeconds % 3600) / 60);
		const seconds = Math.floor(item.remainSeconds % 60);
		return { hours, minutes, seconds, expired: false };
	}
	if (item.exhibition_expire_at) {
		const now = Date.now();
		const expireTime = new Date(item.exhibition_expire_at).getTime();
		const remaining = expireTime - now;
		if (remaining <= 0) {
			return { hours: 0, minutes: 0, seconds: 0, expired: true };
		}
		const hours = Math.floor(remaining / (60 * 60 * 1000));
		const minutes = Math.floor((remaining % (60 * 60 * 1000)) / (60 * 1000));
		const seconds = Math.floor((remaining % (60 * 1000)) / 1000);
		return { hours, minutes, seconds, expired: false };
	}
	return { hours: 0, minutes: 0, seconds: 0, expired: true };
};

// 更新倒计时
const updateCountdowns = () => {
	countdowns.value['currentAsset'] = calculateRemainingTime({ exhibition_expire_at: assetData.value.exhibition_expire_at });
};

const countdownText = computed(() => {
	const countdown = countdowns.value['currentAsset'];
	if (!countdown || countdown.expired) return '';
	const h = String(countdown.hours).padStart(2, '0');
	const m = String(countdown.minutes).padStart(2, '0');
	const s = String(countdown.seconds).padStart(2, '0');
	return `${h}:${m}:${s}`;
});

// 格式化获取时间（字段名 created_at，来自 getAssetDetailApi 响应）
const formattedAcquireTime = computed(() => {
	const raw = assetData.value.created_at || '';
	if (!raw) return '未知';
	const d = new Date(raw);
	if (isNaN(d.getTime())) return raw;
	const yyyy = d.getFullYear();
	const mm = String(d.getMonth() + 1).padStart(2, '0');
	const dd = String(d.getDate()).padStart(2, '0');
	return `${yyyy}.${mm}.${dd}`;
});

// 截断交易哈希
const displayTxHash = computed(() => {
	return assetData.value.tx_hash || '未知';
});

// 隐藏交易哈希（保留前6位 + 6个*）
const hiddenTxHash = computed(() => {
	const hash = assetData.value.tx_hash;
	if (!hash) return '******';
	if (hash.length <= 6) return hash + '******';
	return `${hash.substring(0, 6)}******`;
});

// 隐藏区块链编号（保留前6位 + 6个*）
const hiddenBlockNumber = computed(() => {
	const num = assetData.value.block_number;
	if (!num) return '******';
	const str = String(num);
	if (str.length <= 6) return str + '******';
	return `${str.substring(0, 6)}******`;
});

// 等级徽章图片
const gradeBadgeUrl = computed(() => {
	const grade = assetData.value.grade;
	const gradeMap = {
		1: '/static/starbookcontent/grade/Ndengji.png',
		2: '/static/starbookcontent/grade/Rdengji.png',
		3: '/static/starbookcontent/grade/SRdengji.png',
		4: '/static/starbookcontent/grade/SSRdengji.png',
		5: '/static/starbookcontent/grade/URengji.png',
	};
	return gradeMap[grade] || '/static/starbookcontent/grade/Ndengji.png';
});

// 复制完整哈希
const copyHash = () => {
	const hash = assetData.value.tx_hash;
	if (!hash) return;
	uni.setClipboardData({
		data: hash,
		success: () => uni.showToast({ title: '已复制', icon: 'success', duration: 1500 })
	});
};

// 加载数据
const loadData = async () => {
	loading.value = true;
	loadError.value = '';

	try {
		let assetId = assetIdParam.value;

		if (!assetId && orderIdParam.value) {
			const mintRes = await getMintOrderDetailApi(orderIdParam.value);
			if (mintRes.code === 0 && mintRes.data?.asset?.asset_id) {
				assetId = mintRes.data.asset.asset_id;
			} else {
				throw new Error('获取铸造订单详情失败');
			}
		}

		if (!assetId) throw new Error('藏品信息不完整');

		const res = await getAssetDetailApi(assetId);
		if (res.code === 0 && res.data?.asset) {
			const asset = res.data.asset;
			assetData.value = asset;
			// console.log('[asset-detail] loaded asset:', JSON.stringify({
			// 	id: asset.id,
			// 	cover_url: asset.cover_url,
			// 	image_url: asset.image_url,
			// 	asset_keys: Object.keys(asset).slice(0, 20)
			// }));
			isLiked.value = res.data.asset.is_liked || res.data.is_liked || false;
			likeCount.value = asset.like_count || 0;

			// 加载贴纸素材（从 material_relations 加载）
			if (asset.material_relations || asset.materials) {
				loadStickersForAsset(asset.material_relations || asset.materials)
			}

			if (asset.exhibition_expire_at) {
				startCountdown();
			}

			// 异步加载封面图片，不阻塞页面渲染
			getAssetCoverRealUrl(asset.cover_url).then(async (url) => {
				coverUrl.value = url;
				if (isLenticularAsset.value) {
					// 参考 success.vue 逻辑：通过 bindAssetMaterialsApi 获取素材列表
					let subjectUrl = url
					let bgUrl = ''
					// 调用素材接口获取完整的素材列表
					try {
						const materialsRes = await getAssetMaterialsApi(assetId)
						if (materialsRes.code === 0 && materialsRes.data) {
							const materialsList = Array.isArray(materialsRes.data) ? materialsRes.data : materialsRes.data.materials || []
							// 找到 bg 和 main 素材
							for (const mat of materialsList) {
								if (mat.material_type === 'main' && mat.material_url_signed) {
									subjectUrl = mat.material_url_signed
								}
								if (mat.material_type === 'bg' && mat.material_url_signed) {
									bgUrl = mat.material_url_signed
								}
							}
						}
					} catch (e) {
						console.error('[asset-detail] 获取素材列表失败:', e)
					}
					lenticularLayers.value = bgUrl
						? buildLenticularLayersTwo(bgUrl, subjectUrl)
						: buildLenticularLayers(subjectUrl)
					isLenticularDetail.value = true
				}
			}).catch(err => {
				console.error('加载封面图片失败:', err);
				coverUrl.value = ''; // 失败时不显示默认图片
			});
			// 异步加载点赞用户头像
			loadLikedUsers(assetId);
		} else {
			throw new Error(res.message || '获取藏品详情失败');
		}
	} catch (err) {
		console.error('loadData 出错:', err);
		loadError.value = err.message || '加载失败，请重试';
	} finally {
		loading.value = false;
	}
};

// 倒计时
const startCountdown = () => {
	updateCountdowns(); // 立即显示，不等待1秒
	if (countdownTimer) clearInterval(countdownTimer);
	countdownTimer = setInterval(() => {
		updateCountdowns();
	}, 1000);
};

// 把当前用户乐观插入到点赞用户列表（让「创作者信息模块」立刻反映本次点赞）
const prependCurrentUserToLikers = () => {
	if (!userAvatarUrl.value) return;
	const meAvatar = userAvatarUrl.value;
	// 1) 顶部头像展示列表：去重后插到最前，重新应用椭圆配置
	const nextDisplay = [
		{ avatar: meAvatar },
		...likedUsers.value.filter(u => u.avatar !== meAvatar)
	];
	likedUsers.value = mapLikersToDisplay(nextDisplay);
	// 2) 弹窗用完整列表：去重后插到最前
	const meRow = {
		userId: currentUserId.value,
		nickname: currentUserNickname.value || '我',
		avatar: meAvatar,
		liked_at: Date.now()
	};
	allLikedUsers.value = [
		meRow,
		...allLikedUsers.value.filter(u => u.avatar !== meAvatar)
	];
};

// 点赞
const handleLike = async () => {
	if (liking.value || !assetData.value.asset_id) return;
	liking.value = true;
	try {
		// if (isLiked.value) {
		// 	await unlikeAssetApi(assetData.value.asset_id);
		// 	isLiked.value = false;
		// 	likeCount.value = Math.max(0, likeCount.value - 1);
		// } else {
		const res = await likeAssetApi(assetData.value.asset_id);
		isLiked.value = true;
		// 优先用后端返回的最新 like_count；没有则本地 +1
		const serverCount = res?.data?.like_count;
		likeCount.value = typeof serverCount === 'number' ? serverCount : likeCount.value + 1;
		// 乐观更新「创作者信息模块」——立刻把当前用户头像插到第一位
		prependCurrentUserToLikers();
		// 后台静默拉取一次真实列表，保证后续顺序、昵称、avatar URL 与服务端一致
		loadLikedUsers(assetData.value.asset_id);
		// }
		// 通知展馆页面更新点赞数
		uni.$emit('assetLikeChanged', {
			asset_id: assetData.value.asset_id,
			like_count: likeCount.value,
			is_liked: isLiked.value
		});
	} catch (err) {
		const errMsg = err?.message || '';
		const errData = err?.data?.message || '';
		// 显示后端返回的实际错误信息
		const showMsg = errData || errMsg || '点赞失败';
		uni.showToast({ title: showMsg, icon: 'none', duration: 2000 });
		console.error('点赞失败:', err);
	} finally {
		liking.value = false;
	}
};

const loadCraftConfirm = () => {
	loading.value = false;
	loadError.value = '';
	try {
		const formStr = uni.getStorageSync(CASTLOVE_FORM_KEY);
		if (formStr) {
			craftFormData.value = JSON.parse(formStr);
		}
		const selected = uni.getStorageSync(CRAFT_SELECTED_IMAGE_KEY) || '';
		craftCoverUrl.value = selected;
		if (isCraftLenticular.value) {
			const raw = uni.getStorageSync(LENTICULAR_STUDIO_STORAGE_KEY);
			if (raw) {
				const p = typeof raw === 'string' ? JSON.parse(raw) : raw;
				lenticularLayers.value = buildLenticularLayersTwo(p.bgPath || '', p.subjectPath || '');
			}
		}
	} catch (e) {
		console.error('[asset-detail] craft confirm', e);
		loadError.value = '加载确认页失败';
	}
};

const handleCraftMint = async () => {
	if (craftMinting.value) return;
	const imagePath =
		isCraftLenticular.value
			? lenticularLayers.value.find((l) => l.id === 'mid')?.src || craftCoverUrl.value
			: craftCoverUrl.value;
	if (!imagePath) {
		uni.showToast({ title: '缺少作品图', icon: 'none' });
		return;
	}
	const bgImagePath = isCraftLenticular.value
		? lenticularLayers.value.find((l) => l.id === 'base')?.src || ''
		: undefined;
	craftMinting.value = true;
	uni.showLoading({ title: '铸造中…', mask: true });
	try {
		await submitCraftMintFromPath({ imagePath, bgImagePath, formData: craftFormData.value });
		uni.hideLoading();
		uni.navigateTo({ url: '/pages/castlove/success' });
	} catch (e) {
		uni.hideLoading();
		uni.showToast({ title: e.message || '铸造失败', icon: 'none' });
	} finally {
		craftMinting.value = false;
	}
};

// 返回逻辑
const handleBack = () => {
	// 获取页面栈
	const pages = getCurrentPages();
	if (fromParam.value === 'castlove') {
		uni.reLaunch({ url: '/pages/castlove/mall' });
	} else if (pages.length > 1) {
		// 有上一页，执行返回
		uni.navigateBack();
	} else {
		// 没有上一页，跳转到square页面
		uni.reLaunch({
			url: "/pages/square/square",
		});
	}
};

// 点赞轮询定时器：覆盖跨用户/跨设备「实时」更新（后端暂无点赞 WS）
let likePollingTimer = null;
const LIKE_POLLING_INTERVAL = 30000; // 20s

// 启动点赞轮询
const startLikePolling = () => {
	stopLikePolling();
	if (!assetData.value?.asset_id) return;
	likePollingTimer = setInterval(() => {
		// 页面被切到后台时轮询其实也会跑，但 uni-app 在 H5/App 下 setInterval 仍触发；
		// 定时器在 onHide 清理，onShow 重新启动，天然只在可见时拉取
		if (assetData.value?.asset_id) {
			loadLikedUsers(assetData.value.asset_id);
		}
	}, LIKE_POLLING_INTERVAL);
};

const stopLikePolling = () => {
	if (likePollingTimer) {
		clearInterval(likePollingTimer);
		likePollingTimer = null;
	}
};

// 其他页面触发的点赞变更事件：保持本页面与全局一致
const handleAssetLikeChangedExternal = (data) => {
	if (!data || String(data.asset_id) !== String(assetData.value?.asset_id)) return;
	if (typeof data.like_count === 'number') likeCount.value = data.like_count;
	if (typeof data.is_liked === 'boolean') isLiked.value = data.is_liked;
	// 拉取最新点赞用户列表，更新「创作者信息模块」
	if (assetData.value?.asset_id) {
		loadLikedUsers(assetData.value.asset_id);
	}
};

onLoad((options) => {
	assetIdParam.value = options?.asset_id || '';
	orderIdParam.value = options?.order_id || '';
	fromParam.value = options?.from || '';
	studioKindParam.value = options?.studio_kind || '';
	loadCurrentUser();
	// 订阅跨页面点赞事件
	uni.$on('assetLikeChanged', handleAssetLikeChangedExternal);

	// 触发每日首次浏览藏品任务（spec §3 / Phase F.2）
	// 后端幂等：completed/claimed 状态时 skip；本地不缓存避免跨天去重逻辑复杂
	const starIdBrowse = uni.getStorageSync('star_id') || 1;
	reportEvent('daily_browse_asset', starIdBrowse).catch(err => {
		console.error('上报浏览事件失败:', err);
	});

	// craft_confirm 模式：不需要调用 loadData，而是加载本地表单数据
	if (fromParam.value === 'craft_confirm') {
		craftConfirmMode.value = true;
		loadCraftConfirm();
	}
});

onShow(() => {
	const currentKey = `${assetIdParam.value}_${orderIdParam.value}`;

	// 如果参数变化了，或者是首次加载，则重新加载数据
	if ((assetIdParam.value || orderIdParam.value) && currentKey !== lastLoadKey.value) {
		lastLoadKey.value = currentKey;
		loadData();
	}

	// 页面每次回到前台都重新拉一次点赞列表 —— 兜底「创作者信息模块」时效
	if (assetData.value?.asset_id && !craftConfirmMode.value) {
		loadLikedUsers(assetData.value.asset_id);
		// startLikePolling();
	}
});

onHide(() => {
	stopLikePolling();
	// 2026-07-27 refactor: 页面切到后台时清掉手势中间态,避免回到前台时还残留 pullDistance / isPullActive
	pullStartY.value = null;
	pullDistance.value = 0;
	isPullActive.value = false;
	// 2026-07-28: 同步清掉上拉收起的中间态
	collapseStartY.value = null;
	collapsePullDistance.value = 0;
	isCollapsePullActive.value = false;
	isExpanded.value = false;
	viewMode.value = 'normal';
});

onUnload(() => {
	// 页面被关闭/销毁：清理轮询和事件订阅，避免泄漏到下一页或后台
	stopLikePolling();
	uni.$off('assetLikeChanged', handleAssetLikeChangedExternal);
	// 重置手势状态,避免下一次 mount 后出现"上次残留"假象
	pullStartY.value = null;
	pullDistance.value = 0;
	isPullActive.value = false;
	// 2026-07-28: 同步重置上拉收起手势状态
	collapseStartY.value = null;
	collapsePullDistance.value = 0;
	isCollapsePullActive.value = false;
	isExpanded.value = false;
	viewMode.value = 'normal';
});

onUnmounted(() => {
	if (countdownTimer) clearInterval(countdownTimer);
	stopLikePolling();
	uni.$off('assetLikeChanged', handleAssetLikeChangedExternal);
	// Vue 层卸载兜底
	pullStartY.value = null;
	pullDistance.value = 0;
	isPullActive.value = false;
	// 2026-07-28: 同步重置上拉收起手势状态
	collapseStartY.value = null;
	collapsePullDistance.value = 0;
	isCollapsePullActive.value = false;
	isExpanded.value = false;
});
</script>

<style scoped>
.detail-container {
	position: relative;
	width: 100vw;
	min-height: 100vh;
	overflow: hidden;
	background-color: #0d0820;
}

.background-image {
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;
	width: 100%;
	height: 100%;
	z-index: 0;
}

/* Header */
.header-bar {
	position: fixed;
	/* top: 80rpx; */
	/* #ifdef APP-PLUS */
	/* App 通用（兜底） */
	top: 96rpx;
	/* #endif */
	/* #ifdef H5 */
	/* H5 使用普通高度 */
	top: 16rpx;
	/* #endif */
	left: 32rpx;
	right: 32rpx;
	z-index: 100;
	display: flex;
	align-items: self-start;
	justify-content: space-between;
}

.back-btn {
	display: flex;
	align-items: center;
	justify-content: center;
}

.back-btn:active {
	opacity: 0.6;
}

.back-icon {
	font-size: 48rpx;
	font-weight: bold;
	color: #fff;
}

.header-actions {
	display: flex;
	align-items: center;
	gap: 16rpx;
}

/* 加载/错误 */
.loading-wrapper,
.error-wrapper {
	position: relative;
	z-index: 1;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	height: 100vh;
	gap: 30rpx;
}

/* 旋转光环 */
.loading-ring-outer {
	position: relative;
	width: 160rpx;
	height: 160rpx;
	display: flex;
	align-items: center;
	justify-content: center;
}

.loading-ring {
	width: 160rpx;
	height: 160rpx;
	border-radius: 50%;
	border: 6rpx solid transparent;
	border-top-color: #F0E4B1;
	border-right-color: #F08399;
	border-bottom-color: #B94E73;
	border-left-color: transparent;
	box-shadow:
		0 0 24rpx rgba(240, 131, 153, 0.5),
		inset 0 0 16rpx rgba(183, 78, 115, 0.2);
	animation: ring-spin 1.2s linear infinite;
}

@keyframes ring-spin {
	0% {
		transform: rotate(0deg);
	}

	100% {
		transform: rotate(360deg);
	}
}

/* 三颗脉冲粒子 */
.loading-dots {
	display: flex;
	align-items: center;
	gap: 24rpx;
}

.loading-dot {
	width: 20rpx;
	height: 20rpx;
	border-radius: 50%;
	animation: dot-pulse 1.4s ease-in-out infinite;
}

.dot-1 {
	background: #F0E4B1;
	box-shadow: 0 0 12rpx rgba(240, 228, 177, 0.8);
	animation-delay: 0s;
}

.dot-2 {
	background: #F08399;
	box-shadow: 0 0 12rpx rgba(240, 131, 153, 0.8);
	animation-delay: 0.2s;
}

.dot-3 {
	background: #834B9E;
	box-shadow: 0 0 12rpx rgba(131, 75, 158, 0.8);
	animation-delay: 0.4s;
}

@keyframes dot-pulse {

	0%,
	100% {
		transform: scale(0.6);
		opacity: 0.4;
	}

	50% {
		transform: scale(1.3);
		opacity: 1;
	}
}

/* 加载文字 */
.loading-text {
	font-size: 28rpx;
	color: rgba(255, 255, 255, 0.85);
	font-family: 'yt', sans-serif;
	letter-spacing: 4rpx;
	animation: text-breathe 2s ease-in-out infinite;
}

@keyframes text-breathe {

	0%,
	100% {
		opacity: 0.5;
	}

	50% {
		opacity: 1;
	}
}

.error-text {
	font-size: 32rpx;
	color: rgba(255, 255, 255, 0.8);
	font-family: 'yt', sans-serif;
}

.retry-btn {
	padding: 16rpx 48rpx;
	background: linear-gradient(165deg, #F0E4B1 0%, #F08399 50%, #B94E73 90%, #834B9E 100%);
	color: #fff;
	border-radius: 40rpx;
	font-size: 28rpx;
	border: none;
}

.retry-btn::after {
	border: none;
}

/* 滚动区 */
.content-scroll {
	position: relative;
	z-index: 1;
	height: 100vh;
}

.content-wrapper {
	min-height: 90%;
	padding: 200rpx 40rpx 80rpx;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: space-between;
	gap: 0;
}

/* 卡片区域 */
.card-section {
	width: 100%;
	display: flex;
	flex-direction: column;
	align-items: center;
	overflow: visible;
}

.card-wrapper {
	position: relative;
	width: 352rpx;
	height: 552rpx;
	margin-bottom: 32rpx;
	transform-origin: center center;
	perspective: 800rpx;
	transform-style: preserve-3d;
	overflow: visible;
}

/* 仅 auto 模式（光栅自动摆动时）启用翻转动画；用户手动控制时通过移除 class 关闭 */
.card-wrapper--auto-flip {
	animation: card-3d-flip 15s ease-in-out infinite;
}

@keyframes card-3d-flip {
	0% {
		transform: rotateY(0deg);
	}

	20% {
		transform: rotateY(-30deg);
	}

	50% {
		transform: rotateY(30deg);
	}

	80% {
		transform: rotateY(0deg);
	}

	100% {
		transform: rotateY(0deg);
	}
}

/* .card-wrapper--lenticular {
	width: 520rpx;
	height: 680rpx;
} */

.detail-lenticular-slot {
	position: absolute;
	left: 50%;
	top: 50%;
	width: 88%;
	height: 96%;
	border-radius: 48rpx;
	/* transform: translate(-50%, -50%) rotate(-10deg); */
	transform: translate(-50%, -50%);
	z-index: 2;
	/* overflow: hidden; */
}

.detail-lenticular-card {
	width: 100%;
	height: 100%;
}

.card-image {
	width: 88%;
	height: 96%;
	left: 6%;
	top: 1.5%;
	border-radius: 48rpx;
	transform-origin: center center;
	position: absolute;
	z-index: 3;
	/* overflow: hidden; */
	/* transform: rotate(-10deg); */
}

.card-badge {
	position: absolute;
	top: -8rpx;
	left: -56rpx;
	width: 120rpx;
	height: 120rpx;
	z-index: 4;
	/* transform: rotate(-10deg); */
}

.card-frame {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	z-index: 2;
	/* transform: rotate(-10deg); */

}

.card-sticker {
	position: absolute;
	z-index: 5;
	width: 72rpx;
	height: 72rpx;
	pointer-events: none;
	transform-origin: center center;
	transform-style: preserve-3d;
	will-change: transform;
	filter: drop-shadow(0 2rpx 6rpx rgba(0, 0, 0, 0.35));
}

.export-canvas {
	position: fixed;
	left: -9999px;
	top: -9999px;
}

.card-meta-row {
	width: 100%;
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 24rpx;
}

/* 点赞 */
.like-area {
	display: flex;
	align-items: center;
	background: linear-gradient(to bottom right,
			#F0E4B1 0%,
			#F08399 50%,
			#B94E73 100%);
	border-radius: 999rpx;
	box-shadow:
		0 4rpx 12rpx rgba(255, 143, 158, 0.2),
		inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4);
	padding: 10rpx 28rpx;
}

.like-area:active {
	opacity: 0.75;
}

.like-icon,
.heart-icon,
.crystal-icon {
	width: 44rpx;
	height: 44rpx;
	margin-right: 10rpx;
}

.like-num {
	font-size: 32rpx;
	font-weight: bold;
	color: #fff;
	font-family: 'yt', sans-serif;
	font-variant-numeric: tabular-nums;
	text-shadow: 0 2px 8px rgba(0, 0, 0, 0.7);
}

/* 收益 */
.earnings-area {
	display: flex;
	align-items: center;
	background: linear-gradient(to bottom right,
			#F0E4B1 0%,
			#F08399 50%,
			#B94E73 100%);
	border-radius: 999rpx;
	box-shadow:
		0 4rpx 12rpx rgba(255, 143, 158, 0.2),
		inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4);
	padding: 10rpx 28rpx;
}

.earnings-text {
	font-size: 32rpx;
	font-weight: bold;
	color: #fff;
	font-family: 'yt', sans-serif;
	font-variant-numeric: tabular-nums;
	text-shadow: 0 2px 8px rgba(0, 0, 0, 0.7);
}

/* 倒计时 */
.countdown-area {
	display: flex;
	align-items: center;
	background: linear-gradient(165deg, #F0E4B1 0%, #F08399 50%, #B94E73 90%, #834B9E 100%);
	border-radius: 999rpx;
	padding: 10rpx 28rpx;
}

.countdown-pill {
	font-size: 32rpx;
	font-weight: bold;
	color: #fff;
	font-family: 'yt', sans-serif;
	font-variant-numeric: tabular-nums;
	text-shadow: 0 2px 8px rgba(0, 0, 0, 0.7);
	display: flex;
	align-items: center;
}

.countdown-val {
	font-size: 28rpx;
	font-weight: bold;
	color: #fff;
	font-family: 'yt', sans-serif;
	font-variant-numeric: tabular-nums;
	text-shadow: 0 2px 8px rgba(0, 0, 0, 0.7);
}

/* 信息行（持有人 + 铸造时间） */
.info-row {
	width: 100%;
	display: flex;
	align-items: stretch;
	justify-content: center;
	gap: 20rpx;
	padding: 24rpx 32rpx;
	border-radius: 24rpx;
	margin-bottom: 40rpx;
	flex-direction: column;
}

.info-col {
	flex: 1;
	display: flex;
	align-items: center;
	justify-content: center;
}

.info-item {
	display: flex;
	gap: 8rpx;
	width: 416rpx;
	justify-content: space-between;
	align-items: center;
}

.info-label {
	font-size: 32rpx;
	color: rgba(255, 255, 255, 0.7);
	font-family: 'yt', sans-serif;
}

.info-value {
	font-size: 32rpx;
	font-weight: bold;
	color: #ffffff;
	font-family: 'yt', sans-serif;
	text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.7);
}

.info-avatar {
	width: 48rpx;
	height: 48rpx;
	border-radius: 50%;
	margin-left: 8rpx;
}

.info-nickname {
	display: flex;
	align-items: center;
	gap: 8rpx;
	border-radius: 24rpx;
}

/* 点赞用户头像区域 */
.liked-users-area {
	position: relative;
	flex: 1;
	height: 184rpx;
	display: flex;
	align-items: center;
	justify-content: center;
}

.liked-user-avatar {
	position: absolute;
	width: 80rpx;
	height: 80rpx;
	border-radius: 50%;
	border: 3rpx solid #F0E4B1;
	overflow: hidden;
}

.liked-user-image {
	width: 100%;
	height: 100%;
}

/* 创作者信息模块 */
.creator-section {
	width: calc(100% - 80rpx);
	display: flex;
	flex-direction: row;
	align-items: center;
	gap: 24rpx;
	padding: 16rpx;
	background: url('/static/rank/activity-support-icon/beijingkuang.png') no-repeat center center;
	background-size: 105% 120%;
	border-radius: 24rpx;
	margin-bottom: 64rpx;
}

.creator-avatar {
	width: 100rpx;
	height: 100rpx;
	border-radius: 50%;
}

.creator-info {
	flex: 1;
	display: flex;
	flex-direction: column;
	gap: 12rpx;
}

.creator-title {
	font-size: 28rpx;
	color: #fff;
	font-family: 'yt', sans-serif;
}

.creator-card {
	display: flex;
	flex-direction: row;
	align-items: center;
	flex-direction: column;
	gap: 16rpx;
	border-radius: 16rpx;
}

.creator-tip {
	font-size: 32rpx;
	color: #ffcc00;
	font-family: 'yt', sans-serif;
	text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.7);
}

.creator-preview {
	width: 128rpx;
	height: 80rpx;
	/* border-radius: 12rpx; */
}

/* 链上数据 */
.chain-section {
	width: calc(100% - 80rpx);
	background: url('/static/rank/activity-support-icon/beijingkuang.png') no-repeat center center;
	background-size: 105% 120%;
	border-radius: 24rpx;
	padding: 30rpx 16rpx;
	display: flex;
	flex-direction: row;
	gap: 24rpx;
}

.chain-left {
	flex: 1;
	display: flex;
	flex-direction: column;
}

.chain-row {
	display: flex;
	justify-content: space-between;
	align-items: flex-start;
}

.chain-logo {
	width: 156rpx;
	height: 156rpx;
}

.chain-label {
	font-size: 28rpx;
	color: rgba(255, 255, 255, 0.6);
	font-family: 'yt', sans-serif;
	flex-shrink: 0;
	margin-right: 20rpx;
}

.chain-value {
	font-size: 28rpx;
	color: #ffffff;
	font-family: 'yt', sans-serif;
	flex: 1;
	word-break: break-all;
	display: flex
}

.chain-value-wrap {
	display: flex;
	align-items: center;
	gap: 12rpx;
	width: 240rpx;
}

.toggle-btn {
	padding: 4rpx 8rpx;
}

.toggle-icon {
	width: 32rpx;
	height: 32rpx;
}

.chain-hash {
	color: rgba(255, 255, 255, 0.75);
}

/* 链上哈希遮罩层 */
.txhash-mask {
	position: fixed;
	left: 0;
	right: 0;
	bottom: 24rpx;
	z-index: 999;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 0 64rpx;

}

.txhash-popup {
	border-radius: 24rpx;
	padding: 32rpx;
	background: url('/static/rank/activity-support-icon/beijingkuang.png') no-repeat center center;
	background-size: 105% 120%;
}

.txhash-popup-header {
	display: flex;
	justify-content: space-between;
	align-items: center;
	margin-bottom: 24rpx;
}

.txhash-popup-title {
	font-size: 32rpx;
	font-weight: bold;
	color: #fff;
	font-family: 'yt', sans-serif;
}

.txhash-popup-close {
	padding: 8rpx;
}

.close-icon {
	width: 40rpx;
	height: 40rpx;
}

.txhash-popup-content {
	display: block;
	font-size: 26rpx;
	color: #fff;
	font-family: 'yt', sans-serif;
	word-break: break-all;
	line-height: 1.6;
	background-color: rgba(0, 0, 0, 0.2);
	padding: 20rpx;
	border-radius: 12rpx;
}

/* 点赞用户列表 */
.like-users-list {
	display: flex;
	flex-direction: column;
	gap: 24rpx;
	padding: 16rpx;
}

.like-user-item {
	display: flex;
	flex-direction: row;
	align-items: center;
	padding: 0 16rpx;
	background: linear-gradient(to bottom right,
			#F0E4B1 0%,
			#F08399 50%,
			#B94E73 100%);
	border-radius: 999rpx;
	box-shadow:
		0 4rpx 12rpx rgba(255, 143, 158, 0.2),
		inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4);
}

.like-user-avatar {
	width: 80rpx;
	height: 80rpx;
	border-radius: 50%;
}

.like-user-info {
	flex: 1;
	display: flex;
	flex-direction: column;
	margin: 0 24rpx;
}

.like-user-name {
	font-size: 28rpx;
	color: #fff;
	font-family: 'yt', sans-serif;
}

.like-user-time {
	font-size: 24rpx;
	color: rgba(255, 255, 255, 0.6);
	font-family: 'yt', sans-serif;
}

/* 拜访按钮 - 只显示图标 */
.visit-button {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 80rpx;
	height: 80rpx;
	padding: 8rpx;
	flex-direction: column;
}


.visit-icon {
	width: 80rpx;
	height: 80rpx;
	margin-bottom: 8rpx;
	/* transform: scale(1.2); */
}

.visit-text {
	color: #FFFFFF;
	font-size: 16rpx;
}

/* 离屏 canvas(放 page 级,uni-app 组件内 canvas 不被 createCanvasContext 识别会触发 'reading ready')。
   ⚠️ 不能 display:none(让 layout=0,native canvas surface=0),改用负坐标离屏 */
.share-canvas {
	position: fixed;
	left: -9999px;
	top: -9999px;
	width: 750px;
	height: 1334px;
	pointer-events: none;
}

/* 放大态容器:全屏居中卡片,完全脱离 scroll-view */
.pull-expanded-view {
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 50;
	display: flex;
	align-items: center;
	justify-content: center;
}

/* 2026-07-27 refactor (Finding A): 手势/transform 表面,与 .card-wrapper 分离,
   避免 card-3d-flip 动画的 transform 覆盖父级 :style.transform;仅在正常态存在 */
.card-pull-surface {
	position: relative;
	display: block;
	transform-origin: center top;
	/* 2026-07-28 fix(revert): 不加 transition —— 拉动过程必须严格跟手,
	   任何 transition 都会让 transform 插值 250ms,手感变成"橡皮筋/回弹"。
	   释放后的视觉状态由 v-if/v-else-if 分支切换决定(节点销毁重建),
	   不需要 CSS 过渡跨越这个边界。 */
}

/* 2026-07-28: 上拉收起手势 surface,镜像 .card-pull-surface 在 normal 分支的角色
   (触摸事件 + 跟手 transform)。仅在 expanded 分支挂载。
   2026-07-28 fix(jump): 加 0.15s transition(旧版本 0.25s 让跟手变橡皮筋,这次砍到 0.15s):
   - 拉动过程 touchmove 每 16ms 一帧,0.15s ≈ 9 帧,跟手几乎无感(浏览器从当前插值位置起步,
     新 transition 覆盖旧 transition,视觉上仍紧贴手指)
   - 释放瞬间 collapseCardTransform 从 scale(s) translateY(-pull) → scale(1.6) translateY(0),
     transition 把这个"回弹到锁定态"的过程从瞬时跳变变成 150ms 平滑插值,吃掉用户报的"跳一下"
   - 跨阈值(viewMode=normal)路径下,wrapper 销毁,新分支卡片接管,过渡发生在销毁前的最后
     一帧,残余的 transition 不影响新节点 */
.pull-collapse-surface {
	width: 100%;
	height: 100%;
	display: flex;
	align-items: center;
	justify-content: center;
	transform-origin: center center;
	will-change: transform;
	transition: transform 0.15s cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* 2026-07-27: 卡片下方四个 sibling 模块(card-meta-row / info-row / creator-section / chain-section)
   的统一容器,在父级 asset-detail.vue 的 <script setup> 里通过 siblingFadeOpacity 实时绑 opacity,
   与 pullDistance 同步淡出。transition 已经在 inline :style 声明,这里仅补 will-change 提示 GPU 合成 */
.card-siblings {
	width: 100%;
	display: flex;
	flex-direction: column;
	align-items: center;
	will-change: opacity;
}

/* 2026-07-27 refactor (Finding B): 提示文案由父级渲染,组件不再持有
   2026-07-28 fix: 改 fixed 定位脱离滚动容器与 pull transform,固定在 header(top:96rpx, z-index:100)
   下方;z-index 95 保证盖住卡片和滚动内容,但仍在 header 之下 */
.pull-hint {
	position: fixed;
	top: 220rpx;
	left: 50%;
	transform: translateX(-50%);
	z-index: 95;
	font-size: 28rpx;
	color: rgba(255, 255, 255, 0.85);
	font-family: 'yt', sans-serif;
	text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.6);
	pointer-events: none;
}
</style>
