<?php

/**
 * Deterministic recovery brief for Plugin Builder background failures.
 *
 * @package LolaCore\PluginBuilder\Recovery
 * @license GPL-2.0-or-later
 */

declare(strict_types=1);

namespace LolaCore\PluginBuilder\Recovery;

use LolaCore\PluginBuilder\Async\JobManager;
use LolaCore\PluginBuilder\Handler\PluginBuilderHandler;

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

/**
 * Builds a structured brief the LLM may rephrase but not extend.
 */
final class RecoveryBriefBuilder {

	public const FAILURE_ACTIVATION_SAFETY = 'activation_safety';
	public const FAILURE_QUALITY_LOW       = 'quality_low';
	public const FAILURE_REVIEW_PARSE      = 'review_parse';
	public const FAILURE_STUCK_REPEATING   = 'stuck_repeating';
	public const FAILURE_UNKNOWN           = 'unknown';

	/**
	 * @param array<string, mixed> $jobState Failed async job transient state.
	 * @return array{
	 *   failure_class: string,
	 *   finding_codes: array<int, string>,
	 *   retries_exhausted: bool,
	 *   allowed_next_steps: array<int, array{id: string, label_en: string}>,
	 *   forbidden_suggestions: array<int, string>,
	 *   summary_en: string
	 * }
	 */
	public static function build( array $jobState ): array {
		$error            = trim((string) ( $jobState['error'] ?? '' ));
		$retryCount       = max(0, (int) ( $jobState['retry_count'] ?? 0 ));
		$retriesExhausted = $retryCount >= JobManager::MAX_RETRIES;
		$findingCodes     = self::extractFindingCodes($jobState);
		$failureClass     = self::resolveFailureClass($error, $findingCodes);

		return array(
			'failure_class'         => $failureClass,
			'finding_codes'         => $findingCodes,
			'retries_exhausted'     => $retriesExhausted,
			'allowed_next_steps'    => self::buildAllowedSteps($failureClass, $findingCodes, $retriesExhausted),
			'forbidden_suggestions' => self::buildForbiddenSuggestions($failureClass, $retriesExhausted),
			'summary_en'            => self::buildSummary($failureClass, $findingCodes, $retriesExhausted),
		);
	}

	/**
	 * @param array<string, mixed> $jobState
	 * @return array<int, string>
	 */
	private static function extractFindingCodes( array $jobState ): array {
		$codes = array();
		$snapshot = $jobState['ledger_snapshot'] ?? null;

		if (is_array($snapshot)) {
			if (isset($snapshot['last_fatal_codes']) && is_array($snapshot['last_fatal_codes'])) {
				foreach ($snapshot['last_fatal_codes'] as $code) {
					if (is_string($code) && $code !== '') {
						$codes[] = $code;
					}
				}
			}

			if (isset($snapshot['entries']) && is_array($snapshot['entries'])) {
				foreach ($snapshot['entries'] as $entry) {
					if (! is_array($entry)) {
						continue;
					}
					$code = (string) ( $entry['code'] ?? '' );
					if ($code !== '') {
						$codes[] = $code;
					}
				}
			}
		}

		$codes = array_values(array_unique($codes));
		sort($codes);

		return $codes;
	}

	/**
	 * @param array<int, string> $findingCodes
	 */
	private static function resolveFailureClass( string $error, array $findingCodes ): string {
		if ($error === PluginBuilderHandler::retryStuckFailMessage()) {
			return self::FAILURE_STUCK_REPEATING;
		}

		if ($error === PluginBuilderHandler::safetyCheckFailMessage() || $findingCodes !== array()) {
			return self::FAILURE_ACTIVATION_SAFETY;
		}

		if (
			stripos($error, 'invalid JSON') !== false
			|| stripos($error, 'empty JSON') !== false
		) {
			return self::FAILURE_REVIEW_PARSE;
		}

		if (
			stripos($error, 'confidence') !== false
			|| stripos($error, 'quality checks') !== false
		) {
			return self::FAILURE_QUALITY_LOW;
		}

		return self::FAILURE_UNKNOWN;
	}

