<?php
/**
 * Native WordPress recording diff builder.
 *
 * @package ScheduledChanges
 */

namespace ScheduledChanges\WordPressContent\Recording;

use ScheduledChanges\Domain\Recording\RecordingSession;
use ScheduledChanges\Field\FieldDefinition;
use ScheduledChanges\Field\FieldRegistry;
use ScheduledChanges\Provider\ObjectContext;
use ScheduledChanges\Support\Json;
use ScheduledChanges\Support\ValueRedactor;

/**
 * Compares a native source baseline with its isolated editable copy.
 */
final class NativeWordPressRecordingDiffBuilder {
	private FieldRegistry $fields;

	/**
	 * Builds the diff service from canonical field discovery.
	 */
	public function __construct( FieldRegistry $fields ) {
		$this->fields = $fields;
	}

	/**
	 * @param array<string,mixed> $workspace_snapshot Latest workspace snapshot.
	 * @return array<string,mixed>
	 */
	public function build( RecordingSession $session, array $workspace_snapshot ): array {
		$baseline_object  = $this->firstObject( $session->baselineSnapshot() );
		$workspace_object = $this->firstObject( $workspace_snapshot );
		$source_type      = $session->sourceType();
		$context          = new ObjectContext( $source_type, $session->sourceId(), $source_type );
		$catalog          = $this->fields->discover( $context )->fields();
		$before_values    = $this->providerValues( $baseline_object );
		$after_values     = $this->providerValues( $workspace_object );
		$field_ids        = array_values( array_unique( array_merge( array_keys( $before_values ), array_keys( $after_values ) ) ) );
		$changes          = array();
		sort( $field_ids );

		foreach ( $field_ids as $field_id ) {
			$before = $before_values[ $field_id ] ?? null;
			$after  = $after_values[ $field_id ] ?? null;

			if ( $before === $after ) {
				continue;
			}

			$field      = $catalog->get( $field_id );
			$selectable = null !== $field && $field->writable() && $field->schedulable();
			$sensitive  = null !== $field && $field->sensitive();
			$display    = null === $field ? array() : $field->display();
			$change_id  = hash(
				'sha256',
				Json::canonicalEncode(
					array(
						$session->sourceReference(),
						'wordpress-core',
						$field_id,
						$before,
						$after,
					)
				)
			);

			$changes[] = array(
				'change_id'           => $change_id,
				'group'               => $this->groupLabel( $field ),
				'group_key'           => (string) ( $display['group_key'] ?? '' ),
				'provider_label'      => (string) ( $display['provider_label'] ?? 'WordPress' ),
				'help_text'           => (string) ( $display['help_text'] ?? '' ),
				'warning_text'        => (string) ( $display['warning_text'] ?? '' ),
				'sort_order'          => (int) ( $display['sort_order'] ?? 0 ),
				'source_reference'    => $session->sourceReference(),
				'workspace_reference' => (string) ( $workspace_object['target_reference'] ?? $workspace_object['reference'] ?? '' ),
				'target_id'           => $session->sourceId(),
				'target_type'         => $source_type,
				'subtype'             => $source_type,
				'field_id'            => $field_id,
				'field_label'         => null === $field ? $field_id : $field->label(),
				'before'              => $before,
				'after'               => $after,
				'display_before'      => ValueRedactor::redact( $sensitive, $before ),
				'display_after'       => ValueRedactor::redact( $sensitive, $after ),
				'provider_id'         => 'wordpress-core',
				'sensitive'           => $sensitive,
				'confidence'          => null === $field ? 'unknown' : 'high',
				'selectable'          => $selectable,
				'reversible'          => null !== $field && $field->reversible(),
				'validation_state'    => $selectable ? 'pending' : 'read_only',
				'conflict_policy'     => null === $field ? 'fail' : $field->conflictStrategy(),
				'dependency_summary'  => array(),
			);
		}

		usort(
			$changes,
			static fn ( array $left, array $right ): int => strcmp( (string) $left['change_id'], (string) $right['change_id'] )
		);

		return array(
			'captured_at_utc'     => gmdate( 'c' ),
			'changes'             => $changes,
			'warnings'            => $this->warnings( $baseline_object, $workspace_object ),
			'selected_change_ids' => array_values(
				array_map(
					static fn ( array $change ): string => (string) $change['change_id'],
					array_filter( $changes, static fn ( array $change ): bool => true === $change['selectable'] )
				)
			),
		);
	}

	/**
	 * @param array<string,mixed> $snapshot Snapshot document.
	 * @return array<string,mixed>
	 */
	private function firstObject( array $snapshot ): array {
		$objects = array_values( array_filter( $snapshot['objects'] ?? array(), 'is_array' ) );

		if ( ! isset( $objects[0] ) ) {
			throw new \RuntimeException( 'Native recording snapshot has no object state.' );
		}

		return $objects[0];
	}

	/**
	 * @param array<string,mixed> $snapshot_object Snapshot object.
	 * @return array<string,mixed>
	 */
	private function providerValues( array $snapshot_object ): array {
		$provider = is_array( $snapshot_object['providers']['wordpress-core'] ?? null )
			? $snapshot_object['providers']['wordpress-core']
			: array();

		return is_array( $provider['values'] ?? null ) ? $provider['values'] : array();
	}

	/**
	 * @param array<string,mixed> $baseline Baseline object.
	 * @param array<string,mixed> $workspace Workspace object.
	 * @return array<int,array<string,mixed>>
	 */
	private function warnings( array $baseline, array $workspace ): array {
		$warnings = array();

		foreach ( array_merge( $baseline['warnings'] ?? array(), $workspace['warnings'] ?? array() ) as $warning ) {
			if ( is_array( $warning ) ) {
				$warning['blocking'] = true;
				$warnings[]          = $warning;
			}
		}

		$unsupported = is_array( $workspace['unsupported_metadata'] ?? null )
			? $workspace['unsupported_metadata']
			: array();

		foreach ( array_keys( $unsupported ) as $key ) {
			$warnings[] = array(
				'code'         => 'unsupported_metadata_changed',
				'blocking'     => false,
				'metadata_key' => (string) $key,
				'message'      => 'Unsupported workspace metadata will not be included in the launch.',
			);
		}

		return $warnings;
	}

	/**
	 * Returns the provider-owned display group.
	 */
	private function groupLabel( ?FieldDefinition $field ): string {
		if ( null === $field ) {
			return 'WordPress content';
		}

		$display = $field->display();

		return '' !== trim( (string) ( $display['group_label'] ?? '' ) )
			? (string) $display['group_label']
			: 'WordPress content';
	}
}
