<?php

namespace app\admin\model;

use think\Model;

use app\admin\model\Num;


class History extends Model
{





    // 表名
    protected $name = 'history';

    // 自动写入时间戳字段
    protected $autoWriteTimestamp = false;

    // 定义时间戳字段名
    protected $createTime = false;
    protected $updateTime = false;
    protected $deleteTime = false;

    // 追加属性
    protected $append = [

    ];

    /**
     * 获取走势图数据
     * @param int $periods 查询最近多少期
     * @param string $type 查询类型 all=全部号码 special=仅特码
     * @return array {expects: [], data: [[num1,...], ...], colorMap: []}
     */
    public function getTrendData($periods = 30, $type = 'all')
    {
        // 查询波色映射
        $num_model = new Num();
        $colorMap = $num_model->column('color', 'num');

        $history = $this
            ->field('expect,num1,num2,num3,num4,num5,num6,num7,openTime')
            ->order('openTime', 'desc')
            ->limit($periods)
            ->select();

        if (empty($history)) {
            return ['expects' => [], 'data' => [], 'colorMap' => []];
        }

        $expects = [];
        $data = [];
        foreach ($history as $row) {
            $expects[] = (string)$row['expect'];
            if ($type === 'special') {
                $data[] = ['num7' => (int)$row['num7']];
            } else {
                $row_data = [];
                for ($i = 1; $i <= 7; $i++) {
                    $row_data['num' . $i] = (int)$row['num' . $i];
                }
                $data[] = $row_data;
            }
        }

        // 反转数组，使最远的数据在左边，最近的数据在右边（从左往右，从远到近）
        $expects = array_reverse($expects);
        $data = array_reverse($data);

        return [
            'expects' => $expects,
            'data' => $data,
            'colorMap' => $colorMap
        ];
    }

    /**
     * 获取冷热号码
     * @param int $periods 查询最近多少期
     * @param string $type 查询类型 all=全部号码 special=仅特码
     * @return array {hot: [], cold: [], all: []}
     */
    public function getHotColdNumbers($periods = 30, $type = 'all')
    {
        $num_model = new Num();
        $colorMap = $num_model->column('color', 'num');
        $animalMap = $num_model->column('animal', 'num');

        $history = $this
            ->field('expect,num1,num2,num3,num4,num5,num6,num7,openTime')
            ->order('openTime', 'desc')
            ->limit($periods)
            ->select();

        if (empty($history)) {
            return ['hot' => [], 'cold' => [], 'all' => []];
        }

        $fields = ($type === 'special') ? ['num7'] : ['num1', 'num2', 'num3', 'num4', 'num5', 'num6', 'num7'];

        // 统计每个号码的出现次数
        $count = array_fill(1, 49, 0);
        $totalAppearances = 0;
        foreach ($history as $row) {
            foreach ($fields as $field) {
                $num = (int)$row[$field];
                if ($num >= 1 && $num <= 49) {
                    $count[$num]++;
                    $totalAppearances++;
                }
            }
        }

        $all = [];
        for ($num = 1; $num <= 49; $num++) {
            $percent = $totalAppearances > 0 ? round($count[$num] / $totalAppearances * 100, 1) : 0;
            $all[] = [
                'num' => $num,
                'count' => $count[$num],
                'percent' => $percent,
                'color' => $colorMap[$num] ?? '—',
                'animal' => $animalMap[$num] ?? '—'
            ];
        }

        // 按出现次数降序排序
        $sorted = $all;
        usort($sorted, function ($a, $b) {
            return $b['count'] - $a['count'];
        });

        // 热号: top 10, 冷号: bottom 10
        $hot = array_slice($sorted, 0, 10);
        $cold = array_slice($sorted, -10);
        $cold = array_reverse($cold);

        return ['hot' => $hot, 'cold' => $cold, 'all' => $all];
    }

