<?php
/**
 * Gravity Flow Settings field: dynamic_choice_map (Multiple to Single).
 *
 * Renders a repeater of rows: each row has a multiselect (options) and
 * a single select (target). Value is an array of { option_values: string[], target: string }.
 * Generic so it can map different data types on both sides (e.g. choice options → steps).
 *
 * @package Gravity_Flow
 * @since 3.1.0
 */

namespace Gravity_Flow\Gravity_Flow\Settings\Fields;

use Gravity_Forms\Gravity_Forms\Settings\Fields;

defined( 'ABSPATH' ) || die();

// Load base class.
require_once \GFCommon::get_base_path() . '/includes/settings/class-fields.php';

/**
 * Class Dynamic_Choice_Map
 */
class Dynamic_Choice_Map extends \Gravity_Forms\Gravity_Forms\Settings\Fields\Base {

	/**
	 * Field type.
	 *
	 * @since 3.1.0
	 *
	 * @var string
	 */
	public $type = 'dynamic_choice_map';

	/**
	 * Choices for the "options" (multiple) side. Each item: label, value.
	 *
	 * @since 3.1.0
	 *
	 * @var array
	 */
	public $options = array();

	/**
	 * Choices for the "target" (single) side. Each item: label, value.
	 *
	 * @since 3.1.0
	 *
	 * @var array
	 */
	public $target = array();

	/**
	 * Label for the options column.
	 *
	 * @since 3.1.0
	 *
	 * @var string
	 */
	public $options_label = '';

	/**
	 * Label for the target column.
	 *
	 * @since 3.1.0
	 *
	 * @var string
	 */
	public $target_label = '';

	/**
	 * Get the current value, normalized to an array of { option_values, target }.
	 *
	 * @since 3.1.0
	 *
	 * @return array Array of rows, each containing option_values and target keys.
	 */
	public function get_value() {
		$raw = $this->settings->get_value( $this->name, $this->default_value );
		if ( is_string( $raw ) ) {
			$decoded = json_decode( $raw, true );
			return is_array( $decoded ) ? $decoded : array();
		}
		if ( is_array( $raw ) ) {
			return $raw;
		}
		return array();
	}

	/**
	 * Decode JSON from post and return array for save.
	 *
	 * @since 3.1.0
	 *
	 * @param array|string $value Posted value (may be JSON string).
	 *
	 * @return array The decoded routing rules array.
	 */
	public function save( $value ) {
		if ( is_string( $value ) ) {
			$decoded = json_decode( stripslashes( $value ), true );
			return is_array( $decoded ) ? $decoded : array();
		}
		return is_array( $value ) ? $value : array();
	}

	/**
	 * Validate: an option value must not appear in more than one row.
	 *
	 * @since 3.1.0
	 *
	 * @param array|string $value Posted value (array or JSON string).
	 *
	 * @return void
	 */
	public function do_validation( $value ) {
		$rows = $value;
		if ( is_string( $value ) ) {
			$rows = json_decode( stripslashes( $value ), true );
		}
		if ( ! is_array( $rows ) ) {
			return;
		}

		$option_props = $this->options;
		$seen         = array();
		$row_index    = 0;
		foreach ( $rows as $row ) {
			$row_index++;
			$option_values = isset( $row['option_values'] ) && is_array( $row['option_values'] )
				? $row['option_values']
				: array();
			$option_values = array_filter( array_map( 'strval', $option_values ) );
			if ( empty( $option_values ) ) {
				$this->set_error(
					__( 'You cannot save the step with an empty routing rule.', 'gravityflow' )
				);
				return;
			}
			foreach ( $option_values as $ov ) {
				if ( $ov === '' ) {
					continue;
				}
				if ( isset( $seen[ $ov ] ) ) {
					$label = $ov;
					foreach ( $option_props as $opt ) {
						if ( isset( $opt['value'] ) && (string) $opt['value'] === $ov ) {
							$label = isset( $opt['label'] ) ? $opt['label'] : $ov;
							break;
						}
					}
					$this->set_error(
						sprintf(
							/* translators: 1: option label, 2: row number */
							__( 'Duplicate: "%1$s" is already used in another row (row %2$d).', 'gravityflow' ),
							esc_html( $label ),
							$seen[ $ov ]
						)
					);
					return;
				}
				$seen[ $ov ] = $row_index;
			}
		}
	}

	/**
	 * Render the field: container, hidden input, and React mount point.
	 * The React app (dynamic-choice-map) mounts here and provides the repeater UI.
	 *
	 * @since 3.1.0
	 *
	 * @return string The HTML markup for the field.
	 */
	public function markup() {
		$input_name    = sprintf( '%s_%s', $this->settings->get_input_name_prefix(), $this->name );
		$container_id  = $input_name . '_container';
		$value         = $this->get_value();
		$options       = $this->options;
		$target        = $this->target;
		$options_label = $this->options_label ? $this->options_label : esc_html__( 'Options', 'gravityflow' );
		$target_label  = $this->target_label ? $this->target_label : esc_html__( 'Target', 'gravityflow' );
		$value_json    = wp_json_encode( $value );

		$invalid_row_indices = array();
		if ( $this->get_error() ) {
			foreach ( $value as $idx => $row ) {
				$option_values = isset( $row['option_values'] ) && is_array( $row['option_values'] )
					? $row['option_values']
					: array();
				$option_values = array_filter( array_map( 'strval', $option_values ) );
				if ( empty( $option_values ) ) {
					$invalid_row_indices[] = $idx;
				}
			}
		}

		$js_props = array(
			'inputName'         => $input_name,
			'options'           => $options,
			'target'            => $target,
			'optionsLabel'      => $options_label,
			'targetLabel'       => $target_label,
			'value'             => $value,
			'invalidRowIndices' => $invalid_row_indices,
		);

		$html  = $this->get_description();
		$html .= sprintf(
			'<div class="gform-settings-field__dynamic-choice-map" id="%s_wrapper">',
			esc_attr( $container_id )
		);
		$html .= gravity_flow()->settings_hidden(
			array(
				'name'  => $this->name,
				'value' => $value_json,
			),
			false
		);
		$html .= sprintf(
			'<div id="%s" class="gravityflow-dynamic-choice-map gravityflow-routing" data-js-props="%s"></div>',
			esc_attr( $container_id ),
			esc_attr( wp_json_encode( $js_props ) )
		);
		$html .= '</div>';
		$html .= $this->get_error_icon();

		return $html;
	}
}

Fields::register( 'dynamic_choice_map', '\Gravity_Flow\Gravity_Flow\Settings\Fields\Dynamic_Choice_Map' );
