<?php
/**
 * Has One Relationship
 *
 * @package ZenoChatbot\Framework\Database\Relations
 */

namespace ZenoChatbot\Framework\Database\Relations;

use ZenoChatbot\Framework\Database\Model;
use ZenoChatbot\Framework\Database\ModelCollection;

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

/**
 * Has One Relationship Class
 */
class HasOne extends Relation
{
    /**
     * The foreign key of the parent model
     *
     * @var string
     */
    protected $foreignKey;

    /**
     * The local key of the parent model
     *
     * @var string
     */
    protected $localKey;

    /**
     * Create a new has one relationship instance
     *
     * @param Model $parent
     * @param Model $related
     * @param string $foreignKey
     * @param string $localKey
     */
    public function __construct(Model $parent, Model $related, $foreignKey, $localKey)
    {
        $this->foreignKey = $foreignKey;
        $this->localKey = $localKey;

        parent::__construct($parent, $related);

        $this->addConstraints();
    }

    /**
     * Add constraints to the relationship query
     *
     * @return void
     */
    public function addConstraints()
    {
        if (static::$constraints) {
            $this->query->where($this->foreignKey, '=', $this->getParentKey());
        }
    }

    /**
     * Add eager loading constraints to the relationship query
     *
     * @param array $models
     * @return void
     */
    public function addEagerConstraints(array $models)
    {
        $keys = $this->getKeys($models, $this->localKey);

        $this->query->whereIn($this->foreignKey, $keys);
    }

    /**
     * Initialize the relation on a set of models
     *
     * @param array $models
     * @param string $relation
     * @return array
     */
    public function initRelation(array $models, $relation)
    {
        foreach ($models as $model) {
            $model->setRelation($relation, null);
        }

        return $models;
    }

    /**
     * Match the eagerly loaded results to their parents
     *
     * @param array $models
     * @param ModelCollection $results
     * @param string $relation
     * @return array
     */
    public function match(array $models, ModelCollection $results, $relation)
    {
        $dictionary = $this->buildDictionary($results);

        foreach ($models as $model) {
            $key = $model->getAttribute($this->localKey);

            if (isset($dictionary[$key])) {
                $model->setRelation($relation, $dictionary[$key]);
            }
        }

        return $models;
    }

    /**
     * Get the results of the relationship
     *
     * @return Model|null
     */
    public function getResults()
    {
        return $this->first();
    }

    /**
     * Get the parent key value
     *
     * @return mixed
     */
    protected function getParentKey()
    {
        return $this->parent->getAttribute($this->localKey);
    }

    /**
     * Get an array of keys from the given models
     *
     * @param array $models
     * @param string $key
     * @return array
     */
    protected function getKeys(array $models, $key)
    {
        $keys = [];

        foreach ($models as $model) {
            $value = $model->getAttribute($key);
            if ($value !== null) {
                $keys[] = $value;
            }
        }

        return array_unique($keys);
    }

    /**
     * Build model dictionary keyed by the relation's foreign key
     *
     * @param ModelCollection $results
     * @return array
     */
    protected function buildDictionary(ModelCollection $results)
    {
        $dictionary = [];

        foreach ($results as $result) {
            $key = $result->getAttribute($this->foreignKey);
            $dictionary[$key] = $result;
        }

        return $dictionary;
    }

    /**
     * Set the foreign key attributes for creating a related model
     *
     * @param Model $model
     * @return void
     */
    protected function setForeignAttributesForCreate(Model $model)
    {
        $model->setAttribute($this->foreignKey, $this->getParentKey());
    }

    /**
     * Get the foreign key for the relationship
     *
     * @return string
     */
    public function getForeignKeyName()
    {
        return $this->foreignKey;
    }

    /**
     * Get the local key for the relationship
     *
     * @return string
     */
    public function getLocalKeyName()
    {
        return $this->localKey;
    }

    /**
     * Static constraints flag
     *
     * @var bool
     */
    protected static $constraints = true;

    /**
     * Run a callback with constraints disabled
     *
     * @param callable $callback
     * @return mixed
     */
    public static function noConstraints(callable $callback)
    {
        $previous = static::$constraints;
        static::$constraints = false;

        try {
            return $callback();
        } finally {
            static::$constraints = $previous;
        }
    }

    /**
     * Delete the related model
     *
     * @return int
     */
    public function delete()
    {
        return $this->query->delete();
    }
}
