<?php

declare(strict_types=1);

/**
 * Recipe Schema Service
 *
 * Single source of truth for building and rendering Recipe JSON-LD. Mirrors
 * the FaqSchemaService / HowToSchemaService pattern. Required fields are
 * `name`, `description`, at least one `recipeIngredient`, and at least one
 * `recipeInstructions` step. Optional fields are emitted only when present
 * so the output never contains empty sub-objects Google would reject.
 *
 * @package ProRank\SEO\Core\Services
 * @since 1.3.1
 */

namespace ProRank\SEO\Core\Services;

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


class RecipeSchemaService {

    /**
     * Request-level flag — true once any Recipe schema has been emitted on this request.
     *
     * @var bool
     */
    private static bool $emitted = false;

    /**
     * Build a Recipe JSON-LD array.
     *
     * @param array $config Config with keys:
     *     - name              (string, required)
     *     - description       (string, required)
     *     - recipeIngredient  (array<string>, required, ≥1 non-empty)
     *     - recipeInstructions(array<string|array{text:string}>, required, ≥1 non-empty)
     *     - image             (string|array, optional)
     *     - prepTime          (string ISO 8601, optional)
     *     - cookTime          (string ISO 8601, optional)
     *     - totalTime         (string ISO 8601, optional)
     *     - recipeYield       (string, optional)
     *     - recipeCategory    (string, optional)
     *     - recipeCuisine     (string, optional)
     *     - keywords          (string|array<string>, optional)
     *     - nutrition         (array, optional — passes through allowed keys only)
     * @return array|null Recipe schema, or null when invalid.
     */
    public static function build_schema( array $config ): ?array {
        $name = trim( (string) ( $config['name'] ?? '' ) );
        $description = trim( (string) ( $config['description'] ?? '' ) );
        if ( $name === '' || $description === '' ) {
            return null;
        }

        $ingredients = self::normalize_string_list( $config['recipeIngredient'] ?? [] );
        if ( empty( $ingredients ) ) {
            return null;
        }

        $instructions = self::normalize_instructions( $config['recipeInstructions'] ?? [] );
        if ( empty( $instructions ) ) {
            return null;
        }

        $schema = [
            '@context'           => 'https://schema.org',
            '@type'              => 'Recipe',
            'name'               => wp_strip_all_tags( $name ),
            'description'        => wp_strip_all_tags( $description ),
            'recipeIngredient'   => $ingredients,
            'recipeInstructions' => $instructions,
        ];

        $image = self::normalize_image( $config['image'] ?? null );
        if ( $image !== null ) {
            $schema['image'] = $image;
        }

        foreach ( [ 'prepTime', 'cookTime', 'totalTime' ] as $time_key ) {
            $value = trim( (string) ( $config[ $time_key ] ?? '' ) );
            if ( $value !== '' ) {
                $schema[ $time_key ] = $value;
            }
        }

        foreach ( [ 'recipeYield', 'recipeCategory', 'recipeCuisine' ] as $text_key ) {
            $value = trim( (string) ( $config[ $text_key ] ?? '' ) );
            if ( $value !== '' ) {
                $schema[ $text_key ] = wp_strip_all_tags( $value );
            }
        }

        $keywords = self::normalize_keyword_list( $config['keywords'] ?? '' );
        if ( $keywords !== '' ) {
            $schema['keywords'] = $keywords;
        }

        $nutrition = self::normalize_nutrition( $config['nutrition'] ?? null );
        if ( $nutrition !== null ) {
            $schema['nutrition'] = $nutrition;
        }

        return $schema;
    }

    /**
     * Render the <script type="application/ld+json"> tag, or '' when invalid.
     */
    public static function render_script( array $config, string $class_attr = '' ): string {
        $schema = self::build_schema( $config );

        if ( $schema === null ) {
            return '';
        }

        $class = $class_attr !== ''
            ? ' class="' . esc_attr( $class_attr ) . '"'
            : '';

        return '<script type="application/ld+json"' . $class . '>'
            . wp_json_encode( $schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE )
            . '</script>';
    }

    public static function mark_emitted(): void {
        self::$emitted = true;
    }

    public static function was_emitted(): bool {
        return self::$emitted;
    }

