<?php

namespace SugarCalendarBookings\Support;

use Exception;

/**
 * Abstract data transfer object.
 *
 * @since 1.0.0
 */
abstract class AbstractDTO {

	/**
	 * Required fields.
	 *
	 * @since 1.0.0
	 *
	 * @var array
	 */
	protected $required_fields = [];

	/**
	 * Data.
	 *
	 * @since 1.0.0
	 *
	 * @var array
	 */
	protected $data;

	/**
	 * Constructor.
	 *
	 * @since 1.0.0
	 *
	 * @param array $data Data.
	 *
	 * phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.Missing
	 */
	public function __construct( $data = [] ) {

		// Throw exception only in development mode.
		if ( defined( 'SCBOOKINGS_DEV_MODE' ) && SCBOOKINGS_DEV_MODE ) {
			foreach ( $this->required_fields as $field ) {
				if ( ! array_key_exists( $field, $data ) ) {
					throw new Exception( sprintf( 'Field %s is required', esc_html( $field ) ) );
				}
			}
		}

		foreach ( $data as $key => $value ) {
			$this->set( $key, $value );
		}
	}

	/**
	 * Set a value.
	 *
	 * @since 1.0.0
	 *
	 * @param string $key   Key.
	 * @param mixed  $value Value.
	 */
	protected function set( $key, $value ) {

		$this->data[ $key ] = $value;
	}

	/**
	 * Get a value.
	 *
	 * @since 1.0.0
	 *
	 * @param string $key Key.
	 *
	 * @return mixed
	 */
	protected function get( $key ) {
		/*
		 * We intentionally don't check if the key exists, since
		 * it should be checked via `isset` before calling this method.
		 * It's required to distinguish between `null` and `not set` values.
		 */
		return $this->data[ $key ];
	}

	/**
	 * Get a value via magic method.
	 *
	 * @since 1.0.0
	 *
	 * @param string $key Key.
	 *
	 * @return mixed
	 */
	public function __get( $key ) {

		return $this->get( $key );
	}

	/**
	 * Set a value via magic method.
	 *
	 * @since 1.0.0
	 *
	 * @param string $key   Key.
	 * @param mixed  $value Value.
	 */
	public function __set( $key, $value ) {

		$this->set( $key, $value );
	}

	/**
	 * Check if a value is set.
	 *
	 * @since 1.0.0
	 *
	 * @param string $key Key.
	 *
	 * @return bool
	 */
	public function __isset( $key ) {

		return array_key_exists( $key, $this->data );
	}

	/**
	 * Update the data and return a new instance.
	 *
	 * @since 1.0.0
	 *
	 * @param array $data Data.
	 *
	 * @return static
	 */
	public function update( $data ) {

		return new static( array_merge( $this->data, $data ) );
	}
}
