<?php

namespace Fotogenic_AI;

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

/**
 * Validates and normalizes the persisted Campaign Set recipe contract.
 */
final class Campaign_Set_Recipe {
    public const VERSION = 1;
    public const MIN_OUTPUTS = 2;
    public const MAX_OUTPUTS = 6;

    public const MAX_INSTRUCTIONS_LENGTH = 5000;
    public const MAX_STYLE_LENGTH = 120;
    public const MAX_ASPECT_RATIO_LENGTH = 32;
    private const FORBIDDEN_FIELDS = [ 'selectionOverrides', 'selectionOverrideKeys' ];
    private const ALLOWED_FIELDS = [
        'version',
        'count',
		'quality',
        'customInstructions',
        'style',
        'aspectRatio',
        'subjectAttachmentId',
        'environmentAttachmentId',
    ];

    /**
     * @param mixed $raw Raw recipe data.
     * @return array|\WP_Error
     */
    public static function normalize( $raw ) {
        if ( ! is_array( $raw ) || fotogenic_ai_array_is_list( $raw ) ) {
            return new \WP_Error(
                'fotogenic_ai_invalid_campaign_recipe',
                __( 'A valid Campaign Set recipe is required.', 'fotogenic-ai' ),
                [ 'status' => 400 ]
            );
        }

        $forbidden = array_values( array_intersect( self::FORBIDDEN_FIELDS, array_keys( $raw ) ) );
        if ( ! empty( $forbidden ) ) {
            return new \WP_Error(
                'fotogenic_ai_campaign_selection_overrides_forbidden',
                __( 'Campaign Set recipes do not support selection overrides.', 'fotogenic-ai' ),
                [
                    'status' => 400,
                    'fields' => $forbidden,
                ]
            );
        }

        $unknown = array_values( array_diff( array_keys( $raw ), self::ALLOWED_FIELDS ) );
        if ( ! empty( $unknown ) ) {
            return new \WP_Error(
                'fotogenic_ai_unknown_campaign_recipe_field',
                __( 'The Campaign Set recipe contains unsupported fields.', 'fotogenic-ai' ),
                [
                    'status' => 400,
                    'fields' => self::normalize_field_names( $unknown ),
                ]
            );
        }

        $version = self::normalize_integer( $raw['version'] ?? null );
        if ( self::VERSION !== $version ) {
            return new \WP_Error(
                'fotogenic_ai_unsupported_campaign_recipe_version',
                __( 'The Campaign Set recipe version is not supported.', 'fotogenic-ai' ),
                [ 'status' => 400 ]
            );
        }

        $count = self::normalize_integer( $raw['count'] ?? null );
        if ( $count < self::MIN_OUTPUTS || $count > self::MAX_OUTPUTS ) {
            return new \WP_Error(
                'fotogenic_ai_invalid_campaign_count',
                __( 'Campaign Set count must be between 2 and 6.', 'fotogenic-ai' ),
                [ 'status' => 400 ]
            );
        }

		$quality = is_string( $raw['quality'] ?? null )
			? strtoupper( trim( $raw['quality'] ) )
			: '';
		if ( ! Workflow_Quality_Catalog::supports( 'campaign_set', $quality ) ) {
			return new \WP_Error(
				'fotogenic_ai_invalid_campaign_quality',
				__( 'Campaign Set quality must be 2K or 4K.', 'fotogenic-ai' ),
				[ 'status' => 400 ]
			);
		}

        $instructions = self::optional_string( $raw, 'customInstructions' );
        if ( is_wp_error( $instructions ) ) {
            return $instructions;
        }
        if ( self::text_length( $instructions ) > self::MAX_INSTRUCTIONS_LENGTH ) {
            return new \WP_Error(
                'fotogenic_ai_instructions_too_long',
                __( 'Custom instructions must be 5000 characters or fewer.', 'fotogenic-ai' ),
                [ 'status' => 400 ]
            );
        }
        $instructions = sanitize_textarea_field( $instructions );

        $style = self::optional_string( $raw, 'style' );
        if ( is_wp_error( $style ) ) {
            return $style;
        }
        if ( self::text_length( $style ) > self::MAX_STYLE_LENGTH ) {
            return new \WP_Error(
                'fotogenic_ai_style_too_long',
                __( 'Style must be 120 characters or fewer.', 'fotogenic-ai' ),
                [ 'status' => 400 ]
            );
        }
        $style = sanitize_text_field( $style );

        $aspect_ratio = self::optional_string( $raw, 'aspectRatio' );
        if ( is_wp_error( $aspect_ratio ) ) {
            return $aspect_ratio;
        }
        $aspect_ratio = self::normalize_aspect_ratio( $aspect_ratio );
        if ( is_wp_error( $aspect_ratio ) ) {
            return $aspect_ratio;
        }

        $subject_attachment_id = self::normalize_attachment_id( $raw, 'subjectAttachmentId' );
        if ( is_wp_error( $subject_attachment_id ) ) {
            return $subject_attachment_id;
        }
        $environment_attachment_id = self::normalize_attachment_id( $raw, 'environmentAttachmentId' );
        if ( is_wp_error( $environment_attachment_id ) ) {
            return $environment_attachment_id;
        }

        $recipe = [
			'version' => self::VERSION,
			'count'   => $count,
			'quality' => $quality,
        ];
        if ( '' !== $instructions ) {
            $recipe['customInstructions'] = $instructions;
        }
        if ( '' !== $style ) {
            $recipe['style'] = $style;
        }
        if ( '' !== $aspect_ratio ) {
            $recipe['aspectRatio'] = $aspect_ratio;
        }
        if ( $subject_attachment_id > 0 ) {
            $recipe['subjectAttachmentId'] = $subject_attachment_id;
        }
        if ( $environment_attachment_id > 0 ) {
            $recipe['environmentAttachmentId'] = $environment_attachment_id;
        }

        return $recipe;
    }

