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

// 导入工具函数
import { asyncDelay, onHomeworkFinish, RandomInt } from "../script/util";
// 导入基础场景组件
import { MyCocosSceneComponent } from "../script/MyCocosSceneComponent";
// 导入游戏管理类和相关枚举
import Game, { GAME_STATE, Option } from "./tool/SSAPP2501_Game";
// 导入工具类
import pg from "./tool/SSAPP2501_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分及以上)
}
/**
 * 场景组件类
 * 继承自 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;


    @property({ displayName: "消防车", type: cc.Node }) node_firetruck: cc.Node = null;
    @property({ displayName: "消防车动画节点", type: cc.Node }) node_firetruck_anim: cc.Node = null;
    @property({ displayName: "消防车冲水动画节点", type: cc.Node }) node_firetruck_flush: cc.Node = null;
    @property({ displayName: "提示手指节点", type: cc.Node }) btn_finger: cc.Node = null;
    @property({ displayName: "页面a", type: cc.Node }) page_a: cc.Node = null;
    @property({ displayName: "页面b", type: cc.Node }) page_b: cc.Node = null;
    @property({ displayName: "关闭录音界面按钮", type: cc.Node }) btn_close: cc.Node = null;

    // 游戏数据相关变量
    /** 当前触摸的选项数据 */
    private touchData: Option;
    /** 尝试过的选项数据数组 */
    private tryData: Array<Option>;
    /** 倒计时时间（秒） */
    private countDown: number = 999999;
    /** 当前倒计时显示的时间 */
    private timeCount: number;
    /** 开始倒计时的初始值 */
    private startCount = 15;
    /** 是否播放背景音乐 */
    private isPlayBgAudio: boolean;
    /** 当前页面上存在的火苗节点数组 */
    private activeFireNodes: cc.Node[] = [];
    /** 是否正在触摸中 */
    private touching: boolean = false;
    /** 录音开始的时间（秒） */
    private recordStartTime: number = 0;
    /** 最大录音时长（秒） */
    private maxRecordDuration: number = 30;
    /** 用户无操作计时器（秒） */
    private inactiveTimer: number = 0;
    /** 提示动画显示的时间阈值（秒） */
    private hintThreshold: number = 3;
    /** 提示动画是否正在播放 */
    private isHintAnimPlaying: boolean = false;

    /**
     * 初始化游戏数据
     * 设置游戏状态和变量的初始值
     */
    initData() {
        // 初始化游戏管理器
        Game.getIns().init(this.data);
        Game.getIns().reset();
        // 初始化本地变量
        this.tryData = [];
        this.touchData = null;
        this.isPlayBgAudio = true;
        // 初始化活跃火苗节点数组
        this.activeFireNodes = [];
        // 初始化触摸状态
        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);
        }
        
        // 隐藏提示手指节点
        if (this.btn_finger) {
            this.btn_finger.active = false;
        }
        
        // 重置提示动画状态
        this.isHintAnimPlaying = false;
        this.inactiveTimer = 0;
    }
    /**
     * 初始化事件监听
     * 为UI元素添加事件处理函数
     */
    initEvent() {
        // 音频控制按钮事件
        this.btn_close.on(cc.Node.EventType.TOUCH_END, this.onTouchSpeakClose, this);

        // 添加消防车点击事件
        if (this.node_firetruck) {
            pg.view.touchOn(this.node_firetruck, this.onTouchFiretruck, 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();

        this.aniCarEnter();
    }

    /**
     * 消防车入场动画
     * 从左侧(-1500,-527)移动到右侧(-400,-527)并播放龙骨动画
     * 根据当前页码调整消防车的初始位置和目标位置
     */
    aniCarEnter() {
        // 确保消防车节点存在
        if (!this.node_firetruck || !this.node_firetruck_anim || !this.node_firetruck_flush) {
            return pg.logger.w("消防车节点不存在");
        }
        pg.hw.playLocalAudio('car_in')

        // 获取当前页码
        const currentPage = Game.getIns().getCurrentPageNum();

        // 根据当前页码计算初始位置和目标位置
        const initialX = -1500 + 1920 * currentPage;
        const targetX = -400 + 1920 * currentPage;

        // 设置初始位置
        this.node_firetruck.setPosition(cc.v2(initialX, -330));

        // 隐藏冲水动画节点和提示手指
        this.node_firetruck_flush.active = false;
        this.btn_finger.active = false;
        
        // 重置提示动画状态和计时器
        this.isHintAnimPlaying = false;
        this.inactiveTimer = 0;

        // 播放龙骨动画
        pg.view.playDBAnimation(this.node_firetruck_anim, '移动', -1);
        let dragonDisplay = this.node_firetruck_anim.getComponent(dragonBones.ArmatureDisplay);
        if (dragonDisplay) {
            dragonDisplay.timeScale = 1.2; // 设置动画速度
        }

        // 使用cc.tween实现移动效果
        cc.tween(this.node_firetruck)
            .to(2.0, { x: targetX })
            .call(() => {
                // 播放龙骨动画
                pg.view.playDBAnimation(this.node_firetruck_anim, '待机', -1);
                
                // 消防车到达目标位置后立即显示提示手指
                this.showHintAnimation();
            })
            .start();
    }

    /**
     * 显示当前页面
     * 根据当前页码决定使用page_a还是page_b
     * 并设置fire_0到fire_5这6个交互点
     * 当前页面的内容完成后，layout_game会向左移动一页的距离(1920)
     */
    showPage() {
        // 获取当前页码
        const currentPage = Game.getIns().getCurrentPageNum();
        const prevPage = currentPage - 1;

        // 根据页码的奇偶性决定使用哪个页面
        // 偶数页使用page_a，奇数页使用page_b
        const currentPageNode = currentPage % 2 === 0 ? this.page_a : this.page_b;
        const otherPageNode = currentPage % 2 === 0 ? this.page_b : this.page_a;

        // 显示当前页面，隐藏另一个页面
        currentPageNode.active = true;
        otherPageNode.active = true;

        // 获取当前页的数据
        const pageData = Game.getIns().getPageData();

        // 清空活跃火苗节点数组
        this.activeFireNodes = [];

        // 遍历fire_0到fire_5这6个交互点
        for (let i = 0; i < 6; i++) {
            // 查找当前页面下的fire节点
            const fireNode = cc.find(`fire_${i}`, currentPageNode);
            if (!fireNode) {
                this.log(`找不到节点: fire_${i}`);
                continue;
            }

            // 设置节点可见性
            if (i < pageData.length) {
                // 有数据，显示节点
                fireNode.active = true;
                // 保存题目数据到节点
                fireNode.data = pageData[i];

                // 将活跃的火苗节点添加到数组中
                this.activeFireNodes.push(fireNode);
            } else {
                // 没有数据，隐藏节点
                fireNode.active = false;
            }
        }

        // 如果不是第一页，则添加layout_game向左移动的动画
        if (prevPage >= 0) {
            // 使用cc.tween实现layout_game向左移动一页的距离(1920)
            cc.tween(this.layout_game)
                .to(3.0, { x: -1920 * currentPage }, { easing: 'cubicOut' })
                .start();
            cc.tween(this.node_firetruck)
                .to(3.0, { x: -400 + 1920 * currentPage }, { easing: 'cubicOut' })
                .start();
        }
    }
    /**
     * 消防车点击事件处理
     * 随机选择一个存在的火苗并触发相应的事件
     * 根据选择的火苗编号和当前页码调整消防车位置和喷水角度
     */
    onTouchFiretruck() {
        // 如果正在触摸中，不响应新的触摸
        if (this.touching) return;

        // 检查是否有活跃的火苗节点
        if (this.activeFireNodes.length === 0) {
            this.log("没有可用的火苗节点");

            // 如果当前页面没有活跃火苗，检查是否需要进入下一页
            // if (this.checkIsOver()) {
            //     this.gameOver();
            // }
            return;
        }
        // 如果正在触摸中，不响应新的触摸
        if (this.touching) return;
        this.touching = true;
        
        // 停止提示动画
        this.stopHintAnimation();
        
        // 重置无操作计时器
        this.inactiveTimer = 0;

        // 随机选择一个火苗节点
        const randomIndex = Math.floor(Math.random() * this.activeFireNodes.length);
        const selectedFireNode = this.activeFireNodes[randomIndex];

        // 获取火苗节点的编号
        const nodeName = selectedFireNode.name; // 例如 "fire_0", "fire_1" 等
        const fireIndex = parseInt(nodeName.split('_')[1]);

        // 获取当前页码
        const currentPage = Game.getIns().getCurrentPageNum();

        // 根据火苗编号调整消防车位置
        // fire_0~fire_2 消防车位置 x=-400，fire_3~fire_5 消防车位置 x=-185
        // 并根据当前页码进行偏移
        const baseXList = [-420, -430, -400, -195, -225, -175]
        const baseX = baseXList[fireIndex];
        const targetX = baseX + 1920 * currentPage;

        // 移动消防车到指定位置
        cc.tween(this.node_firetruck)
            .to(0.5, { x: targetX })
            .call(() => {
                // 根据火苗编号设置喷水角度
                // fire_0: -30度, fire_1: -15度, fire_2: 0度
                // fire_3: -30度, fire_4: -15度, fire_5: 0度
                let rotationAngle = 0;
                if (fireIndex === 0 || fireIndex === 3) {
                    rotationAngle = -30;
                } else if (fireIndex === 1 || fireIndex === 4) {
                    rotationAngle = -15;
                } else if (fireIndex === 2 || fireIndex === 5) {
                    rotationAngle = 0;
                }

                // 设置喷水节点的旋转角度
                this.node_firetruck_flush.angle = rotationAngle;

                // 调用火苗点击处理方法
                this.onTouchFireNode({ target: selectedFireNode });
            })
            .start();
    }

    /**
     * 火苗点击事件处理
     * 保存触摸数据并播放喷水动画
     * @param e 事件对象或包含target属性的模拟事件对象
     */
    private currentFireNode;
    async onTouchFireNode(e) {
        const fireNode = e.target;

        pg.hw.playLocalAudio('fire_water');
        // 保存触摸数据
        this.touchData = fireNode.data;
        this.touchData.parent = fireNode;
        this.currentFireNode = fireNode;
        // 1. 播放node_firetruck_flush动画（喷水）
        this.node_firetruck_flush.active = true;
        pg.view.playDBAnimation(this.node_firetruck_flush, '喷水', 1);
        await pg.time.delay(1.5);
        pg.view.playDBAnimation(this.node_firetruck_flush, '持续喷水', 1);
        await pg.time.delay(1);
        this.showCard(true, 'init');
    }

    onFireSuccess() {
        const fireNode = this.currentFireNode;
        // 从活跃火苗列表中移除当前火苗  --- 答题后操作
        const index = this.activeFireNodes.indexOf(fireNode);
        if (index !== -1) {
            this.activeFireNodes.splice(index, 1);
        }

        // 获取火苗节点的编号
        const nodeName = fireNode.name;
        const fireIndex = parseInt(nodeName.split('_')[1]);

        // 隐藏对应的fire节点
        fireNode.active = false;

        // 获取当前页面节点
        const currentPage = Game.getIns().getCurrentPageNum();
        const currentPageNode = currentPage % 2 === 0 ? this.page_a : this.page_b;

        // 查找并显示对应的water节点
        const waterNode = cc.find(`water_${fireIndex}`, currentPageNode);
        if (waterNode) {
            waterNode.active = true;
        } else {
            this.log(`找不到节点: water_${fireIndex}`);
        }
        
        // 停止提示动画
        this.stopHintAnimation();

        pg.view.playDBAnimation(this.node_firetruck_flush, '收水', 1);
        this.scheduleOnce(() => {
            this.node_firetruck_flush.active = false;
            this.touching = false;
            
            // 重置无操作计时器
            this.inactiveTimer = 0;
        }, 1);

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

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

        // 确保火苗节点保持显示状态
        if (fireNode) {
            fireNode.active = true;
        }
        
        // 停止提示动画
        this.stopHintAnimation();
        
        pg.view.playDBAnimation(this.node_firetruck_flush, '收水', 1);
        this.scheduleOnce(() => {
            this.node_firetruck_flush.active = false;
            this.touching = false;
            
            // 重置无操作计时器
            this.inactiveTimer = 0;
        }, 1);
    }
    nextPage() {
        // 切换到下一页
        Game.getIns().addPage();
        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.currentFireNode.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.right;
        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) {
            const score = lastVoice.result.overall;
            if (score >= 10) {
                this.onFireSuccess();
            } else {
                this.onFireFail();
            }
        } else {
            // 如果没有录音数据，默认失败
            this.onFireFail();
        }
    }

    /**
     * 游戏帧更新函数
     * 主要用于更新倒计时显示、自动停止录音和显示提示动画
     * @param dt 帧间隔时间（秒）
     */
    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.touching && this.activeFireNodes.length > 0 && this.layout_speak.active === false) {
            // 增加无操作计时器
            this.inactiveTimer += dt;
            
            // 如果超过阈值且提示动画未播放，显示提示动画
            if (this.inactiveTimer >= this.hintThreshold && !this.isHintAnimPlaying) {
                this.showHintAnimation();
            }
        } else {
            // 重置无操作计时器
            this.inactiveTimer = 0;
            
            // 如果正在播放提示动画且满足停止条件，停止动画
            if (this.isHintAnimPlaying && (this.touching || this.activeFireNodes.length === 0 || this.layout_speak.active === true)) {
                this.stopHintAnimation();
            }
        }
    }



    /**
     * 开始录音按钮点击处理
     * 停止背景音乐，显示倒计时，开始录音
     * 录音最长持续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 showHintAnimation() {
        // 如果提示动画已经在播放，直接返回
        if (this.isHintAnimPlaying) return;
        
        // 标记提示动画正在播放
        this.isHintAnimPlaying = true;
        
        // 显示提示手指
        this.btn_finger.active = true;
        
        // 设置初始缩放
        this.btn_finger.scale = 1.0;
        
        // 创建放大缩小的循环动画
        const scaleAction = cc.tween()
            .to(0.5, { scale: 1.2 })
            .to(0.5, { scale: 1.0 })
            .union();
            
        // 重复执行动画
        cc.tween(this.btn_finger)
            .repeatForever(scaleAction)
            .start();
    }
    
    /**
     * 停止提示动画
     * 隐藏提示手指并停止动画
     */
    private stopHintAnimation() {
        // 如果提示动画没有在播放，直接返回
        if (!this.isHintAnimPlaying) return;
        
        // 标记提示动画已停止
        this.isHintAnimPlaying = false;
        
        // 停止所有动画
        cc.Tween.stopAllByTarget(this.btn_finger);
        
        // 隐藏提示手指
        this.btn_finger.active = false;
        
        // 重置缩放
        this.btn_finger.scale = 1.0;
    }
    /**
     * 结束录音按钮点击处理
     * 停止倒计时，显示播放和重新录制按钮，结束录音
     */
    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": 8,
                "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');
            }

            // 增加正确计数
            Game.getIns().player.addRight();
            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.onFireFail();
                    return;
                }
            } else {
                // 首次尝试失败，显示再试一次提示
                this.updateTips(TIPS_STATE.TRYAGAIN);
                this.tryData.push(this.touchData);
                this.tryAgain = true;

                // 将火苗节点重新添加到活跃列表中，以便再次尝试
                if (this.touchData && this.touchData.parent && !this.activeFireNodes.includes(this.touchData.parent)) {
                    this.activeFireNodes.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); });
        })
    }
}


