<?php

namespace JET_APB;

/**
 * Builds and caches service/provider relation maps for the booking calendar.
 */
class Service_Provider_Relations_Map {

	public function __construct() {
		add_action( 'delete_post', [ $this, 'maybe_flush_cache_by_post' ] );
		add_action( 'added_post_meta', [ $this, 'maybe_flush_cache_by_meta' ], 10, 4 );
		add_action( 'updated_post_meta', [ $this, 'maybe_flush_cache_by_meta' ], 10, 4 );
		add_action( 'deleted_post_meta', [ $this, 'maybe_flush_cache_by_meta' ], 10, 4 );
	}

	public function get_map() {
		$services_cpt  = Plugin::instance()->settings->get( 'services_cpt' );
		$providers_cpt = Plugin::instance()->settings->get( 'providers_cpt' );

		if ( ! $services_cpt || ! $providers_cpt ) {
			return $this->get_empty_map();
		}

		$cache_key = $this->get_cache_key();
		$cached = get_transient( $cache_key );

		if ( false !== $cached ) {
			return $cached;
		}

		$relations_map = $this->get_map_from_providers();

		if ( $this->can_cache_map( $relations_map ) ) {
			set_transient( $cache_key, $relations_map, DAY_IN_SECONDS );
		}

		return $relations_map;
	}

	public function flush_cache() {
		$cache_key = $this->get_cache_key();

		delete_transient( $cache_key );
	}

	public function maybe_flush_cache_by_post( $post_id ) {
		$post_type     = get_post_type( $post_id );
		$services_cpt  = Plugin::instance()->settings->get( 'services_cpt' );
		$providers_cpt = Plugin::instance()->settings->get( 'providers_cpt' );

		if ( $post_type && in_array( $post_type, [ $services_cpt, $providers_cpt ], true ) ) {
			$this->flush_cache();
		}
	}

	public function maybe_flush_cache_by_meta( $meta_id, $object_id, $meta_key, $meta_value ) {
		if ( $meta_key === $this->get_legacy_relation_key() ) {
			$this->flush_cache();
		}
	}

	private function get_empty_map() {
		return [
			'related_services'  => [],
			'related_providers' => [],
		];
	}

	private function get_cache_key() {
		$services_cpt  = Plugin::instance()->settings->get( 'services_cpt' );
		$providers_cpt = Plugin::instance()->settings->get( 'providers_cpt' );

		return 'jet_apb_service_provider_relations' . md5( $services_cpt . '|' . $providers_cpt );
	}

	private function can_cache_map( $relations_map ) {
		if ( wp_using_ext_object_cache() ) {
			return true;
		}

		return strlen( maybe_serialize( $relations_map ) ) <= $this->get_max_cache_size();
	}

	private function get_max_cache_size() {
		return apply_filters( 'jet-apb/service-provider-relations-map/max-cache-size', 1024 * 1024 );
	}

	private function get_map_from_providers() {

		$relations_map = $this->get_empty_map();

		$providers_list = Plugin::instance()->tools->get_posts( 'providers', [
			'post_status'    => 'any',
			'posts_per_page' => -1,
			'fields'         => 'ids',
		] );

		if ( empty( $providers_list ) ) {
			return $relations_map;
		}

		foreach ( $providers_list as $provider_id ) {
			$services = Plugin::instance()->tools->get_services_for_provider( $provider_id );

			if ( empty( $services ) ) {
				continue;
			}

			foreach ( $services as $service_id ) {
				$this->add_map_item( $relations_map['related_services'], $provider_id, $service_id );
				$this->add_map_item( $relations_map['related_providers'], $service_id, $provider_id );
			}
		}

		return $this->stringify_map( $relations_map );
	}

	private function get_legacy_relation_key() {

		$services_cpt  = Plugin::instance()->settings->get( 'services_cpt' );
		$providers_cpt = Plugin::instance()->settings->get( 'providers_cpt' );

		if ( ! $services_cpt || ! $providers_cpt ) {
			return false;
		}

		if ( function_exists( 'jet_engine' ) && ! empty( jet_engine()->relations->legacy ) ) {
			return jet_engine()->relations->legacy->get_relation_hash( $services_cpt, $providers_cpt );
		}

		return false;
	}

	private function add_map_item( &$map, $object_id, $related_id ) {
		if ( empty( $map[ $object_id ] ) ) {
			$map[ $object_id ] = [];
		}

		if ( ! in_array( $related_id, $map[ $object_id ], true ) ) {
			$map[ $object_id ][] = $related_id;
		}
	}

	private function stringify_map( $relations_map ) {
		foreach ( $relations_map as $map_key => $map ) {
			foreach ( $map as $object_id => $related_ids ) {
				$relations_map[ $map_key ][ $object_id ] = array_map( 'strval', $related_ids );
			}
		}

		return $relations_map;
	}
}
