<?php

namespace Shm\ShmTypes\CompositeTypes\VizType;

use Shm\Shm;
use Shm\ShmTypes\CompositeTypes\VizType\VizSubTypes\VizCardType;
use Shm\ShmTypes\IDsType;
use Shm\ShmTypes\IDType;
use Shm\ShmTypes\StructureType;
use Shm\ShmUtils\DisplayValuePrepare;

class VizType extends StructureType
{
    public string $type = 'viz';


    public bool $compositeType = true;



    public function barChartCountBy(string $field, $_pipeline = [])
    {
        $this->row(3);
        $_this = $this;
        $this->setVizResolver(function ($pipeline) use ($_pipeline, $_this, $field) {

            $rootStructure = $_this->getRootParent();

            $fieldType = $rootStructure->findItemByKey($field);

            $pipeline = [
                ...$_pipeline,
                ...$pipeline,
            ];

            if ($fieldType instanceof IDsType) {
                $pipeline[] = [
                    '$unwind' => '$' . $field
                ];
            }

            $pipeline = [
                ...$_pipeline,
                ...$pipeline,
                [
                    '$match' => [
                        $field => ['$ne' => null]
                    ]
                ],
                [
                    '$group' => [
                        "_id" =>  '$' . $field,
                        "count" => [
                            '$sum' => 1,
                        ]

                    ],
                ],
                [
                    '$sort' => [
                        $field => 1,
                    ],
                ]
            ];

            $items = $rootStructure->aggregate($pipeline)->toArray();

            $data = [];

            if ($fieldType instanceof IDsType || $fieldType instanceof IDType) {

                $ids = array_column($items, '_id');
                $valueStructure = $fieldType->getDocument();
                $displayValues = DisplayValuePrepare::prepareByIdsKeyValues($valueStructure, $ids);


                foreach ($items as &$item) {
                    $item['_id'] = $displayValues[(string)$item['_id']] ?? $item['_id'];
                }
                unset($item);
            }



            foreach ($items as $item) {
                $data[] = [
                    "label" => $item['_id'],
                    "value" => $item['count'],
                ];
            }
            return [
                'vizBarChart' => [
                    'items' => $data
                ],
            ];
        });
        return $this;
    }

    public function lineChartCountByDay(string $field, $_pipeline = [])
    {


        $this->row(3);
        $_this = $this;
        $this->setVizResolver(function ($pipeline) use ($_pipeline, $_this, $field) {

            $rootStructure = $_this->getRootParent();

            $pipeline = [
                ...$_pipeline,
                ...$pipeline,
                [
                    '$group' => [
                        "_id" => [
                            'year' => [
                                '$year' => ['$toDate' => ['$multiply' => ['$' . $field, 1000]]],
                            ],
                            'dayOfYear' => [
                                '$dayOfYear' => ['$toDate' => ['$multiply' => ['$' . $field, 1000]]],
                            ],

                        ],
                        "count" => [
                            '$sum' => 1,
                        ],
                        $field => ['$first' => '$' . $field],

                    ],
                ],
                [
                    '$sort' => [
                        $field => 1,
                    ],
                ]
            ];

            $days = $rootStructure->aggregate($pipeline)->toArray();

            $data = [];

            foreach ($days as $day) {
                $data[] = [
                    "label" => date('d.m.Y', $day[$field]),
                    "value" => $day['count'],
                ];
            }
            return [
                'vizLineChart' => [
                    'items' => $data
                ],
            ];
        });
        return $this;
    }

