<?php

namespace Application\Service\Extension\Helper;


use Application\Service\Exceptions\InvalidArgumentException;
use Application\Service\Extension\Helper\base\BaseHelper;
use Application\Service\Extension\Laminas;
use Application\Service\Extension\Helper\DateHelper;

class DoctoradviceHelper
{
    private $matchRatio = 90;

    private $delete_str_arr = ['；','\n',"↑","↓","#","！","~","*","☑","★","◆","。",".","(",")","（","）","?",":","：","√","？","^","<",">","|","、","《","》","，",",","{","}","[","]","【","】","+","--","=","～"];

    //药物字典项
    private $Drug = [];

    //药物信息字典项
    private $Medicaltake = [];

    //黑名单
    private $black_word_arr= [];

    //白名单
    private $white_word_arr= [];

    //黑名单中的白名单
    private $black_white_word_arr= [];

    //是否初次匹配
    private $isFirstMatch = false;

    //医嘱类型
    private int $info_type;

    public function __construct($config = [])
    {
        $this->_getdurg();
        $this->_getwhitelist();
        $this->Medicaltake = Laminas::$serviceManager->collectMedicaltake->fetchAll(['where'=>'is_del = 0 ','columns'=>['id','name','english','take_type','take_number'],'order'=>['take_order']]);
        $this->isFirstMatch = $config['dataReMatch'];
        $this->info_type = $config['info_type'];
    }

    /**
     * Notes : 获取药物字典项信息
     * Author:唐伯虎不虎
     * Date:2024/12/20 10:52
     */
    public function _getdurg()
    {
        $hashKey = 'collect:drug';

        $field = 'drug';

        $cache = Laminas::$serviceManager->redisExtend->getRedisInstance()->hGet($hashKey, $field);

        if(!$cache){
            $drug = Laminas::$serviceManager->collectDrug->fetchAll(['where'=>'is_del = 0 ','columns'=>['id','name'],'order'=>['category_type','drug_order']]);
            $drugDatas = [];
            if(!empty($drug)){

                foreach($drug as  &$drugData){
                    $mate_drug_name = $drugData['name'];

                    $mate_drug_name = mb_substr($mate_drug_name,0,8);

                    if(strpos($mate_drug_name,'(') !== false){
                        $delete_index = strpos($mate_drug_name,'(');
                        $mate_drug_name = substr_replace($mate_drug_name,'',$delete_index);
                        unset($delete_index);
                    }
                    if(strpos($mate_drug_name,'（') !== false){
                        $delete_index = strpos($mate_drug_name,'（');
                        $mate_drug_name = substr_replace($mate_drug_name,'',$delete_index);
                        unset($delete_index);
                    }
                    $mate_drug_name = str_replace($this->delete_str_arr,'',$mate_drug_name);

                    $drugData['name'] = $mate_drug_name;

                    $drugDatas[$drugData['name']] = $drugData;
                }
                unset($drug);
            }
            $this->Drug = $drugDatas;
            Laminas::$serviceManager->redisExtend->getRedisInstance()->hSet($hashKey, $field, json_encode($drugDatas));
            // 设置过期时间
            $ttl = 3600;
            Laminas::$serviceManager->redisExtend->getRedisInstance()->expire($hashKey, $ttl);
        }else{
            $this->Drug = json_decode($cache,true);
        }
    }

    /**
     * Notes : 获取白名单、黑名单
     * Author:唐伯虎不虎
     * Date:2024/12/20 10:54
     */
    public function _getwhitelist()
    {
        $wordDatas = Laminas::$serviceManager->collectMedicalword->wordNameData('is_del = 0');
        $this->white_word_arr = isset($wordDatas['white_word']) && !empty($wordDatas['white_word']) ? $wordDatas['white_word'] : [];
        $this->black_word_arr = isset($wordDatas['black_word']) && !empty($wordDatas['black_word']) ? $wordDatas['black_word'] : [];
        $this->black_white_word_arr = isset($wordDatas['black_white_word']) && !empty($wordDatas['black_white_word']) ? $wordDatas['black_white_word'] : [];
        unset($wordDatas);
    }


