<?php
/*
 本代码由 焕岱科技 创建
 技术支持 WX：15285007200
 严禁反编译、逆向等任何形式的侵权行为，违者将追究法律责任
*/

namespace app\common\model\bbfxshop;

use think\Db;
use Ratchet\Client\Connector;
use React\EventLoop\Loop;
use React\Socket\Connector as SocketConnector;
use app\common\model\bbfxshop\AliyunOss;
use app\common\model\bbfxshop\User;

class ShuzirenVoice
{

    public static function getSet(){
        global $_W;
        $base = $_W['base'];
        if(empty($base)){
            $base = \app\common\model\bbfxshop\Common::getConfig(true);
        }

        return $base['shuziren'];
    }

    public function createTextVoice($user_id,$voice_id,$text){
        require ROOT_PATH.'extend/React/Promise/functions.php';

        $config = self::getSet();

        $api_key = $config['apikey'];
        $websocket_url = 'wss://dashscope.aliyuncs.com/api-ws/v1/inference/'; // WebSocket服务器地址
        $filename = '/ai/voice_'.$user_id.'_'.time().'.mp3';
        $output_file = ROOT_PATH."public".$filename; // 输出文件路径
        // return __json(1,"操作成功",['url'=>'/ai/voice_83_1754639964.mp3']);
        if(!is_dir(ROOT_PATH."public/ai")) {
            mkdir(ROOT_PATH."public/ai");
        }

        $loop = Loop::get();

        if (file_exists($output_file)) {
            // 清空文件内容
            file_put_contents($output_file, '');
        }

        try {
            // 创建自定义的连接器
            $socketConnector = new SocketConnector($loop, [
                'tcp' => [
                    'bindto' => '0.0.0.0:0',
                ],
                'tls' => [
                    'verify_peer' => false,
                    'verify_peer_name' => false,
                ],
            ]);

            $connector = new Connector($loop, $socketConnector);

            $headers = [
                'Authorization' => 'bearer ' . $api_key,
                'X-DashScope-DataInspection' => 'enable'
            ];

            $connector($websocket_url, [], $headers)->then(function ($conn) use ($loop,$text, $output_file,$voice_id) {
                // echo "连接到WebSocket服务器\n";

                // 生成任务ID
                $taskId = $this->generateVoiceTaskId();

                // 发送 run-task 指令
                $this->sendVoiceRunTaskMessage($conn, $taskId,$voice_id);

                // 定义发送 continue-task 指令的函数
                $sendContinueTask = function() use ($conn, $loop, $taskId,$text) {
                    // 待发送的文本
                    $texts = $this->getVoiceTexts($text);
                    // $texts = ["床前明月光", "疑是地上霜", "举头望明月", "低头思故乡"];
                    $continueTaskCount = 0;
                    foreach ($texts as $text) {
                        $continueTaskMessage = json_encode([
                            "header" => [
                                "action" => "continue-task",
                                "task_id" => $taskId,
                                "streaming" => "duplex"
                            ],
                            "payload" => [
                                "input" => [
                                    "text" => $text
                                ]
                            ]
                        ]);
                        // echo "准备发送continue-task指令: " . $continueTaskMessage . "\n";
                        $conn->send($continueTaskMessage);
                        $continueTaskCount++;
                    }
                    // echo "发送的continue-task指令个数为：" . $continueTaskCount . "\n";

                    // 发送 finish-task 指令
                    $this->sendVoiceFinishTaskMessage($conn, $taskId);
                };

                // 标记是否收到 task-started 事件
                $taskStarted = false;

                // 监听消息
                $conn->on('message', function($msg) use ($conn, $sendContinueTask, $loop, &$taskStarted, $taskId, $output_file) {
                    
                    if ($msg->isBinary()) {
                        addFileLog("zl222",$msg->getPayload());
                        // 写入二进制数据到本地文件
                        file_put_contents($output_file, $msg->getPayload(), FILE_APPEND);
                    } else {
                        // 处理非二进制消息
                        $response = json_decode($msg, true);

                        if (isset($response['header']['event'])) {
                            $this->handleVoiceEvent($conn, $response, $sendContinueTask, $loop, $taskId, $taskStarted);
                        } else {
                            // echo "未知的消息格式\n";
                        }
                    }
                });

                // 监听连接关闭
                $conn->on('close', function($code = null, $reason = null) {
                    // echo "连接已关闭\n";
                    if ($code !== null) {
                        // echo "关闭代码: " . $code . "\n";
                        throw new \Exception("连接已关闭，关闭代码: ".$code);
                    }
                    if ($reason !== null) {
                        // echo "关闭原因：" . $reason . "\n";
                        throw new \Exception("连接已关闭，关闭原因：".$reason);
                    }
                });
            }, function ($e) {
                // echo "无法连接：{$e->getMessage()}\n";
                throw new \Exception($e->getMessage());
            });

            $loop->run();

            $result = AliyunOss::upload($filename);
            if($result['code'] == 1){
                $filename = $result['data']['url'];
            }

            $result = __json(1,"操作成功",['url'=>$filename]);

        } catch (\Exception $e) {
            $result = __json(0,$e->getMessage());
        }
        // die;
        return $result;
    }

