<?php
namespace SabaiApps\Directories\Component\Form\Helper;

use SabaiApps\Directories\Application;
use SabaiApps\Directories\Component\Form\Form;
use SabaiApps\Directories\Component\Form\FormComponent;
use SabaiApps\Directories\Request;

/**
 * FormHelper provides utility methods for building, rendering, and managing forms.
 * Handles form caching, field naming, token generation, and upload token management.
 */
class FormHelper {

	// Static property to cache built forms by build ID.
	protected static $_forms = array();

	/**
	 * Builds and returns a Form object from settings.
	 * Handles caching, build ID assignment, storage initialization, and plugin hooks.
	 *
	 * @param Application $application
	 * @param array       $settings
	 * @param bool        $useCache
	 * @param array|null  $values
	 * @param array       $errors
	 * @return Form
	 */
	public function build( Application $application, array $settings, $useCache = true, ?array $values = null, array $errors = array() ) {
		if ( ! isset( $settings['#build_id'] ) ) {
			$settings['#build_id']   = $application->getPlatform()->getRandomId();
			$settings['#is_rebuild'] = false;
		} else {
			// Is the form already built and cached?
			if ( $settings['#build_id'] !== false && isset( self::$_forms[ $settings['#build_id'] ] ) ) {
				// Return cached form if rebuild is not necessary
				if ( $useCache ) {
					return self::$_forms[ $settings['#build_id'] ];
				}
			}
			$settings['#is_rebuild'] = isset( $settings['#is_rebuild'] );
		}
		// Set id if not already set
		if ( ! isset( $settings['#id'] ) ) {
			$settings['#id'] = $this->buildId( $application, $settings );
		}

		$settings['#method'] = isset( $settings['#method'] ) && strtolower( $settings['#method'] ) === 'get' ? 'get' : 'post';

		if ( $settings['#build_id'] !== false
			&& ( $settings['#method'] !== 'get' || ! empty( $settings['#enable_storage'] ) )
		) {
			// Embed build ID in hidden field
			$settings[ FormComponent::FORM_BUILD_ID_NAME ] = array(
				'#type'  => 'hidden',
				'#value' => $settings['#build_id'],
			);
		}

		// Initialize form storage
		$storage = array();
		if ( ! empty( $settings['#enable_storage'] )
			&& $settings['#build_id'] !== false
		) {
			if ( isset( $settings['#initial_storage'] ) ) {
				$storage = $settings['#initial_storage'];
			}

			$application->getComponent( 'Form' )->setFormStorage(
				$settings['#build_id'],
				$storage,
				! empty( $settings['#storage_as_option'] )
			);
		}

		// Allow other plugins to modify form settings and storage
		$application->Action(
			'form_build_form',
			array( &$settings, &$storage )
		);
		// Call with inherited form names
		if ( ! empty( $settings['#inherits'] ) ) {
			foreach ( array_reverse( $settings['#inherits'] ) as $inherited_form_name ) {
				$application->Action(
					'form_build_' . $inherited_form_name,
					array( &$settings, &$storage )
				);
			}
		}
		// Call with the name of current form
		if ( ! empty( $settings['#name'] ) ) {
			$application->Action(
				'form_build_' . $settings['#name'],
				array( &$settings, &$storage )
			);
		}

		if ( isset( $settings['#errors'] ) ) {
			$errors += $settings['#errors'];
		}
		$form = new Form( $application, $settings, $storage, $errors );
		$form->build( $values );

		if ( $settings['#build_id'] !== false ) {
			// Add built form to cache
			self::$_forms[ $settings['#build_id'] ] = $form;
		}

		return $form;
	}

	/**
	 * Generates a unique HTML ID for the form.
	 *
	 * @param Application $application
	 * @param array       $settings
	 * @return string
	 */
	public function buildId( Application $application, array $settings ) {
		return 'drts-form-' . ( $settings['#build_id'] !== false ? $settings['#build_id'] : $application->getPlatform()->getRandomId() );
	}

	/**
	 * Generates a field name or selector for a form field.
	 *
	 * @param Application $application
	 * @param array       $names
	 * @param bool|string $isSelector
	 * @return string
	 */
	public function fieldName( Application $application, array $names, $isSelector = false ) {
		if ( is_array( $names[0] ) ) {
			if ( ! empty( $names[0] ) ) {
				$ret = $names[0][0];
				unset( $names[0][0] );
			} else {
				unset( $names[0] );
				$ret = array_shift( $names );
			}
		} else {
			$ret = array_shift( $names );
		}
		$ret = $application->H( $ret );
		foreach ( $names as $name ) {
			if ( is_array( $name ) ) {
				foreach ( $name as $_name ) {
					$ret .= '[' . $application->H( $_name ) . ']';
				}
			} else {
				$ret .= '[' . $application->H( $name ) . ']';
			}
		}
		if ( $isSelector ) {
			$ret = '[name="' . $ret . '"]';
			if ( is_string( $isSelector ) ) {
				$ret = $isSelector . $ret;
			}
		}

		return $ret;
	}

	/**
	 * Renders a form as HTML.
	 *
	 * @param Application $application
	 * @param Form|array  $form
	 * @return string
	 */
	public function render( Application $application, $form ) {
		if ( $form instanceof Form ) {
			$settings  = $form->settings;
			$use_cache = ! $form->rebuild;
			$values    = $form->values;
			$errors    = $form->getError();
		} elseif ( is_array( $form ) ) {
			$settings  = $form;
			$use_cache = true;
			$values    = null;
			$errors    = array();
		} else {
			return '';
		}

		return $application->Form_Form_build( $settings, $use_cache, $values, $errors )->render();
	}

