/**
 * SSAPP2501 场景组件
 * 这是一个基于 Cocos Creator 的游戏场景，实现了一个打蛋游戏
 * 玩家通过点击蛋壳，使用锤子敲开蛋壳，回答问题获取分数
 */

// 导入工具函数
import { asyncDelay, onHomeworkFinish, RandomInt } from "../script/util";
// 导入基础场景组件
import { MyCocosSceneComponent } from "../script/MyCocosSceneComponent";
// 导入游戏管理类和相关枚举
import Game, { GAME_STATE, Option } from "./tool/SSAPP2502_Game";
// 导入工具类
import pg from "./tool/SSAPP2502_pg";

const { ccclass, property } = cc._decorator;

/**
 * 计算两点之间的角度
 * @param p1 起始点坐标
 * @param p2 终点坐标
 * @returns 返回角度值（度数）
 */
export function getDegree(p1, p2) {
    let vector = p2.sub(p1);
    let degree = Math.atan(vector.y / vector.x) / Math.PI * 180;
    if (vector.x >= 0) {
        if (vector.y < 0) {
            degree += 360;
        }
    } else {
        if (vector.y > 0) {
            degree += 180;
        } else {
            degree = 180 + degree;
        }
    }
    return -(-degree + 90); // 角度计算方法
}
// 获取全局 window 对象
let win: any = window;
// 获取课件对象
let courseware = win.courseware;


/**
 * 提示状态枚举
 */
enum TIPS_STATE {
    TRYAGAIN = 1,    // 再试一次提示 (0星，10分以下)
    COMEON,      // 加油提示 (1星，60分以下)
    NICE,        // 不错提示 (2星，80分以下)
    EXCELLENT,   // 优秀提示 (3星，80分及以上)
}
/**
 * 定义糖果节点接口，继承自cc.Node并添加data属性
 */
interface CandyNode extends cc.Node {
    data: any;
}

/**
 * 场景组件类
 * 继承自 MyCocosSceneComponent，实现游戏的主要逻辑
 */
@ccclass
export default class SceneComponent extends MyCocosSceneComponent {

    /**
     * 预加载图片资源
     * 将问题中的所有图片添加到预加载列表中
     */
    addPreloadImage() {
        // 根据配置预加载图片资源
        this._imageResList.push(...this.data.questions.map(question => {
            return { url: question.image };
        }));
    }

    /**
     * 预加载音频资源
     * 将问题文本音频、背景音乐和所有问题的音频添加到预加载列表中
     */
    addPreloadAudio() {
        // 预加载问题文本音频
        this._audioResList.push({ url: this.data.questionTextAudio });
        // 预加载背景音乐
        this._audioResList.push({ url: this.data.bgAudio });
        // 预加载所有问题的音频
        this._audioResList.push(...this.data.questions.map(question => {
            return { url: question.audio };
        }));
    }

    /**
     * 预加载动画资源
     * 当前未使用
     */
    addPreloadAnima() {
        // 暂无动画需要预加载
    }

    /**
     * 资源加载完成后的回调
     * 初始化数据、视图和事件
     */
    onLoadEnd() {
        // 加载完成后的逻辑
        this.initData();  // 初始化数据
        this.initView();  // 初始化视图
        this.initEvent(); // 初始化事件
        this.initGame();
    }
    /**
     * 组件销毁时的回调
     * 清理事件监听，防止内存泄漏
     */
    protected onDestroy(): void {
        pg.event.clear(); // 清理所有事件监听
    }

    // UI 组件引用
    /** 游戏布局节点 */
    @property(cc.Node)
    layout_game: cc.Node = null;
    /** 提示布局节点 */
    @property(cc.Node)
    layout_tip: cc.Node = null;
    /** 语音布局节点 */
    @property(cc.Node)
    layout_speak: cc.Node = null;


    // 移除了消防车、消防车动画节点、消防车冲水动画节点、提示手指节点、页面a和页面b相关属性
    @property({ displayName: "关闭录音界面按钮", type: cc.Node }) btn_close: cc.Node = null;
    @property({ displayName: "锤子", type: cc.Node }) btn_hammer: cc.Node = null;
    @property({ displayName: "糖果节点", type: cc.Node }) item: cc.Node = null;
    @property({ displayName: "糖果区域", type: cc.Node }) layout: cc.Node = null;

    // 游戏数据相关变量
    /** 当前触摸的选项数据 */
    private touchData: Option;
    /** 尝试过的选项数据数组 */
    private tryData: Array<Option>;
    /** 倒计时时间（秒） */
    private countDown: number = 999999;
    /** 当前倒计时显示的时间 */
    private timeCount: number;
    /** 开始倒计时的初始值 */
    private startCount = 15;
    /** 是否播放背景音乐 */
    private isPlayBgAudio: boolean;
    /** 当前页面上存在的火苗节点数组 */
    private activeNodes: cc.Node[] = [];
    /** 是否正在触摸中 */
    private touching: boolean = false;
    /** 录音开始的时间（秒） */
    private recordStartTime: number = 0;
    /** 最大录音时长（秒） */
    private maxRecordDuration: number = 30;
    /** 用户无操作计时器（秒） */
    private inactiveTimer: number = 0;
    /** 提示动画显示的时间阈值（秒） */
    private hintThreshold: number = 3;
    /** 糖果区域滚动速度 */
    private scrollSpeed: number = 100;
    /** 是否需要滚动糖果区域 */
    private needScroll: boolean = false;
    /** 糖果节点宽度 */
    private candyNodeWidth: number = 0;
    /** 待添加的糖果数据队列 */
    private pendingCandyData: Option[] = [];
    /** 滚动距离累计 */
    private scrollDistance: number = 0;
    // 移除了提示动画相关的变量

