<?php

namespace Limb_Chatbot\Includes\Services\Actions\Parameter_Types;

use Limb_Chatbot\Includes\Data_Objects\Parameter;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Interfaces\Parameter_Type_Interface;
use Limb_Chatbot\Includes\Interfaces\Parameter_Type_Schema_Interface;
use Limb_Chatbot\Includes\Services\Actions\Validators\Parameter_Validator;
use Limb_Chatbot\Includes\Services\Helper;

/**
 * Class Abstract_Parameter_Type
 *
 * Base class for parameter type implementations.
 * Provides common functionality for error handling and required field validation.
 *
 * Each concrete type implements Parameter_Type_Schema_Interface to define its own schema.
 * Delegates field validation to specialized validator classes (SRP).
 *
 * @package Limb_Chatbot\Includes\Services\Actions\Parameter_Types
 * @since 1.0.0
 */
abstract class Abstract_Parameter_Type implements Parameter_Type_Interface, Parameter_Type_Schema_Interface {

	/**
	 * Validation errors
	 *
	 * @var array
	 */
	protected array $errors = [];

	/**
	 * Get validation errors
	 *
	 * @return array
	 * @since 1.0.0
	 */
	public function get_errors(): array {
		return $this->errors;
	}

	/**
	 * Get first validation error
	 *
	 * @return string|null
	 * @since 1.0.0
	 */
	public function get_first_error(): ?string {
		return $this->errors[0] ?? null;
	}

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

	/**
	 * Clear all validation errors
	 *
	 * @return void
	 * @since 1.0.0
	 */
	public function clear_errors(): void {
		$this->errors = [];
	}

	/**
	 * Validate required field
	 *
	 * @param  string  $value  Value to check
	 * @param  Parameter  $parameter  Parameter configuration
	 *
	 * @return bool True if passes required check, false otherwise
	 * @since 1.0.0
	 */
	protected function validate_required( string $value, Parameter $parameter ): bool {
		if ( $this->is_required( $parameter ) && empty( trim( $value ) ) ) {
			$this->add_error( sprintf(
				__( '%s is required.', 'limb-chatbot' ),
				$parameter->get_label() ?? $parameter->get_name()
			) );

			return false;
		}

		return true;
	}

	/**
	 * Check if parameter is required
	 *
	 * @param  Parameter  $parameter  Parameter to check
	 *
	 * @return bool
	 * @since 1.0.0
	 */
	protected function is_required( Parameter $parameter ): bool {
		$required = $parameter->get_required();

		return in_array( $required, [ 1, '1', true ] );
	}

	/**
	 * Add validation error
	 *
	 * @param  string  $error  Error message
	 *
	 * @return void
	 * @since 1.0.0
	 */
	protected function add_error( string $error ): void {
		$this->errors[] = $error;
	}

	/**
	 * Get parameter label for error messages
	 *
	 * @param  Parameter  $parameter  Parameter configuration
	 *
	 * @return string
	 * @since 1.0.0
	 */
	protected function get_label( Parameter $parameter ): string {
		return $parameter->get_label() ?? $parameter->get_name();
	}

	/**
	 * Check if this parameter type has post-collection steps
	 *
	 * @param  Parameter  $parameter  The parameter configuration
	 *
	 * @return bool
	 * @since 1.0.0
	 */
	public function has_post_collection_steps( Parameter $parameter ): bool {
		return false; // Default: no post-collection steps
	}

	/**
	 * Get post-collection step type
	 *
	 * @param  Parameter  $parameter  The parameter configuration
	 *
	 * @return int|null
	 * @since 1.0.0
	 */
	public function get_post_collection_step_type( Parameter $parameter ): ?int {
		return null; // Default: no post-collection steps
	}

	/**
	 * Get post-collection step data
	 *
	 * @param  Parameter  $parameter  The parameter configuration
	 * @param  string  $collected_value  The value collected from user
	 *
	 * @return array
	 * @since 1.0.0
	 */
	public function get_post_collection_step_data( Parameter $parameter, string $collected_value ): array {
		return []; // Default: no additional step data
	}