    public static function reset_for_tests(): void {
        self::$emitted = false;
    }

    /**
     * Normalize an arbitrary list-shaped value to an array of non-empty strings.
     *
     * @param mixed $value
     * @return array<string>
     */
    private static function normalize_string_list( $value ): array {
        if ( is_string( $value ) ) {
            $value = preg_split( '/\r\n|\r|\n/', $value ) ?: [];
        }
        if ( ! is_array( $value ) ) {
            return [];
        }
        $out = [];
        foreach ( $value as $item ) {
            if ( is_array( $item ) ) {
                $item = $item['text'] ?? $item['name'] ?? '';
            }
            $clean = trim( wp_strip_all_tags( (string) $item ) );
            if ( $clean !== '' ) {
                $out[] = $clean;
            }
        }
        return $out;
    }

    /**
     * Normalize recipeInstructions into an array of HowToStep objects so the
     * shape matches Google's preferred Recipe markup.
     */
    private static function normalize_instructions( $value ): array {
        if ( is_string( $value ) ) {
            $value = preg_split( '/\r\n|\r|\n/', $value ) ?: [];
        }
        if ( ! is_array( $value ) ) {
            return [];
        }
        $steps = [];
        foreach ( $value as $entry ) {
            $text = '';
            $name = '';
            if ( is_array( $entry ) ) {
                $text = trim( (string) ( $entry['text'] ?? $entry['description'] ?? '' ) );
                $name = trim( (string) ( $entry['name'] ?? $entry['title'] ?? '' ) );
            } else {
                $text = trim( (string) $entry );
            }
            $text = wp_strip_all_tags( $text );
            $name = wp_strip_all_tags( $name );
            if ( $text === '' ) {
                continue;
            }
            $step = [
                '@type' => 'HowToStep',
                'text'  => $text,
            ];
            if ( $name !== '' ) {
                $step['name'] = $name;
            }
            $steps[] = $step;
        }
        return $steps;
    }

    private static function normalize_image( $value ): ?array {
        if ( is_string( $value ) && trim( $value ) !== '' ) {
            return [
                '@type' => 'ImageObject',
                'url'   => esc_url_raw( trim( $value ) ),
            ];
        }
        if ( is_array( $value ) && ! empty( $value['url'] ) ) {
            $out = [
                '@type' => 'ImageObject',
                'url'   => esc_url_raw( (string) $value['url'] ),
            ];
            if ( ! empty( $value['width'] ) ) {
                $out['width'] = (int) $value['width'];
            }
            if ( ! empty( $value['height'] ) ) {
                $out['height'] = (int) $value['height'];
            }
            return $out;
        }
        return null;
    }

    /**
     * Reduce comma-/newline-separated keywords to a single CSV string per
     * schema.org guidance ("a comma-separated list").
     */
    private static function normalize_keyword_list( $value ): string {
        if ( is_array( $value ) ) {
            $items = $value;
        } else {
            $items = preg_split( '/[,\r\n]+/', (string) $value ) ?: [];
        }
        $clean = [];
        foreach ( $items as $item ) {
            $t = trim( wp_strip_all_tags( (string) $item ) );
            if ( $t !== '' ) {
                $clean[] = $t;
            }
        }
        return implode( ', ', $clean );
    }

    /**
     * Pass through only the known NutritionInformation keys with non-empty
     * values. Returns null when nothing useful remains.
     */
    private static function normalize_nutrition( $value ): ?array {
        if ( ! is_array( $value ) ) {
            return null;
        }
        $allowed = [
            'calories',
            'carbohydrateContent',
            'cholesterolContent',
            'fatContent',
            'fiberContent',
            'proteinContent',
            'saturatedFatContent',
            'sodiumContent',
            'sugarContent',
            'transFatContent',
            'unsaturatedFatContent',
            'servingSize',
        ];
        $out = [];
        foreach ( $allowed as $key ) {
            $v = isset( $value[ $key ] ) ? trim( (string) $value[ $key ] ) : '';
            if ( $v !== '' ) {
                $out[ $key ] = wp_strip_all_tags( $v );
            }
        }
        if ( empty( $out ) ) {
            return null;
        }
        $out['@type'] = 'NutritionInformation';
        return $out;
    }
}