    public function pieCountBucketBy(string $field,  $_pipeline = [])
    {
        $this->row(3);
        $_this = $this;
        $this->setVizResolver(function ($pipeline) use ($_pipeline, $_this, $field) {

            $rootStructure = $_this->getRootParent();

            $pipeline = [
                ...$_pipeline,
                ...$pipeline
            ];

            $pipeline[] = [
                '$match' => [
                    $field => ['$ne' => null]
                ]
            ];

            $pipeline[] = [
                '$bucketAuto' => [
                    'groupBy' => '$' . $field,
                    'buckets' => 10,
                    'output' => [
                        'count' => ['$sum' => 1]
                    ]
                ]
            ];

            $items = $rootStructure->aggregate($pipeline)->toArray();

            $data = [];

            foreach ($items as $item) {

                $label = "";
                if ($item['_id']['min'] == $item['_id']['max']) {
                    $label = $item['_id']['min'];
                } else {
                    $label = $item['_id']['min'] . " - " . $item['_id']['max'];
                }

                $data[] = [
                    "label" => $label,
                    "value" => $item['count'],
                ];
            }

            return [
                'vizPieChart' => [
                    'items' => $data
                ],
            ];
        });
        return $this;
    }


    public function RFM(string $userField, string $amountField, string $dateField, $_pipeline = [])
    {

        $this->row(3);
        $_this = $this;
        $this->setVizResolver(
            function ($pipeline) use ($_pipeline, $_this, $userField, $amountField, $dateField) {

                $rootStructure = $_this->getRootParent();

                $pipeline = [
                    ...$_pipeline,
                    ...$pipeline
                ];

                // 1. Получаем агрегированные данные по клиентам
                $pipeline = [

                    ...$pipeline,
                    [
                        '$match' => [
                            $amountField => ['$gt' => 0],
                            $userField => ['$ne' => null]
                        ]
                    ],

                    [
                        '$addFields' => [
                            'dateObj' => [
                                '$toDate' => [
                                    '$multiply' => ['$' . $dateField, 1000]
                                ]
                            ]
                        ]
                    ],

                    [
                        '$group' => [
                            '_id' => '$' . $userField,
                            'lastOrderDate' => ['$max' => '$dateObj'],
                            'frequency' => ['$sum' => 1],
                            'monetary' => ['$sum' => '$' . $amountField]
                        ]
                    ],

                    [
                        '$addFields' => [
                            'recency' => [
                                '$divide' => [
                                    ['$subtract' => [new \MongoDB\BSON\UTCDateTime(), '$lastOrderDate']],
                                    1000 * 60 * 60 * 24
                                ]
                            ]
                        ]
                    ]
                ];

                $data = $rootStructure->aggregate($pipeline)->toArray();


                if (!$data || empty($data)) {
                    return [
                        'vizRFM' => [
                            'ranges' => null,
                            'items' => [],
                        ],
                    ];
                }




                // 2. Считаем тертили
                $recencies = array_column($data, 'recency');
                $frequencies = array_column($data, 'frequency');
                $monetaries = array_column($data, 'monetary');

                sort($recencies);
                sort($frequencies);
                sort($monetaries);

                $percentile = function (array $arr, float $p) {
                    $index = (int) floor($p * (count($arr) - 1));
                    return $arr[$index];
                };

                $r1 = $percentile($recencies, 0.33);
                $r2 = $percentile($recencies, 0.66);

                $f1 = $percentile($frequencies, 0.33);
                $f2 = $percentile($frequencies, 0.66);

                $m1 = $percentile($monetaries, 0.33);
                $m2 = $percentile($monetaries, 0.66);

                // 3. Расшифровка
                $allSegments = [

                    // 1** — Уходящие
                    111 => "Потерянные экономные",
                    112 => "Одноразовые",
                    113 => "Одноразовые",

                    121 => "Уходящие редкие",
                    122 => "Уходящие редкие",
                    123 => "Уходящие редкие",

                    131 => "Уходящие постоянные",
                    132 => "Уходящие хорошие постоянные",
                    133 => "Уходящие VIP - клиенты",

                    // 2** — Спящие
                    211 => "Спящие разовые с маленьким чеком",
                    212 => "Спящие разовые со средним чеком",
                    213 => "Спящие разовые с высоким чеком",

                    221 => "Спящие редкие с маленьким чеком",
                    222 => "Спящие редкие со средним чеком",
                    223 => "Спящие редкие с высоким чеком",

                    231 => "Спящие постоянные с маленьким чеком",
                    232 => "Спящие постоянные со средним чеком",
                    233 => "Спящие постоянные с высоким чеком",

                    // 3** — Активные
                    311 => "Новички с маленьким чеком",
                    312 => "Новички со средним чеком",
                    313 => "Новички с высоким чеком",

                    321 => "Постоянные с маленьким чеком",
                    322 => "Постоянные со средним чеком",
                    323 => "Постоянные с высоким чеком",

                    331 => "Лояльные с маленьким чеком",
                    332 => "Лояльные со средним чеком",
                    333 => "VIP - клиенты",
                ];

                // 4. Считаем распределение
                $segments = [];

                foreach ($data as $row) {

                    // R
                    if ($row['recency'] <= $r1) {
                        $R = 3;
                    } elseif ($row['recency'] <= $r2) {
                        $R = 2;
                    } else {
                        $R = 1;
                    }

                    // F
                    if ($row['frequency'] <= $f1) {
                        $F = 1;
                    } elseif ($row['frequency'] <= $f2) {
                        $F = 2;
                    } else {
                        $F = 3;
                    }

                    // M
                    if ($row['monetary'] <= $m1) {
                        $M = 1;
                    } elseif ($row['monetary'] <= $m2) {
                        $M = 2;
                    } else {
                        $M = 3;
                    }

                    $segment = "{$R}{$F}{$M}";

                    if (!isset($segments[$segment])) {
                        $segments[$segment] = [
                            'r' => $R,
                            'f' => $F,
                            'm' => $M,
                            'segment' => $segment,
                            'count' => 0,

                        ];
                    }

                    $segments[$segment]['count']++;
                }

                //Добавляем все остальные сегменты
                foreach ($allSegments as $segment => $label) {
                    if (!isset($segments[$segment])) {

                        $segmentString = (string) $segment;
                        $segments[$segmentString] = [
                            'r' => (int) $segmentString[0],
                            'f' => (int) $segmentString[1],
                            'm' => (int) $segmentString[2],
                            'segment' => $segment,
                            'count' => 0,

                        ];
                    }
                }

                //Сортируем по сегменту от меньшего к большему
                usort($segments, function ($a, $b) {
                    return $a['segment'] <=> $b['segment'];
                });




                return [
                    'vizRFM' => [
                        'ranges' => [
                            'r' => [
                                'min' => $r1,
                                'max' => $r2,
                            ],
                            'f' => [
                                'min' => $f1,
                                'max' => $f2,
                            ],
                            'm' => [
                                'min' => $m1,
                                'max' => $m2,
                            ],
                        ],
                        'items' => array_values($segments)
                    ],
                ];
            }
        );

        return $this;
    }

