<?php
/**
 * Form builder for declarative form definitions
 *
 * Converts field definitions into HTML forms, handles validation,
 * sanitization, and submission processing.
 *
 * Usage:
 *   $fields = BW_Schema_Service_Organization::get_field_definitions();
 *   $form = BW_Schema_Form_Builder::build_form( 'organization', $fields );
 *   echo $form->render();
 *   if ( $_POST ) {
 *       $result = $form->validate_and_save( $_POST );
 *   }
 *
 * @package BW_Schema
 * @since 3.0.0-babel
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class BW_Schema_Form_Builder {

	/**
	 * Form ID (for nonce, form attribute)
	 *
	 * @var string
	 */
	private $form_id;

	/**
	 * Form name/slug
	 *
	 * @var string
	 */
	private $form_name;

	/**
	 * Field definitions
	 *
	 * @var array
	 */
	private $fields;

	/**
	 * Current form data (for rendering values)
	 *
	 * @var array
	 */
	private $data;

	/**
	 * Validation errors
	 *
	 * @var array
	 */
	private $errors = array();

	/**
	 * Build a form from field definitions
	 *
	 * Static factory method for creating a form instance.
	 *
	 * @param string $form_name Form name/slug (e.g., 'organization', 'locations')
	 * @param array  $fields Array of field definitions
	 * @param array  $data Initial form data (optional)
	 * @return BW_Schema_Form_Builder Form instance
	 */
	public static function build_form( $form_name, $fields = array(), $data = array() ) {
		return new self( $form_name, $fields, $data );
	}

	/**
	 * Constructor
	 *
	 * @param string $form_name Form name/slug
	 * @param array  $fields Field definitions
	 * @param array  $data Initial form data
	 */
	private function __construct( $form_name, $fields = array(), $data = array() ) {
		$this->form_name = sanitize_key( $form_name );
		$this->form_id   = 'bw-schema-form-' . $this->form_name;
		$this->fields    = $this->normalize_fields( $fields );
		$this->data      = wp_parse_args( $data, $this->get_defaults() );
	}

	/**
	 * Normalize field definitions
	 *
	 * Ensures all fields have required properties.
	 *
	 * @param array $fields Raw field definitions
	 * @return array Normalized fields
	 */
	private function normalize_fields( $fields ) {
		$normalized = array();

		foreach ( $fields as $field ) {
			$normalized[] = wp_parse_args( $field, array(
				'name'        => '',
				'type'        => 'text',
				'label'       => '',
				'section'     => 'general',
				'required'    => false,
				'help'        => '',
				'placeholder' => '',
				'sanitize'    => 'sanitize_text_field',
				'validate'    => array(),
				'map_to'      => null,
				'options'     => array(),
				'rows'        => 5,
				'cols'        => 50,
			));
		}

		return $normalized;
	}

	/**
	 * Get form data defaults from field definitions
	 *
	 * @return array Defaults for all fields
	 */
	private function get_defaults() {
		$defaults = array();

		foreach ( $this->fields as $field ) {
			$key = $field['map_to'] ?? $field['name'];
			$defaults[ $key ] = '';
		}

		return $defaults;
	}

	/**
	 * Render the form HTML
	 *
	 * Outputs a complete form with fields, nonce, and submit button.
	 *
	 * @return string Form HTML
	 */
	public function render() {
		ob_start();

		echo '<form id="' . esc_attr( $this->form_id ) . '" method="post" class="bw-schema-form">';

		// Nonce field
		wp_nonce_field( $this->form_name . '_nonce', $this->form_name . '_nonce_field' );

		// Hidden form identifier
		echo '<input type="hidden" name="bw_schema_form" value="' . esc_attr( $this->form_name ) . '" />';

		// Group fields by section
		$sections = $this->group_by_section();

		foreach ( $sections as $section_name => $section_fields ) {
			// Each section renders as a card (grouped-field skeleton)
			echo '<div class="bw-schema-card">';

			// Section heading
			if ( $section_name !== 'general' ) {
				echo '<h3 class="bw-schema-section-heading">';
				echo esc_html( $this->format_section_name( $section_name ) );
				echo '</h3>';
			}

			// Fields in this section
			echo '<table class="form-table bw-schema-form-table">';

			foreach ( $section_fields as $field ) {
				$this->render_field( $field );
			}

			echo '</table>';
			echo '</div>';
		}

		// Submit button
		echo '<p class="submit">';
		submit_button( __( 'Save Changes', 'bw-schema' ), 'primary', 'bw_schema_submit', true );
		echo '</p>';

		echo '</form>';

		return ob_get_clean();
	}

	/**
	 * Render a single form field
	 *
	 * Outputs the <tr> with label and input for one field.
	 *
	 * @param array $field Field definition
	 * @return void
	 */
	private function render_field( $field ) {
		$field_id = 'bw_schema_' . $field['name'];
		$key      = $field['map_to'] ?? $field['name'];
		$value    = $this->data[ $key ] ?? '';
		$error    = $this->errors[ $field['name'] ] ?? null;

		// Build data attributes for conditional fields
		$data_attrs = '';
		if ( ! empty( $field['show_if'] ) ) {
			foreach ( $field['show_if'] as $condition_key => $condition_value ) {
				// Handle array of values (e.g., show if business_type is hotel OR resort)
				if ( is_array( $condition_value ) ) {
					$values = implode( ',', array_map( 'esc_attr', $condition_value ) );
					$data_attrs .= ' data-show-if="' . esc_attr( $condition_key ) . ':' . $values . '"';
				} else {
					$data_attrs .= ' data-show-if="' . esc_attr( $condition_key ) . ':' . esc_attr( $condition_value ) . '"';
				}
			}
		}

		echo '<tr' . $data_attrs . '>';

		// Label
		echo '<th scope="row">';
		echo '<label for="' . esc_attr( $field_id ) . '">';
		echo esc_html( $field['label'] );

		if ( $field['required'] ) {
			echo ' <span class="bw-schema-required" title="Required">*</span>';
		}

		echo '</label>';
		echo '</th>';

		// Input
		echo '<td>';

		// Error message
		if ( $error ) {
			echo '<p class="bw-schema-error">';
			echo esc_html( $error );
			echo '</p>';
		}

		// Render field by type
		$this->render_field_input( $field, $field_id, $value );

		// Help text
		if ( $field['help'] ) {
			echo '<p class="description">';
			echo esc_html( $field['help'] );
			echo '</p>';
		}

		// "Not used" opt-out — for fields a real organization may
		// deliberately not have (declared via 'optout' => '<field key>').
		// Checked state comes from the organization service; the checkbox
		// posts under bw_schema_field_optout[] and is saved by the page.
		if ( ! empty( $field['optout'] ) && class_exists( 'BW_Schema_Service_Organization' ) ) {
			$optout_key = $field['optout'];
			echo '<label class="bw-schema-optout">';
			echo '<input type="checkbox" name="bw_schema_field_optout[]" value="' . esc_attr( $optout_key ) . '" '
				. checked( BW_Schema_Service_Organization::is_field_opted_out( $optout_key ), true, false ) . '> ';
			echo esc_html__( 'Not used — this organization doesn\'t have this, don\'t count it as missing', 'bw-schema' );
			echo '</label>';
		}

		echo '</td>';
		echo '</tr>';
	}

	/**
	 * Render field input element by type
	 *
	 * @param array  $field Field definition
	 * @param string $field_id HTML ID attribute
	 * @param mixed  $value Current field value
	 * @return void
	 */
	private function render_field_input( $field, $field_id, $value ) {
		$type = $field['type'];

		switch ( $type ) {
			case 'text':
			case 'email':
			case 'url':
			case 'tel':
				$this->render_text_input( $field, $field_id, $value, $type );
				break;

			case 'textarea':
				$this->render_textarea( $field, $field_id, $value );
				break;

			case 'select':
				$this->render_select( $field, $field_id, $value );
				break;

			case 'radio':
				$this->render_radio( $field, $field_id, $value );
				break;

			case 'checkbox':
				$this->render_checkbox( $field, $field_id, $value );
				break;

			case 'checkboxes':
				$this->render_checkboxes( $field, $field_id, $value );
				break;

			case 'date':
			case 'time':
			case 'datetime-local':
				$this->render_datetime_input( $field, $field_id, $value, $type );
				break;

			default:
				// Fallback to text input
				$this->render_text_input( $field, $field_id, $value, 'text' );
		}
	}

	/**
	 * Render text/email/url/tel input
	 *
	 * @param array  $field Field definition
	 * @param string $field_id HTML ID
	 * @param mixed  $value Current value
	 * @param string $type Input type
	 * @return void
	 */
	private function render_text_input( $field, $field_id, $value, $type ) {
		$field_name = $field['map_to'] ?? $field['name'];

		echo '<input type="' . esc_attr( $type ) . '" ';
		echo 'id="' . esc_attr( $field_id ) . '" ';
		echo 'name="' . esc_attr( $field_name ) . '" ';
		echo 'value="' . esc_attr( $value ) . '" ';
		echo 'class="regular-text' . ( $field['required'] ? ' required' : '' ) . '" ';

		if ( $field['placeholder'] ) {
			echo 'placeholder="' . esc_attr( $field['placeholder'] ) . '" ';
		}

		echo '/>';
	}

	/**
	 * Render textarea
	 *
	 * @param array  $field Field definition
	 * @param string $field_id HTML ID
	 * @param mixed  $value Current value
	 * @return void
	 */
	private function render_textarea( $field, $field_id, $value ) {
		$field_name = $field['map_to'] ?? $field['name'];

		echo '<textarea ';
		echo 'id="' . esc_attr( $field_id ) . '" ';
		echo 'name="' . esc_attr( $field_name ) . '" ';
		echo 'rows="' . absint( $field['rows'] ) . '" ';
		echo 'cols="' . absint( $field['cols'] ) . '" ';
		echo 'class="large-text' . ( $field['required'] ? ' required' : '' ) . '" ';
		echo '>' . esc_textarea( $value ) . '</textarea>';
	}

	/**
	 * Render select dropdown
	 *
	 * @param array  $field Field definition
	 * @param string $field_id HTML ID
	 * @param mixed  $value Current value
	 * @return void
	 */
	private function render_select( $field, $field_id, $value ) {
		$field_name = $field['map_to'] ?? $field['name'];

		echo '<select ';
		echo 'id="' . esc_attr( $field_id ) . '" ';
		echo 'name="' . esc_attr( $field_name ) . '" ';
		echo 'class="' . ( $field['required'] ? 'required' : '' ) . '" ';
		echo '>';

		// Empty option
		echo '<option value="">' . esc_html__( '— Select —', 'bw-schema' ) . '</option>';

		// Options
		foreach ( $field['options'] as $option_value => $option_label ) {
			echo '<option value="' . esc_attr( $option_value ) . '" ';
			selected( $value, $option_value );
			echo '>' . esc_html( $option_label ) . '</option>';
		}

		echo '</select>';
	}

	/**
	 * Render radio buttons
	 *
	 * @param array  $field Field definition
	 * @param string $field_id HTML ID
	 * @param mixed  $value Current value
	 * @return void
	 */
	private function render_radio( $field, $field_id, $value ) {
		$field_name = $field['map_to'] ?? $field['name'];

		foreach ( $field['options'] as $option_value => $option_label ) {
			$option_id = $field_id . '_' . sanitize_key( $option_value );

			echo '<label>';
			echo '<input type="radio" ';
			echo 'id="' . esc_attr( $option_id ) . '" ';
			echo 'name="' . esc_attr( $field_name ) . '" ';
			echo 'value="' . esc_attr( $option_value ) . '" ';
			checked( $value, $option_value );
			echo ' /> ';
			echo esc_html( $option_label );
			echo '</label><br />';
		}
	}

	/**
	 * Render single checkbox
	 *
	 * @param array  $field Field definition
	 * @param string $field_id HTML ID
	 * @param mixed  $value Current value
	 * @return void
	 */
	private function render_checkbox( $field, $field_id, $value ) {
		$field_name = $field['map_to'] ?? $field['name'];

		echo '<label>';
		echo '<input type="checkbox" ';
		echo 'id="' . esc_attr( $field_id ) . '" ';
		echo 'name="' . esc_attr( $field_name ) . '" ';
		echo 'value="1" ';
		checked( $value, 1 );
		checked( $value, 'yes' );
		echo ' /> ';
		echo esc_html( $field['label'] );
		echo '</label>';
	}

	/**
	 * Render multiple checkboxes
	 *
	 * @param array  $field Field definition
	 * @param string $field_id HTML ID
	 * @param mixed  $value Current value(s)
	 * @return void
	 */
	private function render_checkboxes( $field, $field_id, $value ) {
		$field_name = $field['map_to'] ?? $field['name'];
		$values     = is_array( $value ) ? $value : array( $value );

		foreach ( $field['options'] as $option_value => $option_label ) {
			$option_id = $field_id . '_' . sanitize_key( $option_value );

			echo '<label>';
			echo '<input type="checkbox" ';
			echo 'id="' . esc_attr( $option_id ) . '" ';
			echo 'name="' . esc_attr( $field_name ) . '[]" ';
			echo 'value="' . esc_attr( $option_value ) . '" ';
			checked( in_array( $option_value, $values, true ), true );
			echo ' /> ';
			echo esc_html( $option_label );
			echo '</label><br />';
		}
	}

	/**
	 * Render date/time input
	 *
	 * @param array  $field Field definition
	 * @param string $field_id HTML ID
	 * @param mixed  $value Current value
	 * @param string $type Input type (date, time, datetime-local)
	 * @return void
	 */
	private function render_datetime_input( $field, $field_id, $value, $type ) {
		$field_name = $field['map_to'] ?? $field['name'];

		echo '<input type="' . esc_attr( $type ) . '" ';
		echo 'id="' . esc_attr( $field_id ) . '" ';
		echo 'name="' . esc_attr( $field_name ) . '" ';
		echo 'value="' . esc_attr( $value ) . '" ';
		echo 'class="regular-text' . ( $field['required'] ? ' required' : '' ) . '" ';
		echo '/>';
	}

	/**
	 * Group fields by section
	 *
	 * @return array Grouped fields
	 */
	private function group_by_section() {
		$grouped = array();

		foreach ( $this->fields as $field ) {
			$section = $field['section'] ?? 'general';

			if ( ! isset( $grouped[ $section ] ) ) {
				$grouped[ $section ] = array();
			}

			$grouped[ $section ][] = $field;
		}

		return $grouped;
	}

	/**
	 * Format section name for display
	 *
	 * @param string $section_name Section key
	 * @return string Formatted section name
	 */
	private function format_section_name( $section_name ) {
		return ucwords( str_replace( '_', ' ', $section_name ) );
	}

	/**
	 * Validate form data
	 *
	 * Runs all validation rules for all fields.
	 *
	 * @param array $post_data Form POST data
	 * @return bool|array True if valid, array of errors if not
	 */
	public function validate( $post_data ) {
		$this->errors = array();

		foreach ( $this->fields as $field ) {
			$field_name = $field['map_to'] ?? $field['name'];
			$value      = $post_data[ $field_name ] ?? null;

			// Check required
			if ( $field['required'] && ( empty( $value ) || ( is_array( $value ) && count( $value ) === 0 ) ) ) {
				$this->errors[ $field['name'] ] = sprintf(
					__( '%s is required.', 'bw-schema' ),
					$field['label']
				);
				continue;
			}

			// Run validation rules
			$validate_rules = (array) $field['validate'];

			foreach ( $validate_rules as $rule ) {
				if ( is_string( $rule ) ) {
					$valid = BW_Schema_Form_Validator::validate( $rule, $value, $field );
				} elseif ( is_callable( $rule ) ) {
					$valid = call_user_func( $rule, $value );
				} else {
					$valid = true;
				}

				if ( ! $valid ) {
					$this->errors[ $field['name'] ] = sprintf(
						__( '%s is invalid.', 'bw-schema' ),
						$field['label']
					);
					break;
				}
			}
		}

		return empty( $this->errors );
	}

	/**
	 * Sanitize form data
	 *
	 * Applies sanitization functions to all fields.
	 *
	 * @param array $post_data Form POST data
	 * @return array Sanitized data
	 */
	public function sanitize( $post_data ) {
		$sanitized = array();

		foreach ( $this->fields as $field ) {
			$field_name = $field['map_to'] ?? $field['name'];
			$key        = $field['map_to'] ?? $field['name'];
			$value      = $post_data[ $field_name ] ?? null;

			if ( null === $value ) {
				continue;
			}

			// Apply sanitization function
			$sanitize_fn = $field['sanitize'];

			if ( is_string( $sanitize_fn ) && function_exists( $sanitize_fn ) ) {
				$sanitized[ $key ] = $sanitize_fn( $value );
			} elseif ( is_callable( $sanitize_fn ) ) {
				$sanitized[ $key ] = call_user_func( $sanitize_fn, $value );
			} else {
				$sanitized[ $key ] = $value;
			}
		}

		return $sanitized;
	}

	/**
	 * Validate and save (all-in-one)
	 *
	 * Validates form data, sanitizes it, and calls save callback.
	 *
	 * @param array  $post_data Form POST data
	 * @param string $nonce_field Nonce field name (optional)
	 * @return array|WP_Error Success array or WP_Error
	 */
	public function validate_and_save( $post_data, $nonce_field = null ) {
		// Verify nonce
		$nonce_field = $nonce_field ?? ( $this->form_name . '_nonce_field' );
		$nonce       = isset( $post_data[ $nonce_field ] ) ? $post_data[ $nonce_field ] : '';

		if ( ! wp_verify_nonce( $nonce, $this->form_name . '_nonce' ) ) {
			return new WP_Error(
				'nonce_failed',
				__( 'Form verification failed. Please try again.', 'bw-schema' )
			);
		}

		// Validate
		if ( ! $this->validate( $post_data ) ) {
			return new WP_Error(
				'validation_failed',
				__( 'Form validation failed.', 'bw-schema' )
			);
		}

		// Sanitize
		$sanitized = $this->sanitize( $post_data );

		return array(
			'success'   => true,
			'sanitized' => $sanitized,
		);
	}

	/**
	 * Get validation errors
	 *
	 * @return array Validation errors by field name
	 */
	public function get_errors() {
		return $this->errors;
	}

	/**
	 * Check if form has errors
	 *
	 * @return bool True if there are validation errors
	 */
	public function has_errors() {
		return ! empty( $this->errors );
	}
}