    /**
     * 计算遗漏号码
     * @param int $periods 查询最近多少期
     * @param string $type 查询类型 all=全部号码 special=仅特码
     * @return array [{num: int, omit: int, color: string}, ...]
     */
    public function getMissingNumbers($periods = 10, $type = 'all')
    {
        // 查询最近 $periods 期开奖数据
        if ($type === 'special') {
            $history = $this
                ->field('expect,num7')
                ->order('openTime', 'desc')
                ->limit($periods)
                ->select();
        } else {
            $history = $this
                ->field('expect,num1,num2,num3,num4,num5,num6,num7')
                ->order('openTime', 'desc')
                ->limit($periods)
                ->select();
        }

        // 收集最近 $periods 期出现过的号码
        $appeared = [];
        foreach ($history as $row) {
            if ($type === 'special') {
                $fields = ['num7'];
            } else {
                $fields = ['num1', 'num2', 'num3', 'num4', 'num5', 'num6', 'num7'];
            }
            foreach ($fields as $field) {
                if ($row[$field] !== null && $row[$field] !== '') {
                    $appeared[(int)$row[$field]] = true;
                }
            }
        }

        // 获取遗漏号码（1-49中未出现的）
        $missing = [];
        for ($num = 1; $num <= 49; $num++) {
            if (!isset($appeared[$num])) {
                $missing[] = $num;
            }
        }

        // 查询更多历史数据用于计算遗漏期数
        if ($type === 'special') {
            $allHistory = $this
                ->field('num7')
                ->order('openTime', 'desc')
                ->limit(500)
                ->select();
        } else {
            $allHistory = $this
                ->field('num1,num2,num3,num4,num5,num6,num7')
                ->order('openTime', 'desc')
                ->limit(500)
                ->select();
        }

        // 查询波色映射
        $num_model = new Num();
        $colorMap = $num_model->column('color', 'num');

        // 计算遗漏期数并组装结果
        $result = [];
        foreach ($missing as $num) {
            $omitCount = $this->calcOmitCount($num, $allHistory, $type);
            $result[] = [
                'num' => $num,
                'omit' => $omitCount,
                'color' => $colorMap[$num] ?? '—'
            ];
        }

        // 按遗漏期数降序排序
        usort($result, function ($a, $b) {
            return $b['omit'] - $a['omit'];
        });

        return $result;
    }

    /**
     * 计算某个号码的遗漏期数
     * @param int $num 号码
     * @param array $allHistory 历史数据（已按openTime DESC排序）
     * @param string $type 查询类型 all=全部号码 special=仅特码
     * @return int 遗漏期数
     */
    private function calcOmitCount($num, $allHistory, $type = 'all')
    {
        foreach ($allHistory as $idx => $row) {
            if ($type === 'special') {
                if ((int)$row['num7'] === $num) {
                    return $idx;
                }
            } else {
                for ($i = 1; $i <= 7; $i++) {
                    if ((int)$row['num' . $i] === $num) {
                        return $idx;
                    }
                }
            }
        }
        return count($allHistory);
    }

    /**
     * 波色分析
     */
    public function getColorWaveAnalysis($periods = 30, $type = 'all')
    {
        $num_model = new Num();
        $colorMap = $num_model->column('color', 'num');

        $history = $this->field('expect,num1,num2,num3,num4,num5,num6,num7')->order('openTime', 'desc')->limit($periods)->select();
        if (empty($history)) return ['red' => 0, 'blue' => 0, 'green' => 0, 'red_pct' => 0, 'blue_pct' => 0, 'green_pct' => 0, 'details' => []];

        $fields = ($type === 'special') ? ['num7'] : ['num1','num2','num3','num4','num5','num6','num7'];
        $colors = ['红' => 0, '蓝' => 0, '绿' => 0];
        $total = 0;
        foreach ($history as $row) {
            foreach ($fields as $f) {
                $num = (int)$row[$f];
                $color = $colorMap[$num] ?? '';
                if (strpos($color, '红') !== false) { $colors['红']++; $total++; }
                elseif (strpos($color, '蓝') !== false) { $colors['蓝']++; $total++; }
                elseif (strpos($color, '绿') !== false) { $colors['绿']++; $total++; }
            }
        }
        return [
            'red' => $colors['红'], 'blue' => $colors['蓝'], 'green' => $colors['绿'],
            'red_pct' => $total ? round($colors['红']/$total*100,1) : 0,
            'blue_pct' => $total ? round($colors['蓝']/$total*100,1) : 0,
            'green_pct' => $total ? round($colors['绿']/$total*100,1) : 0,
            'total' => $total
        ];
    }