    /**
     * 初始化游戏数据
     * 设置游戏状态和变量的初始值
     */
    initData() {
        // 初始化游戏管理器
        Game.getIns().init(this.data);
        Game.getIns().reset();
        // 初始化本地变量
        this.tryData = [];
        this.touchData = null;
        this.isPlayBgAudio = true;
        // 初始化活跃火苗节点数组
        this.activeNodes = [];
        // 初始化触摸状态
        this.touching = false;
    }
    /** 音频播放ID */
    private audioId: any;

    /**
     * 初始化游戏视图
     * 设置UI元素的初始状态和显示内容
     */
    async initView() {
        // 停止所有音效
        cc.audioEngine.stopAllEffects();

        // 如果游戏已经开始，停止音频播放
        if (this.audioId > -1 && Game.getIns().state == GAME_STATE.RUNNING) {
            cc.audioEngine.stopAllEffects();
            pg.audio.stopAudio(this.audioId);
        }

        // 移除了提示手指节点相关代码

        // 重置无操作计时器
        this.inactiveTimer = 0;
    }
    /**
     * 初始化事件监听
     * 为UI元素添加事件处理函数
     */
    initEvent() {
        // 音频控制按钮事件
        this.btn_close.on(cc.Node.EventType.TOUCH_END, this.onTouchSpeakClose, this);

        // 移除了消防车点击事件
    }

    initGame() {
        // 设置游戏状态为运行中
        Game.getIns().state = GAME_STATE.RUNNING;

        // 停止问题音频
        if (this.audioId > -1 && Game.getIns().state != GAME_STATE.WAIT) {
            cc.audioEngine.stopAllEffects();
            pg.audio.stopAudio(this.audioId);
        }

        // 播放背景音乐
        pg.audio.playBgAudioByUrl(Game.getIns().bgAudio);


        this.layout_speak.active = false;
        this.layout_tip.active = false;
        // 显示问题
        this.showPage();
    }

    /**
     * 移除了消防车入场动画方法
     */

    /**
     * 更新糖果节点的内容
     * @param candyNode 要更新的糖果节点
     * @param data 节点数据
     * @param index 题目在数组中的索引，用于确定显示哪个糖果图标
     */
    updateItem(candyNode: CandyNode, data: Option, index: number = 0) {
        // 设置基本属性
        candyNode.data = data;
        candyNode.active = true;
        candyNode.y = 0;
        candyNode.x = index * 320 + 150;
        // 获取子节点
        const iconNode = candyNode.getChildByName('icon');
        const bgFullNode = candyNode.getChildByName('bg_full');
        const bgBrokenNode = candyNode.getChildByName('bg_broken');

        // 设置背景状态：默认显示bg_full，隐藏bg_broken
        pg.view.visible(bgFullNode, true);
        pg.view.visible(bgBrokenNode, false);

        // 根据索引设置糖果图标（candy_0 ~ candy_4）
        if (iconNode) {
            // 确保索引在有效范围内（0-4）
            const candyIndex = index % 5;
            const candyIconName = `candy_${candyIndex}`;

            // 使用pg.view.setImg加载对应的糖果图标
            pg.view.setNodeImg(iconNode, pg.view.find(this, `res/img/${candyIconName}`));
            iconNode.active = true;
        }

        // 添加点击事件（如果节点上没有该事件）
        pg.view.touchOn(candyNode, this.onTouchItemNode, this);
    }

    /**
     * 获取未回答过的题目
     * 根据Game.getIns().player.voices中存储的已回答正确的题目ID，过滤出未回答的题目
     * @returns {Array<Option>} 未回答的题目数组
     */
    private getUnansweredQuestions(): Option[] {
        // 获取所有题目数据
        const allQuestions = Game.getIns().getPageData();

        // 获取已回答正确的题目ID列表
        const answeredIds = Game.getIns().player.voices.map(voice => voice.questionId);

        // 过滤出未回答的题目
        return allQuestions.filter(question => !answeredIds.includes(question.id));
    }

    /**
     * 显示当前页面
     * 实现无限滚动逻辑，初始化糖果区域
     */
    showPage() {
        // 获取未回答的题目数据
        const unansweredQuestions = this.getUnansweredQuestions();

        // 重置滚动状态
        this.layout.x = 0;
        this.scrollDistance = 0;
        this.needScroll = unansweredQuestions.length > 5; // 如果有超过5个未回答题目，启用滚动

        // 初始化数据队列
        const initialVisibleCount = Math.min(5, unansweredQuestions.length);
        const visibleData = unansweredQuestions.slice(0, initialVisibleCount);
        this.pendingCandyData = unansweredQuestions.slice(initialVisibleCount);

        // 清空现有节点
        this.activeNodes = [];
        this.layout.removeAllChildren();

        // 添加初始可见的糖果节点
        visibleData.forEach((data, index) => {
            const candyNode = cc.instantiate(this.item) as CandyNode;
            this.layout.addChild(candyNode);
            this.updateItem(candyNode, data, index);
            if (index === 0) {
                this.candyNodeWidth = candyNode.width;
            }
            this.activeNodes.push(candyNode);
        });
    }

