<?php

namespace SugarCalendarBookings\Support\Database;

/**
 * ModelQueryBuilder for Models.
 *
 * Extends the QueryBuilder to hydrate results as model instances and support eager loading.
 *
 * @since 1.0.0
 *
 * @template TModel of AbstractModel
 */
class ModelQueryBuilder extends QueryBuilder {

	/**
	 * The model class to hydrate results into.
	 *
	 * @since 1.0.0
	 *
	 * @var class-string<TModel>
	 */
	protected $model_class;

	/**
	 * The relationships to eager load.
	 *
	 * @since 1.0.0
	 *
	 * @var array
	 */
	protected $with = [];

	/**
	 * The relations loader instance.
	 *
	 * @since 1.0.0
	 *
	 * @var RelationsLoader
	 */
	private $relations_loader;

	/**
	 * Constructor.
	 *
	 * @since 1.0.0
	 */
	public function __construct() {

		parent::__construct();

		$this->relations_loader = new RelationsLoader();
	}

	/**
	 * Set the model class for hydration.
	 *
	 * @since 1.0.0
	 *
	 * @param class-string<TModel> $model_class_name Model class name.
	 *
	 * @return $this
	 */
	public function set_model( $model_class_name ) {

		$this->model_class = $model_class_name;

		$this->table( $model_class_name::get_raw_table_name() );

		return $this;
	}

	/**
	 * Execute the query and return hydrated model instances.
	 *
	 * @since 1.0.0
	 *
	 * @return array<TModel>
	 */
	public function get() {

		$models = $this->model_class::hydrate( parent::get() );

		if ( $this->with && ! empty( $models ) ) {
			$this->relations_loader->load_relations( $models, $this->with );
		}

		return $models;
	}

	/**
	 * Execute the query and return the first hydrated model instance or null.
	 *
	 * @since 1.0.0
	 *
	 * @return TModel|null
	 */
	public function first() {

		$this->limit( 1 );
		$models = $this->get();

		return $models[0] ?? null;
	}

	/**
	 * Specify relationships to eager load, supporting nested relations via dot notation and constraints.
	 *
	 * @since 1.0.0
	 *
	 * @param array<string|int, mixed> $relations Array of relationship names (dot notation for nesting) or [relation => constraint].
	 *
	 * @return $this
	 */
	public function with( $relations ) {

		$this->with = $this->relations_loader->build_relations_tree( $relations );

		return $this;
	}

	/**
	 * Execute query and convert models to arrays with casted values.
	 *
	 * @since 1.2.0
	 *
	 * @return array
	 */
	public function to_array() {

		$models = $this->get();
		$result = [];

		foreach ( $models as $model ) {
			$row = [];

			foreach ( $model->get_attributes() as $key => $value ) {
				$row[ $key ] = $model->__get( $key ); // Use magic getter for casts.
			}
			$result[] = $row;
		}

		return $result;
	}
}