	/**
	 * Handle post-collection step processing
	 *
	 * @param  Parameter  $parameter  The parameter configuration
	 * @param  array  $step_data  The step data from action plan step
	 * @param  string  $user_input  The user's input
	 *
	 * @since 1.0.0
	 */
	public function process_post_collection_step( Parameter $parameter, array $step_data, string $user_input ) {
		// Default: always successful
	}

	/**
	 * Get validation rules schema for this parameter type.
	 *
	 * Override in concrete type classes to define type-specific rules.
	 *
	 * @return array Validation rules schema
	 * @since 1.0.0
	 */
	public function get_validation_rules_schema(): array {
		return [];
	}

	/**
	 * Get configuration schema for this parameter type.
	 *
	 * Override in concrete type classes to define type-specific configs.
	 *
	 * @return array Configuration schema
	 * @since 1.0.0
	 */
	public function get_config_schema(): array {
		return [];
	}

	/**
	 * Check if validation rule is valid for this type.
	 *
	 * Override in concrete classes for custom validation logic.
	 *
	 * @param  string  $rule_key    Rule key
	 * @param  mixed   $rule_value  Rule value
	 *
	 * @return bool True if valid
	 * @since 1.0.0
	 */
	public function is_valid_rule( string $rule_key, $rule_value ): bool {
		$schema = $this->get_validation_rules_schema();
		return $this->validate_against_schema( $rule_key, $rule_value, $schema );
	}

	/**
	 * Check if configuration is valid for this type.
	 *
	 * Override in concrete classes for custom validation logic.
	 *
	 * @param  string  $config_key    Config key
	 * @param  mixed   $config_value  Config value
	 *
	 * @return bool True if valid
	 * @since 1.0.0
	 */
	public function is_valid_config( string $config_key, $config_value ): bool {
		$schema = $this->get_config_schema();
		return $this->validate_against_schema( $config_key, $config_value, $schema );
	}

	/**
	 * Validate value against schema definition.
	 *
	 * @param  string  $key    Key to validate
	 * @param  mixed   $value  Value to validate
	 * @param  array   $schema Schema to validate against
	 *
	 * @return bool True if valid
	 * @since 1.0.0
	 */
	protected function validate_against_schema( string $key, $value, array $schema ): bool {
		if ( ! isset( $schema[ $key ] ) ) {
			return false;
		}

		$rule = $schema[ $key ];

		// Check type
		if ( isset( $rule['type'] ) ) {
			if ( ! Helper::check_type($value, $rule['type']) ) {
				return false;
			}
		}
		// ... Add other validations

		return true;
	}

	/**
	 * Create a new parameter.
	 *
	 * Delegates to specialized validators.
	 *
	 * @param  int     $action_id  ID of the parent action
	 * @param  array   $params     Parameter configuration
	 *
	 * @return Parameter The created parameter instance
	 *
	 * @throws Exception If critical validation fails
	 * @since 1.0.0
	 */
	public function create( $action_id, array $params ): Parameter {
		$parameter = new Parameter();
		$parameter->set_action_id( $action_id );

		// Use specialized validators
		$validator = new Parameter_Validator($this);
		$parameter = $validator->validate($parameter, $params);

		try {
			$parameter->save();
		} catch ( \Exception $e ) {
			throw new Exception( Error_Codes::TECHNICAL_ERROR, __( 'Failed to save', 'limb-chatbot' ) );
		}

		return $parameter;
	}

	/**
	 * Update an existing parameter.
	 *
	 * Delegates to specialized validators.
	 *
	 * @param  Parameter  $parameter  Parameter which should be updated
	 * @param  array  $params  The parameter configuration
	 *
	 * @return Parameter The updated instance.
	 * @throws Exception If critical validation fails.
	 * @since 1.0.0
	 */
	public function update( Parameter $parameter, $params ): Parameter {
		foreach ( $parameter::FILLABLE as $key => $value ) {
			if ( ! array_key_exists( $value, $params ) ) {
				$params[ $value ] = $parameter->{$value};
			}
		}

		$validator = new Parameter_Validator( $this );
		$parameter = $validator->validate( $parameter, $params );

		try {
			$parameter->save();
		} catch ( \Exception $e ) {
			throw new Exception( Error_Codes::TECHNICAL_ERROR, __( 'Failed to save', 'limb-chatbot' ) );
		}

		return $parameter;
	}
}

