<?php

namespace Clevercherry\Backslash4\ScoutExtensions;

use Illuminate\Support\Facades\Config;
use Laravel\Scout\Builder;
use Laravel\Scout\Engines\TypesenseEngine;
use Typesense\Client as Typesense;
use Typesense\Collection as TypesenseCollection;
use Typesense\Documents;
use Typesense\Exceptions\ObjectNotFound;

class BsTypesenseEngine extends TypesenseEngine
{
    /**
     * these should all nbe implemented in TypesenseEngine
     * but we can override them to extend the search functionality
     * 
     * abstract public function update($models);
     * abstract public function delete($models);
     * abstract public function search(Builder $builder);
     * abstract public function paginate(Builder $builder, $perPage, $page);
     * abstract public function mapIds($results);
     * abstract public function map(Builder $builder, $results, $model);
     * abstract public function getTotalCount($results);
     * abstract public function flush($model);
     */

    /**
     * Create new Typesense engine instance.
     *
     * @param  Typesense  $typesense
     */
    public function __construct()
    {
        $this->typesense = new Typesense(config('scout.typesense.client-settings'));
    }

    /**
     * If the settings are in the model function getBs4TypesenseModelSettings()
     * override the config settings with this info
     * 
     * Because the collection schema is fetched from the config in a protected function, 
     * so we shall override then settings in the config before we get there
     */
    private function overrideConfigFromModel($model)
    {
        //if the settings are in the model, we shall stuff them into the config
        if(method_exists($model, 'getBs4TypesenseModelSettings'))
        {
            $modelSearchParams = $model->getBs4TypesenseModelSettings();
            if(!empty($modelSearchParams))
            {
                Config::set('scout.typesense.model-settings.'.get_class($model), $modelSearchParams);

                //dd($prev, config('scout.typesense.model-settings',null));
            }
        }
    }

    /**
     * Perform the given search on the engine.
     *
     * @param  \Laravel\Scout\Builder  $builder
     * @param  array  $options
     * @return mixed
     *
     * @throws \Http\Client\Exception
     * @throws \Typesense\Exceptions\TypesenseClientError
     */
    protected function performMultiSearch(Builder $builder): mixed
    {
        $options = array_filter($this->buildSearchParameters($builder, 1, $builder->limit));
        
        $searches =[];
        $numberOfPages = config('scout.typesense.model-settings.'.get_class($builder->model).'.multisearch_pages',10);
        for($i=1; $i<$numberOfPages; $i++)
        {
            $searches[] = [
                'q' =>  $builder->query,
                'page' => $i,
                'per_page' => $builder->limit,
            ];
        }

        $multiResults = $this->typesense->getMultiSearch()->perform(            
            [
                'searches' => $searches,
            ],
            [
                'query_by' => config('scout.typesense.model-settings.'.get_class($builder->model).'.search-parameters.query_by'),
                'collection' => $builder->model->searchableAs()
            ]
        );
        
        $results = null;
        foreach($multiResults['results'] as $page)
        {
            if($results===null)
            {   $results = $page;
            } else
            {
                if(count($page['hits'])>0)
                {
                    $results['hits'] = array_merge($results['hits'], $page['hits']);
                }
            }
        }

        if ($builder->callback) {
            return call_user_func($builder->callback, $results, $builder->query, $options);
        }

        return $results;
    }

    /**
     * Perform the given search on the engine.
     *
     * @param  \Laravel\Scout\Builder  $builder
     * @return mixed
     *
     * @throws \Http\Client\Exception
     * @throws \Typesense\Exceptions\TypesenseClientError
     */
    public function search(Builder $builder)
    {
        // see if the model has setings instead of the config/scout file, we shall overrid them
        $this->overrideConfigFromModel($builder->model);

        // @TODO: we could buffer the search result in a file, and check for it's results here
        // for even faster searches

        // this is either from the model config in the scout config file, or from the settings in the  model
        $searchMethod = config('scout.typesense.model-settings.'.get_class($builder->model).'.use-search-method',1);
        // use-search-method => 0 – will use the typesense function that is limited to 250 results
        // use-search-method => 1 – will use multiple http requests and join them together
        // use-search-method => 2 – will use typesenses searchMultiAPI call – under development

        if($searchMethod==0)
        {
            // will use the typesense function that is limited to 250 results
            $results = parent::search($builder);

        } else if($searchMethod==2)
        {   // will use the typesense searchMulti API call
            // custom function herein
            $results = $this->performMultiSearch($builder);

        } else // if($searchMethod==1)) -- default
        {
            // use a series of hhtp requests to get everything above the normal 250 limit
            // default method

            $page = 1;
            $results = null;
            //$moreResults=true;
            $found=9999;
            while($found>0)
            {   
                if($page==1 || $results==null)
                {
                    $results = $this->performSearch(
                                    $builder,
                                    array_filter($this->buildSearchParameters($builder, 1, $builder->limit))
                            );
                    $found = $results['found'] - count($results['hits']); 
                    //$moreResults=!empty($results['hits']);
                } else
                {
                    $r = $this->performSearch(
                        $builder,
                        array_filter($this->buildSearchParameters($builder, $page, $builder->limit))
                    );
                    $found = $found - count($r['hits']); 
                    //dd($results, $r, $found);
                    //$moreResults=!empty($r['hits']);
    
                    if(count($r['hits'])>0)
                    {
                        $results['hits'] = array_merge($results['hits'], $r['hits']);
                    }
                }
                $page++;
            }
        }

        return $results;
        //return parent::search($builder);
    }

    /**
     * Build the search parameters for a given Scout query builder.
     *
     * @param  \Laravel\Scout\Builder  $builder
     * @param  int  $page
     * @param  int|null  $perPage
     * @return array
     */
    public function buildSearchParameters(Builder $builder, int $page, int|null $perPage): array
    {
        // $parameters = [
        //     'q' => $builder->query,
        //     'query_by' => config('scout.typesense.model-settings.'.get_class($builder->model).'.search-parameters.query_by') ?? '',
        //     'filter_by' => $this->filters($builder),
        //     'per_page' => $perPage,
        //     'page' => $page,
        //     'highlight_start_tag' => '<mark>',
        //     'highlight_end_tag' => '</mark>',
        //     'snippet_threshold' => 30,
        //     'exhaustive_search' => false,
        //     'use_cache' => false,
        //     'cache_ttl' => 60,
        //     'prioritize_exact_match' => true,
        //     'enable_overrides' => true,
        //     'highlight_affix_num_tokens' => 4,
        // ];

        // if (! empty($builder->options)) {
        //     $parameters = array_merge($parameters, $builder->options);
        // }

        // if (! empty($builder->orders)) {
        //     if (! empty($parameters['sort_by'])) {
        //         $parameters['sort_by'] .= ',';
        //     } else {
        //         $parameters['sort_by'] = '';
        //     }

        //     $parameters['sort_by'] .= $this->parseOrderBy($builder->orders);
        // }
        // let the default function do its work

        // if the config settings are in the model, we shall override them
        $this->overrideConfigFromModel($builder->model);

        return parent::buildSearchParameters($builder, $page, $perPage);
    }


    /**
     * Get collection from model or create new one.
     *
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @return \TypesenseCollection
     *
     * @throws \Typesense\Exceptions\TypesenseClientError
     * @throws \Http\Client\Exception
     */
    protected function getOrCreateCollectionFromModel($model): TypesenseCollection
    {
        $this->overrideConfigFromModel($model);

        return parent::getOrCreateCollectionFromModel($model);
    }

}