	/**
	 * Builds an array of submit buttons for the form.
	 *
	 * @param Application $application
	 * @param array|null  $buttons
	 * @param array       $options
	 * @return array
	 */
	public function submitButtons( Application $application, ?array $buttons = null, array $options = array() ) {
		if ( ! isset( $buttons ) ) {
			$buttons = array(
				array(
					'#btn_label' => __( 'Submit', 'directories-pro' ),
					'#btn_color' => 'primary',
				),
			);
		} elseif ( empty( $buttons ) ) {
				return;
		}

		$submits = array();

		// Add submit button and cancel link
		$margin_class = DRTS_BS_PREFIX . 'me-3';
		foreach ( $buttons as $name => $button ) {
			if ( ! empty( $options['disable'] )
				&& ! isset( $button['#disabled'] )
			) {
				$button['#disabled'] = true;
			}
			if ( ! isset( $button['#attributes'] ) ) {
				$button['#attributes'] = array();
			}
			$submits[ $name ] = $button + array(
				'#type'  => 'submit',
				'#value' => $name,
			);
			if ( $submits[ $name ]['#type'] !== 'submit' ) {
				if ( ! isset( $submits[ $name ]['#tree'] ) ) {
					// Do not prefix with FORM_SUBMIT_BUTTON_NAME
					$submits[ $name ]['#tree'] = false;
				}
				if ( ! isset( $submits[ $name ]['#class'] ) ) {
					$submits[ $name ]['#class'] = '';
				}
				$submits[ $name ]['#class'] .= ' ' . $margin_class;
				continue;
			}
			if ( ! isset( $submits[ $name ]['#attributes']['class'] ) ) {
				$submits[ $name ]['#attributes']['class'] = '';
			}
			$submits[ $name ]['#attributes']['class'] .= ' ' . $margin_class;
			if ( isset( $options['default_callback'] ) && ! isset( $submits[ $name ]['#submit'] ) ) {
				$submits[ $name ]['#submit'] = array(
					10 => array( // weight
						$options['default_callback'],
					),
				);
				// Prevent callback called more than once
				unset( $options['default_callback'] );
			}
		}

		if ( empty( $submits ) ) {
			return;
		}

		$margin          = DRTS_BS_PREFIX . 'mt-' . intval( $options['margin'] ?? 0 );
		$justify_content = count( $buttons ) > 1 ? ' ' . DRTS_BS_PREFIX . 'justify-content-between' : '';
		$submits        += array(
			'#tree'   => true,
			'#weight' => 99999,
			'#group'  => false,
			'#prefix' => '<div class="drts-form-buttons ' . DRTS_BS_PREFIX . 'd-block ' . DRTS_BS_PREFIX . 'd-sm-flex ' . DRTS_BS_PREFIX . 'flex-row ' . DRTS_BS_PREFIX . 'align-items-center ' . $margin . $justify_content . ' ' . DRTS_BS_PREFIX . 'w-100">',
			'#suffix' => '</div>',
		);

		return $submits;
	}

	/**
	 * Creates a CSRF token for the form.
	 *
	 * @param Application $application
	 * @param string      $tokenId
	 * @param int         $tokenLifetime
	 * @param bool        $reobtainable
	 * @return string
	 */
	public function createToken( Application $application, $tokenId, $tokenLifetime = 1800, $reobtainable = false ) {
		return \SabaiApps\Framework\Token::create( $tokenId, $tokenLifetime, $reobtainable )->getValue();
	}

	/**
	 * Returns HTML for a hidden CSRF token field.
	 *
	 * @param Application $application
	 * @param string      $tokenId
	 * @param int         $tokenLifetime
	 * @param bool        $reobtainable
	 * @param string      $tokenName
	 * @return string
	 */
	public function tokenHtml( Application $application, $tokenId, $tokenLifetime = 1800, $reobtainable = false, $tokenName = Request::PARAM_TOKEN ) {
		return sprintf(
			'<input type="hidden" name="%s" value="%s" id="%s" />',
			$application->H( $tokenName ),
			$application->Form_Form_createToken( $tokenId, $tokenLifetime, $reobtainable ),
			'drts-' . strtolower( str_replace( array( '_', ' ' ), '-', $application->H( $tokenId ) ) ) . '-token'
		);
	}

	/**
	 * Validates a CSRF token value.
	 *
	 * @param Application $application
	 * @param string      $tokenValue
	 * @param string      $tokenId
	 * @param bool        $reuseable
	 * @return bool
	 */
	public function validateToken( Application $application, $tokenValue, $tokenId, $reuseable = false ) {
		return \SabaiApps\Framework\Token::validate( $tokenValue, $tokenId, $reuseable );
	}

	/**
	 * Stores or retrieves an upload token for a file upload field.
	 *
	 * @param Application $application
	 * @param string      $formBuildId
	 * @param string      $formFieldId
	 * @param array|null  $token
	 * @return array|null
	 */
	public function useUploadToken( Application $application, $formBuildId, $formFieldId, ?array $token = null ) {
		if ( ! $storage = $application->getComponent( 'Form' )->getFormStorage( $formBuildId ) ) {
			$storage = array();
		}
		if ( ! isset( $storage['form_upload_tokens'] ) ) {
			$storage['form_upload_tokens'] = array();
		}
		if ( isset( $token ) ) {
			$token                                        += array(
				'max_num_files'   => 0,
				'upload_settings' => array(),
			);
			$storage['form_upload_tokens'][ $formFieldId ] = $token;
			$application->getComponent( 'Form' )->setFormStorage( $formBuildId, $storage );
		} else {
			$token = isset( $storage['form_upload_tokens'][ $formFieldId ] ) ? $storage['form_upload_tokens'][ $formFieldId ] : null;
		}

		return $token;
	}
}
