<?php
/**
 * Shared readiness analysis for saved auto-reply rules.
 *
 * @package SubmissionInbox
 */

namespace SUBIN\AutoReplies;

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

if ( ! class_exists( AutoReplyTemplateRepository::class ) ) {
	require_once __DIR__ . '/AutoReplyTemplateRepository.php';
}

/**
 * Compares a saved rule with the current form schema and effective content.
 */
class AutoReplyRuleReadiness {
	/** @var AutoReplyConditionEngine */
	private $conditions;

	/** @var AutoReplyMergeTagRenderer */
	private $renderer;

	/** @var AutoReplyRuleRepository|null */
	private $rules;

	/** @var AutoReplyTemplateRepository|null */
	private $templates;

	/**
	 * @param AutoReplyConditionEngine|null   $conditions Condition engine.
	 * @param AutoReplyMergeTagRenderer|null  $renderer Merge-tag renderer.
	 * @param AutoReplyRuleRepository|null    $rules Rule repository for bounded readiness reads.
	 * @param AutoReplyTemplateRepository|null $templates Template repository for bounded readiness reads.
	 */
	public function __construct(
		?AutoReplyConditionEngine $conditions = null,
		?AutoReplyMergeTagRenderer $renderer = null,
		?AutoReplyRuleRepository $rules = null,
		?AutoReplyTemplateRepository $templates = null
	) {
		$this->conditions = $conditions ?: new AutoReplyConditionEngine();
		$this->renderer   = $renderer ?: new AutoReplyMergeTagRenderer();
		$this->rules      = $rules;
		$this->templates  = $templates;
	}

	/**
	 * Analyze one rule without changing it.
	 *
	 * @param array<string,mixed>      $rule Saved rule or query projection.
	 * @param array<int,array<string,mixed>> $fields Current normalized form fields.
	 * @param bool                     $form_exists Whether the form still exists.
	 * @param array<string,mixed>|null $template Resolved template projection.
	 * @return array<string,mixed>
	 */
	public function analyze( array $rule, array $fields, $form_exists = true, $template = null ) {
		$field_map = array();
		foreach ( $fields as $field ) {
			if ( ! is_array( $field ) ) {
				continue;
			}
			$key = sanitize_key( (string) ( $field['key'] ?? $field['field_key'] ?? '' ) );
			if ( '' === $key ) {
				continue;
			}
			$field_map[ $key ] = array(
				'key'     => $key,
				'type'    => sanitize_key( (string) ( $field['type'] ?? $field['field_type'] ?? '' ) ),
				'options' => $this->normalize_options( $field['options'] ?? $field['field_options'] ?? array() ),
			);
		}

		$email_key         = sanitize_key( (string) ( $rule['visitor_email_field'] ?? '' ) );
		$email_field       = $field_map[ $email_key ] ?? null;
		$email_field_valid = is_array( $email_field ) && 'email' === $email_field['type'];
		$condition_result  = $this->analyze_conditions( $rule, $field_map );
		$content           = $this->analyze_content( $rule, $template, array_keys( $field_map ) );
		$content_ready     = $content['present'] && $content['within_limits'];
		$ready             = (bool) $form_exists && $email_field_valid && $condition_result['valid'] && $content_ready;

		return array(
			'ready'                 => $ready,
			'form_exists'           => (bool) $form_exists,
			'email_field_valid'     => $email_field_valid,
			'content_ready'         => $content_ready,
			'content_present'       => $content['present'],
			'content_within_limits' => $content['within_limits'],
			'subject_within_limits' => $content['subject_within_limits'],
			'body_within_limits'    => $content['body_within_limits'],
			'conditions_valid'      => $condition_result['valid'],
			'condition_count'       => $condition_result['count'],
		);
	}

