<?php

namespace onespace\tools\models\traits;

use onespace\tools\enum\general\HTTPMethod;
use onespace\tools\models\BaseActiveApiModel;
use onespace\tools\models\traits\ApiClientHelperTrait;
use onespace\tools\models\traits\BaseApiHelperTrait;
use yii\helpers\ArrayHelper;

/**
 * This trait assist with quering an API and returning active models.
 * 
 * Designed to work with models based on `\common\models\api\base\BaseActiveApiModel`
 * 
 * @deprecated for library
 */
trait QueryHelperTrait {

    use ApiClientHelperTrait;
    use BaseApiHelperTrait;

    /**
     * The default endpoint for querying the API.
     * 
     * @var string  $findDefaultEndpoint    Default: 'view'
     * 
     * @access  public
     */

    protected string $findDefaultEndpoint = 'view';

    /**
     * The where params of the to filter the API by.
     * 
     * @var array    $_params   Default: []
     * 
     * @access  private
     */

    private array $_params = [];

    /**
     * Whether or not to return the data as an array of arrays (true) or an array of models (false).
     * 
     * @var bool     $_asArray  Default: false
     * 
     * @access  private
     */

    private bool $_asArray = false;

    /**
     * Limit the number of records returned, how many results to actually return, regardless of what
     * how many results there are.
     * 
     * @var int  $_limit    Default: -1
     * 
     * @access  private
     */

    private int $_limit = -1;

    /**
     * Offset the results from this defined point. Usually used with limit for pagination.
     * 
     * @var int  $_offset   Default: 0
     * 
     * @access  private
     */

    private int $_offset = 0;

    /**
     * What form of sorting is required. Typically sent as a the field name, with a `-` in front for `DESC`, without for `ASC`.
     * 
     * @var ?string  $_sort Default: null
     * 
     * @access  private
     */

    private ?string $_sort = null;

    /**
     * The total search results returned by the `find()` query. Needed for possible later referrence.
     * 
     * @var int $totalSearchResults
     * 
     * @access  private
     */

    private int $totalSearchResults;

    /**
     * The sorting param, returned from the `find()` query.  Needed for possible later referrence.
     * 
     * @var string  $sortResults
     * 
     * @access  private
     */

    private string $sortResults;

    /**
     * The sorting param, returned from the `find()` query.  Needed for possible later referrence.
     * 
     * @var int $limitResults
     * 
     * @access  private
     */

    private int $limitResults;

    /**
     * The sorting param, returned from the `find()` query.  Needed for possible later referrence.
     * 
     * @var int $offsetResults
     * 
     * @access  private
     */

    private int $offsetResults;


    /**
     * Task to perform on the model after a `find()->one()`, `find()->all()`,
     * `findOne()` or `findAll()` methods.
     * 
     * @access  public
     */

    abstract public function afterFind(): void;


    /**
     * Initial an active API query.
     * 
     * @return  static  A new model instance of the current model.
     * 
     * @static
     * @access  public
     */

    public static function find(): static {
        $model = new static;
        return $model;
    }


    /**
     * Perform inline a search for a single result.
     * 
     * @param   array   $params The filtering params to perform the search.
     * 
     * @return  \common\models\api\BaseActiveApiModel|null
     * 
     * @static
     * @access  public
     */

    public static function findOne(array $params): BaseActiveApiModel|null {
        return self::find()->where($params)->one();
    }


    /**
     * Perform inline a search expencting multiple results.
     * 
     * @param   array   $params The filtering params to perform the search.
     * 
     * @return  \onespace\tools\models\BaseActiveApiModel[]|null
     * 
     * @static
     * @access  public
     */

    public static function findAll(array $params): ?array {
        return self::find()->where($params)->all();
    }


