<?php

namespace SugarCalendarBookings\Support\Database\Relations;

use Exception;
use SugarCalendarBookings\Support\Database\AbstractModel;
use SugarCalendarBookings\Support\Database\ModelQueryBuilder;
use SugarCalendarBookings\Support\Database\QueryBuilder;
use SugarCalendarBookings\Support\Database\RelationsLoader;

/**
 * Abstract base class for all relation types.
 *
 * @since 1.0.0
 */
abstract class AbstractRelation {

	/**
	 * Parent model instance.
	 *
	 * @since 1.0.0
	 *
	 * @var AbstractModel
	 */
	protected $parent;

	/**
	 * Related model class name.
	 *
	 * @since 1.0.0
	 *
	 * @var class-string<AbstractModel>
	 */
	protected $related;

	/**
	 * Query builder instance for the relations loading query.
	 *
	 * @since 1.0.0
	 *
	 * @var QueryBuilder
	 */
	protected $query;

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

	/**
	 * Constructor.
	 *
	 * @since 1.0.0
	 *
	 * @param AbstractModel               $parent  Parent model instance.
	 * @param class-string<AbstractModel> $related Related model class name.
	 */
	public function __construct( $parent, $related ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.parentFound

		$this->parent           = $parent;
		$this->related          = $related;
		$this->query            = $related::query();
		$this->relations_loader = new RelationsLoader();
	}

	/**
	 * Get the parent model instance.
	 *
	 * @since 1.0.0
	 *
	 * @return AbstractModel
	 */
	public function get_parent() {

		return $this->parent;
	}

	/**
	 * Get the related model class.
	 *
	 * @since 1.0.0
	 *
	 * @return class-string<AbstractModel>
	 */
	public function get_related() {

		return $this->related;
	}

	/**
	 * Get the query builder instance.
	 *
	 * @since 1.0.0
	 *
	 * @return ModelQueryBuilder
	 */
	public function query() {

		return $this->query;
	}

	/**
	 * Get the default value for the relation.
	 *
	 * @since 1.0.0
	 *
	 * @return mixed
	 */
	public function get_default_value() {

		return null;
	}

	// Query builder proxy methods.

	/**
	 * Set the columns to select.
	 *
	 * @since 1.0.0
	 *
	 * @param array $columns Columns to select.
	 *
	 * @return $this
	 */
	public function select( $columns ) {

		$this->query->select( $columns );

		return $this;
	}

	/**
	 * Add a where condition or a nested group via closure.
	 *
	 * @since 1.0.0
	 *
	 * @param string|callable $column      Column name or closure for nested group.
	 * @param string|null     $operator    Operator (e.g., '=', '>', etc.).
	 * @param mixed|null      $value       Value to compare.
	 * @param string          $placeholder Placeholder for value (%s, %d, %f).
	 *
	 * @return $this
	 */
	public function where( $column, $operator = null, $value = null, $placeholder = null ) {

		$this->query->where( $column, $operator, $value, $placeholder );

		return $this;
	}

	/**
	 * Add an order by clause.
	 *
	 * @since 1.0.0
	 *
	 * @param string $column    Column name.
	 * @param string $direction Direction (ASC or DESC).
	 *
	 * @return $this
	 */
	public function order_by( $column, $direction = 'ASC' ) {

		$this->query->order_by( $column, $direction );

		return $this;
	}

	/**
	 * Set the limit.
	 *
	 * @since 1.0.0
	 *
	 * @param int $limit Limit value.
	 *
	 * @return $this
	 */
	public function limit( $limit ) {

		$this->query->limit( $limit );

		return $this;
	}

	/**
	 * Get the relation.
	 *
	 * @since 1.0.0
	 *
	 * @return mixed
	 *
	 * phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.Missing
	 */
	public function get() {

		if ( ! $this->parent->allow_lazy_loading && defined( 'SCBOOKINGS_DEV_MODE' ) && SCBOOKINGS_DEV_MODE === true ) {
			throw new Exception(
				'N+1 detected: Use with() to eager load relationships when working with collections.'
			);
		}

		$relation_map = $this->relations_loader->load_relation(
			[ $this->parent ],
			$this
		);

		return $relation_map[ $this->parent->id ] ?? $this->get_default_value();
	}
}
