<?php

final class Types_Field_Type_Converter {

	private static $instance;


	public static function get_instance() {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}

		return self::$instance;
	}


	private function __clone() {
	}


	private function __construct() {
		// Toolset Maps uses priority 10 for this filter, we need to run later and override.
		add_filter( 'wpcf_filter_field_control_change_type_allowed_types_from', array(
			$this,
			'google_address_field_control_change_type_allowed_from',
		), 15, 2 );
	}


	private $conversion_matrix;


	/**
	 * Construct a matrix of allowed field type conversions.
	 *
	 * For each field type as key it contains an array of types it can be converted into.
	 *
	 * Usually, you shouldn't use this method directly, but instead use dedicated methods for a specific field type.
	 *
	 * Note: An empty row for a field type can be interpreted in a way that any conversion is allowed. If that's not
	 * desired, make sure each type can be converted to itself.
	 *
	 * Note: The conversion matrix is cached, so any used filters must be hooked into soon enough.
	 *
	 * @return string[][]|mixed
	 * @since 2.0
	 */
	public function get_conversion_matrix() {

		if ( null === $this->conversion_matrix ) {

			$simple_string_types = array(
				'audio',
				'email',
				'embed',
				'file',
				'google_address',
				'image',
				'numeric',
				'phone',
				'textfield',
				'url',
				'video',
				'colorpicker',
				'textarea',
			);

			$allowed_conversion_source = array(
				'audio' => $simple_string_types,
				'colorpicker' => $simple_string_types,
				'checkbox' => array( 'checkbox' ),
				'checkboxes' => array( 'checkboxes' ),
				'date' => array( 'date' ),
				'email' => $simple_string_types,
				'embed' => $simple_string_types,
				'file' => $simple_string_types,
				'google_address' => $simple_string_types,
				'image' => $simple_string_types,
				'numeric' => $simple_string_types,
				'phone' => $simple_string_types,
				'radio' => array( 'radio' ),
				'select' => array( 'select' ),
				'skype' => array( 'skype' ),
				'textarea' => $simple_string_types,
				'textfield' => $simple_string_types,
				'url' => $simple_string_types,
				'video' => $simple_string_types,
				'wysiwyg' => array( 'wysiwyg' ),
			);


			$filtered_conversion_matrix = array();
			foreach ( $allowed_conversion_source as $field_type_slug => $allowed_conversions ) {

				/**
				 * wpcf_filter_field_control_change_type_allowed_types_from
				 *
				 * Filter the field types that you can switch to, given a type field
				 *
				 * @param string[] $allowed_conversions Valid targets for a given origin type.
				 * @param string $field_type_slug Field type to switch from.
				 *
				 * @since 1.8.9
				 */
				$filtered_conversion_matrix[ $field_type_slug ] = apply_filters( 'wpcf_filter_field_control_change_type_allowed_types_from', $allowed_conversions, $field_type_slug );
			}


			/**
			 * wpcf_filter_field_control_change_type_allowed_types
			 *
			 * Filter the pairs field type origin -> valid field type targets when using the fields control change field type feature
			 *
			 * @param array $allowed_conversion_matrix Valid correspondence between field types and target field types
			 *
			 * @since 1.8.9
			 */
			$result = apply_filters( 'wpcf_filter_field_control_change_type_allowed_types', $filtered_conversion_matrix );


			$this->conversion_matrix = $result;
		}

		return $this->conversion_matrix;
	}


	/**
	 * @param Toolset_Field_Type_Definition $type
	 *
	 * @return Toolset_Field_Type_Definition[]
	 */
	public function get_possible_conversions( $type ) {

		if ( ! $type instanceof Toolset_Field_Type_Definition ) {
			throw new InvalidArgumentException( 'Not a field type definition' );
		}

		$matrix = $this->get_conversion_matrix();
		$allowed_slugs = toolset_ensarr( toolset_getarr( $matrix, $type->get_slug() ) );

		return Toolset_Field_Type_Definition_Factory::get_instance()
			->load_multiple_definitions( $allowed_slugs );
	}


	/**
	 * @param Toolset_Field_Type_Definition|mixed $from_type
	 * @param Toolset_Field_Type_Definition|mixed $to_type
	 *
	 * @return bool
	 */
	public function is_conversion_possible( $from_type, $to_type ) {
		if (
			! $from_type instanceof Toolset_Field_Type_Definition
			|| ! $to_type instanceof Toolset_Field_Type_Definition
		) {
			throw new InvalidArgumentException( 'Not a field type definition' );
		}

		$possible_conversions = $this->get_possible_conversions( $from_type );

		return array_key_exists( $to_type->get_slug(), $possible_conversions );
	}


	public function is_conversion_two_way( $type_1, $type_2 ) {
		if ( ! $type_1 instanceof Toolset_Field_Type_Definition
			|| ! $type_2
				instanceof
				Toolset_Field_Type_Definition ) {
			throw new InvalidArgumentException( 'Not a field type definition' );
		}

		return ( $this->is_conversion_possible( $type_1, $type_2 )
			&& $this->is_conversion_possible( $type_2, $type_1 ) );
	}


	/**
	 * Disallow certain conversion from or to google address field.
	 *
	 * This is hooked into the wpcf_filter_field_control_change_type_allowed_types_from filter with higher priority
	 * than the Toolset Maps hook that adds the google_address field type to the conversion matrix.
	 *
	 * This way we can properly handle the allowed conversions not depending on the particular Toolset Maps version
	 * that is active.
	 *
	 * @param string[] $targets Field type slugs TO which the conversion is allowed from $origin.
	 * @param string $origin Field type slug FROM which the conversion can happen.
	 *
	 * @return string[] Updated $targets.
	 * @since 2.0
	 */
	public function google_address_field_control_change_type_allowed_from( $targets, $origin ) {

		$disallowed_targets = array(
			Toolset_Field_Type_Definition_Factory::DATE,
			Toolset_Field_Type_Definition_Factory::CHECKBOX,
			Toolset_Field_Type_Definition_Factory::CHECKBOXES,
			Toolset_Field_Type_Definition_Factory::RADIO,
			Toolset_Field_Type_Definition_Factory::SELECT,
			Toolset_Field_Type_Definition_Factory::SKYPE,
			Toolset_Field_Type_Definition_Factory::WYSIWYG,
		);

		if ( Toolset_Field_Type_Definition_Factory::GOOGLE_ADDRESS === $origin ) {
			// Here we're filtering possible conversions FROM google address field
			$safe_targets = array_diff( $targets, $disallowed_targets );
			$targets = $safe_targets;
		} elseif ( in_array( $origin, $disallowed_targets, true ) ) {
			// Here we're filtering possible conversions TO google address field from an undesired field type
			if ( in_array( Toolset_Field_Type_Definition_Factory::GOOGLE_ADDRESS, $targets ) ) {
				$item_pos = array_search( Toolset_Field_Type_Definition_Factory::GOOGLE_ADDRESS, $targets );
				unset( $targets[ $item_pos ] );
			}
		}

		return $targets;
	}

}
