<?php
/**
 * Native WordPress recording snapshot capture.
 *
 * @package ScheduledChanges
 */

namespace ScheduledChanges\WordPressContent\Recording;

use ScheduledChanges\Provider\ObjectContext;
use ScheduledChanges\Provider\ProviderRegistry;
use ScheduledChanges\Recording\RecordingWorkspaceMetadata;
use ScheduledChanges\Support\Json;
use ScheduledChanges\WordPressContent\WordPressMetaFieldRegistry;

/**
 * Captures the certified native provider state for one post or page.
 */
final class NativeWordPressRecordingSnapshotter {
	private ProviderRegistry $providers;
	private WordPressMetaFieldRegistry $metadata;

	/**
	 * Builds the snapshotter from the provider and metadata registries.
	 */
	public function __construct( ProviderRegistry $providers, ?WordPressMetaFieldRegistry $metadata = null ) {
		$this->providers = $providers;
		$this->metadata  = $metadata ?? new WordPressMetaFieldRegistry();
	}

	/**
	 * @return array<string,mixed>
	 */
	public function capture( int $post_id, ?string $reference = null, bool $workspace = false ): array {
		$post = get_post( $post_id );

		if ( ! $post instanceof \WP_Post || ! in_array( $post->post_type, array( 'post', 'page' ), true ) ) {
			throw new \RuntimeException( 'Native recording snapshots require an existing post or page.' );
		}

		$provider = $this->providers->get( 'wordpress-core' );

		if ( null === $provider ) {
			throw new \RuntimeException( 'The native WordPress provider is unavailable.' );
		}

		$context   = new ObjectContext( $post->post_type, $post_id, $post->post_type );
		$providers = array();
		$warnings  = array();

		try {
			$snapshot = $provider->snapshot( $context );
			$values   = $snapshot->values();

			if ( $workspace ) {
				$desired_status = sanitize_key( (string) get_post_meta( $post_id, RecordingWorkspaceMetadata::DESIRED_STATUS, true ) );

				if ( in_array( $desired_status, array( 'draft', 'pending', 'private', 'publish', 'future' ), true ) ) {
					$values['wordpress.status'] = $desired_status; // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText -- Canonical field ID.
				}
			}

			$providers['wordpress-core'] = array(
				'provider_id'      => $snapshot->providerId(),
				'provider_version' => $snapshot->providerVersion(),
				'values'           => $values,
				'checksum'         => hash( 'sha256', Json::canonicalEncode( $values ) ),
			);
		} catch ( \Throwable $throwable ) {
			$warnings[] = array(
				'provider_id' => 'wordpress-core',
				'code'        => 'snapshot_failed',
				'message'     => $throwable->getMessage(),
			);
		}

		$object                = array(
			'target_reference'     => $reference ?? $post->post_type . ':' . $post_id,
			'target_id'            => $post_id,
			'target_type'          => $post->post_type,
			'subtype'              => $post->post_type,
			'providers'            => $providers,
			'warnings'             => $warnings,
			'unsupported_metadata' => $this->unsupportedMetadata( $post ),
		);
		$object['fingerprint'] = hash( 'sha256', Json::canonicalEncode( $object['providers'] ) );

		return array(
			'captured_at_utc' => gmdate( 'c' ),
			'objects'         => array( $object ),
		);
	}

	/**
	 * @return array<string,string>
	 */
	private function unsupportedMetadata( \WP_Post $post ): array {
		$known       = array_fill_keys(
			array_merge(
				array(
					'_thumbnail_id',
					'_wp_page_template',
					RecordingWorkspaceMetadata::NATIVE_MARKER,
					RecordingWorkspaceMetadata::SESSION_UUID,
					RecordingWorkspaceMetadata::SOURCE_ID,
					RecordingWorkspaceMetadata::OWNER_ID,
					RecordingWorkspaceMetadata::DESIRED_STATUS,
				),
				array_keys( $this->metadata->allowedFor( $post->post_type ) )
			),
			true
		);
		$unsupported = array();

		foreach ( get_post_meta( $post->ID ) as $key => $values ) {
			if (
				isset( $known[ $key ] )
				|| 0 === strpos( $key, '_edit_' )
				|| 0 === strpos( $key, '_wp_old_' )
				|| 0 === strpos( $key, '_wp_trash_' )
			) {
				continue;
			}

			$unsupported[ $key ] = hash( 'sha256', Json::canonicalEncode( $values ) );
		}

		ksort( $unsupported );

		return $unsupported;
	}
}
