<?php

namespace Elementor\Modules\Variables\Utils;

use Elementor\Core\Utils\Template_Library_Element_Iterator;
use Elementor\Core\Utils\Template_Library_Import_Export_Utils;
use Elementor\Core\Utils\Template_Library_Snapshot_Processor;
use Elementor\Modules\Variables\Storage\Constants;
use Elementor\Modules\Variables\Storage\Variables_Collection;
use Elementor\Modules\Variables\Storage\Variables_Repository;
use Elementor\Plugin;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Template_Library_Variables_Snapshot_Builder extends Template_Library_Snapshot_Processor {

	private static ?self $instance = null;

	public static function make(): self {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	public static function extract_used_variable_ids_from_elements( array $elements ): array {
		$ids = [];
		$variable_types = Variable_Type_Keys::get_all();

		if ( empty( $elements ) ) {
			return [];
		}

		Template_Library_Element_Iterator::iterate(
			$elements,
			function ( $element_data ) use ( &$ids, $variable_types ) {
				self::collect_variable_ids_from_element( $element_data, $ids, $variable_types );
				return $element_data;
			}
		);

		return array_values( array_unique( $ids ) );
	}

	public static function build_snapshot_for_ids( array $ids ): ?array {
		if ( empty( $ids ) ) {
			return null;
		}

		$instance = self::make();
		if ( ! $instance->can_access_repository() ) {
			return null;
		}

		$ids = Template_Library_Import_Export_Utils::normalize_string_ids( $ids );
		if ( empty( $ids ) ) {
			return null;
		}

		$all_data = $instance->load_current_data();
		$all_variables = $all_data['items'] ?? [];
		$filtered_data = Template_Library_Import_Export_Utils::filter_items_by_ids( $all_variables, $ids );

		if ( empty( $filtered_data ) ) {
			return null;
		}

		return self::build_snapshot_from_data(
			$filtered_data,
			$all_data['version'] ?? Constants::FORMAT_VERSION_V1
		);
	}

	public static function build_snapshot_for_elements( array $elements, ?array $global_classes_snapshot = null ): ?array {
		$ids = self::extract_used_variable_ids_from_elements( $elements );

		if ( ! empty( $global_classes_snapshot ) ) {
			$ids_from_classes = self::extract_variable_ids_from_data( $global_classes_snapshot );
			$ids = array_merge( $ids, $ids_from_classes );
		}

		$ids = array_values( array_unique( $ids ) );

		if ( empty( $ids ) ) {
			return null;
		}

		return self::build_snapshot_for_ids( $ids );
	}

	public static function extract_variable_ids_from_data( array $data ): array {
		$ids = [];
		$variable_types = Variable_Type_Keys::get_all();
		self::extract_variable_ids_recursive( $data, $ids, $variable_types );
		return array_values( array_unique( $ids ) );
	}

	public static function merge_snapshot_and_get_id_map( array $snapshot ): array {
		return self::make()->merge_and_get_id_map( $snapshot );
	}

	public static function create_snapshot_as_new( array $snapshot ): array {
		return self::make()->create_all_as_new( $snapshot );
	}

	protected function is_matching_item( array $existing_item, array $incoming_item ): bool {
		// For variables, if the type and label match, we consider them the same item
		// when merging, so we reuse the existing variable and ignore incoming values or extra fields.
		return ( $existing_item['type'] ?? '' ) === ( $incoming_item['type'] ?? '' );
	}

	protected function get_item_prefix(): string {
		return Template_Library_Import_Export_Utils::VARIABLE_ID_PREFIX;
	}

	protected function get_max_items(): int {
		return Constants::TOTAL_VARIABLES_COUNT;
	}

	protected function can_access_repository(): bool {
		return null !== $this->get_repository_or_null();
	}

	protected function load_current_data(): array {
		$repository = $this->get_repository_or_null();

		if ( ! $repository ) {
			return [
				'items' => [],
				'order' => [],
				'watermark' => 0,
				'version' => Constants::FORMAT_VERSION_V1,
			];
		}

		$collection = $repository->load();
		$serialized = $collection->serialize();

		return [
			'items' => $serialized['data'] ?? [],
			'order' => [],
			'watermark' => $serialized['watermark'] ?? 0,
			'version' => $serialized['version'] ?? Constants::FORMAT_VERSION_V1,
		];
	}

	protected function parse_incoming_snapshot( array $snapshot ): ?array {
		$incoming_data = $snapshot['data'] ?? [];
		if ( empty( $incoming_data ) ) {
			return null;
		}
		return $snapshot;
	}

	protected function get_incoming_items( array $parsed_snapshot ): array {
		return $parsed_snapshot['data'] ?? [];
	}

	protected function count_current_items( array $items ): int {
		$count = 0;
		foreach ( $items as $item ) {
			if ( empty( $item['deleted'] ) ) {
				++$count;
			}
		}
		return $count;
	}

	protected function save_data( array $items, array $metadata ): array {
		$repository = $this->get_repository_or_null();

		if ( ! $repository ) {
			return [];
		}

		$updated_collection = Variables_Collection::hydrate( [
			'data' => $items,
			'watermark' => $metadata['watermark'] ?? 0,
			'version' => $metadata['version'] ?? Constants::FORMAT_VERSION_V1,
		] );

		$repository->save( $updated_collection );

		return [
			'variables' => [
				'data' => $items,
				'watermark' => $updated_collection->watermark(),
			],
		];
	}

	private static function extract_variable_ids_recursive( $data, array &$ids, array $variable_types ): void {
		if ( ! is_array( $data ) ) {
			return;
		}

		if ( isset( $data['$$type'] ) && in_array( $data['$$type'], $variable_types, true ) ) {
			if ( isset( $data['value'] ) && is_string( $data['value'] ) && '' !== $data['value'] ) {
				$ids[] = $data['value'];
			}
		}

		foreach ( $data as $value ) {
			if ( is_array( $value ) ) {
				self::extract_variable_ids_recursive( $value, $ids, $variable_types );
			}
		}
	}

	private static function collect_variable_ids_from_element( array $element_data, array &$ids, array $variable_types ): void {
		if ( ! empty( $element_data['settings'] ) && is_array( $element_data['settings'] ) ) {
			self::extract_variable_ids_recursive( $element_data['settings'], $ids, $variable_types );
		}

		if ( ! empty( $element_data['styles'] ) && is_array( $element_data['styles'] ) ) {
			self::extract_variable_ids_recursive( $element_data['styles'], $ids, $variable_types );
		}
	}

	private function get_repository_or_null(): ?Variables_Repository {
		$kit = Plugin::instance()->kits_manager->get_active_kit();
		if ( ! $kit ) {
			return null;
		}

		return new Variables_Repository( $kit );
	}

	private static function build_snapshot_from_data( array $data, int $version ): array {
		return [
			'data' => $data,
			'watermark' => 0,
			'version' => $version,
		];
	}
}