    /**
     * 生肖分析
     */
    public function getZodiacAnalysis($periods = 30, $type = 'all')
    {
        $num_model = new Num();
        $animalMap = $num_model->column('animal', 'num');
        $colorMap = $num_model->column('color', 'num');

        $history = $this->field('expect,num1,num2,num3,num4,num5,num6,num7')->order('openTime', 'desc')->limit($periods)->select();
        if (empty($history)) return ['list' => []];

        $fields = ($type === 'special') ? ['num7'] : ['num1','num2','num3','num4','num5','num6','num7'];
        $counts = [];
        foreach ($history as $row) {
            foreach ($fields as $f) {
                $num = (int)$row[$f];
                $animal = $animalMap[$num] ?? '未知';
                if (!isset($counts[$animal])) $counts[$animal] = ['animal' => $animal, 'count' => 0, 'color' => $colorMap[$num] ?? '—'];
                $counts[$animal]['count']++;
            }
        }
        $list = array_values($counts);
        usort($list, function ($a, $b) { return $b['count'] - $a['count']; });
        $total = array_sum(array_column($list, 'count'));
        foreach ($list as &$item) { $item['percent'] = $total ? round($item['count']/$total*100, 1) : 0; }
        return ['list' => $list];
    }

    /**
     * 奇偶分析
     */
    public function getOddEvenAnalysis($periods = 30, $type = 'all')
    {
        $history = $this->field('expect,num1,num2,num3,num4,num5,num6,num7')->order('openTime', 'desc')->limit($periods)->select();
        if (empty($history)) return ['odd' => 0, 'even' => 0, 'odd_pct' => 0, 'even_pct' => 0, 'per_period' => []];

        $fields = ($type === 'special') ? ['num7'] : ['num1','num2','num3','num4','num5','num6','num7'];
        $odd = 0; $even = 0; $perPeriod = [];
        foreach ($history as $row) {
            $p_odd = 0; $p_even = 0;
            foreach ($fields as $f) {
                $num = (int)$row[$f];
                if ($num % 2 == 0) { $even++; $p_even++; } else { $odd++; $p_odd++; }
            }
            $perPeriod[] = ['expect' => $row['expect'], 'odd' => $p_odd, 'even' => $p_even];
        }
        $total = $odd + $even;
        return [
            'odd' => $odd, 'even' => $even,
            'odd_pct' => $total ? round($odd/$total*100, 1) : 0,
            'even_pct' => $total ? round($even/$total*100, 1) : 0,
            'per_period' => $perPeriod
        ];
    }

    /**
     * 大小分析（1-24为小，25-49为大）
     */
    public function getBigSmallAnalysis($periods = 30, $type = 'all')
    {
        $history = $this->field('expect,num1,num2,num3,num4,num5,num6,num7')->order('openTime', 'desc')->limit($periods)->select();
        if (empty($history)) return ['big' => 0, 'small' => 0, 'big_pct' => 0, 'small_pct' => 0, 'per_period' => []];

        $fields = ($type === 'special') ? ['num7'] : ['num1','num2','num3','num4','num5','num6','num7'];
        $big = 0; $small = 0; $perPeriod = [];
        foreach ($history as $row) {
            $p_big = 0; $p_small = 0;
            foreach ($fields as $f) {
                $num = (int)$row[$f];
                if ($num >= 25) { $big++; $p_big++; } else { $small++; $p_small++; }
            }
            $perPeriod[] = ['expect' => $row['expect'], 'big' => $p_big, 'small' => $p_small];
        }
        $total = $big + $small;
        return [
            'big' => $big, 'small' => $small,
            'big_pct' => $total ? round($big/$total*100, 1) : 0,
            'small_pct' => $total ? round($small/$total*100, 1) : 0,
            'per_period' => $perPeriod
        ];
    }

    /**
     * 特码走势
     */
    public function getSpecialTrend($periods = 30)
    {
        $num_model = new Num();
        $colorMap = $num_model->column('color', 'num');

        // 先取最近 $periods 条数据
        $history = $this->field('expect,num7,openTime')->order('openTime', 'desc')->limit($periods)->select();
        if (empty($history)) return ['expects' => [], 'specials' => [], 'colors' => []];

        // 反转，使数据从左到右为从远到近
        $history = array_reverse($history);
        $expects = []; $specials = []; $colors = [];
        foreach ($history as $row) {
            $expects[] = (string)$row['expect'];
            $num = (int)$row['num7'];
            $specials[] = $num;
            $colors[] = $colorMap[$num] ?? '';
        }
        return ['expects' => $expects, 'specials' => $specials, 'colors' => $colors];
    }

