<?php
/**
 * Base Relation Class
 *
 * @package ZenoChatbot\Framework\Database\Relations
 */

namespace ZenoChatbot\Framework\Database\Relations;

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

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

/**
 * Abstract Base Relation Class
 */
abstract class Relation
{
    /**
     * The parent model instance
     *
     * @var Model
     */
    protected $parent;

    /**
     * The related model instance
     *
     * @var Model
     */
    protected $related;

    /**
     * The query builder instance
     *
     * @var QueryBuilder
     */
    protected $query;

    /**
     * Create a new relation instance
     *
     * @param Model $parent
     * @param Model $related
     */
    public function __construct(Model $parent, Model $related)
    {
        $this->parent = $parent;
        $this->related = $related;
        $this->query = $related->newQuery();
    }

    /**
     * Get the results of the relationship
     *
     * @return mixed
     */
    abstract public function getResults();

    /**
     * Add constraints to the relationship query
     *
     * @return void
     */
    abstract public function addConstraints();

    /**
     * Add eager loading constraints to the relationship query
     *
     * @param array $models
     * @return void
     */
    abstract public function addEagerConstraints(array $models);

    /**
     * Initialize the relation on a set of models
     *
     * @param array $models
     * @param string $relation
     * @return array
     */
    abstract public function initRelation(array $models, $relation);

    /**
     * Match the eagerly loaded results to their parents
     *
     * @param array $models
     * @param ModelCollection $results
     * @param string $relation
     * @return array
     */
    abstract public function match(array $models, ModelCollection $results, $relation);

    /**
     * Get the underlying query builder instance
     *
     * @return QueryBuilder
     */
    public function getQuery()
    {
        return $this->query;
    }

    /**
     * Get the parent model
     *
     * @return Model
     */
    public function getParent()
    {
        return $this->parent;
    }

    /**
     * Get the related model
     *
     * @return Model
     */
    public function getRelated()
    {
        return $this->related;
    }

    /**
     * Add a WHERE clause to the relationship query
     *
     * @param string $column
     * @param string $operator
     * @param mixed $value
     * @return $this
     */
    public function where($column, $operator = null, $value = null)
    {
        // Preserve argument arity to allow QueryBuilder to infer operator/value correctly
        if (func_num_args() === 2) {
            $this->query->where($column, $operator);
        } else {
            $this->query->where($column, $operator, $value);
        }
        return $this;
    }

    /**
     * Add an OR WHERE clause to the relationship query
     *
     * @param string $column
     * @param string $operator
     * @param mixed $value
     * @return $this
     */
    public function orWhere($column, $operator = null, $value = null)
    {
        if (func_num_args() === 2) {
            $this->query->orWhere($column, $operator);
        } else {
            $this->query->orWhere($column, $operator, $value);
        }
        return $this;
    }

    /**
     * Add a WHERE IN clause to the relationship query
     *
     * @param string $column
     * @param array $values
     * @return $this
     */
    public function whereIn($column, array $values)
    {
        $this->query->whereIn($column, $values);
        return $this;
    }

    /**
     * Add an ORDER BY clause to the relationship query
     *
     * @param string $column
     * @param string $direction
     * @return $this
     */
    public function orderBy($column, $direction = 'ASC')
    {
        $this->query->orderBy($column, $direction);
        return $this;
    }

    /**
     * Add a LIMIT clause to the relationship query
     *
     * @param int $limit
     * @return $this
     */
    public function limit($limit)
    {
        $this->query->limit($limit);
        return $this;
    }

    /**
     * Get the first result of the relationship
     *
     * @return Model|null
     */
    public function first()
    {
        $result = $this->query->first();

        if ($result) {
            // If result is already a Model instance, return it as-is
            if ($result instanceof Model) {
                return $result;
            }
            // Otherwise, convert array to Model instance
            return $this->related->newFromBuilder($result);
        }

        return null;
    }

    /**
     * Get all results of the relationship
     *
     * @return ModelCollection
     */
    public function get()
    {
        $results = $this->query->get();

        return $results->map(function ($result) {
            // If result is already a Model instance, return it as-is
            if ($result instanceof Model) {
                return $result;
            }
            // Otherwise, convert array to Model instance
            return $this->related->newFromBuilder($result);
        });
    }

    /**
     * Get the count of relationship results
     *
     * @return int
     */
    public function count()
    {
        return $this->query->count();
    }

    /**
     * Check if any relationship results exist
     *
     * @return bool
     */
    public function exists()
    {
        return $this->query->exists();
    }

    /**
     * Create a new related model
     *
     * @param array $attributes
     * @return Model
     */
    public function create(array $attributes = [])
    {
        $instance = $this->related->newInstance($attributes);
        $this->setForeignAttributesForCreate($instance);
        $instance->save();

        return $instance;
    }

    /**
     * Set the foreign key attributes for creating a related model
     *
     * @param Model $model
     * @return void
     */
    protected function setForeignAttributesForCreate(Model $model)
    {
        // Override in specific relation classes
    }

    /**
     * Get the foreign key for the relationship
     *
     * @return string
     */
    protected function getForeignKey()
    {
        return $this->parent->getTable() . '_' . $this->parent->getKeyName();
    }

    /**
     * Get the qualified foreign key name
     *
     * @return string
     */
    protected function getQualifiedForeignKeyName()
    {
        return $this->related->getTable() . '.' . $this->getForeignKey();
    }

    /**
     * Get the qualified parent key name
     *
     * @return string
     */
    protected function getQualifiedParentKeyName()
    {
        return $this->parent->getTable() . '.' . $this->parent->getKeyName();
    }

    /**
     * Get the related model instance
     *
     * @return Model
     */
    public function getModel()
    {
        return $this->related;
    }

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

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

    /**
     * Handle dynamic method calls
     *
     * @param string $method
     * @param array $parameters
     * @return mixed
     */
    public function __call($method, $parameters)
    {
        $result = $this->query->$method(...$parameters);

        if ($result === $this->query) {
            return $this;
        }

        return $result;
    }
}
