<?php
declare(strict_types=1);

/**
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 * @link          https://cakephp.org CakePHP(tm) Project
 * @since         0.0.1
 * @license       https://www.opensource.org/licenses/mit-license.php MIT License
 */
namespace Cake\ElasticSearch;

use Cake\Datasource\EntityInterface;
use Cake\Datasource\EntityTrait;
use Elastica\Result;

/**
 * Represents a document stored in a Elastic Search Index
 */
class Document implements EntityInterface
{
    use EntityTrait;

    public int|string $version;

    /**
     * Holds an instance of a Result object that's passed into the constructor
     * from a search query. It can contain extra information about this document
     * concerning the search operation, such as highlights, score and version.
     *
     * @var \Elastica\Result
     */
    protected Result $_result;

    /**
     * Takes either an array or a Result object form a search and constructs
     * a document representing an entity in a elastic search index,
     *
     * @param \Elastica\Result|array $data An array or Result object that
     *  represents an Elasticsearch document
     * @param array $options An array of options to set the state of the
     *  document
     */
    public function __construct(Result|array $data = [], array $options = [])
    {
        if ($data instanceof Result) {
            $options['result'] = $data;
            $id = $data->getId();
            $data = $data->getData();
            if ($id !== []) {
                $data['id'] = $id;
            }
        }

        $options += [
            'useSetters' => true,
            'markClean' => false,
            'markNew' => null,
            'guard' => false,
            'source' => null,
            'result' => null,
        ];

        if (!empty($options['source'])) {
            $this->setSource($options['source']);
        }

        if ($options['markNew'] !== null) {
            $this->setNew($options['markNew']);
        }

        if ($options['result'] !== null) {
            $this->_result = $options['result'];
        }

        if (!empty($data) && $options['markClean'] && !$options['useSetters']) {
            $this->_fields = $data;

            return;
        }

        if (!empty($data)) {
            $this->set($data, [
                'setter' => $options['useSetters'],
                'guard' => $options['guard'],
            ]);
        }

        if ($options['markClean']) {
            $this->clean();
        }
    }

    /**
     * Returns the Elasticsearch index name from which this document came from.
     *
     * If this is a new document, this function returns null
     *
     * @return string|null
     */
    public function index(): ?string
    {
        if (isset($this->_result)) {
            return $this->_result->getIndex();
        }

        return null;
    }

    /**
     * Returns the version number of this document as returned by Elasticsearch
     *
     * If this is a new document, this function returns 1
     *
     * @return int
     */
    public function version(): int
    {
        if (isset($this->_result)) {
            return intval($this->_result->getVersion());
        }

        return 1;
    }

    /**
     * Returns the highlights array for document as returned by Elasticsearch
     * for the executed query.
     *
     * If this is a new document, or the query used to create it did not ask for
     * highlights, this function will return an empty array.
     *
     * @return array
     */
    public function highlights(): array
    {
        if (isset($this->_result)) {
            return $this->_result->getHighlights();
        }

        return [];
    }

    /**
     * Returns the explanation array for this document as returned from Elasticsearch.
     *
     * If this is a new document, or the query used to create it did not ask for
     * explanation, this function will return an empty array.
     *
     * @return array
     */
    public function explanation(): array
    {
        if (isset($this->_result)) {
            return $this->_result->getExplanation();
        }

        return [];
    }
}
