<?php
/**
 * Schema Utils
 *
 * Utility functions for schema types: registration, default schemas,
 * field parsing, and unique ID generation.
 *
 * @package SEOvaultSEO\Schema
 */

namespace SEOvaultSEO\Schema;

use SEOvaultSEO\Schema\Types\WebSite;
use SEOvaultSEO\Schema\Types\WebPage;
use SEOvaultSEO\Schema\Types\Organization;
use SEOvaultSEO\Schema\Types\Article;
use SEOvaultSEO\Schema\Types\BreadcrumbList;
use SEOvaultSEO\Schema\Types\SearchAction;
use SEOvaultSEO\Schema\Types\Person;
use SEOvaultSEO\Schema\Types\Product;
use SEOvaultSEO\Schema\Types\FAQ;
use SEOvaultSEO\Schema\Types\HowTo;
use SEOvaultSEO\Schema\Types\Review;
use SEOvaultSEO\Schema\Types\Recipe;
use SEOvaultSEO\Schema\Types\Event;
use SEOvaultSEO\Schema\Types\Course;
use SEOvaultSEO\Schema\Types\SoftwareApplication;
use SEOvaultSEO\Schema\Types\JobPosting;

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

/**
 * Shared schema catalogue, activation, and field helpers.
 */
class SchemaUtils {

	/**
	 * Get schema types enabled for zero-configuration output.
	 *
	 * Content-specific rich-result types remain available in the catalog, but
	 * must be explicitly enabled by the site owner or a template/integration.
	 *
	 * Compatibility adapter: values come from SchemaTypePolicyRegistry built-ins
	 * (`default_enabled`), then the legacy filter is applied unchanged.
	 *
	 * @return string[]
	 */
	public static function get_default_enabled_types() {
		return apply_filters(
			'seovaultseo_schema_default_enabled_types',
			SchemaTypePolicyRegistry::get_default_enabled_types()
		);
	}

	/**
	 * Whether a type is enabled by default.
	 *
	 * @param string $type Schema type.
	 * @return bool
	 */
	public static function is_type_enabled_by_default( $type ) {
		return in_array( (string) $type, self::get_default_enabled_types(), true );
	}

	/**
	 * Resolve an entry's effective enabled state.
	 *
	 * @param array $schema Schema configuration.
	 * @return bool
	 */
	public static function resolve_schema_enabled( $schema ) {
		if ( ! is_array( $schema ) ) {
			return false;
		}
		if ( array_key_exists( 'enabled', $schema ) ) {
			return (bool) $schema['enabled'];
		}
		return self::is_type_enabled_by_default( $schema['type'] ?? 'Thing' );
	}

	/**
	 * Get default schema type class mappings.
	 *
	 * @return array
	 */
	public static function get_default_schema_types() {
		return array(
			'WebSite'             => WebSite::class,
			'WebPage'             => WebPage::class,
			'Organization'        => Organization::class,
			'BreadcrumbList'      => BreadcrumbList::class,
			'Article'             => Article::class,
			'SearchAction'        => SearchAction::class,
			'Person'              => Person::class,
			'Product'             => Product::class,
			'FAQPage'             => FAQ::class,
			'HowTo'               => HowTo::class,
			'Review'              => Review::class,
			'Recipe'              => Recipe::class,
			'Event'               => Event::class,
			'Course'              => Course::class,
			'SoftwareApplication' => SoftwareApplication::class,
			'JobPosting'          => JobPosting::class,
		);
	}

	/**
	 * Get all schema types (filterable for extensions).
	 *
	 * @return array
	 */
	public static function get_schema_types() {
		return apply_filters( 'seovaultseo_schema_types', self::get_default_schema_types() );
	}

	/**
	 * Build default schemas with unique IDs.
	 *
	 * Guarded against re-entrancy: filters fired while building defaults may
	 * call Options/helpers that cold-load schemas again. Nested callers get an
	 * empty set; the outer call still returns the complete filtered list.
	 *
	 * @return array
	 */
	public static function get_default_schemas() {
		static $building = false;

		if ( $building ) {
			return array();
		}

		$building = true;
		try {
			$schemas = apply_filters( 'seovaultseo_default_schemas', self::build_schemas_from_types( self::get_default_schema_types() ) );
			return self::build_final_schemas( $schemas );
		} finally {
			$building = false;
		}
	}