    public function match($content)
    {
        $dischargeorderTime = '';

        $datas = json_decode($content);

        foreach($datas as $key=>$value){
            //出院医嘱
            if($this->info_type == 1){
                $this->dischargeAdviceMatch($value);
                $arr['ocr_end_date'] =  '';
                if($arr['ocr_start_date'] != '')$dischargeorderTime = $arr['ocr_start_date'];
                continue;
            }

            //非初次匹配、切换匹配规则
            if($this->isFirstMatch == 1){
                $contentIndex = [];
            }else{
                list($startIndex,$endIndex,$contentIndex) = $this->mergeData($value[0]);
                //可能存在表头在第二行的情况，向下冗余，提高准确率
                if(empty($contentIndex)){
                    list($startIndex,$endIndex,$contentIndex) = $this->mergeData($value[1]);
                }
            }
            $arr['ocr_start_date'] = '';
            $arr['ocr_end_date'] = '';
            foreach($value as $dataK => $dataV){

                $dataV = (array)$dataV;
                //空数据
                $filtered = array_filter($dataV);
                if( count($filtered) === 0 && is_array($dataV) == true) continue;

                if(empty($contentIndex)){
                    //统一方法失效时获取
                    $arr = ArrayHelper::merge($arr,$this->defaultMatch($dataV));
                }else{
                    //第一行是表头 不需要处理
                    if($dataK == 0) continue;
                    //统一方法获取
                    $arr = ArrayHelper::merge($arr,$this->headerMatch($dataV,$startIndex,$endIndex,$contentIndex));
                }
                //全匹配 可能是一整张单子识别成1条了
                $arr['matchall'] = count($value) <= 3 ? true : false;
                //如果结束时间没有则开始时间赋值结束时间
                if(empty($arr['ocr_end_date']) && !empty($arr['ocr_start_date'])) $arr['ocr_end_date'] =$arr['ocr_start_date'];
                if(!empty($arr['ocr_start_date']) && $startTime == '') $startTime = $arr['ocr_start_date'];
                if(!empty($arr['ocr_end_date']) && $endTime == '') $endTime = $arr['ocr_end_date'];
                $this->transformData($arr,$white_word_arr,$black_word_arr,$black_white_word_arr,$drugDatas,$takeDatas);
            }
        }
    }


    /**
     * Notes : 出院医嘱匹配
     * @param $value
     * @return mixed
     * Author:唐伯虎不虎
     * Date:2024/12/20 14:03
     */
    public function dischargeAdviceMatch($value)
    {
        $arr['medical_name'] = str_replace($this->delete_str_arr,'',$value[1]);
        //匹配由汉字、英文字母（大小写）、全角括号（（））、半角括号（()）组成的字符串，且长度至少为 1。 仅数字、仅包含特殊符号 或 前缀非指定字符不匹配
        preg_match('/[\x{4e00}-\x{9fa5}a-zA-Z（）()]+/u', $value[1], $matches);
        $arr['medical_name_reduce'] = $matches[0];

        $arr['ocr_start_date'] = ($value[2] != '未提供') ? $this->timeHandler($value[2]) : '';

        return $arr;
    }


    /**
     * Notes : 默认匹配
     * @param $dataV
     * @return array
     * Author:唐伯虎不虎
     * Date:2024/12/20 14:07
     */
    public function defaultMatch($dataV)
    {
        $arr = [];
        foreach($dataV as $k=>$v){
            //判断当前是否是时间格式 年月日、月日、十分等情况
            $v = str_replace("\n",'',$v);

            $timecheck = $this->timeHandler($v);
            if(!empty($timecheck) && $k<2 ){
                $arr['ocr_start_date'] .= $timecheck;
            }elseif(!empty($timecheck) && $k>2){
                $arr['ocr_end_date'] .= $timecheck;
            }else{
                $arr['medical_name'] .= $v;
                $arr['medical_name_reduce'] .= $v;
            }
        }
        return $arr;
    }