    /**
     * 添加新的糖果节点到布局的最后位置
     * 如果待添加队列为空，会重新获取未回答的题目
     */
    private addNewCandyNode() {
        // 检查是否有糖果节点模板
        if (!this.item) return;

        // 如果待添加队列为空，重新获取未回答的题目
        if (this.pendingCandyData.length === 0) {
            const unansweredQuestions = this.getUnansweredQuestions();

            // 如果没有未回答的题目，则不添加新节点
            if (unansweredQuestions.length === 0) {
                this.needScroll = false; // 停止滚动
                return;
            }

            // 如果未回答题目数量小于5，停止滚动并重新刷新列表
            if (unansweredQuestions.length < 5) {
                this.needScroll = false; // 停止滚动
                // 重新显示页面
                this.showPage();
                return;
            }

            // 将未回答的题目添加到待添加队列
            this.pendingCandyData = unansweredQuestions;
        }

        // 从待添加队列中取出一个数据
        const data = this.pendingCandyData.shift();
        if (!data) return;

        // 创建新的糖果节点
        const candyNode = cc.instantiate(this.item) as CandyNode;
        this.layout.addChild(candyNode);
        this.updateItem(candyNode, data, this.addCount + 4);
        this.activeNodes.push(candyNode);

        // 如果有足够的节点，启用滚动
        if (this.activeNodes.length > 5) {
            this.needScroll = true;
        }
    }
    /**
     * 火苗点击事件处理
     * 保存触摸数据并显示卡片
     * 移除了喷水动画相关代码
     * @param e 事件对象或包含target属性的模拟事件对象
     */
    private currentTouchNode: CandyNode;
    async onTouchItemNode(e) {
        if (this.touching) return;
        this.touching = true;
        const candyNode = e.target as CandyNode;
        // 保存触摸数据
        this.touchData = candyNode.data;
        this.touchData.parent = candyNode;
        this.currentTouchNode = candyNode;

        // 获取糖果节点的位置和背景节点
        const bgFullNode = candyNode.getChildByName('bg_full');
        const bgBrokenNode = candyNode.getChildByName('bg_broken');

        // 计算锤子移动到糖果右侧偏移量100,50的位置
        const targetPos = candyNode.convertToWorldSpaceAR(cc.v2(160, -30));
        const hammerWorldPos = this.btn_hammer.parent.convertToNodeSpaceAR(targetPos);

        // 保存锤子原始位置和旋转角度
        const originalPos = cc.v2(this.btn_hammer.x, this.btn_hammer.y);
        const originalRotation = this.btn_hammer.angle;
        // 播放锤子移动到目标位置的动画
        cc.tween(this.btn_hammer)
            .to(0.2, { x: hammerWorldPos.x, y: hammerWorldPos.y })
            .call(() => {
                // 播放锤子旋转敲击的动画
                cc.tween(this.btn_hammer)
                    .to(0.15, { angle: -45 }) // 锤子向上旋转准备敲击
                    .to(0.15, { angle: 30 }) // 锤子向下旋转敲击
                    .call(() => {
                        pg.hw.playLocalAudio('candy_broken')
                        pg.view.visible(bgFullNode, false);
                        pg.view.visible(bgBrokenNode, true);
                    })
                    .delay(0.3) // 等待0.3秒
                    .to(0.2, { x: originalPos.x, y: originalPos.y, angle: originalRotation }) // 锤子回到原位
                    .call(() => {
                    })
                    .start();
            })
            .start();

        // 等待动画完成后显示卡片
        await pg.time.delay(1.1);
        this.showCard(true, 'init');
    }