    /**
     * The filtering where part of a query. Should be something like `['guid' => '78dfe62c-0b01-4563-9e49-3dd14b893ebb']`.
     * Currently this is a simple filter, joining each param with a simple `AND`, and not performing various other logical
     * SQL filtering (`OR`, `IN`, `IS NOT NULL` etc.).
     * 
     * @param   array   $params The filtering params
     * 
     * @return  static
     * 
     * @access  public
     */

    public function where(array $params): static {
        $this->_params = $params;
        return $this;
    }


    /**
     * If passed, this ensures the results return as an array of arrays rather than an
     * array of models. The tradeoff is a reduced memory footprint at the expense of
     * model functionality.
     * 
     * @return  static
     * 
     * @access  public
     */

    public function asArray(): static {
        $this->_asArray = true;
        return $this;
    }


    /**
     * Set a limit on the number of results expected.
     * 
     * @param   int $limit  The number the results should be limited to.
     * 
     * @return  static
     * 
     * @access  public
     */

    public function limit(int $limit): static {
        $this->_limit = $limit;
        return $this;
    }


    /**
     * Where to offset the results. Typically used with limit to perform pagination.
     * 
     * @param   int $offset The offset on the results.
     * 
     * @return  static
     * 
     * @access  public
     */

    public function offset(int $offset): static {
        $this->_offset = $offset;
        return $this;
    }


    /**
     * How to sort the results, something like `field` (ASC) or `-field` (DESC).
     * 
     * @param   string|null  $sort   The sorting command. Null for no deliberate sorting.
     * 
     * @return  static
     * 
     * @access  public
     */

    public function sort(?string $sort): static {
        $this->_sort = $sort;
        return $this;
    }


    /**
     * Executes the API and returns the single expected result as a model or array (depending on `asArray`),
     * or null if no result is found.
     * 
     * @return \common\models\api\BaseActiveApiModel|array|null
     * 
     * @access  public
     */

    public function one(): BaseActiveApiModel|array|null {
        $this->prepareEndpoint($this->findDefaultEndpoint);
        $this->prepareMethod(HTTPMethod::GET);

        $request = $this->getClient()->search(
            $this->_endpoint,
            $this->_params,
            $this->_offset,
            $this->_limit,
            $this->_sort,
            $this->_method
        );
        $this->checkResponse($request);

        $data = $request->data;
        $this->setLastResponseData($data);
        if (!$data['success'] || ($data['total'] ?? 0) == 0) {
            return null;
        }
        if (isset($data['data'][0])) {
            $data['data'] = $data['data'][0];
        }
        if ($this->_asArray) {
            return ArrayHelper::toArray(new static($data['data']));
        } else {
            $model = new static($data['data']);
            $this->setResponseDataProperties($model, $data);
            $model->isNew = false;
            $model->afterFind();
            return $model;
        }
    }


    /**
     * Executes the API and returns all the expected results as an array of arrays or models (depending on `asArray`),
     * or null if no results are found.
     * 
     * @return  \common\models\api\BaseActiveApiModel[]|array|null
     * 
     * @access  public
     */

    public function all(): ?array {
        $this->prepareEndpoint($this->findDefaultEndpoint);
        $this->prepareMethod(HTTPMethod::GET);

        $request = $this->getClient()->search(
            $this->_endpoint,
            $this->_params,
            $this->_offset,
            $this->_limit,
            $this->_sort,
            $this->_method
        );
        $this->checkResponse($request);

        $data = $request->data;
        $this->setLastResponseData($data);
        if (!$data['success'] || $data['total'] == 0) {
            return [];
        }
        $models = [];
        foreach ($data['data'] as $entry) {
            if ($this->_asArray) {
                $models[] = ArrayHelper::toArray(new static($entry));
            } else {
                $model = new static($entry);
                $this->setResponseDataProperties($model, $data);
                $model->isNew = false;
                $model->afterFind();
                $models[] = $model;
            }
        }
        return $models;
    }


    /**
     * Refresh the data in the model from the API. This may or
     * may not work, depending on if the primaryKey() is known.
     * 
     * @return  bool
     * 
     * @access  public
     */