    public function pieCountBy(string $field,  $_pipeline = [])

    {


        $_this = $this;
        $this->setVizResolver(function ($pipeline) use ($_pipeline, $_this, $field) {

            $rootStructure = $_this->getRootParent();

            $pipeline = [
                ...$_pipeline,
                ...$pipeline
            ];

            $fieldType = $rootStructure->findItemByKey($field);


            if ($fieldType instanceof IDsType) {
                $pipeline[] = [
                    '$unwind' => '$' . $field
                ];
            }


            $pipeline = [
                ...$pipeline,
                [
                    '$group' => [
                        "_id" => '$' . $field,
                        "count" => [
                            '$sum' => 1,
                        ],
                        $field => ['$first' => '$' . $field],

                    ],
                ],
                [
                    '$sort' => [
                        $field => 1,
                    ],
                ]
            ];

            $days = $rootStructure->aggregate($pipeline)->toArray();

            $data = [];

            foreach ($days as $day) {
                $data[] = [
                    "label" => date('d.m.Y', $day[$field]),
                    "value" => $day['count'],
                ];
            }
            return [
                'vizLineChart' => [
                    'items' => $data
                ],
            ];
        });
        return $this;

        $expect = $collection->expect();
        if ($expect) {
            $byKeyExpectType = $expect->items[$byKey] ?? null;
            if ($byKeyExpectType && in_array($byKeyExpectType->customType ?? null, ["ID", "IDs"]) && isset($byKeyExpectType->collection)) {
                $this->setIconByCollection($byKeyExpectType->collection);
            }
        }



        $this->calculateFunction = function () use ($collection, $byKey, $pipeline) {


            $expect = $collection->expect();

            if (!$expect) {
                return null;
            }

            $byKeyExpectType = $expect->items[$byKey] ?? null;




            $currentPipeline = [];

            if ($byKeyExpectType && ($byKeyExpectType->customType ?? null) == "IDs") {

                $currentPipeline[] = [
                    '$unwind' => '$' . $byKey
                ];
            }

            $currentPipeline[] = [
                '$match' => [
                    $byKey => ['$exists' => true]
                ]
            ];

            $currentPipeline[] = [
                '$group' => [
                    "_id" => [
                        $byKey => '$' . $byKey,
                    ],
                    "count" => [
                        '$sum' => 1,
                    ],

                ],
            ];

            $currentPipeline[] = [
                '$sort' => [
                    'count' => -1,
                ],
            ];


            /**
             * Если ID или IDs именю связанный объект, то
             * формируем названиея
             */
            //Если это вложенные данные
            if ($byKeyExpectType && in_array($byKeyExpectType->customType ?? null, ["ID", "IDs"]) && isset($byKeyExpectType->collection)) {


                $currentPipeline[] = [
                    '$lookup' => ['from' => $byKeyExpectType->collection->collection, 'localField' => "_id." . $byKey, 'foreignField' => '_id', 'as' => 'label']
                ];
                $currentPipeline[] = [
                    '$addFields' => ['label' => ['$first' => '$label']]
                ];


                $display = $byKeyExpectType->display ?? "title name surname phone email title_ru title.ru";

                $displayItems = explode(" ", $display);

                $concatArray = [];
                foreach ($displayItems as $val) {
                    $concatArray[] = [
                        '$cond' => [
                            // Проверка: является ли значение строкой или числом
                            ['$in' => [['$type' => '$label.' . $val], ['string', 'int', 'long', 'double', 'decimal']]],
                            // Если да, то проверяем, является ли значение числом
                            ['$cond' => [
                                ['$in' => [['$type' => '$label.' . $val], ['int', 'long', 'double', 'decimal']]],
                                ['$toString' => '$label.' . $val], // Если это число, преобразуем его в строку
                                '$label.' . $val, // Иначе, используем значение как есть (должно быть строкой)
                            ]],
                            '', // Для всех остальных типов используем пустую строку
                        ],
                    ];
                    $concatArray[] = " "; // Добавляем пробелы между значениями полей
                }
                array_pop($concatArray); // Удаляем последний ненужный пробел

                $currentPipeline[] = [
                    '$addFields' => [
                        'label' => [
                            '$concat' => $concatArray,
                        ],
                    ],


                ];
            }




            $result = $collection::doAggregate([...$pipeline, ...$currentPipeline]);





            $data = [];
            foreach ($result as $item) {


                $data[] = [
                    "label" => trim((string) ($item['label'] ?? $item['_id'][$byKey])),
                    "value" => $item['count'],
                ];
            }
            return $data;
        };

        return $this;
    }