    /**
     * Notes : 表头匹配
     * @param $dataV
     * @param $startIndex
     * @param $endIndex
     * @param $contentIndex
     * @return array
     * Author:唐伯虎不虎
     * Date:2024/12/20 14:09
     */
    public function headerMatch($dataV,$startIndex,$endIndex,$contentIndex)
    {
        $arr = [];
        foreach($dataV as $k=>$v){
            $v = str_replace("\n",'',$v);
            if(in_array($k,$startIndex)){
                $arr['ocr_start_date'] .= $this->timeHandler($v);;
            }

            if(in_array($k,$endIndex)){
                $arr['ocr_end_date'] .= $this->timeHandler($v);;
                continue;
            }

            if(in_array($k,$contentIndex)){
                $arr['medical_name'] .= str_replace($this->delete_str_arr,'',$v);
                preg_match('/(\d+)[a-zA-Z]/', $arr['medical_name'], $matches);
                $arr['dosage_num'] = isset($matches[1]) ? $matches[1] : '';
                preg_match('/^[\p{L}\p{N}\p{Zs}\(\)]+/u', str_replace("\n",'',$arr['medical_name']), $matches);
                $arr['medical_name_reduce'] .= $matches[0];
                continue;
            }
        }
        return $arr;
    }


    public function transformData($data)
    {
        $medical_name = trim($data['medical_name']);
        //识别时间拼接医嘱名称
        $mate_medical_name = trim($data['ocr_start_date']).trim($data['medical_name']).trim($data['ocr_end_date']);

        //字符串替换
        $mate_medical_name = str_replace($this->delete_str_arr,'',$mate_medical_name);

        $data['medical_name'] = $mate_medical_name;

        $mate_type = 0;

        //白名单黑名单匹配状态
        $mate_type = $this->blacklistMatch($mate_medical_name);

        //更新日期
        $medical_date = trim($data['ocr_start_date']);
        $medical_date = strlen($medical_date) > 3 ? $medical_date : '';
        $medical_date = Laminas::$serviceManager->ocrRawdata->replaceRawdata($medical_date,1);
        $data['medical_date'] = $medical_date != '' ? $medical_date : '';

        //更新日期
        $end_date = trim($data['ocr_end_date']);
        $end_date = strlen($end_date) > 3 ? $end_date : '';
        $end_date = Laminas::$serviceManager->ocrRawdata->replaceRawdata($end_date,1);
        $data['end_date'] = $end_date != '' ? $end_date : '';

        //获取匹配的药物名称
        $dosage_id = $dosage_unit = $dosage_way = $dosage_times = $is_dispensary = $drug_id = 0;
        $drug_name = '';

        if($data['matchall'] == true) $mate_type = 1;

        if($mate_type == 1){
            if($data['matchall'] == true){
                unset($data['matchall']);
                $mateAllDrug = $this->mateAllDrugData($data);
                if(!empty($mateAllDrug)){

                    foreach($mateAllDrug as $k=>$v){
                        $originalData = [
                            'mate_type' => 1,
                            'is_lock'=>0,
                            'drug_id'=>$v['id'],
                            'name'=>$v['name']
                        ];
                        if($data['info_type'] == 1) $originalData['is_dispensary'] = 1;

                        Laminas::$serviceManager->doctoradviceOriginal->insert(array_merge($originalData,$data));
                    }
                }
                return true;
            }else{
                $mate_drug = $this->mateDrugdata($data);
            }


            $drug_id = $mate_drug['id'];
            $drug_name = $mate_drug['name'];
            //如果符合白名单并且没有匹配上药物则默认未匹配
            if(!$drug_id && $mate_type == 1) return true;
            if(!empty($takeDatas)){
                foreach($takeDatas as $takeDataType=>$takeData){

                    $takeData_id = $takeData['id'];
                    $takeData_name = $takeData['name'];
                    $takeData_english = $takeData['english'];
                    $takeData_type = $takeData['take_type'];

                    //规格
                    if($takeData_type == 1){
                        if(empty($dosage_id) && strpos($medical_name,$takeData_name) !== false){
                            $dosage_id = $takeData_id;
                            continue;
                        }
                        if(empty($dosage_id) && !empty($takeData_english) && strpos($medical_name,$takeData_english) !== false){
                            $dosage_id = $takeData_id;
                            continue;
                        }
                    }

                    //剂量单位
                    if($takeData_type == 2){

                        if(empty($dosage_unit) && strpos($medical_name,$takeData_name) !== false){
                            $dosage_unit = $takeData_id;
                            continue;
                        }
                        if(empty($dosage_unit) && !empty($takeData_english) && strpos($medical_name,$takeData_english) !== false){
                            $dosage_unit = $takeData_id;
                            continue;
                        }

                    }

                    //服用方式
                    if($takeData_type == 3){
                        if(empty($dosage_way) && strpos($medical_name,$takeData_name) !== false){
                            $dosage_way = $takeData_id;
                            continue;
                        }
                        if(empty($dosage_way) && !empty($takeData_english) && strpos($medical_name,$takeData_english) !== false){
                            $dosage_way = $takeData_id;
                            continue;
                        }
                    }

                    //服用频率
                    if($takeData_type == 4){
                        if(empty($dosage_times) && strpos($medical_name,$takeData_name) !== false){
                            $dosage_times = $takeData_id;
                            continue;
                        }
                        if(empty($dosage_times) && !empty($takeData_english) && strpos($medical_name,$takeData_english) !== false){
                            $dosage_times = $takeData_id;
                            continue;
                        }
                    }

                    //出院带药
                    if($takeData_type == 5){
                        if(empty($is_dispensary) && strpos($medical_name,$takeData_name) !== false){
                            $is_dispensary = $takeData_id;
                            continue;
                        }
                        if(empty($is_dispensary) && !empty($takeData_english) && strpos($medical_name,$takeData_english) !== false){
                            $is_dispensary = $takeData_id;
                            continue;
                        }
                    }
                }
            }

            $originalData = [
                'mate_type' => $mate_type,
                'is_lock'=>0,
                'medical_date'=>$medical_date,
                'end_date'=>$end_date,
                'drug_id'=>$drug_id,
                'dosage_id'=>$dosage_id,
                'dosage_unit'=>$dosage_unit,
                'dosage_way'=>$dosage_way,
                'dosage_times'=>$dosage_times,
                'is_dispensary'=>$is_dispensary,
                'name'=>$drug_name
            ];
            $originalData = array_merge($originalData,$data);

            //出院医嘱 取药（出院带药） 默认为 是
            if($data['info_type'] == 1){
                $originalData['is_dispensary'] = 1;

                $update = [];
                $changeOcrEndDate = $this->timeHandler(trim($data['ocr_end_date']));
                if(!empty($changeOcrEndDate)){
                    $update['ocr_end_date'] = $changeOcrEndDate;
                }
                $changeEndDate = $this->timeHandler(trim($data['end_date']));
                if($this->timeHandler(trim($data['end_date']))){
                    $update['end_date'] = $changeEndDate;
                }

                //查询已保存数据
                $storageDatas = Laminas::$serviceManager->doctoradviceOriginal->fetchCol('medical_name',"is_del = 0 and mate_type = 1 and annex_id = ".$data['annex_id']);
                if(!empty($storageDatas)){
                    foreach($storageDatas as $key=>$value){
                        $update['medical_name'] = $update['ocr_end_date'].$value;
                        Laminas::$serviceManager->doctoradviceOriginal->update($update,['id'=>$key]);
                    }
                }

            }
            unset($originalData['matchall']);
            Laminas::$serviceManager->doctoradviceOriginal->insert($originalData);
        }


    }

