<?php

namespace Helpers\Pagination;

use Helpers\Filter\FilterTable;
use Helpers\Log\LogSysteme;
use Helpers\Outil\ArrayHelper;
use Helpers\QueryBuilder\ETokenType;
use Helpers\QueryBuilder\TokenizerSql;

class PaginatorEtatStat
{

    private ?int $itemCount = null;
    private ?int $pageSize;
    /** @var array<mixed> */
    private array $post;

    public static PaginatorEtatStat $lastPagination;

    /**
     * @param array<mixed> $post
     */
    public function __construct(array $post)
    {
        $this->pageSize = $post['paginator'] ?? null;
        if ($this->pageSize != null && $this->pageSize <= 0) {
            throw new \Exception('Invalid pageSize');
        }
        $this->post = $post;
    }

    /**
     * @param array<mixed> $param
     */
    public function sql(int $page, array &$param) :string
    {
        $param[':paginator_offset'] = ($page - 1) * $this->pageSize;
        $param[':paginator_limit'] = $this->pageSize;
        return  " OFFSET (:paginator_offset) ROWS FETCH NEXT :paginator_limit ROWS ONLY";
    }
    
    /**
     * @param string $req       : requete sql
     * @param array<mixed> $post       : parametre envoyer pour filtrage
     * @param array<mixed> $param
     */
    public function injecterSql(
        $req,
        $post,
        &$param
    ) :string {

        if ($this->pageSize === null) {
            return $req;
        }

        $req = trim($req);

        try {
            $listToken = (new TokenizerSql())->tokenize($req);
            $queryInfo = TokenizerSql::getWith($listToken);
        } catch (\Exception $e) {
            LogSysteme::prefixErrorLog($e);
            return $req;
        }
        
        //________________________
        // Rewrite ORDER BY
        
        $decomp = TokenizerSql::decompose($queryInfo->main);

        array_shift($decomp->orderBy); // Remove token ORDER BY
        $listOrderExpr = [];
        
        // Retrieve list of order expression
        $i = 0;
        TokenizerSql::walk($decomp->orderBy, function (
            $current,
            int $depth
        ) use (
            &$listOrderExpr,
            &$i
) {
            if ($depth == 0 && $current->type == ETokenType::Comma) {
                $i++;
                return;
            }

            // Filter ASC / DESC
            if ($current->type == ETokenType::OrderDir) {
                return;
            }
            
            $listOrderExpr[ '_paginator_order_' . $i][] = $current;
        });

        // Move order list into select as _paginator_order_X
        foreach ($listOrderExpr as $key => $listToken) {
            $decomp->select = array_merge(
                $decomp->select,
                [(object)['content' => ',']],
                $listToken,
                [(object)['content' => ' as '.$key]],
            );
        }

        $decomp->orderBy = []; // Remove order by from main query

        //__________

        $hasWith = ArrayHelper::findFirst($queryInfo->with, function ($token) {
            return $token->type == ETokenType::With;
        });

        return
            ($hasWith ? '' : 'WITH ') // Add WITH if missing
            . implode('', array_column($queryInfo->with, 'content'))
            . ($hasWith ? ',' : ' ')
            . 'OxaPaginator as ('
            . TokenizerSql::composeSql($decomp)
            . '), OxacountPaginator as (
                select COUNT(*) OxaCountPaginator from OxaPaginator
            )
            select * from OxaPaginator, OxacountPaginator 
            ORDER BY '
            . (empty($listOrderExpr) ? '1' : implode(',', array_map(function ($fieldName) {
                return '[OxaPaginator].' . $fieldName;
            }, array_keys($listOrderExpr))))
            . $this->sql(intval($post['page'] ?? 1), $param)
            . ' OPTION (MAXDOP 1)' // Prevent CPU overuse
        ;
    }

    /**
     * @param array<mixed> $return
     * @return void
     */
    public function update(array &$return):void
    {
        if ($this->pageSize == null) {
            return;
        }
        $this->itemCount = $return[0]['OxaCountPaginator'] ?? 0;
        self::$lastPagination = $this;
    }

    /**
     * @param array<mixed> $return
     */
    public static function cleanUpResult(array &$return) :void
    {
        foreach ($return as &$row) {
            $row = ArrayHelper::filterByKey($row, function ($key) {
                return $key != 'OxaCountPaginator'
                    && ! str_starts_with($key, '_paginator_order_');
            });
        }
    }

    /**
     * @return array<mixed>
     */
    public function getInfoPagination()
    {
        return [
            'page' => $this->post['page'] ?? 1,
            'nombreDePage' => $this->getPageCount(),
            'post' => $this->generatPostForJquery($this->post),
        ];
    }

    private function getPageCount() :int
    {
        if (empty($this->itemCount)) {
            return 0;
        }
        return intval(ceil($this->itemCount / $this->pageSize));
    }

    /**
     * @param array<mixed> $params
     * @return string
     */
    public function generatPostForJquery(array $params = [])
    {
        // TODO : improve using json_encode ?
        $i = 0;
        $param = '';
        foreach ($params as $key => $value) {
            if ($i != 0) {
                $param .= ',';
            }

            if (\is_array($value)) {
                $valueString = '{';
                $arrayCountString = 0;

                foreach ($value as $val) {
                    if ($arrayCountString != 0) {
                        $valueString .= ',';
                    }
                    $valueString .= "'" . $val . "' : '" . $val . "'";
                    $arrayCountString++;
                }

                $valueString .= '}';
            } else {
                $valueString = "'" . $value . "'";
            }

            $param .= "'" . $key . "' : " . $valueString;
            $i++;
        }
        // $this->params= "'baba' : 'lolo'";
        return $param;
    }
}