    onRecordSuccess() {
        const fireNode = this.currentTouchNode;
        const index = this.activeNodes.indexOf(fireNode);
        if (index !== -1) this.activeNodes.splice(index, 1);
        fireNode.active = false;
        this.touching = false;
        // 重置无操作计时器
        this.inactiveTimer = 0;

        // 更新待添加的糖果数据队列，获取最新的未回答题目
        // 这样在无限滚动时会排除刚刚回答正确的题目
        const unansweredQuestions = this.getUnansweredQuestions();
        this.pendingCandyData = unansweredQuestions;

        // // 如果未回答题目数量小于5，停止滚动并重新刷新列表
        // if (unansweredQuestions.length < 5 && unansweredQuestions.length > 0) {
        //     this.needScroll = false; // 停止滚动
        //     // 延迟一段时间后重新显示页面
        //     this.scheduleOnce(() => {
        //         this.showPage();
        //     }, 0.1); // 延迟1秒，给用户时间看到当前页面的完成状态
        //     return;
        // }

        // 在滚动列表中查找并隐藏对应题目的节点
        if (this.touchData && this.layout && this.layout.childrenCount > 0) {
            const currentQuestionId = this.touchData.id;
            // 遍历布局中的所有节点
            for (let i = 0; i < this.layout.childrenCount; i++) {
                const node = this.layout.children[i] as CandyNode;
                // 检查节点是否有data属性且id匹配
                if (node.data && node.data.id === currentQuestionId) {
                    // 从活跃节点列表中移除
                    const nodeIndex = this.activeNodes.indexOf(node);
                    if (nodeIndex !== -1) {
                        this.activeNodes.splice(nodeIndex, 1);
                    }
                    // 隐藏节点
                    node.active = false;
                }
            }
        }

        // 检查当前页面是否所有火苗都已处理完毕
        if (this.activeNodes.length === 0) {
            // 延迟一段时间后检查是否需要切换到下一页
            this.scheduleOnce(() => {
                // 检查是否需要进入下一页
                this.nextPage();
            }, 1.0); // 延迟1秒，给用户时间看到当前页面的完成状态
        }
    }
    onRecordFail() {
        const fireNode = this.currentTouchNode;

        // 将火苗节点重新添加到活跃列表中，以便再次尝试
        if (fireNode && !this.activeNodes.includes(fireNode)) {
            this.activeNodes.push(fireNode);
        }

        // 确保火苗节点保持显示状态
        if (fireNode) {
            fireNode.active = true;

            // 更新糖果背景状态：显示bg_full，隐藏bg_broken
            const bgFullNode = fireNode.getChildByName('bg_full');
            const bgBrokenNode = fireNode.getChildByName('bg_broken');

            if (bgFullNode) {
                bgFullNode.active = true;
            }
            if (bgBrokenNode) {
                bgBrokenNode.active = false;
            }
        }

        // 移除了与消防车冲水动画相关的代码
        this.touching = false;

        // 重置无操作计时器
        this.inactiveTimer = 0;
    }
    /**
     * 切换到下一页
     * 在无限滚动模式下，更新未回答题目列表并重新显示页面
     */
    nextPage() {
        // 切换到下一页
        Game.getIns().addPage();

        // 获取最新的未回答题目
        const unansweredQuestions = this.getUnansweredQuestions();
        this.pendingCandyData = unansweredQuestions;

        // 如果没有未回答的题目，游戏结束
        if (unansweredQuestions.length === 0) {
            // 可以在这里添加游戏结束逻辑
            this.gameOver();
            return;
        }

        // 如果未回答题目数量小于5，设置不滚动
        if (unansweredQuestions.length < 5) {
            this.needScroll = false;
        }

        // 重新显示页面
        this.showPage();
    }

    /**
     * 显示或隐藏录音卡片
     * 根据录音状态动态显示不同的按钮：
     * - 未录制：显示 btn_start
     * - 录制中：显示 btn_stop
     * - 录制完成：显示 btn_record_replay 和 btn_start_reset
     * @param hide 是否隐藏卡片，默认为false（显示）
     * @param recordState 录音状态，可选值：'init'（初始状态）, 'recording'（录制中）, 'recorded'（录制完成）
     */
    /**
     * 播放录音动画效果
     * 循环显示btn_record_0~btn_record_2三个按钮，实现录音中的动画效果
     */
    playVoiceAni() {
        // 获取录音按钮节点
        let btn_record_0 = pg.view.find(this.layout_speak, 'btn_record_0');
        let btn_record_1 = pg.view.find(this.layout_speak, 'btn_record_1');
        let btn_record_2 = pg.view.find(this.layout_speak, 'btn_record_2');

        // 确保所有按钮都存在
        if (!btn_record_0 || !btn_record_1 || !btn_record_2) {
            return this.log('录音按钮节点不存在');
        }

        // 初始状态：显示第一个按钮，隐藏其他按钮
        btn_record_0.active = true;
        btn_record_1.active = false;
        btn_record_2.active = false;

        // 清除可能存在的旧定时器
        if (this._voiceAniTimer) {
            clearInterval(this._voiceAniTimer);
            this._voiceAniTimer = null;
        }

        // 当前显示的按钮索引
        let currentIndex = 0;

        // 创建定时器，每隔200毫秒切换一次按钮显示
        this._voiceAniTimer = setInterval(() => {
            // 隐藏所有按钮
            btn_record_0.active = false;
            btn_record_1.active = false;
            btn_record_2.active = false;

            // 根据当前索引显示对应按钮
            switch (currentIndex) {
                case 0:
                    btn_record_0.active = true;
                    break;
                case 1:
                    btn_record_1.active = true;
                    break;
                case 2:
                    btn_record_2.active = true;
                    break;
            }

            // 更新索引，循环显示三个按钮
            currentIndex = (currentIndex + 1) % 3;
        }, 200);
    }

    /**
     * 停止录音动画效果
     * 停止按钮切换，只显示btn_record_0
     */
    stopVoiceAni() {
        // 清除定时器
        if (this._voiceAniTimer) {
            clearInterval(this._voiceAniTimer);
            this._voiceAniTimer = null;
        }

        // 获取录音按钮节点
        let btn_record_0 = pg.view.find(this.layout_speak, 'btn_record_0');
        let btn_record_1 = pg.view.find(this.layout_speak, 'btn_record_1');
        let btn_record_2 = pg.view.find(this.layout_speak, 'btn_record_2');

        // 确保所有按钮都存在
        if (!btn_record_0 || !btn_record_1 || !btn_record_2) {
            return this.log('录音按钮节点不存在');
        }

        // 只显示第一个按钮，隐藏其他按钮
        btn_record_0.active = true;
        btn_record_1.active = false;
        btn_record_2.active = false;
    }

