<?php
/**
 * Abstract FormHandler class.
 *
 * Provides a base for form data providers and savers for CPT-based settings in Firevert modules.
 * Each concrete handler must define how to retrieve and save data.
 *
 * @package Ilabs\Firevert\Core\Form
 */

declare( strict_types=1 );

namespace Ilabs\Firevert\Core\Form;

use Ilabs\Firevert\Core\Form\Sanitization\MetaFieldSanitizer;
use Ilabs\Firevert\Core\Form\Validation\FormValidatorInterface;
use Ilabs\Firevert\Core\Services\Helper\Integration\LanguageHelper;
use Ilabs\Firevert\Core\Settings\Form\DefaultSettingsProviderInterface;
use WP_Post;

/**
 * Abstract class FormHandler.
 *
 * Responsible for form data hydration and persistence per CPT instance.
 */
abstract class FormHandler {
	/**
	 * Module nonce field.
	 *
	 * @var string
	 */
	protected string $nonce = '';

	/**
	 * Module action field.
	 *
	 * @var string
	 */
	protected string $action = '';

	/**
	 * Module section slug.
	 *
	 * @var string
	 */
	protected string $section_slug = '';

	/**
	 * Fields and types used in form.
	 *
	 * @var array<string, string>
	 */
	protected array $fields = array();

	/**
	 * The current post instance.
	 *
	 * @var WP_Post
	 */
	protected WP_Post $post;

	/**
	 * Constructor.
	 *
	 * @param WP_Post $post The post instance to operate on.
	 */
	public function __construct( WP_Post $post ) {
		$this->post = $post;
	}

	/**
	 * Returns array od fields and types
	 *
	 * @return array Fields and types used in form
	 *
	 * @since 1.0.0
	 */
	public function get_fields(): array {
		return $this->fields;
	}

	/**
	 * Returns list of translatable fields.
	 *
	 * Can be overridden by specific FormHandler implementations.
	 *
	 * @return array<string>
	 *
	 * @since 1.0.0
	 */
	public function get_translatable_fields(): array {
		return array();
	}

	/**
	 * Returns sanitized and structured data used in the form view.
	 *
	 * @return array Associative array of data for rendering the form.
	 */
	abstract public function get_data(): array;

	/**
	 * Saves the global module settings.
	 *
	 * @return void
	 *
	 * @since 1.0.0
	 */
	public function save(): void {
		/**
		 * Get from DI container.
		 *
		 * @var MetaFieldSanitizer $sanitizer
		 */
		$sanitizer = firevert_container()->get( 'meta_field_sanitizer' );

		if (
			empty( $_POST[ $this->nonce ] ) ||
			! wp_verify_nonce(
				sanitize_text_field( wp_unslash( $_POST[ $this->nonce ] ) ),
				$this->action
			)
		) {
			return;
		}

		$validator = $this->get_validator();
		if ( $validator ) {
			$sanitized_data = array();

			foreach ( $this->fields as $key => $type ) {
				$sanitized_data[ $key ] = $sanitizer->sanitize( $key, $type );
			}

			$result = $validator->validate( $sanitized_data );

			if ( ! $result->is_valid() ) {
				set_transient( 'firevert_validation_errors', $result->get_errors(), 30 );
				return;
			}
		}

		$lang         = LanguageHelper::get_active_region_code() ?? get_locale();
		$translatable = $this->get_translatable_fields();

		foreach ( $this->fields as $key => $type ) {
			$value    = $sanitizer->sanitize( $key, $type );
			$meta_key = $this->prepare_key_if_translatable( $translatable, $key, $lang );

			if ( null === $value ) {
				delete_post_meta( $this->post->ID, $meta_key );
				continue;
			}

			update_post_meta( $this->post->ID, $meta_key, $value );
		}

		if ( method_exists( $this, 'after_save' ) ) {
			$this->after_save();
		}
	}

	/**
	 * Saves default values if they do not exist yet.
	 *
	 * @param string $module_slug Module slug.
	 *
	 * @return void
	 */
	public function save_defaults_if_missing( string $module_slug ): void {
		if ( ! $this instanceof DefaultSettingsProviderInterface ) {
			return;
		}

		$defaults     = $this->get_default_settings();
		$flag_key     = sprintf( 'firevert_%s_%s_defaults_saved', $module_slug, $this->section_slug );
		$lang         = LanguageHelper::get_active_region_code() ?? get_locale();
		$translatable = $this->get_translatable_fields();

		$already_saved = get_post_meta( $this->post->ID, $flag_key, true );
		if ( $already_saved ) {
			return;
		}

		foreach ( $defaults as $key => $default_value ) {
			$meta_key = $this->prepare_key_if_translatable( $translatable, $key, $lang );

			update_post_meta( $this->post->ID, $meta_key, $default_value );
		}

		update_post_meta( $this->post->ID, $flag_key, 1 );
	}

	/**
	 * Returns all meta data for the defined fields.
	 *
	 * @return array<string, mixed>
	 *
	 * @since 1.0.0
	 */
	protected function get_all_field_data(): array {
		$data         = array();
		$lang         = LanguageHelper::get_active_region_code() ?? get_locale();
		$translatable = $this->get_translatable_fields();

		foreach ( $this->fields as $key => $type ) {
			$default  = ( 'bool' === $type ) ? false : '';
			$meta_key = ( in_array( $key, $translatable, true ) && $lang ) ? "{$key}__{$lang}" : $key;
			$value    = $this->get( $meta_key, $default );

			$data[ $key ] = ( 'bool' === $type ) ? (bool) $value : $value;
		}

		return $data;
	}

	/**
	 * Retrieves a meta field value with optional default.
	 *
	 * @param string $key Meta key.
	 * @param mixed  $default_value Default value if meta is empty.
	 *
	 * @return mixed Sanitized meta value or default.
	 *
	 * @since 1.0.0
	 */
	protected function get( string $key, $default_value = '' ) {
		$value = get_post_meta( $this->post->ID, $key, true );

		return '' !== $value ? $value : $default_value;
	}

	/**
	 * Returns validator instance if defined.
	 *
	 * Can be overridden in subclasses to enable validation.
	 *
	 * @return FormValidatorInterface|null
	 *
	 * @since 1.0.0
	 */
	protected function get_validator(): ?FormValidatorInterface {
		return null;
	}

	/**
	 * Returns prepared field key based on translatable config.
	 *
	 * @param array  $translatable_fields Array of trasnlatable fields.
	 * @param string $key Key to be translated.
	 * @param string $lang Lang code.
	 *
	 * @return string
	 *
	 * @since 1.0.0
	 */
	private function prepare_key_if_translatable( array $translatable_fields, string $key, string $lang ): string {
		$is_translatable = in_array( $key, $translatable_fields, true );

		return $is_translatable ? "{$key}__{$lang}" : $key;
	}
}