    /**
     * 连号分析
     */
    public function getConsecutiveNumbers($periods = 30)
    {
        $history = $this->field('expect,num1,num2,num3,num4,num5,num6,num7')->order('openTime', 'desc')->limit($periods)->select();
        if (empty($history)) return ['pairs' => [], 'triples' => []];

        $pairCount = []; $tripleCount = [];
        foreach ($history as $row) {
            $nums = [];
            for ($i = 1; $i <= 7; $i++) $nums[] = (int)$row['num' . $i];
            sort($nums);
            // 找连号对
            for ($i = 0; $i < count($nums) - 1; $i++) {
                if ($nums[$i + 1] - $nums[$i] === 1) {
                    $pair = $nums[$i] . '-' . $nums[$i + 1];
                    $pairCount[$pair] = isset($pairCount[$pair]) ? $pairCount[$pair] + 1 : 1;
                }
            }
            // 找连号三连
            for ($i = 0; $i < count($nums) - 2; $i++) {
                if ($nums[$i + 1] - $nums[$i] === 1 && $nums[$i + 2] - $nums[$i + 1] === 1) {
                    $triple = $nums[$i] . '-' . $nums[$i + 1] . '-' . $nums[$i + 2];
                    $tripleCount[$triple] = isset($tripleCount[$triple]) ? $tripleCount[$triple] + 1 : 1;
                }
            }
        }
        arsort($pairCount); arsort($tripleCount);
        return ['pairs' => $pairCount, 'triples' => $tripleCount];
    }

    /**
     * 尾数分析
     */
    public function getTailNumbers($periods = 30, $type = 'all')
    {
        $num_model = new Num();
        $colorMap = $num_model->column('color', 'num');
        $animalMap = $num_model->column('animal', 'num');

        $history = $this->field('expect,num1,num2,num3,num4,num5,num6,num7')->order('openTime', 'desc')->limit($periods)->select();
        if (empty($history)) return ['tails' => [], 'all' => []];

        $fields = ($type === 'special') ? ['num7'] : ['num1','num2','num3','num4','num5','num6','num7'];
        $tailCount = array_fill(0, 10, 0);
        $all = [];
        foreach ($history as $row) {
            foreach ($fields as $f) {
                $num = (int)$row[$f];
                $tail = $num % 10;
                $tailCount[$tail]++;
            }
        }
        $total = array_sum($tailCount);
        for ($t = 0; $t <= 9; $t++) {
            $all[] = ['tail' => $t, 'count' => $tailCount[$t], 'percent' => $total ? round($tailCount[$t]/$total*100, 1) : 0];
        }
        usort($all, function ($a, $b) { return $b['count'] - $a['count']; });
        return ['all' => $all];
    }

    /**
     * 综合统计面板
     */
    public function getDashboardData($periods = 30, $type = 'all')
    {
        return [
            'hotcold' => $this->getHotColdNumbers($periods, 'special'),
            'colorwave' => $this->getColorWaveAnalysis($periods, 'special'),
            'zodiac' => $this->getZodiacAnalysis($periods, 'special'),
            'oddeven' => $this->getOddEvenAnalysis($periods, 'special'),
            'bigsmall' => $this->getBigSmallAnalysis($periods, 'special'),
            'special' => $this->getSpecialTrend($periods),
            'tailnumbers' => $this->getTailNumbers($periods, 'special'),
            'heatmap' => $this->getSpecialHeatmap($periods),
            'zonetransition' => $this->getZoneTransition($periods),
            'colorwavetransition' => $this->getColorWaveTransition($periods),
            'zonetocolortransition' => $this->getZoneToZoneColor($periods),
            'zodiactransition' => $this->getZodiacTransition($periods)
        ];
    }

