<?php
/**
 * Form element for a deployment item.
 *
 * @package EaLab\WP_Deployer
 */

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

/**
 * Requires.
 */
require_once __DIR__ . '/../utility/component-based-object.php';
require_once __DIR__ . '/../utility/deployment-item.php';
require_once __DIR__ . '/../utility/importability.php';
require_once __DIR__ . '/deployment-item--diff-button.php';
require_once __DIR__ . '/deployment-item--label-and-uuid.php';
require_once __DIR__ . '/deployment-item--merge-button.php';
require_once __DIR__ . '/deployment-item--single-import-button.php';
require_once __DIR__ . '/deployment-item--single-send-to-prod-button.php';

use EaLab\WP_Deployer\vendor\Ealab\Deployer\Exception\WpDeployException;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Import\Importability;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\TrackingManager;

/**
 * Returns the HTML for the form of a deployment item.
 *
 * @param array $item The deployment item.
 * @param array $context The context. It contains the following keys:
 *   - deployment_bundle (array): The deployment bundle.
 *   - deployment_item__form__render_stack (array): The stack of items being rendered.
 *   - label (string): The label of the item. If empty, a default one will be provided.
 *   - recursion_count (int): The number of times this function has been called recursively.
 *   - screen_label_prefix (string): The prefix of the screen label.
 *
 * @return string Markup.
 *
 * @throws WpDeployException Error on build.
 */