    // 录音动画定时器
    private _voiceAniTimer: any = null;

    /**
     * 显示或隐藏卡片
     * @param show 是否显示卡片，默认为true
     * @param recordState 录音状态，默认为'init'
     */
    showCard(show: boolean = true, recordState: string = 'init') {
        if (show) {
            // 显示卡片
            const currentData = this.currentTouchNode.data;
            console.log('showCard', currentData)
            this.layout_speak.active = true;

            // 获取UI元素
            let btn_start = pg.view.find(this.layout_speak, 'btn_start');
            let btn_start_reset = pg.view.find(this.layout_speak, 'btn_start_reset');
            let btn_stop = pg.view.find(this.layout_speak, 'btn_stop');
            let btn_record_replay = pg.view.find(this.layout_speak, 'btn_record_replay');
            let bg_card = pg.view.find(this.layout_speak, 'bg_card');

            // 渲染卡片内容（语音按钮、图标、文字、进度）
            if (recordState == 'init') {
                this.renderCardContent(bg_card, currentData);
                // 绑定按钮事件
                this.bindCardButtonEvents(btn_start, btn_start_reset, btn_stop, btn_record_replay);
            }

            // 根据录音状态设置按钮显示
            this.updateButtonsByRecordState(recordState, btn_start, btn_stop, btn_start_reset, btn_record_replay);
        } else {
            // 隐藏录音界面
            this.layout_speak.active = false;
            // 停止录音动画
            this.stopVoiceAni();
        }
    }

    /**
     * 渲染卡片内容（语音按钮、图标、文字、进度）
     * @param bg_card 卡片背景节点
     * @param currentData 当前题目数据
     */
    private renderCardContent(bg_card, currentData) {
        let btn_voice = pg.view.find(bg_card, 'btn_voice');
        let icon = pg.view.find(bg_card, 'icon');
        let word = pg.view.find(bg_card, 'word');
        let label_progress = pg.view.find(bg_card, 'label_progress');

        // 处理btn_voice按钮显示和音频播放
        this.renderVoiceButton(btn_voice, currentData);

        // 处理icon和word的显示逻辑
        this.renderIconAndWord(icon, word, currentData);

        // 设置进度显示
        this.renderProgressLabel(label_progress);
    }

    /**
     * 处理语音按钮的显示和事件绑定
     * @param btn_voice 语音按钮节点
     * @param currentData 当前题目数据
     */
    private renderVoiceButton(btn_voice, currentData) {
        if (currentData.audioUrl) {
            // 如果数据中有audio，显示语音按钮
            pg.view.visible(btn_voice, true);
            // 绑定点击事件播放音频
            pg.view.touchOn(btn_voice, () => {
                // 使用pg.ts的API播放音频
                pg.audio.playAudioByUrl(currentData.audioUrl);
            }, this);
        } else {
            // 没有音频则隐藏按钮
            pg.view.visible(btn_voice, false);
        }
    }

    /**
     * 处理图标和文字的显示逻辑
     * @param icon 图标节点
     * @param word 文字节点
     * @param currentData 当前题目数据
     */
    private renderIconAndWord(icon, word, currentData) {
        if (currentData.type === 'img') {
            // 如果类型是img，显示图片，隐藏文字
            pg.view.visible(icon, true);
            pg.view.visible(word, false);
            // 设置图片内容
            if (currentData.picUrl) {
                pg.view.setNetImg(icon, currentData.picUrl, { w: 945, h: 500 });
            }
        } else {
            // 否则显示文字，隐藏图片
            pg.view.visible(icon, false);
            pg.view.visible(word, true);
            // 设置文字内容
            if (currentData.txt) {
                pg.view.setString(word, currentData.txt);
            }
        }
    }

    /**
     * 设置进度标签显示
     * @param label_progress 进度标签节点
     */
    private renderProgressLabel(label_progress) {
        // 获取当前正确答题的数量和总题目数量
        // 使用player.right表示录音返回结果不是tryagain的答题数据数量
        const correctAnswers = Game.getIns().player.voices.length;
        const totalQuestions = Game.getIns().total;
        // 设置进度文本 (正确答题数量)/(总数)
        pg.view.setString(label_progress, `${correctAnswers + 1}/${totalQuestions}`);
    }

    /**
     * 绑定卡片按钮事件
     * @param btn_start 开始按钮节点
     * @param btn_start_reset 重新开始按钮节点
     * @param btn_stop 停止按钮节点
     * @param btn_record_replay 回放按钮节点
     */
    private bindCardButtonEvents(btn_start, btn_start_reset, btn_stop, btn_record_replay) {
        pg.view.touchOn(btn_start, this.onTouchRecord, this);
        pg.view.touchOn(btn_start_reset, this.onTouchRecord, this);
        pg.view.touchOn(btn_stop, this.onTouchRecordEnd, this);
        pg.view.touchOn(btn_record_replay, this.onTouchPlayRecord, this);
    }