    /**
     * 批量查询所有期号特码相对于前N期的冷热状态
     * @param int $lookback 向前推算期数
     * @param int $limit 查询总期数（从最新往前取）
     * @return array [{expect, specialNum, count, avgCount, status, rank, totalPeriods}, ...]
     */
    public function getSpecialHotColdList($lookback = 30, $limit = 100)
    {
        // 查询最近 $limit 期数据，按时间倒序（最新在前）
        $history = $this
            ->field('expect,num7,openTime')
            ->order('openTime', 'desc')
            ->limit($limit)
            ->select();

        if (empty($history)) {
            return [];
        }

        // 查询更多历史数据用于统计
        $allHistory = $this
            ->field('expect,num7,openTime')
            ->order('openTime', 'desc')
            ->limit($limit + 200)
            ->select();

        // 按openTime排序（确保顺序）
        $historySorted = [];
        foreach ($history as $row) {
            $historySorted[] = $row;
        }
        $allHistorySorted = [];
        foreach ($allHistory as $row) {
            $allHistorySorted[] = $row;
        }

        $result = [];

        // 对每一期，计算它前面lookback期的冷热状态
        for ($i = 0; $i < count($historySorted); $i++) {
            $row = $historySorted[$i];
            $specialNum = (int)$row['num7'];

            // 找到该期在全量数据中的位置
            $targetTime = $row['openTime'];
            $count = array_fill(1, 49, 0);
            $periodCount = 0;

            // 往前统计lookback期（跳过该期本身）
            for ($j = 0; $j < count($allHistorySorted); $j++) {
                $checkRow = $allHistorySorted[$j];
                $checkTime = $checkRow['openTime'];

                // 只统计比该期更早的数据
                if ($checkTime >= $targetTime) {
                    continue;
                }

                if ($periodCount >= $lookback) {
                    break;
                }

                $num = (int)$checkRow['num7'];
                if ($num >= 1 && $num <= 49) {
                    $count[$num]++;
                }
                $periodCount++;
            }

            if ($periodCount === 0) {
                $result[] = [
                    'expect' => (string)$row['expect'],
                    'specialNum' => $specialNum,
                    'count' => 0,
                    'avgCount' => 0,
                    'status' => 'unknown',
                    'rank' => 0
                ];
                continue;
            }

            $targetCount = $count[$specialNum];
            $avgCount = $periodCount / 49;

            $status = 'normal';
            if ($avgCount > 0) {
                if ($targetCount > $avgCount * 1.5) {
                    $status = 'hot';
                } elseif ($targetCount < $avgCount * 0.5) {
                    $status = 'cold';
                }
            }

            // 计算排名
            $sorted = [];
            for ($num = 1; $num <= 49; $num++) {
                $sorted[] = ['num' => $num, 'count' => $count[$num]];
            }
            usort($sorted, function ($a, $b) {
                return $b['count'] - $a['count'];
            });

            $rank = 0;
            foreach ($sorted as $idx => $item) {
                if ($item['num'] === $specialNum) {
                    $rank = $idx + 1;
                    break;
                }
            }

            $result[] = [
                'expect' => (string)$row['expect'],
                'specialNum' => $specialNum,
                'count' => $targetCount,
                'avgCount' => round($avgCount, 2),
                'status' => $status,
                'rank' => $rank
            ];
        }

        // 基于最新一期（result[0]）的lookback窗口，计算当前所有49个号码的冷热状态
        $current = $this->_computeCurrentHotCold($allHistorySorted, $historySorted[0], $lookback);

        return [
            'list' => $result,
            'current' => $current
        ];
    }

    /**
     * 基于指定期的lookback窗口，计算所有49个号码的冷热状态
     * @param array $allHistorySorted 全量历史数据
     * @param mixed $targetRow 目标期数据
     * @param int $lookback 向前期数
     * @return array {hot: [{num, count}], cold: [{num, count}], warm: [{num, count}]}
     */
    private function _computeCurrentHotCold($allHistorySorted, $targetRow, $lookback)
    {
        $targetTime = $targetRow['openTime'];
        $count = array_fill(1, 49, 0);
        $periodCount = 0;

        for ($j = 0; $j < count($allHistorySorted); $j++) {
            $checkRow = $allHistorySorted[$j];
            $checkTime = $checkRow['openTime'];

            if ($checkTime >= $targetTime) {
                continue;
            }

            if ($periodCount >= $lookback) {
                break;
            }

            $num = (int)$checkRow['num7'];
            if ($num >= 1 && $num <= 49) {
                $count[$num]++;
            }
            $periodCount++;
        }

        $avgCount = $periodCount > 0 ? $periodCount / 49 : 0;
        $hot = [];
        $cold = [];
        $warm = [];

        for ($num = 1; $num <= 49; $num++) {
            $status = 'warm';
            if ($avgCount > 0) {
                if ($count[$num] > $avgCount * 1.5) {
                    $status = 'hot';
                } elseif ($count[$num] < $avgCount * 0.5) {
                    $status = 'cold';
                }
            }

            $item = ['num' => $num, 'count' => $count[$num]];
            if ($status === 'hot') {
                $hot[] = $item;
            } elseif ($status === 'cold') {
                $cold[] = $item;
            } else {
                $warm[] = $item;
            }
        }

        // 热号按次数降序，冷号按次数升序
        usort($hot, function ($a, $b) { return $b['count'] - $a['count']; });
        usort($cold, function ($a, $b) { return $a['count'] - $b['count']; });
        usort($warm, function ($a, $b) { return $b['count'] - $a['count']; });

        return ['hot' => $hot, 'cold' => $cold, 'warm' => $warm];
    }

