<?php
/**
 * Native WordPress post and page field schema.
 *
 * @package ScheduledChanges
 */

namespace ScheduledChanges\WordPressContent;

use ScheduledChanges\Field\FieldCollection;
use ScheduledChanges\Field\FieldDataType;
use ScheduledChanges\Field\FieldDefinition;
use ScheduledChanges\Provider\ObjectContext;
use ScheduledChanges\Taxonomy\TaxonomyModule;
use ScheduledChanges\Taxonomy\TaxonomyRegistration;

/**
 * Produces deterministic field definitions for built-in WordPress content.
 */
final class NativeWordPressFieldSchema {
	private const PROVIDER_ID = 'wordpress-core';

	private WordPressMetaFieldRegistry $metadata;
	private ?TaxonomyModule $taxonomies;
	/** @var TaxonomyRegistration[] */
	private array $taxonomy_registrations;
	private bool $include_metadata;

	/**
	 * Builds the schema with its metadata allowlist.
	 *
	 * @param TaxonomyRegistration[] $taxonomy_registrations Selected taxonomy registrations.
	 */
	public function __construct(
		?WordPressMetaFieldRegistry $metadata = null,
		?TaxonomyModule $taxonomies = null,
		array $taxonomy_registrations = array(),
		bool $include_metadata = false
	) {
		$this->metadata               = $metadata ?? new WordPressMetaFieldRegistry();
		$this->taxonomies             = $taxonomies;
		$this->taxonomy_registrations = $taxonomy_registrations;
		$this->include_metadata       = $include_metadata;
	}

	/**
	 * Returns deterministic fields for one supported context.
	 */
	public function definitions( ObjectContext $context ): FieldCollection {
		$post_type = $context->targetType();
		$fields    = array(
			$this->field( 'wordpress.title', 'Title', 'The post or page title.', $post_type, FieldDataType::STRING, 'text', 'post_title' ),
			$this->field( 'wordpress.slug', 'Slug', 'The URL-safe post or page slug.', $post_type, FieldDataType::STRING, 'text', 'post_name' ),
			$this->field( 'wordpress.content', 'Content', 'Block or classic editor content.', $post_type, FieldDataType::HTML, 'rich-text', 'post_content' ),
			$this->field( 'wordpress.excerpt', 'Excerpt', 'The manual post or page excerpt.', $post_type, FieldDataType::HTML, 'rich-text', 'post_excerpt' ),
			$this->field( 'wordpress.status', 'Status', 'The WordPress publication status.', $post_type, FieldDataType::STRING, 'select', 'post_status' ),
			$this->field( 'wordpress.publish_date', 'Publish date', 'The publication date in UTC.', $post_type, FieldDataType::DATETIME, 'datetime', 'post_date_gmt' ),
			$this->field( 'wordpress.author', 'Author', 'The WordPress user ID assigned as author.', $post_type, FieldDataType::ID, 'user', 'post_author' ),
			$this->field( 'wordpress.featured_image', 'Featured image', 'The attachment ID used as the featured image.', $post_type, FieldDataType::ID, 'media', 'thumbnail_id' ),
			$this->field( 'wordpress.menu_order', 'Menu order', 'The integer menu order.', $post_type, FieldDataType::INTEGER, 'number', 'menu_order' ),
		);

		if ( 'page' === $post_type ) {
			$fields[] = $this->field( 'wordpress.parent', 'Parent page', 'The parent page ID, or zero for no parent.', $post_type, FieldDataType::ID, 'content-reference', 'post_parent' ); // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText -- Canonical machine ID.
			$fields[] = $this->field( 'wordpress.page_template', 'Page template', 'The registered theme page template, or default.', $post_type, FieldDataType::STRING, 'select', 'page_template' ); // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText -- Canonical machine ID.
		}

		foreach ( $this->selectedTaxonomies( $post_type ) as $taxonomy => $taxonomy_config ) {
			$registration = $taxonomy_config['registration'];
			$fields[]     = $this->field(
				$this->dynamicFieldId( 'wordpress.taxonomy.', $taxonomy ), // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText -- Canonical machine ID.
				$registration->label(),
				'Term IDs assigned in the registered ' . $taxonomy . ' taxonomy.',
				$post_type,
				FieldDataType::ID_LIST,
				'term-selector',
				'taxonomy',
				array(
					'taxonomy'        => $taxonomy,
					'registration_id' => $registration->id(),
					'hierarchical'    => $registration->hierarchical(),
					'term_creation'   => $registration->allowsTermCreation(),
				),
				$registration->capability(),
				array( 'ordered' => false )
			);
		}

		$metadata_fields = $this->include_metadata ? $this->metadata->allowedFor( $post_type ) : array();
		foreach ( $metadata_fields as $key => $metadata ) {
			$registration = $metadata['registration'];
			$options      = $metadata['options'];
			$single       = (bool) ( $registration['single'] ?? false );
			$type         = (string) ( $registration['type'] ?? 'string' );
			$label        = trim( (string) ( $options['label'] ?? $registration['label'] ?? '' ) );

			$fields[] = $this->field(
				$this->dynamicFieldId( 'wordpress.meta.', $key ), // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText -- Canonical machine ID.
				'' !== $label ? $label : ucwords( str_replace( array( '_', '-' ), ' ', $key ) ),
				(string) ( $options['description'] ?? $registration['description'] ?? 'Explicitly allowlisted registered WordPress metadata.' ),
				$post_type,
				$single ? $this->metadataDataType( $type ) : FieldDataType::ARRAY,
				$single ? $this->metadataUiComponent( $type ) : 'structured-data',
				'post_meta',
				array(
					'meta_key'        => $key, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Field metadata, not a query.
					'registered_type' => $type,
					'single'          => $single,
				),
				'edit_post',
				$single ? array() : array( 'ordered' => true ),
				(bool) ( $options['sensitive'] ?? false )
			);
		}

		return new FieldCollection( $fields );
	}