    //匹配药物
    public function mateDrugdata(array $medicalData) {
        //医嘱为空，返回[]
        if(empty($medicalData['medical_name_reduce']))   return ['id'=>'','name'=>''];

        $drugDatas = $this->Drug;
        //如果能直接命中 则直接返回
        $mateDrugId = 0;
        if(isset($drugDatas[$medicalData['medical_name_reduce']])) {
            return ['id'=>$drugDatas[$medicalData['medical_name_reduce']]['id'],'name'=>$drugDatas[$medicalData['medical_name_reduce']]['name']];
        }
        $firstArr = [];
        foreach($drugDatas as &$drugData) {

            if($isMate = $this->mateStr($medicalData['medical_name_reduce'], $drugData['name'])) {

                $matchRate = $this->stringdistance($drugData['name'],$medicalData['medical_name_reduce']);

                if(isset($firstArr['matchRate']) && $matchRate > $firstArr['matchRate']){
                    $firstArr['id'] = $drugData['id'];
                    $firstArr['name'] = $drugData['name'];
                    $firstArr['matchRate'] =$matchRate;
                }elseif(!isset($firstArr['matchRate'])){
                    $firstArr['id'] = $drugData['id'];
                    $firstArr['name'] = $drugData['name'];
                    $firstArr['matchRate'] =$matchRate;
                }
                if($matchRate >= $this->matchRatio) break;

            }
        }

        if(!empty($firstArr)){
            $TmpStr = str_replace('-','',$firstArr['name']);
            if(ctype_digit($TmpStr)) return ['id'=>'','name'=>''];
            return $firstArr;
        }

        if(!$isMate) {
            $match = [];
            $arr = [];
            $number = mb_strlen($medicalData['medical_name_reduce']);
            for ($i = 0; $i < $number; $i++) {
                $searhWord = mb_substr($medicalData['medical_name_reduce'],$i,2,'UTF-8');
                if(mb_strlen($searhWord) < 2) continue;
                foreach($drugDatas as $drugvalue) {
                    if(!is_string($drugvalue['name'])) continue;
                    if($this->mateStr($drugvalue['name'], $searhWord)) {
                        $matchRate = $this->stringdistance($drugvalue['name'],$medicalData['medical_name_reduce']);
                        $match[$drugvalue['id']] += $matchRate;
                        if($matchRate >= $this->matchRatio) break 2;
                    }
                }
            }
        }
        if(!empty($match)){
            $maxValue = max($match);
            $maxKey = array_search($maxValue, $match);
            $arr['id'] = $maxKey;
            $arr['name'] = Laminas::$serviceManager->collectDrug->getOneFieldVal('name',"id = $maxKey");
            unset($match);
        }



        return ['id'=>empty($arr) ? 0 : $arr['id'],'name'=>empty($arr) ? 0 : $arr['name']];
    }