	/**
	 * Compare effective sources with the same rendering and size boundaries used for delivery.
	 *
	 * Existing fields receive representative values so a legitimate field-only message
	 * remains potentially deliverable, while tags for deleted fields disappear exactly as
	 * they do in the sender.
	 *
	 * @param array<string,mixed>      $rule Rule.
	 * @param array<string,mixed>|null $template Template.
	 * @param array<int,string>        $field_keys Current field keys.
	 * @return array{present:bool,within_limits:bool,subject_within_limits:bool,body_within_limits:bool}
	 */
	private function analyze_content( array $rule, $template, array $field_keys ) {
		$template          = is_array( $template ) ? $template : array();
		$rule_projection   = ! empty( $rule['readiness_projection'] );
		$template_projection = ! empty( $template['readiness_projection'] );
		$rule_has_subject  = $rule_projection
			? ! empty( $rule['readiness_subject_override'] )
			: '' !== trim( (string) ( $rule['subject'] ?? '' ) );
		$rule_has_body     = $rule_projection
			? ! empty( $rule['readiness_body_override'] )
			: '' !== trim( (string) ( $rule['body'] ?? '' ) );

		if ( $rule_has_subject ) {
			$subject       = (string) ( $rule['subject'] ?? '' );
			$subject_bytes = $rule_projection
				? absint( $rule['readiness_subject_bytes'] ?? 0 )
				: strlen( sanitize_text_field( $subject ) );
		} else {
			$subject       = (string) ( $template['subject'] ?? '' );
			$subject_bytes = $template_projection
				? absint( $template['readiness_subject_bytes'] ?? 0 )
				: strlen( sanitize_text_field( $subject ) );
		}

		if ( $rule_has_body ) {
			$body_bytes = $rule_projection
				? absint( $rule['readiness_body_bytes'] ?? 0 )
				: strlen( (string) ( $rule['body'] ?? '' ) );
			$body = $rule_projection && $body_bytes <= AutoReplyDeliverySnapshot::MAX_BODY_BYTES
				? $this->projected_rule_body( absint( $rule['id'] ?? 0 ) )
				: ( $rule_projection ? '' : (string) ( $rule['body'] ?? '' ) );
		} else {
			$body_bytes = $template_projection
				? absint( $template['readiness_body_bytes'] ?? 0 )
				: strlen( AutoReplyTemplateRepository::effective_body( $template ) );
			$body = $template_projection && $body_bytes <= AutoReplyDeliverySnapshot::MAX_BODY_BYTES
				? $this->projected_template_body( absint( $template['id'] ?? 0 ) )
				: ( $template_projection ? '' : AutoReplyTemplateRepository::effective_body( $template ) );
		}

		$context = array(
			'site_name'       => 'Value',
			'form_name'       => 'Value',
			'submission_date' => 'Value',
			'lead_id'         => '1',
			'visitor_email'   => 'visitor@example.test',
		);
		$values                = array_fill_keys( $field_keys, 'Value' );
		$rendered              = $body_bytes <= AutoReplyDeliverySnapshot::MAX_BODY_BYTES
			? $this->renderer->render_html( $body, $context, $values, AutoReplyDeliverySnapshot::MAX_BODY_BYTES )
			: array( 'content' => '', 'overflowed' => true );
		$potential_body        = (string) ( $rendered['content'] ?? '' );
		$subject_within_limits = $subject_bytes <= AutoReplyDeliverySnapshot::MAX_SUBJECT_BYTES;
		$body_within_limits    = $body_bytes <= AutoReplyDeliverySnapshot::MAX_BODY_BYTES && empty( $rendered['overflowed'] );

		return array(
			'present'               => self::has_rendered_content( $potential_body ),
			'within_limits'         => $subject_within_limits && $body_within_limits,
			'subject_within_limits' => $subject_within_limits,
			'body_within_limits'    => $body_within_limits,
		);
	}

	/** @param int $rule_id Rule id. @return string */
	private function projected_rule_body( $rule_id ) {
		$rule_id = absint( $rule_id );
		if ( $rule_id <= 0 ) {
			return '';
		}
		if ( ! $this->rules ) {
			if ( ! class_exists( AutoReplyRuleRepository::class ) ) {
				require_once __DIR__ . '/AutoReplyRuleRepository.php';
			}
			$this->rules = new AutoReplyRuleRepository( $this->conditions );
		}
		$row = $this->rules->get_readiness_content( $rule_id, AutoReplyDeliverySnapshot::MAX_BODY_BYTES );

		return is_array( $row ) && is_string( $row['body'] ?? null ) ? $row['body'] : '';
	}

