<?php

namespace SugarCalendarBookings\Migration;

use SugarCalendarBookings\Appointments\AppointmentModel;
use SugarCalendarBookings\Customers\CustomerModel;
use SugarCalendarBookings\Employees\EmployeeModel;
use SugarCalendarBookings\Locations\LocationModel;
use SugarCalendarBookings\Services\ServiceModel;
use SugarCalendarBookings\Plugin;

/**
 * Registry for migration entity types and their corresponding models.
 *
 * Centralizes entity type to Model class mapping and provides common lookup utilities.
 * Uses Plugin::get() to resolve model classes, allowing Pro to override Lite models.
 *
 * @since 1.3.0
 */
class ModelRegistry {

	/**
	 * Cached model class mapping.
	 *
	 * @since 1.3.0
	 *
	 * @var array<string, class-string>|null
	 */
	private static ?array $models = null;

	/**
	 * Get the entity type to Model class mapping.
	 *
	 * Uses Plugin::get() to resolve actual model classes, respecting Pro overrides.
	 *
	 * @since 1.3.0
	 *
	 * @return array<string, class-string>
	 */
	private static function get_models(): array {

		if ( self::$models === null ) {
			self::$models = [
				'service'     => get_class( Plugin::get( ServiceModel::class ) ),
				'customer'    => get_class( Plugin::get( CustomerModel::class ) ),
				'employee'    => get_class( Plugin::get( EmployeeModel::class ) ),
				'location'    => get_class( Plugin::get( LocationModel::class ) ),
				'appointment' => get_class( Plugin::get( AppointmentModel::class ) ),
			];
		}

		return self::$models;
	}

	/**
	 * Get model class for entity type.
	 *
	 * @since 1.3.0
	 *
	 * @param string $entity_type Entity type.
	 *
	 * @return class-string|null Model class or null if not found.
	 */
	public static function get_class( string $entity_type ): ?string {

		$models = self::get_models();

		return $models[ $entity_type ] ?? null;
	}

	/**
	 * Get table name for entity type.
	 *
	 * @since 1.3.0
	 *
	 * @param string $entity_type Entity type.
	 *
	 * @return string Table name (plural form of entity type).
	 */
	public static function get_table_name( string $entity_type ): string {

		return $entity_type . 's';
	}

	/**
	 * Find existing record by unique identifier.
	 *
	 * @since 1.3.0
	 *
	 * @param string               $entity_type Entity type.
	 * @param array<string, mixed> $mapped      Mapped data.
	 *
	 * @return object|null Found model or null.
	 */
	public static function find_existing( string $entity_type, array $mapped ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		$class = self::get_class( $entity_type );

		if ( ! $class ) {
			return null;
		}

		switch ( $entity_type ) {
			case 'service':
			case 'location':
				return $class::query() // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.AddEmptyLineBeforeReturnStatement
					->where( 'name', '=', $mapped['name'], '%s' )
					->first();

			case 'customer':
			case 'employee':
				return $class::query() // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.AddEmptyLineBeforeReturnStatement
					->where( 'email', '=', $mapped['email'] ?? '', '%s' )
					->first();

			case 'appointment':
				return $class::query() // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.AddEmptyLineBeforeReturnStatement
					->where( 'customer_id', '=', $mapped['customer_id'], '%d' )
					->where( 'service_id', '=', $mapped['service_id'], '%d' )
					->where( 'start_at', '=', $mapped['start_at'], '%s' )
					->first();

			default:
				return null; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.AddEmptyLineBeforeReturnStatement
		}
	}

	/**
	 * Find entity by Amelia ID meta.
	 *
	 * @since 1.3.0
	 *
	 * @param string $entity_type Entity type.
	 * @param int    $amelia_id   Amelia ID to look up.
	 *
	 * @return object|null Found model or null.
	 */
	public static function find_by_amelia_id( string $entity_type, int $amelia_id ) {

		$class = self::get_class( $entity_type );
		$table = self::get_table_name( $entity_type );

		if ( ! $class ) {
			return null;
		}

		return $class::query()
			->join( 'meta', "{$table}.id", '=', 'meta.object_id' )
			->where( 'meta.object_type', '=', $entity_type, '%s' )
			->where( 'meta.meta_key', '=', '_amelia_id', '%s' )
			->where( 'meta.meta_value', '=', (string) $amelia_id, '%s' )
			->first();
	}

	/**
	 * Find entity ID by Amelia ID with optional fallback.
	 *
	 * Consolidates the common pattern of looking up an SCB entity ID from an Amelia ID,
	 * with support for a fallback callback when no match is found.
	 *
	 * @since 1.3.0
	 *
	 * @param string        $entity_type Entity type (service, customer, employee, location).
	 * @param int           $amelia_id   Amelia ID to look up.
	 * @param callable|null $fallback    Optional callback to get fallback ID if not found.
	 *
	 * @return int|null SCB entity ID or null if not found.
	 */
	public static function find_id_by_amelia_id( string $entity_type, int $amelia_id, ?callable $fallback = null ): ?int {

		// If Amelia ID provided, attempt lookup.
		if ( $amelia_id ) {
			$model = self::find_by_amelia_id( $entity_type, $amelia_id );

			if ( $model ) {
				return $model->id;
			}
		}

		// No match found - use fallback if provided.
		return $fallback ? $fallback() : null;
	}
}
