<?php
/**
 * Recipes — loads play templates from locale-aware JSON files.
 *
 * Files live in: config/recipes/{locale}/*.json
 * Falls back to en_US when locale-specific files are not found.
 *
 * @package CartCue
 */

namespace CartCue;

defined( 'ABSPATH' ) || exit;

final class Recipes {

	private const DEFAULT_LOCALE = 'en_US';

	/**
	 * Return all built-in recipes, sorted by filename (natural sort).
	 *
	 * Third-party plugins can add recipe directories via the filter:
	 *   cartcue_recipe_directories  (array of absolute directory paths)
	 *
	 * @return array<string, array>  Keyed by recipe 'key' field.
	 */
	public static function all(): array {
		static $memo = null;
		if ( null !== $memo ) {
			return $memo;
		}

		$cache_key = 'cartcue_recipes_' . get_locale();
		$cached    = wp_cache_get( $cache_key, 'cartcue' );
		if ( false !== $cached && is_array( $cached ) ) {
			return $memo = $cached;
		}

		$recipes = self::load_from_disk();
		wp_cache_set( $cache_key, $recipes, 'cartcue', HOUR_IN_SECONDS );

		return $memo = $recipes;
	}

	/**
	 * @return array<string, array>
	 */
	private static function load_from_disk(): array {
		$dirs    = self::get_dirs();
		$recipes = [];

		foreach ( $dirs as $dir ) {
			$files = (array) @scandir( $dir );
			natsort( $files );

			foreach ( $files as $file ) {
				if ( ! str_ends_with( $file, '.json' ) ) {
					continue;
				}
				$path = $dir . '/' . $file;
				$raw  = file_get_contents( $path );
				if ( false === $raw ) {
					continue;
				}
				if ( str_starts_with( $raw, "\xEF\xBB\xBF" ) ) {
					$raw = substr( $raw, 3 );
				}
				$decoded = json_decode( $raw, true );
				if ( is_array( $decoded ) && ! empty( $decoded['key'] ) ) {
					$recipes[ $decoded['key'] ] = RecipeNormalizer::normalize( $decoded );
				}
			}
		}

		return $recipes;
	}

	/**
	 * Retrieve a single recipe by key, or null if not found.
	 *
	 * @param string $key
	 * @return array|null
	 */
	public static function get( string $key ): ?array {
		return self::all()[ $key ] ?? null;
	}

	/**
	 * Hydrate a recipe for the play editor (normalized, editor-ready).
	 *
	 * @param string $key Recipe key from JSON `key` field.
	 * @return array<string, mixed>|null
	 */
	public static function hydrate_for_editor( string $key ): ?array {
		$recipe = self::get( $key );
		if ( ! $recipe ) {
			return null;
		}

		/**
		 * Filter hydrated recipe before it is passed to the play editor.
		 *
		 * @param array<string, mixed> $recipe Normalized recipe array.
		 * @param string               $key    Recipe key.
		 */
		return apply_filters( 'cartcue_recipe_hydrated', $recipe, $key );
	}

	// -------------------------------------------------------------------------
	// Internal
	// -------------------------------------------------------------------------

	/**
	 * Resolve which recipe directories to scan.
	 *
	 * Tries the current WP locale first, falls back to en_US.
	 *
	 * @return string[]  Absolute directory paths (may be multiple).
	 */
	private static function get_dirs(): array {
		$base   = CARTCUE_PLUGIN_DIR . 'config/recipes/';
		$locale = (string) get_locale();

		// Normalize common variants:
		// - "en-US" -> "en_US"
		// - "en_US.UTF-8" -> "en_US"
		$locale = explode( '.', $locale )[0];
		$locale = str_replace( '-', '_', $locale );

		$candidates = [
			$base . $locale,
			$base . self::DEFAULT_LOCALE,
			$base . str_replace( '_', '-', self::DEFAULT_LOCALE ),
		];

		$dirs = [];
		foreach ( $candidates as $dir ) {
			if ( is_dir( $dir ) ) {
				$dirs[] = $dir;
			}
		}

		$dirs = (array) apply_filters( 'cartcue_recipe_directories', $dirs );
		$dirs = array_values( array_unique( $dirs ) );

		// Safety net: never end up with 0 dirs when the default exists.
		if ( empty( $dirs ) && is_dir( $base . self::DEFAULT_LOCALE ) ) {
			$dirs = [ $base . self::DEFAULT_LOCALE ];
		}

		return $dirs;
	}
}
