<?php
/**
 * WooCommerce recording workspace adapter.
 *
 * @package ScheduledChanges
 */

namespace ScheduledChangesWooCommerce\CoreCompat\Recording;

// phpcs:disable WordPress.DB -- Recording shadow cleanup uses scheduler-owned shadow metadata.
// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are escaped at UI boundaries.

use ScheduledChanges\Domain\Recording\RecordingSession;
use ScheduledChanges\Domain\Shared\Uuid;
use ScheduledChanges\Recording\RecordingCloneContext;
use ScheduledChanges\Recording\RecordingWorkspaceAdapter;
use ScheduledChanges\Recording\RecordingWorkspaceRequest;
use ScheduledChanges\Recording\RecordingWorkspaceResult;

/**
 * Adapts the existing shadow-product implementation to the neutral workspace contract.
 */
final class WooCommerceRecordingWorkspaceAdapter implements RecordingWorkspaceAdapter {
	private ShadowProductCloner $cloner;
	private RecordingSnapshotter $snapshotter;
	private RecordingDiffBuilder $diffs;

	public function __construct( ShadowProductCloner $cloner, RecordingSnapshotter $snapshotter, RecordingDiffBuilder $diffs ) {
		$this->cloner      = $cloner;
		$this->snapshotter = $snapshotter;
		$this->diffs       = $diffs;
	}

	public function sourceType(): string {
		return 'product';
	}

	public function workspaceType(): string {
		return 'shadow_product';
	}

	public function assertCanStart( int $source_id ): void {
		$source = wc_get_product( $source_id );

		if ( ! $source instanceof \WC_Product || $source instanceof \WC_Product_Variation ) {
			throw new \RuntimeException( 'Record Changes requires an existing source product.' );
		}

		if ( ! current_user_can( 'scheduled_changes_create_schedules' ) || ! current_user_can( 'edit_post', $source_id ) ) {
			throw new \RuntimeException( 'Current user cannot start a Scheduled Changes recording session.' );
		}
	}

	public function assertCanUse( RecordingSession $session ): void {
		if ( ! current_user_can( 'edit_post', $session->sourceProductId() ) ) {
			throw new \RuntimeException( 'Current user cannot use this recording session.' );
		}
	}

	public function captureSourceBaseline( int $source_id ): array {
		return $this->snapshotter->captureProduct( $source_id );
	}

	public function create( RecordingWorkspaceRequest $request ): RecordingWorkspaceResult {
		$clone = $this->cloner->clone(
			$request->sourceId(),
			$request->sessionUuid(),
			$request->ownerId()
		);

		$provider_map = array_merge(
			$clone->providerMap(),
			array(
				'workspace_provider_id' => 'woocommerce-core',
				'workspace_type'        => $this->workspaceType(),
				'workspace_reference'   => 'shadow_product:' . $clone->shadowProductId(),
			)
		);

		return new RecordingWorkspaceResult(
			$clone->shadowProductId(),
			$clone->variationMap(),
			$provider_map
		);
	}

	public function captureWorkspace( RecordingSession $session ): array {
		if ( null === $session->shadowProductId() ) {
			throw new \RuntimeException( 'Recording session has no workspace.' );
		}

		return $this->snapshotter->captureProduct( (int) $session->shadowProductId() );
	}

	public function captureSource( RecordingSession $session ): array {
		return $this->snapshotter->captureProduct( $session->sourceProductId() );
	}

	public function buildDiff( RecordingSession $session, array $workspace_snapshot ): array {
		return $this->diffs->build( $session, $workspace_snapshot );
	}

	public function cleanup( RecordingSession $session, string $reason ): void {
		if ( null === $session->shadowProductId() ) {
			return;
		}

		$this->cleanupAddOnCloneData( $session, $reason );
		$this->deleteShadow( $session->shadowProductId() );
	}