    /**
     * 区域转移概率统计
     * 将1-49分为5个区域，统计特码从一个区域转移到另一个区域的概率
     * 区域1: 1-10, 区域2: 11-20, 区域3: 21-30, 区域4: 31-40, 区域5: 41-49
     * @param int $periods 查询最近多少期
     * @return array {zones: [], matrix: [], probabilities: [], total_transitions: int}
     */
    public function getZoneTransition($periods = 100)
    {
        $history = $this
            ->field('expect,num7,openTime')
            ->order('openTime', 'desc')
            ->limit($periods)
            ->select();

        if (empty($history) || count($history) < 2) {
            return ['zones' => ['1-10','11-20','21-30','31-40','41-49'], 'matrix' => [], 'probabilities' => [], 'total_transitions' => 0];
        }

        // 反转为升序，从旧到新
        $history = array_reverse($history);

        $zoneLabels = ['1-10', '11-20', '21-30', '31-40', '41-49'];
        $matrix = array_fill(0, 5, array_fill(0, 5, 0));
        $rowTotals = array_fill(0, 5, 0);

        $getZone = function ($num) {
            if ($num <= 10) return 0;
            if ($num <= 20) return 1;
            if ($num <= 30) return 2;
            if ($num <= 40) return 3;
            return 4;
        };

        $totalTransitions = 0;
        for ($i = 0; $i < count($history) - 1; $i++) {
            $currentNum = (int)$history[$i]['num7'];
            $nextNum = (int)$history[$i + 1]['num7'];
            if ($currentNum < 1 || $currentNum > 49 || $nextNum < 1 || $nextNum > 49) continue;

            $from = $getZone($currentNum);
            $to = $getZone($nextNum);
            $matrix[$from][$to]++;
            $rowTotals[$from]++;
            $totalTransitions++;
        }

        // 计算概率矩阵
        $probabilities = array_fill(0, 5, array_fill(0, 5, 0));
        for ($i = 0; $i < 5; $i++) {
            if ($rowTotals[$i] > 0) {
                for ($j = 0; $j < 5; $j++) {
                    $probabilities[$i][$j] = round($matrix[$i][$j] / $rowTotals[$i] * 100, 1);
                }
            }
        }

        return [
            'zones' => $zoneLabels,
            'matrix' => $matrix,
            'probabilities' => $probabilities,
            'row_totals' => $rowTotals,
            'total_transitions' => $totalTransitions
        ];
    }