function deploy_sync_content__deployment_item__form( array $item, array $context ): string {
	if ( empty( $item ) ) {
		return '';
	}

	$item_full_uuid = deploy_sync_content__get_uuid_from_deployment_item( $item );

	if ( empty( $item_full_uuid ) ) {
		// We don't want incomplete data to show up.
		return '';
	}

	if ( ! empty( $context['deployment_item__form__render_stack'] ) && in_array( $item_full_uuid, $context['deployment_item__form__render_stack'], true ) ) {
		// This item is already being rendered.
		// Prevent infinite recursion.
		return '';
	}

	$context['deployment_item__form__render_stack'][] = $item_full_uuid;

	try {
		$tracking_manager = deploy_sync_content__get_root_cbo( TrackingManager::class );

		if ( empty( $tracking_manager ) ) {
			// Is that really possible?
			return '';
		}

		require_once DEPLOY_SYNC_CONTENT__PLUGIN_DIR . 'includes/helpers.php';
		$item_title     = deploy_sync_content__capture_output( fn() => deploy_sync_content__deployment_item__label_uuid( $context['label'] ?? null, $item_full_uuid, $context ) );
		$action_buttons = '';

		// Add counting info to the build state.
		$context['build_state']             = $context['build_state'] ?? new Deploy_Sync_Content__State();
		$context['build_state']->item_count = ( $context['build_state']->item_count ?? 0 ) + 1;

		if ( $context['build_state']->ignore_production_checks ?? false ) {
			// A previous script has told us to ignore production checks.
			$importability = Importability::UNREACHABLE_PRODUCTION_INSTANCE;
		} else {
			// Check if the item can be imported in the production instance.
			$importability = $context['importability'][ $item_full_uuid ] ?? null;

			if ( null === $importability ) {
				// Do a single check.
				$results       = deploy_sync_content__check_importability( array( $item_full_uuid => $item ), $context );
				$importability = $results[ $item_full_uuid ];
			}
		}

		// Work with the subordinated items first to get their count.
		// Retrieve the subordinated items.
		// First, attempt to get the subordination info.
		// This only works if the subordinated trackers are initialized / available.
		// When importing, non-initialized trackers will show their deployment items
		// as if they were not subordinated. See deploy_sync_content__item_tracker__form() for more info.
		$item_tracker = $tracking_manager->getItemTrackerByItemFullUuid( $item_full_uuid );

		if ( empty( $item_tracker ) ) {
			// Is that really possible, again?
			return '';
		}

		try {
			$subordinated_trackers = $tracking_manager->getItemTrackerSubordinatesOf( $item_tracker );
		} catch ( WpDeployException $e ) {
			// Is that really possible, againx2 ?
			return '';
		}

		// Embed the subordinated items of the current item.
		$subordinated_items = '';

		$subordinated_items_list = array();

		foreach ( $subordinated_trackers as $subordinated_tracker ) {
			try {
				$subordinated_tracker_id = $subordinated_tracker->getTrackerId();
			} catch ( WpDeployException $e ) {
				// Is that really possible, againx3 ?
				continue;
			}

			$items_from_subordinated_tracker = array_filter(
				$context['deployment_bundle'],
				function ( $sub_item ) use ( $subordinated_tracker_id, $tracking_manager, $item ) {
					// First, perform a fast check to filter out the items
					// that are not from the current subordinated tracker.
					if ( ! ( ( $sub_item['tracker'] ?? null ) === $subordinated_tracker_id ) ) {
						return false;
					}

					try {
						// Then, perform a more accurate check to filter out the items
						// that are not subordinated to the current item.
						return $tracking_manager->isDeploymentItemSubordinatedTo( $sub_item, $item );
					} catch ( WpDeployException $e ) {
						// TODO: should we report this?
						return false;
					}
				}
			);

			// This will take care of the sub items.
			$subordinated_items .= deploy_sync_content__deployment_item_list__form(
				$items_from_subordinated_tracker,
				array_merge(
					$context,
					array(
						'recursion_count'     => ( $context['recursion_count'] ?? 0 ) + 1,
						'screen_label_prefix' => $subordinated_tracker_id . ': ',
					),
				)
			);

			$subordinated_items_list = array_merge( $subordinated_items_list, $items_from_subordinated_tracker );
		}

		/*
		 * Now process the current item.
		 */
		$increment_valid_for_import_item_count = function () use ( &$context ) {
			$context['build_state']->valid_for_import_item_count = ( $context['build_state']->valid_for_import_item_count ?? 0 ) + 1;
		};

		$complete_importability_string = '';

		if ( ! empty( get_transient( 'deploy_sync_content__debug' ) ) ) {
			$complete_importability_strings = array();

			$append_importability_string = function ( $what ) use ( &$complete_importability_strings ) {
				$complete_importability_strings[] = '<li>' . $what . '</li>';
			};

			( $importability & Importability::BASED_ON_SAME_PRODUCTION_VALUES ) && $append_importability_string( 'BASED_ON_SAME_PRODUCTION_VALUES' );
			( $importability & Importability::SAME_LAST_MODIFICATION_DATE ) && $append_importability_string( 'SAME_LAST_MODIFICATION_DATE' );
			( $importability & Importability::LOCAL_ITEM_HAS_BEEN_MODIFIED ) && $append_importability_string( 'LOCAL_ITEM_HAS_BEEN_MODIFIED (prod)' );
			( $importability & Importability::INCOMING_ITEM_HAS_BEEN_MODIFIED ) && $append_importability_string( 'INCOMING_ITEM_HAS_BEEN_MODIFIED (dev)' );
			( $importability & Importability::ABORTED_CHECK ) && $append_importability_string( 'ABORTED_CHECK' );
			( $importability & Importability::REQUIRES_NEW_TRACKER ) && $append_importability_string( 'REQUIRES_NEW_TRACKER' );
			( $importability & Importability::UNREACHABLE_PRODUCTION_INSTANCE ) && $append_importability_string( 'UNREACHABLE_PRODUCTION_INSTANCE' );
			( $importability & Importability::HAS_CHANGES_TO_IMPORT ) && $append_importability_string( 'HAS_CHANGES_TO_IMPORT' );
			( $importability & Importability::CAN_IMPORT ) && $append_importability_string( 'CAN_IMPORT' );
			( $importability & Importability::LOCAL_ITEM_EXISTS ) && $append_importability_string( 'LOCAL_ITEM_EXISTS' );
			( $importability & Importability::MERGE_IS_VALID ) && $append_importability_string( 'MERGE_IS_VALID' );
			( $importability & Importability::TRACKER_CONFIG_MISMATCH ) && $append_importability_string( 'TRACKER_CONFIG_MISMATCH' );

			$complete_importability_string = implode( '', $complete_importability_strings );
		}

		// This is used by the merge button component.
		$get_uuids_for_merge_button = static function () use ( $item_full_uuid, $subordinated_items_list ): array {
			return array(
				'all_item_full_uuids'    => array_merge( array( $item_full_uuid ), array_keys( $subordinated_items_list ) ),
				'primary_item_full_uuid' => $item_full_uuid,
			);
		};

		if ( 'export' === $context['form_context'] ) {
			if ( $tracking_manager->isProductionEnvironment() ) {
				// We are on the export form on the production instance. There is no diff button.
				$status_label       = esc_html__( 'prod', 'deploy-sync-content' );
				$importability_icon = 'bi-check-circle';

				$increment_valid_for_import_item_count();
			} elseif ( $importability & Importability::UNREACHABLE_PRODUCTION_INSTANCE ) {
				// The production instance is unreachable. We cannot check if the item is modified.
				// But don't block the export.
				$status_label       = esc_html__( 'Unreachable production instance', 'deploy-sync-content' );
				$importability_icon = 'bi-x-circle';

				// Don't block the export. The conflicts will be resolved when importing on the production instance.
				$increment_valid_for_import_item_count();
			} elseif ( ! ( $importability & Importability::HAS_CHANGES_TO_IMPORT ) ) {
				$status_label       = esc_html__( 'Unmodified', 'deploy-sync-content' );
				$importability_icon = 'bi-check-circle';

				// This should not block the global export, so we do as if it was valid for import.
				$increment_valid_for_import_item_count();
			} elseif ( $importability & Importability::CAN_IMPORT ) {
				if ( $importability & Importability::LOCAL_ITEM_EXISTS ) {
					$status_label = esc_html__( 'Modified', 'deploy-sync-content' );
				} else {
					$status_label = esc_html__( 'New', 'deploy-sync-content' );
				}

				$importability_icon = 'bi-check-circle';

				if ( empty( $context['no_item_action_button'] ) ) {
					$action_buttons .= deploy_sync_content__deployment_item__diff_button( $item_full_uuid, $context );
					$action_buttons .= deploy_sync_content__deployment_item__single_send_to_prod_button( $item_full_uuid, $context );
				}

				$increment_valid_for_import_item_count();
			} elseif ( $importability & Importability::MERGE_IS_VALID ) {
				// The item has changes to import on prod and the user has resolved the merge.
				$status_label       = esc_html__( 'Merged', 'deploy-sync-content' );
				$importability_icon = 'bi-check-circle';

				if ( empty( $context['no_item_action_button'] ) ) {
					$action_buttons .= deploy_sync_content__deployment_item__merge_button(
						array_merge(
							array(
								'button_label' => esc_html__( 'Re-merge', 'deploy-sync-content' ),
							),
							$context,
							$get_uuids_for_merge_button(),
						)
					);
					$action_buttons .= deploy_sync_content__deployment_item__single_send_to_prod_button( $item_full_uuid, $context );
				}

				$increment_valid_for_import_item_count();
			} else {
				$status_label       = esc_html__( 'Conflict', 'deploy-sync-content' );
				$importability_icon = 'bi-x-circle';

				if ( empty( $context['no_item_action_button'] ) ) {
					$action_buttons .= deploy_sync_content__deployment_item__merge_button(
						array_merge(
							$context,
							$get_uuids_for_merge_button(),
						)
					);
				}
			}
		} elseif ( 'import' === $context['form_context'] ) {
			if ( $importability & Importability::CAN_IMPORT ) {
				if ( ! ( $importability & Importability::HAS_CHANGES_TO_IMPORT ) ) {
					// This should not block the global import, so we do as if it was valid for import.
					$status_label       = esc_html__( 'Unmodified', 'deploy-sync-content' );
					$importability_icon = 'bi-check-circle';
				} elseif ( $importability & Importability::MERGE_IS_VALID ) {
					// The item has changes to import and the user has resolved the merge.
					$status_label       = esc_html__( 'Merged', 'deploy-sync-content' );
					$importability_icon = 'bi-check-circle';

					if ( empty( $context['no_item_action_button'] ) ) {
						$action_buttons .= deploy_sync_content__deployment_item__merge_button(
							array_merge(
								array(
									'button_label' => esc_html__( 'Re-merge', 'deploy-sync-content' ),
								),
								$context,
								$get_uuids_for_merge_button(),
							)
						);
						$action_buttons .= deploy_sync_content__deployment_item__single_import_button( $item_full_uuid, $context );
					}
				} else {
					if ( $importability & Importability::LOCAL_ITEM_EXISTS ) {
						$status_label = esc_html__( 'Modified', 'deploy-sync-content' );
					} else {
						$status_label = esc_html__( 'New', 'deploy-sync-content' );
					}

					$importability_icon = 'bi-check-circle';

					if ( empty( $context['no_item_action_button'] ) ) {
						$action_buttons .= deploy_sync_content__deployment_item__diff_button( $item_full_uuid, $context );
						$action_buttons .= deploy_sync_content__deployment_item__single_import_button( $item_full_uuid, $context );
					}
				}

				$increment_valid_for_import_item_count();
			} elseif ( $importability & Importability::TRACKER_CONFIG_MISMATCH ) {
				$status_label       = esc_html__( 'Tracker config mismatch', 'deploy-sync-content' );
				$importability_icon = 'bi-x-circle';
			} elseif ( ! ( $importability & Importability::HAS_CHANGES_TO_IMPORT ) ) {
				// In theory, this should never happen due to the implementation of the importability system.
				// An item that cannot be imported should always have changes to import,
				// unless the tracker config is mismatching.
				$status_label       = esc_html__( 'Unmodified', 'deploy-sync-content' );
				$importability_icon = 'bi-check-circle';

				// This should not block the global import, so we do as if it was valid for import.
				$increment_valid_for_import_item_count();
			} else {
				$status_label       = esc_html__( 'Conflict', 'deploy-sync-content' );
				$importability_icon = 'bi-x-circle';

				if ( empty( $context['no_item_action_button'] ) ) {
					$action_buttons .= deploy_sync_content__deployment_item__merge_button(
						array_merge(
							$context,
							$get_uuids_for_merge_button(),
						)
					);
				}
			}
		}

		$recursion_tab      = str_repeat( '<span style="margin-right: 1.5rem"></span>', $context['recursion_count'] ?? 0 ) . ( 0 < ( $context['recursion_count'] ?? 0 ) ? '<span style="margin-right: 0.5rem">↳</span>' : '' );
		$item_html_value    = esc_attr( $item_full_uuid );
		$importability_icon = esc_attr( $importability_icon );
		$is_child_tracker   = ( isset( $context['recursion_count'] ) && $context['recursion_count'] > 0 ) ? 'child-tracker-item' : 'parent-tracker-item';
		$reviewing_changes  = 'export' === ( $context['form_context'] ?? null ) && in_array( 'review', ( $context['form_context_tags'] ?? array() ), true );
		$already_exported   = $reviewing_changes && in_array( $item_full_uuid, ( $context['already_exported_item_full_uuids'] ?? array() ), true );

		$increment_prechecked_items_count = function () use ( &$context ) {
			$context['prechecked_items_count'] = $context['prechecked_items_count'] ?? new Deploy_Sync_Content__State( 0 );
			++$context['prechecked_items_count']->value;
		};

		$increment_disabled_items_count = function () use ( &$context ) {
			$context['disabled_items_count'] = $context['disabled_items_count'] ?? new Deploy_Sync_Content__State( 0 );
			++$context['disabled_items_count']->value;
		};

		// This is the best location to increment the all_items_count,
		// because it is called for every item, including ones from subordinated trackers.
		$context['all_items_count'] = $context['all_items_count'] ?? new Deploy_Sync_Content__State( 0 );
		++$context['all_items_count']->value;

		if ( ! $reviewing_changes ) {
			// Creating the bundle. Add a pre-checked normal checkbox.
			$increment_prechecked_items_count();
			$checkbox_input = "<input class='ml-15 checkbox-tracker-item $is_child_tracker' name='optionstodeploy[]' type='checkbox' value='$item_html_value' checked>";
		} elseif ( $already_exported ) {
			// When reviewing changes, we only pre-check the items which are already exported and have no changes.
			if ( in_array( $item_full_uuid, ( $context['updated_since_from_date'] ?? array() ), true ) ) {
				// Has changes since "from_date" (or last generation if not available).
				// Add a non pre-checked normal checkbox.
				$checkbox_input = "<input class='ml-15 checkbox-tracker-item $is_child_tracker' name='optionstodeploy[]' type='checkbox' value='$item_html_value'>";
			} else {
				// No changes since "from_date" (or last generation if not available).
				// Add a pre-checked disabled checkbox.
				$increment_prechecked_items_count();
				$increment_disabled_items_count();

				// Add a disabled checkbox.
				$checkbox_input = "<input class='ml-15 checkbox-tracker-item $is_child_tracker' name='optionstodeploy[]' type='checkbox' value='$item_html_value' checked disabled>";
			}
		} else {
			// New item since generation. Add a non pre-checked normal checkbox.
			$checkbox_input = "<input class='ml-15 checkbox-tracker-item $is_child_tracker' name='optionstodeploy[]' type='checkbox' value='$item_html_value'>";
		}

		if ( ! empty( $subordinated_items ) ) {
			$subordinated_items = '<details style="margin-left: 1.5rem; margin-top: 0.2rem;"><summary>' . esc_html__( 'Additional content', 'deploy-sync-content' ) . '</summary>' . $subordinated_items . '</details>';
		}

		return '
<div class="row g-3 text-left">
    <div class="col-7">' . $recursion_tab . $checkbox_input . $item_title . '</div>
    <div class="col-2"><i class="bi ' . $importability_icon . '"></i> ' . $status_label . ' <ul style="font-size: 0.7em">' . $complete_importability_string . '</ul></div>
    <div class="col-3">' . $action_buttons . '</div>
	' . $subordinated_items . '
</div>
';
	} finally {
		$pop_item = array_pop( $context['deployment_item__form__render_stack'] );

		if ( $pop_item !== $item_full_uuid ) {
			// Is that really possible, againx4 ?
			throw new WpDeployException( 'The item render stack is corrupted.' );
		}
	}
}