    //一条医嘱内容有多个药物匹配
    public function mateAllDrugData(array $medicalData, array $drugDatas)
    {
        $mateDrug = [];
        foreach($drugDatas as &$drugData) {
            if($isMate = $this->mateStr($medicalData['medical_name'], $drugData['name'])) {

                $mateDrug[$drugData['id']]['id'] = $drugData['id'];
                $mateDrug[$drugData['id']]['name'] = $drugData['name'];
            }
        }
        $result = [];
        if(!empty($mateDrug)) $result = $this->getSpecificNamesFromArray($mateDrug);

        return $result;
    }

    private function mateStr(string $str, string $searchStr) {
        return stripos($str, $searchStr) !== false;
    }

    public function stringdistance($string1,$string2)
    {
        $distance = levenshtein($string1, $string2);

        $maxLength = max(strlen($string1), strlen($string2));
        return  round((1 - ($distance / $maxLength)) * 100, 2);
    }

    // 优先提取更精确的匹配
    public function getSpecificNamesFromArray(array $items) {
        // 按照 name 的长度从大到小排序
        usort($items, function($a, $b) {
            return strlen($b['name']) - strlen($a['name']);
        });

        $filtered = [];

        foreach ($items as $item) {
            $alreadyMatched = false;
            foreach ($filtered as $fItem) {
                if (strpos($fItem['name'], $item['name']) !== false) {
                    $alreadyMatched = true;
                    break;
                }
            }
            // 如果没有更具体的匹配，将当前项加入结果集
            if (!$alreadyMatched) {
                $filtered[] = $item;
            }
        }

        return $filtered;
    }
    /**
     * Notes : 获取医嘱表头
     * @param $data
     * @return array
     * Author:唐伯虎不虎
     * Date:2024/12/20 14:03
     */
    public function mergeData($data)
    {

        $startTime = ['100'=>'开始时间','95'=>'日期','90'=>'时间','98'=>'起始'];

        $doctorAdvice = ['长期医嘱','医嘱','医'];

        $endtTime = ['80'=>'执行日期','85'=>'日期','20'=>'停','30'=>'时间','100'=> '停止时间'];

        $startTimeKey = [];

        $endtTimeKey = [];

        $doctorAdviceKey = [];

        foreach($data as $k=>$v){
            $Pending = trim($v);
            $Pending = str_replace("\n",'',$Pending);

            foreach($startTime as $startk=>$startv){
                if(strpos($Pending, $startv) !== false && count($startTimeKey) < 2) {
                    $startTimeKey[$startk] = $k;
                    if(isset($data[$k-1]) && empty($data[$k-1]) && !in_array($k-1,$startTimeKey))$startTimeKey[] = $k-1;
                    if(isset($data[$k+1]) && empty($data[$k+1]) && !in_array($k+1,$startTimeKey))$startTimeKey[] = $k+1;
                }
                continue;
            }


            foreach($endtTime as $endk=>$endv){
                if(strpos($Pending, $endv) !== false && count($endtTimeKey) < 2 && !in_array($k,$startTimeKey)) {
                    $endtTimeKey[$endk] = $k;
                }
                continue;
            }

            foreach($doctorAdvice as $endk=>$endv){
                if(strpos($Pending, $endv) !== false ) {
                    $doctorAdviceKey[] = $k;
                    //复杂表格可能出现医嘱列名与内容不对称的情况
                    if(isset($data[$k-1]) && empty($data[$k-1]) )$doctorAdviceKey[] = $k-1;
                    if(isset($data[$k+1]) && empty($data[$k+1]) )$doctorAdviceKey[] = $k+1;

                }
                continue;
            }
        }
        if(!empty($doctorAdviceKey)) $doctorAdviceKey = array_unique($doctorAdviceKey);
        if(empty($endtTimeKey) && !empty($startTimeKey)) $endtTimeKey = $startTimeKey;
        return [$startTimeKey,$endtTimeKey,$doctorAdviceKey];
    }