    /**
     * 波色转移概率统计
     * 统计特码从一种波色转移到另一种波色的概率（红/蓝/绿）
     * @param int $periods 查询最近多少期
     * @return array {colors: [], matrix: [], probabilities: [], row_totals: [], total_transitions: int}
     */
    public function getColorWaveTransition($periods = 100)
    {
        $history = $this
            ->field('expect,num7,openTime')
            ->order('openTime', 'desc')
            ->limit($periods)
            ->select();

        if (empty($history) || count($history) < 2) {
            return ['colors' => ['红波','蓝波','绿波'], 'matrix' => [], 'probabilities' => [], 'row_totals' => [], 'total_transitions' => 0];
        }

        $history = array_reverse($history);
        $num_model = new Num();
        $colorMap = $num_model->column('color', 'num');

        $colorLabels = ['红波', '蓝波', '绿波'];
        $matrix = array_fill(0, 3, array_fill(0, 3, 0));
        $rowTotals = array_fill(0, 3, 0);

        $getColorIdx = function ($num) use ($colorMap) {
            $color = $colorMap[$num] ?? '';
            if (strpos($color, '红') !== false) return 0;
            if (strpos($color, '蓝') !== false) return 1;
            if (strpos($color, '绿') !== false) return 2;
            return -1;
        };

        $totalTransitions = 0;
        for ($i = 0; $i < count($history) - 1; $i++) {
            $currentNum = (int)$history[$i]['num7'];
            $nextNum = (int)$history[$i + 1]['num7'];
            $from = $getColorIdx($currentNum);
            $to = $getColorIdx($nextNum);
            if ($from < 0 || $to < 0) continue;

            $matrix[$from][$to]++;
            $rowTotals[$from]++;
            $totalTransitions++;
        }

        $probabilities = array_fill(0, 3, array_fill(0, 3, 0));
        for ($i = 0; $i < 3; $i++) {
            if ($rowTotals[$i] > 0) {
                for ($j = 0; $j < 3; $j++) {
                    $probabilities[$i][$j] = round($matrix[$i][$j] / $rowTotals[$i] * 100, 1);
                }
            }
        }

        return [
            'colors' => $colorLabels,
            'matrix' => $matrix,
            'probabilities' => $probabilities,
            'row_totals' => $rowTotals,
            'total_transitions' => $totalTransitions
        ];
    }

    /**
     * 生肖转移概率统计
     * 统计特码从一种生肖转移到另一种生肖的概率（12生肖）
     * @param int $periods 查询最近多少期
     * @return array {animals: [], matrix: [], probabilities: [], row_totals: [], total_transitions: int}
     */
    public function getZodiacTransition($periods = 100)
    {
        $history = $this
            ->field('expect,num7,openTime')
            ->order('openTime', 'desc')
            ->limit($periods)
            ->select();

        if (empty($history) || count($history) < 2) {
            return ['animals' => ['鼠','牛','虎','兔','龙','蛇','马','羊','猴','鸡','狗','猪'], 'matrix' => [], 'probabilities' => [], 'row_totals' => [], 'total_transitions' => 0];
        }

        $history = array_reverse($history);
        $num_model = new Num();
        $animalMap = $num_model->column('animal', 'num');

        $animalLabels = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪'];
        $numAnimals = 12;
        $matrix = array_fill(0, $numAnimals, array_fill(0, $numAnimals, 0));
        $rowTotals = array_fill(0, $numAnimals, 0);

        $getAnimalIdx = function ($num) use ($animalMap, $animalLabels) {
            $animal = $animalMap[$num] ?? '';
            $idx = array_search($animal, $animalLabels);
            return $idx === false ? -1 : $idx;
        };

        $totalTransitions = 0;
        for ($i = 0; $i < count($history) - 1; $i++) {
            $currentNum = (int)$history[$i]['num7'];
            $nextNum = (int)$history[$i + 1]['num7'];
            $from = $getAnimalIdx($currentNum);
            $to = $getAnimalIdx($nextNum);
            if ($from < 0 || $to < 0) continue;

            $matrix[$from][$to]++;
            $rowTotals[$from]++;
            $totalTransitions++;
        }

        $probabilities = array_fill(0, $numAnimals, array_fill(0, $numAnimals, 0));
        for ($i = 0; $i < $numAnimals; $i++) {
            if ($rowTotals[$i] > 0) {
                for ($j = 0; $j < $numAnimals; $j++) {
                    $probabilities[$i][$j] = round($matrix[$i][$j] / $rowTotals[$i] * 100, 1);
                }
            }
        }

        return [
            'animals' => $animalLabels,
            'matrix' => $matrix,
            'probabilities' => $probabilities,
            'row_totals' => $rowTotals,
            'total_transitions' => $totalTransitions
        ];
    }