    /**
     * 根据录音状态更新按钮显示
     * @param recordState 录音状态
     * @param btn_start 开始按钮节点
     * @param btn_stop 停止按钮节点
     * @param btn_start_reset 重新开始按钮节点
     * @param btn_record_replay 回放按钮节点
     */
    private updateButtonsByRecordState(recordState, btn_start, btn_stop, btn_start_reset, btn_record_replay) {
        switch (recordState) {
            case 'init': // 初始状态，未录制
                pg.hw.playLocalAudio('pop_card');
                btn_start.active = true;
                btn_stop.active = false;
                btn_start_reset.active = false;
                btn_record_replay.active = false;
                // 停止录音动画，显示初始状态
                this.stopVoiceAni();
                break;
            case 'recording': // 录制中
                btn_start.active = false;
                btn_stop.active = true;
                btn_start_reset.active = false;
                btn_record_replay.active = false;
                // 开始播放录音动画
                this.playVoiceAni();
                break;
            case 'recorded': // 录制完成
                btn_start.active = false;
                btn_stop.active = false;
                btn_start_reset.active = true;
                btn_record_replay.active = true;
                // 停止录音动画，显示初始状态
                this.stopVoiceAni();
                break;
            default: // 默认显示初始状态
                btn_start.active = true;
                btn_stop.active = false;
                btn_start_reset.active = false;
                btn_record_replay.active = false;
                // 停止录音动画，显示初始状态
                this.stopVoiceAni();
        }
    }
    onTouchSpeakClose() {
        this.showCard(false);

        // 获取最后一次录音数据
        const lastVoice = Game.getIns().player.getLastVoice();
        // 如果有录音数据，则使用其分数
        if (lastVoice && lastVoice.result && lastVoice.questionId == this.touchData.id) {
            const score = lastVoice.result.overall;
            if (score >= 10) {
                this.onRecordSuccess();
            } else {
                this.onRecordFail();
            }
        } else {
            // 如果没有录音数据，默认失败
            this.onRecordFail();
        }

        // 更新滚动状态，根据剩余未回答题目数量决定是否继续滚动
        this.needScroll = this.activeNodes.length > 5 || this.pendingCandyData.length > 0;
    }

    /**
     * 游戏帧更新函数
     * 主要用于更新倒计时显示、自动停止录音
     * 移除了提示手指动画相关的代码
     * @param dt 帧间隔时间（秒）
     */
    private addCount: number;
    update(dt) {
        // 只在游戏运行状态下更新
        if (Game.getIns().state != GAME_STATE.RUNNING) return;

        // 处理录音倒计时
        if (this.countDown < 999999) {
            // 减少倒计时时间
            this.countDown -= dt;

            // 计算当前录音时长
            const currentTime = Date.now() / 1000;
            const recordDuration = currentTime - this.recordStartTime;

            // 如果倒计时结束或录音时长超过最大值，自动停止录音
            if (this.countDown <= 0 || recordDuration >= this.maxRecordDuration) {
                // 调用结束录音函数
                this.onTouchRecordEnd();
            }
        }

        // 处理糖果区域滚动
        if (this.needScroll && !this.touching && this.layout && this.layout.childrenCount > 0) {
            // 计算本帧滚动距离
            const frameScrollDistance = this.scrollSpeed * dt;

            // 移动糖果区域
            this.layout.x -= frameScrollDistance;

            // 累计滚动距离
            this.scrollDistance = Math.abs(this.layout.x) - this.addCount * 100;
            if (this.layout.childrenCount <= 6) {
                if (!this.addCount && this.pendingCandyData.length > 0) {
                    this.addCount = 1;
                    this.addNewCandyNode(); // 添加新节点
                } else {
                    if (this.pendingCandyData.length == 0) {
                        let unansweredQuestions = this.getUnansweredQuestions();
                        this.pendingCandyData = unansweredQuestions.concat();
                    }
                    this.addCount++;
                    this.addNewCandyNode(); // 添加新节点
                }
            }

            // 检查第一个糖果节点是否已经移出视图
            const firstCandy = this.layout.children[0];
            if (firstCandy && firstCandy.x + this.layout.x + firstCandy.width / 2 < 0) {
                // 从活跃节点数组中移除第一个节点
                if (this.activeNodes.includes(firstCandy)) {
                    const index = this.activeNodes.indexOf(firstCandy);
                    if (index !== -1) {
                        this.activeNodes.splice(index, 1);
                    }
                }

                // 从布局中移除第一个节点
                firstCandy.removeFromParent();
                firstCandy.destroy();

                console.log("当前节点数量" + this.layout.childrenCount);
            }
        }
    }



    /**
     * 开始录音按钮点击处理
     * 停止背景音乐，显示倒计时，开始录音
     * 录音最长持续30秒，超时自动停止
     */
    private onTouchRecord() {
        // 停止背景音乐
        this.stopBgAudio();
        // 获取UI元素
        // 使用更新后的showCard方法，传入'recording'状态
        this.showCard(true, 'recording');
        // 显示倒计时并设置时间
        let requestedTime = this.touchData.time || this.startCount; //使用问题指定的时间或默认时间
        // 确保倒计时不超过30秒
        this.countDown = Math.min(requestedTime, 30);

        // 记录录音开始时间
        this.recordStartTime = Date.now() / 1000; // 转换为秒

        // 开始录音
        courseware && courseware.startTest(this.touchData.txt);
    }

