<?php
/**
 * Base Entity Factory.
 *
 * @package automattic/jetpack-crm
 * @since 6.2.0
 */

namespace Automattic\Jetpack\CRM\Entities\Factories;

/**
 * Base Entity Factory.
 *
 * @since 6.2.0
 */
abstract class Entity_Factory {

	/**
	 * Database field name mapping.
	 *
	 * Each array entry represents a map of the database name and the corresponding model field.
	 *
	 * Example: array( 'db_column' => 'entity_property' ).
	 *
	 * @since 6.2.0
	 * @var string[]
	 */
	protected static $field_map = array();

	/**
	 * Associative field map.
	 *
	 * For tags, invoices, transactions, quotes, tasks, etc.
	 *
	 * @since 6.2.0
	 * @var string[]
	 */
	protected static $associative_field_map = array();

	/**
	 * Create the instance of the class based on the data from DAL.
	 *
	 * @since 6.2.0
	 *
	 * @param array $data The data to create the instance with.
	 * @return mixed The entity instance.
	 */
	abstract public static function create( array $data );

	/**
	 * Create the entity instance from a generic/tidy data array.
	 *
	 * @since 6.2.0
	 *
	 * @param array $tidy_data An array with the tidy data from DAL.
	 * @return mixed The entity instance.
	 *
	 * @throws Factory_Exception If the entity class is invalid.
	 */
	protected static function create_from_tidy_data( array $tidy_data ) {

		$entity = self::entity_new_instance();

		$fields_map = static::get_fields_map();

		// Process primary fields
		foreach ( $tidy_data as $field => $value ) {
			if ( in_array( $field, $fields_map, true ) ) {
				$entity->{ $field } = $value;
			}
		}

		$associative_field_map = static::get_associative_field_map();

		// Process associative fields
		foreach ( $associative_field_map as $field ) {
			if ( array_key_exists( $field, $tidy_data ) ) {
				$entity->{ $field } = $tidy_data[ $field ];
			}
		}

		return $entity;
	}

	/**
	 * Create the entity instance from the database data array.
	 *
	 * @since 6.2.0
	 *
	 * @param array $db_data The data array from the database.
	 * @return mixed The entity instance.
	 *
	 * @throws Factory_Exception If the entity class is invalid.
	 */
	protected static function create_from_db( array $db_data ) {
		$entity = self::entity_new_instance();

		$fields_map = static::get_fields_map();

		foreach ( $db_data as $key => $value ) {
			if ( array_key_exists( $key, $fields_map ) ) {
				$entity->{ $fields_map[ $key ] } = $value;
			}
		}

		return $entity;
	}

	/**
	 * Get the data (tidy) as an array from the entity instance.
	 *
	 * @since 6.2.0
	 *
	 * @param mixed $entity The entity instance.
	 * @return array The tidy data array.
	 *
	 * @throws Factory_Exception If the entity class is invalid.
	 */
	public static function tidy_data( $entity ): array {

		$entity_class = static::get_entity_class();

		if ( ! $entity instanceof $entity_class ) {
			throw new Factory_Exception( 'Invalid entity instance provided.', Factory_Exception::INVALID_ENTITY_CLASS );
		}

		$fields_map = static::get_fields_map();

		$tidy_data = array();
		foreach ( $fields_map as $value ) {
			$tidy_data[ $value ] = $entity->{ $value };
		}

		return $tidy_data;
	}

	/**
	 * Get the data from the entity instance as an array ready for the DAL.
	 *
	 * @since 6.2.0
	 *
	 * @param mixed $entity The entity instance.
	 * @return array The data array for the DAL.
	 */
	public static function data_for_dal( $entity ): array {
		$db_input_data = array(
			'id'    => $entity->id,
			'owner' => $entity->owner,
			'data'  => array(),
		);

		$skip_fields = array( 'id', 'owner' );

		$fields_map = static::get_fields_map();

		foreach ( $fields_map as $entity_field ) {
			if ( in_array( $entity_field, $skip_fields, true ) ) {
				continue;
			}
			$db_input_data['data'][ $entity_field ] = $entity->{ $entity_field };
		}
		return $db_input_data;
	}

	/**
	 * Create an empty entity instance.
	 *
	 * @since 6.2.0
	 *
	 * @return mixed The entity instance.
	 * @throws Factory_Exception If the entity class is invalid.
	 */
	protected static function entity_new_instance() {
		$entity_class = static::get_entity_class();

		if ( class_exists( $entity_class ) ) {
			return new $entity_class();
		} else {
			throw new Factory_Exception( 'Invalid entity class provided.', Factory_Exception::INVALID_ENTITY_CLASS );
		}
	}

	/**
	 * Return the fields map.
	 *
	 * 'db_column' => 'entity_property'
	 *
	 * @since 6.2.0
	 *
	 * @return array The fields map.
	 */
	public static function get_fields_map(): array {
		return static::$field_map;
	}

	/**
	 * Return the associative fields map.
	 *
	 * Tags, files, etc.
	 *
	 * @since 6.2.0
	 *
	 * @return array The associative fields map.
	 */
	public static function get_associative_field_map(): array {
		return static::$associative_field_map;
	}

	/**
	 * Return the entity class handle by the Factory.
	 *
	 * @since 6.2.0
	 *
	 * @return string|null The entity class.
	 */
	abstract public static function get_entity_class(): ?string;
}