	/**
	 * @return array<string,array{object:\WP_Taxonomy,registration:TaxonomyRegistration}>
	 */
	private function selectedTaxonomies( string $post_type ): array {
		$taxonomies = get_object_taxonomies( $post_type, 'objects' );
		$taxonomies = is_array( $taxonomies ) ? $taxonomies : array();
		$selected   = array();

		if ( $this->taxonomies ) {
			foreach ( $this->taxonomies->forContext( self::PROVIDER_ID, $post_type, $this->taxonomy_registrations ) as $registration ) {
				$selected[ $registration->taxonomy() ] = $registration;
			}
		}

		$result = array();
		foreach ( $selected as $taxonomy => $registration ) {
			if ( isset( $taxonomies[ $taxonomy ] ) && is_object( $taxonomies[ $taxonomy ] ) ) {
				$result[ $taxonomy ] = array(
					'object'       => $taxonomies[ $taxonomy ],
					'registration' => $registration,
				);
			}
		}
		ksort( $result );

		return $result;
	}

	/**
	 * @param array<string,mixed> $rules Field-specific validation rules.
	 * @param array<string,mixed> $normalization Canonical normalization options.
	 */
	private function field(
		string $id,
		string $label,
		string $description,
		string $post_type,
		string $data_type,
		string $ui_component,
		string $storage_key,
		array $rules = array(),
		string $capability = 'edit_post',
		array $normalization = array(),
		bool $sensitive = false
	): FieldDefinition {
		$rules['storage_key'] = $storage_key;

		return new FieldDefinition(
			$id,
			$label,
			$description,
			'WordPress',
			self::PROVIDER_ID,
			array( $post_type ),
			array( $post_type ),
			$data_type,
			$ui_component,
			true,
			true,
			true,
			true,
			$sensitive,
			array(),
			'fail',
			$rules,
			$capability,
			array( 'post_update' ),
			array( 'posts', 'post_meta', 'terms', 'object_cache' ),
			$normalization,
			array(
				'group_key'      => 'wordpress-content',
				'group_label'    => 'WordPress content',
				'provider_label' => 'WordPress',
			)
		);
	}

	/**
	 * Builds a collision-resistant canonical ID for a dynamic key.
	 */
	private function dynamicFieldId( string $prefix, string $key ): string {
		$segment = sanitize_key( trim( $key, '_' ) );
		$segment = '' !== $segment ? $segment : 'field';

		return $prefix . $segment . '-' . substr( hash( 'sha256', $key ), 0, 8 );
	}

	/**
	 * Maps registered metadata types to canonical types.
	 */
	private function metadataDataType( string $type ): string {
		switch ( $type ) {
			case 'boolean':
				return FieldDataType::BOOLEAN;
			case 'integer':
				return FieldDataType::INTEGER;
			case 'number':
				return FieldDataType::DECIMAL;
			case 'array':
				return FieldDataType::ARRAY;
			case 'object':
				return FieldDataType::OBJECT;
			default:
				return FieldDataType::STRING;
		}
	}

	/**
	 * Maps registered metadata types to editor components.
	 */
	private function metadataUiComponent( string $type ): string {
		switch ( $type ) {
			case 'boolean':
				return 'toggle';
			case 'integer':
			case 'number':
				return 'number';
			case 'array':
			case 'object':
				return 'structured-data';
			default:
				return 'text';
		}
	}
}