    /**
     * 移除了提示手指动画相关的方法
     */
    /**
     * 结束录音按钮点击处理
     * 停止倒计时，显示播放和重新录制按钮，结束录音
     */
    private onTouchRecordEnd() {
        // 使用更新后的showCard方法，传入'recorded'状态
        this.showCard(true, 'recorded');

        // 隐藏倒计时并设置一个很大的值防止倒计时继续
        this.countDown = 999999;

        // 重置录音开始时间
        this.recordStartTime = 0;

        let testData = {
            "audioUrl": "https://staging-teach.cdn.ireadabc.com/a8ea7bb153a46941e6f28b7d0dda49f4.mp3",
            "eof": 1,
            "file": "/storage/emulated/0/Android/data/com.example.oxford/cache/shengtong/gradeVoice.mp3",
            "result": {
                "duration": "2.630",
                "fluency": 84,
                "integrity": 100,
                "kernel_version": "3.7.1",
                "overall": 68,
                "pronunciation": 88,
                "resource_version": "2.3.11",
                "rhythm": 46,
                "sentences": [
                    {
                        "beginIndex": 0,
                        "details": [
                            {
                                "charType": 0,
                                "end": 0,
                                "overall": 69,
                                "prominence": 0,
                                "start": 0,
                                "word": "day"
                            },
                            {
                                "charType": 0,
                                "end": 0,
                                "overall": 100,
                                "prominence": 0,
                                "start": 0,
                                "word": "day"
                            },
                            {
                                "charType": 0,
                                "end": 0,
                                "overall": 100,
                                "prominence": 0,
                                "start": 0,
                                "word": "up"
                            },
                            {
                                "charType": 0,
                                "end": 0,
                                "overall": 100,
                                "prominence": 0,
                                "start": 0,
                                "word": "day"
                            },
                            {
                                "charType": 0,
                                "end": 0,
                                "overall": 100,
                                "prominence": 0,
                                "start": 0,
                                "word": "up"
                            }
                        ],
                        "end": 0,
                        "endIndex": 0,
                        "overall": 88,
                        "sentence": "day day up day up",
                        "start": 0
                    }
                ],
                "speed": 170
            },
            "tokenId": "632be7a73327935c2b000005"
        }
        if (!courseware) return this.recrodEnd(testData)
        // await pg.time.delay(5);
        courseware && courseware.stopTest((data) => {
            data = JSON.parse(data);
            this.recrodEnd(data);
        }); //结束录音
    }
    onTouchPlayRecord() {
        const voice = Game.getIns().player.getLastVoice();
        // voice.audioUrl;
        if (voice) {
            pg.audio.playAudioByUrl(voice.audioUrl);
        }
    }


    /** 是否需要再次尝试 */
    private tryAgain: boolean = false;
    /** 是否正在触摸/操作中 */
    private recordTouching: boolean = false;

    /**
     * 处理录音结束后的逻辑
     * 根据录音评分结果决定是否通过，显示相应提示
     * @param data 录音评分数据
     */
    private async recrodEnd(data) {
        this.log("recrodEnd!!!!!")
        // 将当前题目ID添加到录音数据中
        data.questionId = this.touchData.id;

        // 获取评分和检查单词正确性
        let score = data.result.overall;
        this.log("score: " + score);
        let isSuccess = this.checkWords(data);
        this.log("data: " + JSON.stringify(data));

        // 根据评分和单词正确性显示不同的提示
        if (isSuccess) {
            this.recordTouching = false;

            // 根据分数区间显示不同的提示
            if (score >= 80) {
                // 80分及以上，显示优秀提示（3星）
                this.updateTips(TIPS_STATE.EXCELLENT);
                pg.hw.playLocalAudio('score_success');
                // 保存录音数据
                Game.getIns().player.addVoice(data);
            } else if (score >= 60) {
                // 60-80分，显示不错提示（2星）
                this.updateTips(TIPS_STATE.NICE);
                pg.hw.playLocalAudio('score_success');
                // 保存录音数据
                Game.getIns().player.addVoice(data);
            } else if (score >= 10) {
                // 10-60分，显示加油提示（1星）
                this.updateTips(TIPS_STATE.COMEON);
                pg.hw.playLocalAudio('score_success');
                // 保存录音数据
                Game.getIns().player.addVoice(data);
            } else {
                // 10分以下，显示再试一次提示（0星）
                this.updateTips(TIPS_STATE.TRYAGAIN);
                pg.hw.playLocalAudio('score_fail');
            }

            // 增加正确计数
            this.tryAgain = false;

            // 如果有父节点（火苗节点），隐藏它
            if (this.touchData && this.touchData.parent) {
                this.touchData.parent.active = false;
            }

            // 延迟后隐藏卡片和提示，进入下一题
            await pg.time.delay(2);
            this.updateTips(null);
            return;
        } else {
            // 单词不正确
            if (this.tryData.some(td => td.id == this.touchData.id)) {
                // 已经尝试过的题目，显示加油提示
                this.updateTips(TIPS_STATE.COMEON);

                if (this.tryAgain) {
                    // 第二次尝试后，无论结果如何都进入下一题
                    this.tryAgain = false;

                    // 如果有父节点（火苗节点），隐藏它
                    if (this.touchData && this.touchData.parent) {
                        this.touchData.parent.active = false;
                    }

                    await pg.time.delay(3);
                    this.showCard(false);
                    this.updateTips(null);
                    this.recordTouching = false;
                    this.onRecordFail();
                    return;
                }
            } else {
                // 首次尝试失败，显示再试一次提示
                this.updateTips(TIPS_STATE.TRYAGAIN);
                this.tryData.push(this.touchData);
                this.tryAgain = true;

                // 将火苗节点重新添加到活跃列表中，以便再次尝试
                if (this.touchData && this.touchData.parent && !this.activeNodes.includes(this.touchData.parent)) {
                    this.activeNodes.push(this.touchData.parent);
                }
            }
        }

        // 延迟后隐藏提示
        await pg.time.delay(3);
        this.updateTips(null);

        // 根据是否需要再次尝试决定显示卡片或进入下一题
        if (this.tryAgain) {
            // 需要再次尝试，显示卡片和录音界面，重置为初始状态
            this.showCard(true, 'init')
        } else {
            // 不需要再次尝试，隐藏卡片和录音界面
            this.showCard(false)
            this.recordTouching = false;
        }
    }