	/**
	 * @param array<int, string> $findingCodes
	 * @return array<int, array{id: string, label_en: string}>
	 */
	private static function buildAllowedSteps( string $failureClass, array $findingCodes, bool $retriesExhausted ): array {
		if ($failureClass === self::FAILURE_REVIEW_PARSE) {
			$steps = array();

			if (! $retriesExhausted) {
				$steps[] = array(
					'id'       => 'try_again_button',
					'label_en' => 'Use the Try again button on the build card, or ask me to retry the build.',
				);
			} else {
				$steps[] = array(
					'id'       => 'change_model',
					'label_en' => 'Switch to a stronger model in LolaCore Settings, then try again.',
				);
			}

			return $steps;
		}

		$steps = array();

		if (! $retriesExhausted) {
			$steps[] = array(
				'id'       => 'try_again_button',
				'label_en' => 'Use the Try again button on the build card, or ask me to retry the build.',
			);
		}

		if (self::hasWooCommerceFinding($findingCodes)) {
			$steps[] = array(
				'id'       => 'simplify_wc_settings',
				'label_en' => 'Simplify the WooCommerce part of the request — for example, skip the admin settings page for now.',
			);
		}

		if ($failureClass === self::FAILURE_QUALITY_LOW || $failureClass === self::FAILURE_UNKNOWN) {
			$steps[] = array(
				'id'       => 'simplify_request',
				'label_en' => 'Try a simpler description with fewer features, then build again.',
			);
		}

		if ($failureClass === self::FAILURE_STUCK_REPEATING || $failureClass === self::FAILURE_ACTIVATION_SAFETY) {
			$steps[] = array(
				'id'       => 'simplify_request',
				'label_en' => 'Try a simpler description with fewer moving parts.',
			);
		}

		if ($retriesExhausted) {
			$steps[] = array(
				'id'       => 'change_model',
				'label_en' => 'Switch to a stronger model in LolaCore Settings, then try again.',
			);
		}

		return self::dedupeSteps($steps);
	}

	/**
	 * @param array<int, array{id: string, label_en: string}> $steps
	 * @return array<int, array{id: string, label_en: string}>
	 */
	private static function dedupeSteps( array $steps ): array {
		$seen    = array();
		$unique  = array();

		foreach ($steps as $step) {
			$id = (string) ( $step['id'] ?? '' );
			if ($id === '' || isset($seen[ $id ])) {
				continue;
			}
			$seen[ $id ] = true;
			$unique[]    = $step;
		}

		return $unique;
	}

	private static function buildForbiddenSuggestions( string $failureClass, bool $retriesExhausted ): array {
		$forbidden = array();

		if (
			$failureClass === self::FAILURE_ACTIVATION_SAFETY
			|| $failureClass === self::FAILURE_STUCK_REPEATING
			|| $failureClass === self::FAILURE_UNKNOWN
			|| $failureClass === self::FAILURE_QUALITY_LOW
			|| $failureClass === self::FAILURE_REVIEW_PARSE
		) {
			$forbidden[] = 'split_php_js_css';
		}

		if (! $retriesExhausted) {
			$forbidden[] = 'change_model_unless_exhausted';
		}

		return $forbidden;
	}

	/**
	 * @param array<int, string> $findingCodes
	 */
	private static function buildSummary( string $failureClass, array $findingCodes, bool $retriesExhausted ): string {
		if ($failureClass === self::FAILURE_STUCK_REPEATING) {
			return 'The build hit the same safety issue twice and could not fix it automatically.';
		}

		if ($failureClass === self::FAILURE_ACTIVATION_SAFETY) {
			return 'The build failed a safety check that would risk a fatal error when the plugin loads.';
		}

		if ($failureClass === self::FAILURE_QUALITY_LOW) {
			return 'The build finished generating, but it did not pass internal quality checks.';
		}

		if ($failureClass === self::FAILURE_REVIEW_PARSE) {
			return 'The build finished generating, but an internal review step failed before delivery.';
		}

		if (in_array('early_woocommerce_exit', $findingCodes, true)) {
			return 'The build had a WooCommerce guard pattern that is safe to ship but worth simplifying on the next try.';
		}

		if ($retriesExhausted) {
			return 'The background build failed after several attempts.';
		}

		return 'The background plugin build did not finish successfully.';
	}

	/**
	 * @param array<int, string> $findingCodes
	 */
	private static function hasWooCommerceFinding( array $findingCodes ): bool {
		$wooCodes = array(
			'early_woocommerce_exit',
			'unknown_parent_class_at_file_scope',
			'stripped_vendor_namespace',
		);

		foreach ($findingCodes as $code) {
			if (in_array($code, $wooCodes, true)) {
				return true;
			}
		}

		return false;
	}
}