	/**
	 * Build schemas array from type class mappings.
	 *
	 * @param array $schema_types Type => class.
	 * @return array
	 */
	public static function build_schemas_from_types( $schema_types ) {
		$schemas = array();
		foreach ( $schema_types as $key => $class ) {
			if ( class_exists( $class ) ) {
				$instance          = new $class();
				$schema            = $instance->schema_data();
				$type              = $schema['type'] ?? $key;
				$schema['enabled'] = self::is_type_enabled_by_default( $type );
				$schemas[]         = $schema;
			}
		}
		return $schemas;
	}

	/**
	 * Add newly registered types to a saved configuration without duplicating
	 * entries whose generated UUIDs differ.
	 *
	 * @param mixed $saved    Saved schema configuration.
	 * @param mixed $defaults Current factory schemas.
	 * @return array
	 */
	public static function merge_with_defaults( $saved, $defaults = array() ) {
		$saved    = is_array( $saved ) ? $saved : array();
		$defaults = ! empty( $defaults ) ? $defaults : self::get_default_schemas();

		$existing_types = array();
		foreach ( $saved as $schema ) {
			if ( is_array( $schema ) && ! empty( $schema['type'] ) ) {
				$existing_types[] = (string) $schema['type'];
			}
		}

		foreach ( $defaults as $id => $schema ) {
			$type = is_array( $schema ) ? (string) ( $schema['type'] ?? '' ) : '';
			if ( '' === $type || in_array( $type, $existing_types, true ) ) {
				continue;
			}
			$saved[ $id ]     = $schema;
			$existing_types[] = $type;
		}

		return $saved;
	}

	/**
	 * Assign unique IDs to each schema.
	 *
	 * @param array $schemas Schema data.
	 * @return array
	 */
	public static function build_final_schemas( $schemas ) {
		$result = array();
		foreach ( $schemas as $key => $schema ) {
			$unique_id            = wp_generate_uuid4();
			$result[ $unique_id ] = $schema;
		}
		return $result;
	}

	/**
	 * Get schema type data (field definitions for each type).
	 *
	 * @return array
	 */
	public static function get_schema_type_data() {
		$schema_data = array();
		foreach ( self::get_default_schema_types() as $key => $class ) {
			if ( class_exists( $class ) ) {
				$instance            = new $class();
				$schema_data[ $key ] = $instance->get();
			}
		}
		return apply_filters( 'seovaultseo_schema_type_data', $schema_data );
	}

	/**
	 * Parse schema fields into a flat key => value array.
	 *
	 * @param array $fields Field definitions.
	 * @return array
	 */
	public static function parse_fields( array $fields ) {
		$parsed = array();
		foreach ( $fields as $field ) {
			if ( empty( $field['id'] ) || ( empty( $field['required'] ) && empty( $field['show'] ) ) ) {
				continue;
			}

			$type  = $field['type'] ?? 'Text';
			$value = $field['std'] ?? '';

			if ( ! empty( $value ) ) {
				$parsed[ $field['id'] ] = $value;
			}
		}
		return $parsed;
	}

	/**
	 * Build a flat field-id => type map for a schema type (top-level fields only).
	 *
	 * @param string $schema_type Schema type name.
	 * @return array<string, string>
	 */
	public static function get_field_type_map( $schema_type ) {
		$type_data = self::get_schema_type_data();
		$fields    = $type_data[ $schema_type ] ?? array();

		if ( ! is_array( $fields ) ) {
			return array();
		}

		$map = array();
		foreach ( $fields as $field ) {
			if ( empty( $field['id'] ) ) {
				continue;
			}
			$map[ $field['id'] ] = $field['type'] ?? 'Text';
		}

		return $map;
	}
}