    /**
     * Notes : 判断当前药物是否命中黑名单
     * @param $mate_medical_name
     * @return int
     * Author:唐伯虎不虎
     * Date:2024/12/20 14:19
     */
    public function blacklistMatch($mate_medical_name)
    {
        $white_word = [];
        if(!empty($this->white_word_arr)){
            foreach($this->white_word_arr as $white_word_str){
                if(strpos($mate_medical_name,$white_word_str) !== false){
                    $white_word[] = $white_word_str;
                }
            }
        }

        if (!empty($white_word)) {
            //已匹配上黑名单的词
            $black_word = [];
            if(!empty($this->black_word_arr)){
                foreach($this->black_word_arr as $black_word_str){
                    if(strpos($mate_medical_name,$black_word_str) !== false){
                        $black_word[] = $black_word_str;
                    }
                }
            }

            if (!empty($black_word)) {
                //已匹配上黑名单下的白名单词
                $black_white_word = [];
                if(!empty($this->black_white_word_arr)){
                    foreach($this->black_white_word_arr as $black_white_word_str){
                        if(strpos($mate_medical_name,$black_white_word_str) !== false){
                            $black_white_word[] = $black_white_word_str;
                        }
                    }
                }
                if (empty($black_white_word)) {
                    $mate_type = 2;
                } else {
                    $mate_type = 1;
                }
                unset($black_white_word);
            } else {
                $mate_type = 1;
            }
            unset($black_word);
        }
        return $mate_type;
    }

    //时间处理
    public function timeHandler($date)
    {
        //验证是否是年月日格式
        if(DateHelper::isValidDate($date)){
            return $date;
        }
        //年月日时分
        if(DateHelper::YMDHSisValidDate($date)){
            return $date;
        }

        //验证是否是日/月格式
        if(DateHelper::isDayMonthTime($date)){
            return DateHelper::dateToMonthDay($date);
        }

        return '';
    }

}