	/** @param int $template_id Template id. @return string */
	private function projected_template_body( $template_id ) {
		$template_id = absint( $template_id );
		if ( $template_id <= 0 ) {
			return '';
		}
		if ( ! $this->templates ) {
			$this->templates = new AutoReplyTemplateRepository();
		}
		$row = $this->templates->get_readiness_content( $template_id, AutoReplyDeliverySnapshot::MAX_BODY_BYTES );

		return AutoReplyTemplateRepository::effective_body( $row );
	}

	/** @param string $html HTML. @return bool */
	private static function has_rendered_content( $html ) {
		return '' !== trim( wp_strip_all_tags( (string) $html ) ) || false !== stripos( (string) $html, '<img' );
	}

	/**
	 * Validate condition structure, field types, and current selectable values.
	 *
	 * @param array<string,mixed> $rule Rule.
	 * @param array<string,array<string,mixed>> $field_map Fields by key.
	 * @return array{valid:bool,count:int}
	 */
	private function analyze_conditions( array $rule, array $field_map ) {
		$mode     = sanitize_key( (string) ( $rule['condition_match'] ?? 'all' ) );
		$document = $rule['conditions_document'] ?? $rule['conditions'] ?? null;
		if ( ! is_array( $document ) && isset( $rule['conditions_json'] ) ) {
			$document = json_decode( (string) $rule['conditions_json'], true );
		}
		if ( null === $document && ! array_key_exists( 'conditions_json', $rule ) ) {
			$document = array(
				'version'    => AutoReplyConditionEngine::VERSION,
				'mode'       => $mode,
				'conditions' => array(),
			);
		}
		if ( is_array( $document ) && ! array_key_exists( 'conditions', $document ) ) {
			$document = array(
				'version'    => AutoReplyConditionEngine::VERSION,
				'mode'       => $mode,
				'conditions' => $document,
			);
		}

		$errors     = $this->conditions->validate_conditions( $document, $mode );
		$clean      = is_array( $document ) ? $this->conditions->sanitize_conditions( $document, $mode ) : array( 'conditions' => array() );
		$conditions = is_array( $clean['conditions'] ?? null ) ? $clean['conditions'] : array();
		if ( ! empty( $errors ) ) {
			return array( 'valid' => false, 'count' => count( $conditions ) );
		}

		foreach ( $conditions as $condition ) {
			$key   = sanitize_key( (string) ( $condition['field'] ?? '' ) );
			$field = $field_map[ $key ] ?? null;
			if ( ! is_array( $field ) || (string) $field['type'] !== (string) ( $condition['type'] ?? '' ) ) {
				return array( 'valid' => false, 'count' => count( $conditions ) );
			}
			if ( in_array( (string) ( $condition['operator'] ?? '' ), array( 'is_empty', 'is_not_empty' ), true ) ) {
				continue;
			}
			if ( ! in_array( (string) $field['type'], array( 'select', 'radio', 'checkbox' ), true ) ) {
				continue;
			}
			$allowed = array_map( 'strval', wp_list_pluck( $field['options'], 'value' ) );
			$values  = is_array( $condition['value'] ?? null ) ? $condition['value'] : array( $condition['value'] ?? null );
			foreach ( $values as $value ) {
				if ( null !== $value && ! in_array( (string) $value, $allowed, true ) ) {
					return array( 'valid' => false, 'count' => count( $conditions ) );
				}
			}
		}

		return array( 'valid' => true, 'count' => count( $conditions ) );
	}

	/** @param mixed $options Options. @return array<int,array{label:string,value:string}> */
	private function normalize_options( $options ) {
		if ( is_string( $options ) ) {
			$decoded = json_decode( $options, true );
			$options = is_array( $decoded ) ? $decoded : array();
		}
		$normalized = array();
		foreach ( is_array( $options ) ? $options : array() as $option ) {
			if ( is_array( $option ) ) {
				$value = sanitize_text_field( (string) ( $option['value'] ?? $option['label'] ?? '' ) );
				$label = sanitize_text_field( (string) ( $option['label'] ?? $value ) );
			} elseif ( is_scalar( $option ) ) {
				$value = sanitize_text_field( (string) $option );
				$label = $value;
			} else {
				continue;
			}
			if ( '' !== $value ) {
				$normalized[] = array( 'label' => $label, 'value' => $value );
			}
		}

		return $normalized;
	}
}