    public function cardAverage(string $field, $_pipeline = [])
    {

        $_this = $this;

        $this->setVizResolver(function ($pipeline) use ($_pipeline, $_this, $field) {
            $rootStructure = $_this->getRootParent();


            $pipeline = [
                ...$_pipeline,
                ...$pipeline,
                [
                    '$match' => [
                        '$or' => [
                            [
                                $field => ['$gte' => 0]
                            ],
                            [
                                $field => ['$lte' => 0]

                            ]
                        ]

                    ]
                ],
                [
                    '$group' => [
                        '_id' => null,
                        "value" => [
                            '$avg' => '$' . $field,
                        ],


                    ],
                ],
            ];



            return [
                'vizCard' => [
                    'value' => $rootStructure->aggregate($pipeline)->toArray()[0]['value'] ?? 0,
                ]
            ];
        });

        return $this;
    }

    public function cardCount($_pipeline = [])
    {

        $_this = $this;

        $this->setVizResolver(function ($pipeline) use ($_pipeline, $_this) {
            $rootStructure = $_this->getRootParent();


            $pipeline = [
                ...$_pipeline,
                ...$pipeline,
            ];



            $pipeline[] = [
                '$group' => [
                    '_id' => null,
                    'value' => [
                        '$sum' => 1,
                    ],
                ],
            ];

            return [
                'vizCard' => [
                    'value' => $rootStructure->aggregate($pipeline)->toArray()[0]['value'] ?? 0,
                ]
            ];
        });

        return $this;
    }