    public function refresh(): bool {
        $key = $this->primaryKey();
        $updatedModel = self::findOne([$key => $this->$key]);
        if ($updatedModel) {
            foreach (get_object_vars($updatedModel) as $property => $value) {
                $this->$property = $value;
            }
            return true;
        }

        return false;
    }


    /**
     * Sets each of the relevant info properties on the freshly created model.
     * 
     * @param   BaseActiveApiModel  $model  The model onto which the data should be set.
     * @param   array   $data   The returned data from the API.
     * 
     * @access  private
     */

    private function setResponseDataProperties(BaseActiveApiModel $model, array $data): void {
        if (isset($data['total'])) {
            $this->setTotalSearchResults($data['total'], $model);
        }
        if (isset($data['sort'])) {
            $this->setSortResults($data['sort'], $model);
        }
        if (isset($data['offset'])) {
            $this->setOffsetResults($data['offset'], $model);
        }
        if (isset($data['limit'])) {
            $this->setLimitResults($data['limit'], $model);
        }
    }


    /**
     * Returns the total results found by the API query. Not the total amount returned (as limited
     * by the offset and limit), but the actual total found.
     * 
     * @return  int
     * 
     * @access  public
     */

    public function getTotalSearchResults(): int {
        return $this->totalSearchResults;
    }


    /**
     * Sets the total search results.
     * 
     * @param   int $value  The new value.
     * @param   \onespace\tools\models\BaseActiveApiModel|null $model  The model to attach this data to.
     *                                          If null, it'll attach to `this` model.
     *                                          Default: null
     * 
     * @access  public
     */

    public function setTotalSearchResults(int $value, ?BaseActiveApiModel $model = null): void {
        if ($model === null) {
            $this->totalSearchResults = $value;
        } else {
            $model->totalSearchResults = $value;
        }
    }


    /**
     * Returns the sort property, returned from the API call.
     * 
     * @return  string
     * 
     * @access  public
     */

    public function getSortResults(): string {
        return $this->sortResults;
    }


    /**
     * Sets the sort results property
     * 
     * @param   string  $value  The new value.
     * @param   \onespace\tools\models\BaseActiveApiModel|null $model  The model to attach this data to. 
     *                                          If null, it'll attach to `this` model.
     *                                          Default: null
     * 
     * @access  public
     */

    public function setSortResults(string $value, ?BaseActiveApiModel $model = null): void {
        if ($model === null) {
            $this->sortResults = $value;
        } else {
            $model->sortResults = $value;
        }
    }


    /**
     * Returns the limit value, as returned by the API.
     * 
     * @return  int
     * 
     * @access  public
     */

    public function getLimitResults(): int {
        return $this->limitResults;
    }


    /**
     * Sets the limit value.
     * 
     * @param   int $value  The new value.
     * @param   \onespace\tools\models\BaseActiveApiModel|null $model  The model to attach this data to. 
     *                                          If null, it'll attach to `this` model.
     *                                          Default: null
     * 
     * @access  public
     */

    public function setLimitResults(int $value, ?BaseActiveApiModel $model = null): void {
        if ($model === null) {
            $this->limitResults = $value;
        } else {
            $model->limitResults = $value;
        }
    }


    /**
     * Returns the offset as returned from the API call.
     * 
     * @return  int
     * 
     * @access  public
     */

    public function getOffsetResults(): int {
        return $this->offsetResults;
    }


    /**
     * Sets the offset value.
     * 
     * @param   int                     $value  The new value.
     * @param   \onespace\tools\models\BaseActiveApiModel|null $model  The model to attach this data to. 
     *                                          If null, it'll attach to `this` model.
     *                                          Default: null
     * 
     * @access  public
     */

    public function setOffsetResults(int $value, ?BaseActiveApiModel $model = null): void {
        if ($model === null) {
            $this->offsetResults = $value;
        } else {
            $model->offsetResults = $value;
        }
    }
}