    /**
     * @param mixed $value Raw integer value.
     */
    private static function normalize_integer( $value ): int {
        return is_int( $value ) ? $value : -1;
    }

    /** @return int|\WP_Error */
    private static function normalize_attachment_id( array $raw, string $key ) {
        if ( ! array_key_exists( $key, $raw ) ) {
            return 0;
        }
        $attachment_id = self::normalize_integer( $raw[ $key ] );
        if ( $attachment_id < 0 ) {
            return new \WP_Error(
                'fotogenic_ai_invalid_campaign_reference',
                __( 'Campaign Set reference images must be valid attachment IDs.', 'fotogenic-ai' ),
                [ 'status' => 400 ]
            );
        }
        return $attachment_id;
    }

    /** @return string|\WP_Error */
    private static function normalize_aspect_ratio( string $aspect_ratio ) {
        if ( self::text_length( $aspect_ratio ) > self::MAX_ASPECT_RATIO_LENGTH ) {
            return new \WP_Error(
                'fotogenic_ai_invalid_aspect_ratio',
                __( 'Select a valid aspect ratio.', 'fotogenic-ai' ),
                [ 'status' => 400 ]
            );
        }
        $aspect_ratio = trim( sanitize_text_field( $aspect_ratio ) );
        if ( '' === $aspect_ratio || 'auto' === strtolower( $aspect_ratio ) ) {
            return '';
        }
        if ( ! preg_match( '/^[A-Za-z0-9][A-Za-z0-9:_-]{0,31}$/', $aspect_ratio ) ) {
            return new \WP_Error(
                'fotogenic_ai_invalid_aspect_ratio',
                __( 'Select a valid aspect ratio.', 'fotogenic-ai' ),
                [ 'status' => 400 ]
            );
        }
        return $aspect_ratio;
    }

    /**
     * Read an optional raw string without coercing arrays, objects, booleans, or numbers.
     *
     * @return string|\WP_Error
     */
    private static function optional_string( array $raw, string $key, string $default = '' ) {
        if ( ! array_key_exists( $key, $raw ) ) {
            return $default;
        }
        if ( ! is_string( $raw[ $key ] ) ) {
            return new \WP_Error(
                'fotogenic_ai_invalid_campaign_recipe_type',
                __( 'Campaign Set recipe values must use their documented types.', 'fotogenic-ai' ),
                [
                    'status' => 400,
                    'field'  => $key,
                ]
            );
        }
        return $raw[ $key ];
    }

    private static function normalize_field_names( array $fields ): array {
        $normalized = [];
        foreach ( $fields as $field ) {
            $name = is_string( $field ) ? sanitize_key( $field ) : 'invalid';
            $normalized[] = '' !== $name ? substr( $name, 0, 80 ) : 'invalid';
        }
        return array_values( array_unique( $normalized ) );
    }

    private static function text_length( string $value ): int {
        return function_exists( 'mb_strlen' ) ? mb_strlen( $value ) : strlen( $value );
    }
}