    public function __construct()
    {


        //   if (!in_array($viz, self::VIZ_TYPES)) {
        //       throw new \Exception("Invalid viz type: " . $viz);
        //  }

        // self::$viz = $viz;



        parent::__construct([
            'vizCard' => Shm::structure([
                'value' => Shm::int(),
            ])->staticBaseTypeName("VizCard"),
            'vizLineChart' => Shm::structure([
                'items' => Shm::arrayOf(Shm::structure([
                    'label' => Shm::string(),
                    'value' => Shm::float(),
                ])->staticBaseTypeName("VizLineChart"))
            ])->staticBaseTypeName("VizLineChartData"),
            'vizBarChart' => Shm::structure([
                'items' => Shm::arrayOf(Shm::structure([
                    'label' => Shm::string(),
                    'value' => Shm::float(),
                ])->staticBaseTypeName("VizBarChart"))
            ])->staticBaseTypeName("VizBarChartData"),
            'vizPieChart' => Shm::structure([
                'items' => Shm::arrayOf(Shm::structure([
                    'label' => Shm::string(),
                    'value' => Shm::float(),
                ])->staticBaseTypeName("VizPieChart"))
            ])->staticBaseTypeName("VizPieChartData"),
            'vizRadarChart' => Shm::structure([
                'items' => Shm::arrayOf(Shm::structure([
                    'label' => Shm::string(),
                    'value' => Shm::float(),
                ])->staticBaseTypeName("VizRadarChart"))
            ])->staticBaseTypeName("VizRadarChartData"),

            'vizRFM' => Shm::structure([
                'ranges' => Shm::structure([
                    'r' => Shm::structure([
                        'min' => Shm::int(),
                        'max' => Shm::int(),
                    ])->staticBaseTypeName("VizRFMCalulatedRangeRecency"),
                    'f' => Shm::structure([
                        'min' => Shm::int(),
                        'max' => Shm::int(),
                    ])->staticBaseTypeName("VizRFMCalulatedRangeFrequency"),
                    'm' => Shm::structure([
                        'min' => Shm::int(),
                        'max' => Shm::int(),
                    ])->staticBaseTypeName("VizRFMCalulatedRangeMonetary"),
                ])->staticBaseTypeName("VizRFMCalulatedRanges"),
                'items' => Shm::arrayOf(Shm::structure([
                    'r' => Shm::int(),
                    'f' => Shm::int(),
                    'm' => Shm::int(),
                    'segment' => Shm::string(),
                    'count' => Shm::int(),
                ])->staticBaseTypeName("VizRFM"))
            ])->staticBaseTypeName("VizRFMData"),

        ]);
        $this->staticBaseTypeName("VizData");
        // $this->baseTypeName("VizData");
    }

    public $vizResolver = null;

    /**
     * Set the resolver for the viz
     * @param callable $resolver The resolver function 
     * @param array $pipeline The pipeline to be used in the resolver
     * @return void
     * @example
     * $viz->setVizResolver(function ($pipeline) {
     *     return [
     *         'vizCard' => [
     *             'value' => $pipeline->aggregate($pipeline)->toArray()[0]['value'] ?? 0,
     *         ]
     *     ];
     * });
     */
    public function setVizResolver(callable $resolver): void
    {
        $this->vizResolver = $resolver;
    }


    public function callVizResolver(array $pipeline): mixed
    {
        if (!$this->vizResolver) {
            throw new \Exception("Resolver is not set.");
        }
        return call_user_func($this->vizResolver, $pipeline);
    }
}