<?php

namespace SugarCalendarBookings\Integrations\CalendarEvents;

use SugarCalendarBookings\Integrations\OAuthRelay\OAuthConnectionModel;

/**
 * Diff between existing calendar event links and currently deliverable connections.
 *
 * Performs a single batched DB lookup to hydrate the connections referenced by
 * the existing links, then classifies links into push/update/delete/orphan
 * buckets without any further side effects or registry lookups.
 *
 * Decision rules:
 *   1. Existing link, provider not currently registered -> orphan_keep.
 *   2. Existing link, connection gone or inactive       -> dropped silently.
 *   3. Existing link, connection still deliverable      -> to_update.
 *   4. Existing link, connection no longer deliverable  -> to_delete.
 *   5. Deliverable connection not in existing links     -> to_push.
 *
 * @since 1.5.0
 */
class EventLinkDiffer {

	/**
	 * Diff existing links against the current deliverable snapshot.
	 *
	 * @since 1.5.0
	 *
	 * @param EventLinkCollection      $existing    Current persisted links.
	 * @param DeliverableConnectionMap $deliverable Snapshot of deliverable connections.
	 *
	 * @return EventLinkDiff
	 */
	public function diff(
		EventLinkCollection $existing,
		DeliverableConnectionMap $deliverable
	): EventLinkDiff {

		$to_push     = [];
		$to_update   = [];
		$to_delete   = [];
		$orphan_keep = [];

		// Batch-resolve connections for every existing link in a single query
		// (avoids N+1 on appointment lifecycle hooks for accounts with many
		// previously-synced provider connections).
		$connections_by_id = OAuthConnectionModel::get_many( $existing->connection_ids() );

		// Classify existing links.
		foreach ( $existing->all() as $link ) {

			$slug          = $link->provider_slug();
			$connection_id = $link->connection_id();

			// Rule 1: provider not currently registered -> keep orphan untouched.
			if ( ! in_array( $slug, $deliverable->provider_slugs(), true ) ) {
				$orphan_keep[] = $link;

				continue;
			}

			$connection = $connections_by_id[ $connection_id ] ?? null;

			// Rule 2: connection gone or inactive -> drop silently.
			if ( ! $connection instanceof OAuthConnectionModel || $connection->status !== 'active' ) {
				continue;
			}

			// Rule 3 vs 4.
			if ( $deliverable->contains( $slug, $connection ) ) {
				$to_update[] = [
					'link'       => $link,
					'connection' => $connection,
				];
				continue;
			}

			$to_delete[] = [
				'link'       => $link,
				'connection' => $connection,
			];
		}

		// Rule 5: newly deliverable connections not in existing.
		foreach ( $deliverable->provider_slugs() as $slug ) {

			foreach ( $deliverable->for_provider( $slug ) as $connection ) {

				if ( $existing->has( $slug, (int) $connection->id ) ) {
					continue;
				}

				$to_push[] = [
					'slug'       => $slug,
					'connection' => $connection,
				];
			}
		}

		return new EventLinkDiff( $to_push, $to_update, $to_delete, $orphan_keep );
	}
}