    /**
     * 更新提示状态
     * 根据不同状态显示不同的提示图标和播放相应音效
     * @param state 提示状态枚举值，为空则隐藏提示
     */
    private updateTips(state?: TIPS_STATE) {
        // 如果状态为空，隐藏提示
        if (!state) {
            this.layout_tip.active = false;
            return;
        }

        // 显示提示布局
        this.layout_tip.active = true;

        // 获取所有提示图标
        let ani_tryagain = pg.view.find(this.layout_tip, 'ani_tryagain'); // 完成提示
        let ani_comeon = pg.view.find(this.layout_tip, 'ani_comeon'); // 完成提示
        let ani_nice = pg.view.find(this.layout_tip, 'ani_nice'); // 完成提示
        let ani_excellent = pg.view.find(this.layout_tip, 'ani_excellent'); // 完成提示

        ani_tryagain.active = false;
        ani_comeon.active = false;
        ani_nice.active = false;
        ani_excellent.active = false;

        switch (state) {
            case TIPS_STATE.TRYAGAIN: // 再试一次（0星，10分以下）
                ani_tryagain.active = true;
                pg.view.playDBAnimation(ani_tryagain, '结算', 1);
                break;
            case TIPS_STATE.COMEON: // 加油（1星，60分以下）
                ani_comeon.active = true;
                pg.view.playDBAnimation(ani_comeon, '结算', 1);
                break;
            case TIPS_STATE.NICE: // 不错（2星，80分以下）
                ani_nice.active = true;
                pg.view.playDBAnimation(ani_nice, '结算', 1);
                break;
            case TIPS_STATE.EXCELLENT: // 优秀（3星，80分及以上）
                ani_excellent.active = true;
                pg.view.playDBAnimation(ani_excellent, '结算', 1);
                break;
        }
    }


    /**
     * 检查录音单词的正确性
     * 通过分析录音评分数据判断单词发音是否正确
     * @param data 录音评分数据
     * @returns 如果所有单词发音正确，返回true；否则返回false
     */
    private checkWords(data) {
        const sentences = data.result.sentences;
        for (let i = 0; i < sentences.length; i++) {
            const details = sentences[i].details;
            // 如果有任何单词的评分低于40，则认为发音不正确
            const isSuccess = details.some(item => item.overall <= 40);
            if (isSuccess) {
                return false;
            }
        }
        return true;
    }


    /**
     * 游戏结束处理
     * 计算得分并提交结果
     */
    private gameOver() {
        // 设置游戏状态为结束
        Game.getIns().state = GAME_STATE.OVER;

        // 准备提交的数据
        let data: any = {};
        data.total = Game.getIns().getTotal;        // 总题目数
        data.right = Game.getIns().player.right;    // 正确题目数
        data.scores = Game.getIns().player.voices;  // 语音记录
        data.stone = Math.floor(10 * data.right / data.total);  // 计算得分

        // 记录日志
        this.log("total: " + data.total + "      right: " + data.right + "   stone: " + data.stone);

        // 提交作业完成数据
        onHomeworkFinish(data)
    }

    /**
     * 音频开启按钮点击处理
     * 关闭背景音乐
     */
    private onAudioOn() {
        this.isPlayBgAudio = false;
        this.stopBgAudio();
    }

    /**
     * 音频关闭按钮点击处理
     * 开启背景音乐
     */
    private onAudioOff() {
        this.isPlayBgAudio = true;
        this.playBgAudio();
    }

    /**
     * 播放背景音乐
     */
    private playBgAudio() {
        if (!this.isPlayBgAudio) return;
        cc.audioEngine.resumeMusic();
    }

    /**
     * 停止背景音乐
     */
    private stopBgAudio() {
        cc.audioEngine.pauseMusic();
    }
    /**
     * 播放本地音频
     * @param audioName 音频资源名称
     * @returns Promise 音频播放完成后解析
     */
    playLocalAudio(audioName) {
        // 获取音频组件
        const audio = cc.find(`Canvas/res/audio/${audioName}`).getComponent(cc.AudioSource);

        // 播放音效并返回Promise
        return new Promise((resolve, reject) => {
            const id = cc.audioEngine.playEffect(audio.clip, false);
            // 设置播放完成回调
            cc.audioEngine.setFinishCallback(id, () => { resolve(id); });
        })
    }
}