    /**
     * 区域→区域转移矩阵，单元格内显示波色概率
     * 统计上一期特码所在区域后，下一期特码在各区域的分布，以及每个区域内的波色占比
     * @param int $periods 查询最近多少期
     * @return array {zones: [], matrix: [[zone_count]], color_probs: [[{red, blue, green}]]}
     */
    public function getZoneToZoneColor($periods = 100)
    {
        $history = $this
            ->field('expect,num7,openTime')
            ->order('openTime', 'desc')
            ->limit($periods)
            ->select();

        if (empty($history) || count($history) < 2) {
            return ['zones' => ['1-10','11-20','21-30','31-40','41-49'], 'matrix' => [], 'color_probs' => []];
        }

        $history = array_reverse($history);
        $num_model = new Num();
        $colorMap = $num_model->column('color', 'num');

        $zoneLabels = ['1-10', '11-20', '21-30', '31-40', '41-49'];
        $matrix = array_fill(0, 5, array_fill(0, 5, 0));
        $colorCounts = [];
        for ($i = 0; $i < 5; $i++) {
            for ($j = 0; $j < 5; $j++) {
                $colorCounts[$i][$j] = ['red' => 0, 'blue' => 0, 'green' => 0];
            }
        }

        $getZone = function ($num) {
            if ($num <= 10) return 0;
            if ($num <= 20) return 1;
            if ($num <= 30) return 2;
            if ($num <= 40) return 3;
            return 4;
        };

        for ($i = 0; $i < count($history) - 1; $i++) {
            $currentNum = (int)$history[$i]['num7'];
            $nextNum = (int)$history[$i + 1]['num7'];
            $from = $getZone($currentNum);
            $to = $getZone($nextNum);
            $color = $colorMap[$nextNum] ?? '';

            $matrix[$from][$to]++;
            if (strpos($color, '红') !== false) $colorCounts[$from][$to]['red']++;
            elseif (strpos($color, '蓝') !== false) $colorCounts[$from][$to]['blue']++;
            elseif (strpos($color, '绿') !== false) $colorCounts[$from][$to]['green']++;
        }

        // 计算每个单元格波色概率
        $colorProbs = array_fill(0, 5, array_fill(0, 5, ['red' => 0, 'blue' => 0, 'green' => 0]));
        for ($i = 0; $i < 5; $i++) {
            for ($j = 0; $j < 5; $j++) {
                $total = $matrix[$i][$j];
                if ($total > 0) {
                    $colorProbs[$i][$j] = [
                        'red' => round($colorCounts[$i][$j]['red'] / $total * 100, 1),
                        'blue' => round($colorCounts[$i][$j]['blue'] / $total * 100, 1),
                        'green' => round($colorCounts[$i][$j]['green'] / $total * 100, 1),
                    ];
                }
            }
        }

        return [
            'zones' => $zoneLabels,
            'matrix' => $matrix,
            'color_probs' => $colorProbs
        ];
    }

    /**
     * 特码热力图数据
     * @param int $periods 查询最近多少期
     * @return array {expects: [], heatmap: [[x, y, value]], colors: [号码对应颜色]}
     */
    public function getSpecialHeatmap($periods = 30)
    {
        $num_model = new Num();
        $colorMap = $num_model->column('color', 'num');

        // 查询最近 N 期特码数据
        $history = $this
            ->field('expect,num7,openTime')
            ->order('openTime', 'desc')
            ->limit($periods)
            ->select();

        if (empty($history)) {
            return ['expects' => [], 'heatmap' => [], 'colors' => []];
        }

        // 反转数组，使数据从左到右为从远到近
        $history = array_reverse($history);
        $expects = [];
        $heatmap = [];

        // 构建热力图数据：[x_index, y_index, value]
        // x_index = 期号索引（0到periods-1）
        // y_index = 号码-1（0到48，号码1-49）
        // value = 1（出现）或 0（未出现）
        foreach ($history as $idx => $row) {
            $expects[] = (string)$row['expect'];
            $specialNum = (int)$row['num7'];
            // 标记该期特码号码出现
            if ($specialNum >= 1 && $specialNum <= 49) {
                $heatmap[] = [$idx, $specialNum - 1, 1];
            }
        }

        // 补充号码颜色映射（索引0对应号码1）
        $colors = [];
        for ($num = 1; $num <= 49; $num++) {
            $color = $colorMap[$num] ?? '';
            if (strpos($color, '红') !== false) {
                $colors[] = '#e74c3c';
            } elseif (strpos($color, '蓝') !== false) {
                $colors[] = '#3498db';
            } elseif (strpos($color, '绿') !== false) {
                $colors[] = '#2ecc71';
            } else {
                $colors[] = '#95a5a6';
            }
        }

        return [
            'expects' => $expects,
            'heatmap' => $heatmap,
            'colors' => $colors,
            'nums' => range(1, 49) // 号码列表
        ];
    }


}