    public function getVoiceTexts($text){

        if(mb_strlen($text, "utf8") <= 1800){
            
            return [$text];
        }

        $fh = ['。','；','！','？','.',';','?','!'];
        $list = mb_str_split($text);
        $num = 0;
        $text = '';
        $texts = [];
        
        foreach ($list as $key => $value) {
            if (!preg_match('/[\x{4e00}-\x{9fa5}]/u', $value)) {
                $ii = 1;
            }
            else{
                $ii = 2;
            }
            $s = $num + $ii;
            if(($s >= 1200 && $s <= 2000 && in_array($value, $fh)) || $key == count($list) -1) {
                $texts[] = $text.$value;
                $text = '';
                $num = 0;
            }
            else{
                $text .= $value;
                $num += $ii;
            }
        }

        return $texts;
    }
    
    public function generateVoiceTaskId(){
        return bin2hex(random_bytes(16));
    }

    /**
     * 发送 run-task 指令
     * @param $conn
     * @param $taskId
     */
    public function sendVoiceRunTaskMessage($conn, $taskId,$voice_id) {

        $config = self::getSet();
        $api_key = $config['apikey'];

        $runTaskMessage = json_encode([
            "header" => [
                "action" => "run-task",
                "task_id" => $taskId,
                "streaming" => "duplex"
            ],
            "payload" => [
                "task_group" => "audio",
                "task" => "tts",
                "function" => "SpeechSynthesizer",
                // "model" => "qwen-tts-realtime-2025-07-15",// 系统音色
                "model" => "cosyvoice-v2",// 自定义音色
                "parameters" => [
                    "text_type" => "PlainText",
                    "voice" => $voice_id,
                    "format" => "mp3",
                    "sample_rate" => 22050,
                    "volume" => 50,
                    "rate" => 1,
                    "pitch" => 1,
                    "language_hints"=>['zh']
                ],
                "input" => (object) []
            ]
        ]);

        $voice = Db::name("bbfx_shuziren_voice")->where(['apikey'=>$api_key])->find();
        if(!empty($voice)){
            $runTaskMessage['payload']['parameters']['voice'] = $voice['voice_id'];
        }
        // echo "准备发送run-task指令: " . $runTaskMessage . "\n";
        $conn->send($runTaskMessage);
        // echo "run-task指令已发送\n";
    }

    /**
     * 发送 finish-task 指令
     * @param $conn
     * @param $taskId
     */
    public function sendVoiceFinishTaskMessage($conn, $taskId) {
        $finishTaskMessage = json_encode([
            "header" => [
                "action" => "finish-task",
                "task_id" => $taskId,
                "streaming" => "duplex"
            ],
            "payload" => [
                "input" => (object) []
            ]
        ]);
        // echo "准备发送finish-task指令: " . $finishTaskMessage . "\n";
        $conn->send($finishTaskMessage);
        // echo "finish-task指令已发送\n"; 
    }

    /**
     * 处理事件
     * @param $conn
     * @param $response
     * @param $sendContinueTask
     * @param $loop
     * @param $taskId
     * @param $taskStarted
     */
    public function handleVoiceEvent($conn, $response, $sendContinueTask, $loop, $taskId, &$taskStarted) {
        switch ($response['header']['event']) {
            case 'task-started':
                // echo "任务开始，发送continue-task指令...\n";
                $taskStarted = true;
                // 发送 continue-task 指令
                $sendContinueTask();
                break;
            case 'result-generated':
                // 忽略result-generated事件
                break;
            case 'task-finished':
                // echo "任务完成\n";
                $conn->close();
                break;
            case 'task-failed':
                // echo "任务失败\n";
                // echo "错误代码：" . $response['header']['error_code'] . "\n";
                // echo "错误信息：" . $response['header']['error_message'] . "\n";
                $conn->close();
                break;
            case 'error':
                // echo "错误：" . $response['payload']['message'] . "\n";
                break;
            default:
                // echo "未知事件：" . $response['header']['event'] . "\n";
                break;
        }

        // 如果任务已完成，关闭连接
        if ($response['header']['event'] == 'task-finished') {
            // 等待1秒以确保所有数据都已传输完毕
            $loop->addTimer(1, function() use ($conn) {
                $conn->close();
                // echo "客户端关闭连接\n";
            });
        }

        // 如果没有收到 task-started 事件，关闭连接
        if (!$taskStarted && in_array($response['header']['event'], ['task-failed', 'error'])) {
            $conn->close();
        }
    }
}