<?php
/**
 * ValidationException class for validation failures.
 *
 * @package    UltimateSecurity
 * @subpackage Support\Exceptions
 * @since      1.0.0
 */

namespace WPUltimateSecurity\UltimateSecurity\Support\Exceptions;

use Exception;
use WPUltimateSecurity\UltimateSecurity\Support\Result;

/**
 * Exception thrown when validation fails.
 *
 * Holds an array of validation errors keyed by field name.
 *
 * Usage:
 *   throw new ValidationException(['email' => 'Invalid email format.']);
 *   throw ValidationException::forField('email', 'Invalid email format.');
 *
 * Catching:
 *   try {
 *       // validation code
 *   } catch (ValidationException $e) {
 *       $errors = $e->getErrors();
 *       $emailError = $e->getError('email');
 *   }
 *
 * @since 1.0.0
 */
class ValidationException extends Exception {

    /**
     * Validation errors array.
     *
     * @since 1.0.0
     * @var array<string, string>
     */
    private array $errors;

    /**
     * HTTP status code for this exception.
     *
     * @since 1.0.0
     * @var int
     */
    private int $status_code = 422;

    /**
     * Create a new ValidationException.
     *
     * @since 1.0.0
     *
     * @param array<string, string> $errors  Associative array of field => error message.
     * @param string                $message Optional overall error message.
     * @param int                   $code    Optional error code.
     * @param Exception|null        $previous Optional previous exception.
     */
    public function __construct(
        array $errors,
        string $message = '',
        int $code = 0,
        ?Exception $previous = null
    ) {
        $this->errors = $errors;

        // Use first error as message if not provided.
        if ( empty( $message ) && ! empty( $errors ) ) {
            $message = reset( $errors );
        }

        if ( empty( $message ) ) {
            $message = __( 'Validation failed.', 'ultimate-security' );
        }

        parent::__construct( $message, $code, $previous );
    }

    /**
     * Create a ValidationException for a single field.
     *
     * @since 1.0.0
     *
     * @param string $field   The field name.
     * @param string $message The error message.
     * @return self
     */
    public static function forField( string $field, string $message ): self {
        return new self( [ $field => $message ] );
    }

    /**
     * Create a ValidationException for multiple fields.
     *
     * @since 1.0.0
     *
     * @param array<string, string> $errors Associative array of field => error message.
     * @return self
     */
    public static function forFields( array $errors ): self {
        return new self( $errors );
    }

    /**
     * Create a ValidationException for a required field.
     *
     * @since 1.0.0
     *
     * @param string $field The field name.
     * @return self
     */
    public static function required( string $field ): self {
        return new self( [
            $field => sprintf(
                /* translators: %s: field name */
                __( 'The %s field is required.', 'ultimate-security' ),
                $field
            ),
        ] );
    }

    /**
     * Create a ValidationException for an invalid format.
     *
     * @since 1.0.0
     *
     * @param string $field          The field name.
     * @param string $expected_format Description of expected format.
     * @return self
     */
    public static function invalidFormat( string $field, string $expected_format = '' ): self {
        $message = empty( $expected_format )
            ? sprintf(
                /* translators: %s: field name */
                __( 'The %s field has an invalid format.', 'ultimate-security' ),
                $field
            )
            : sprintf(
                /* translators: 1: field name, 2: expected format */
                __( 'The %1$s field must be %2$s.', 'ultimate-security' ),
                $field,
                $expected_format
            );

        return new self( [ $field => $message ] );
    }

    /**
     * Get all validation errors.
     *
     * @since 1.0.0
     *
     * @return array<string, string> Associative array of field => error message.
     */
    public function getErrors(): array {
        return $this->errors;
    }

    /**
     * Get a specific field's error.
     *
     * @since 1.0.0
     *
     * @param string $field The field name.
     * @return string|null The error message, or null if no error for that field.
     */
    public function getError( string $field ): ?string {
        return $this->errors[ $field ] ?? null;
    }

    /**
     * Check if a specific field has an error.
     *
     * @since 1.0.0
     *
     * @param string $field The field name.
     * @return bool
     */
    public function hasError( string $field ): bool {
        return isset( $this->errors[ $field ] );
    }

    /**
     * Get all field names with errors.
     *
     * @since 1.0.0
     *
     * @return array<string>
     */
    public function getErrorFields(): array {
        return array_keys( $this->errors );
    }

    /**
     * Get the HTTP status code.
     *
     * @since 1.0.0
     *
     * @return int
     */
    public function getStatusCode(): int {
        return $this->status_code;
    }

    /**
     * Convert to a Result object.
     *
     * @since 1.0.0
     *
     * @return Result
     */
    public function toResult(): Result {
        return Result::validation( $this->errors );
    }

    /**
     * Convert to array format suitable for REST API responses.
     *
     * @since 1.0.0
     *
     * @return array{success: bool, error: string, code: string, validation_errors: array}
     */
    public function toArray(): array {
        return [
            'success'           => false,
            'error'             => $this->getMessage(),
            'code'              => 'validation_error',
            'validation_errors' => $this->errors,
        ];
    }

    /**
     * Convert to a WP_Error object.
     *
     * @since 1.0.0
     *
     * @return \WP_Error
     */
    public function toWpError(): \WP_Error {
        return new \WP_Error(
            'validation_error',
            $this->getMessage(),
            [
                'status'            => $this->status_code,
                'validation_errors' => $this->errors,
            ]
        );
    }

    /**
     * Convert to a REST response.
     *
     * @since 1.0.0
     *
     * @return \WP_REST_Response
     */
    public function toRestResponse(): \WP_REST_Response {
        return new \WP_REST_Response( $this->toArray(), $this->status_code );
    }

    /**
     * Add an error to the exception.
     *
     * Note: This creates a new exception instance (immutable pattern).
     *
     * @since 1.0.0
     *
     * @param string $field   The field name.
     * @param string $message The error message.
     * @return self A new ValidationException with the error added.
     */
    public function withError( string $field, string $message ): self {
        $errors           = $this->errors;
        $errors[ $field ] = $message;
        return new self( $errors );
    }

    /**
     * Merge errors from another ValidationException.
     *
     * Note: This creates a new exception instance (immutable pattern).
     *
     * @since 1.0.0
     *
     * @param ValidationException $other The other exception.
     * @return self A new ValidationException with merged errors.
     */
    public function merge( ValidationException $other ): self {
        return new self( array_merge( $this->errors, $other->getErrors() ) );
    }
}