	public function cleanupCreated( RecordingWorkspaceRequest $request, RecordingWorkspaceResult $workspace, string $reason ): void {
		$provider_map    = $workspace->providerMap();
		$placeholder_map = is_array( $provider_map['unique_placeholders'] ?? null )
			? $provider_map['unique_placeholders']
			: array();

		$result = $this->cloner->cleanupAddOnCloneData(
			new RecordingCloneContext(
				$request->sessionUuid(),
				$request->sourceId(),
				$workspace->workspaceId(),
				$request->ownerId(),
				$workspace->relationMap(),
				'cleanup',
				(string) ( $provider_map['product_type'] ?? 'unknown' ),
				$placeholder_map,
				'delete_shadow'
			),
			$reason
		);

		if ( $result->hasFailures() ) {
			throw new \RuntimeException(
				'Recording clone cleanup failed: ' . $result->failureSummary()
			);
		}

		$this->deleteShadow( $workspace->workspaceId() );
	}

	public function lockTarget( int $source_id ): string {
		return 'recording:product:' . $source_id;
	}

	public function defaultLaunchName( RecordingSession $session ): string {
		return 'Recorded changes for product ' . $session->sourceProductId();
	}

	public function context( Uuid $uuid, int $source_id, ?int $workspace_id, int $owner_id, string $phase ): array {
		return array(
			'session_uuid'          => $uuid->value(),
			'source_provider_id'    => 'woocommerce-core',
			'source_type'           => $this->sourceType(),
			'source_reference'      => 'product:' . $source_id,
			'source_id'             => $source_id,
			'workspace_provider_id' => null === $workspace_id ? null : 'woocommerce-core',
			'workspace_type'        => $this->workspaceType(),
			'workspace_reference'   => null === $workspace_id ? null : 'shadow_product:' . $workspace_id,
			'workspace_id'          => $workspace_id,
			'source_product_id'     => $source_id,
			'shadow_product_id'     => $workspace_id,
			'owner_id'              => $owner_id,
			'phase'                 => $phase,
		);
	}

	private function cleanupAddOnCloneData( RecordingSession $session, string $reason ): void {
		$provider_map    = $session->providerMap();
		$placeholder_map = is_array( $provider_map['unique_placeholders'] ?? null )
			? $provider_map['unique_placeholders']
			: array();

		$result = $this->cloner->cleanupAddOnCloneData(
			new RecordingCloneContext(
				$session->uuid(),
				$session->sourceProductId(),
				(int) $session->shadowProductId(),
				$session->ownerId(),
				$session->variationMap(),
				'cleanup',
				(string) ( $provider_map['product_type'] ?? 'unknown' ),
				$placeholder_map,
				'delete_shadow'
			),
			$reason
		);

		if ( ! $result->hasFailures() ) {
			return;
		}

		throw new \RuntimeException(
			'Recording clone cleanup failed: ' . $result->failureSummary()
		);
	}

	private function deleteShadow( int $shadow_product_id ): void {
		global $wpdb;

		$shadow_variation_ids = $wpdb->get_col(
			$wpdb->prepare(
				"SELECT p.ID
				FROM {$wpdb->posts} p
				INNER JOIN {$wpdb->postmeta} m
					ON p.ID = m.post_id
					AND m.meta_key = %s
					AND m.meta_value = %s
				WHERE p.post_parent = %d
					AND p.post_type = %s",
				ShadowProductAccessGuard::SHADOW_META_KEY,
				'1',
				$shadow_product_id,
				'product_variation'
			)
		);

		foreach ( $shadow_variation_ids as $shadow_variation_id ) {
			$variation = wc_get_product( (int) $shadow_variation_id );

			if ( $variation instanceof \WC_Product_Variation ) {
				$variation->delete( true );
				continue;
			}

			wp_delete_post( (int) $shadow_variation_id, true );
		}

		$product = wc_get_product( $shadow_product_id );

		if ( $product instanceof \WC_Product ) {
			$product->delete( true );
			return;
		}

		wp_delete_post( $shadow_product_id, true );
	}
}
