<?php

/**
 * Short-lived cache for Plugin Builder AI generation between preview and execute.
 *
 * @package LolaCore\PluginBuilder\Generation
 * @license GPL-2.0-or-later
 */

declare(strict_types=1);

namespace LolaCore\PluginBuilder\Generation;

// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Diagnostic exception messages; escaped at REST/chat presentation layer.

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

/**
 * Stores generated plugin payloads so execute reuses preview output (zero extra tokens).
 */
final class PluginBuilderGenerationCache {

	public const PARAM_KEY = '_preview_cache_key';

	private const TRANSIENT_PREFIX = 'lolacore_pb_gen_';

	private const TTL_SECONDS = 7200;

	/**
	 * @param array<string, mixed> $params  Action params (without internal cache key).
	 * @param array<string, mixed> $payload Cached generation payload.
	 */
	public static function store( string $actionId, array $params, array $payload ): string {
		$key = wp_generate_uuid4();

		set_transient(
			self::TRANSIENT_PREFIX . $key,
			array(
				'action_id'          => $actionId,
				'params_fingerprint' => self::fingerprint($actionId, $params),
				'payload'            => $payload,
				'created_at'         => time(),
			),
			self::TTL_SECONDS
		);

		return $key;
	}

	/**
	 * @param array<string, mixed> $params Action params including optional cache key.
	 * @return array<string, mixed>|null
	 *
	 * @throws \Exception When a cache key was supplied but is missing or invalid.
	 */
	public static function consume( string $actionId, array $params ): ?array {
		$cacheKey = self::extractCacheKey($params);
		if ($cacheKey === '') {
			return null;
		}

		$entry = get_transient(self::TRANSIENT_PREFIX . $cacheKey);
		if (! is_array($entry) || empty($entry['payload']) || ! is_array($entry['payload'])) {
			throw new \Exception(__('The plugin preview expired or is no longer available. Ask Lola to run the action again.', 'lolacore'));
		}

		if (($entry['action_id'] ?? '') !== $actionId) {
			throw new \Exception(__('The cached preview does not match this action.', 'lolacore'));
		}

		$expected = self::fingerprint($actionId, $params);
		if (($entry['params_fingerprint'] ?? '') !== $expected) {
			throw new \Exception(__('The cached preview no longer matches the requested parameters.', 'lolacore'));
		}

		delete_transient(self::TRANSIENT_PREFIX . $cacheKey);

		return $entry['payload'];
	}

	public static function delete( string $cacheKey ): void {
		if ($cacheKey === '') {
			return;
		}

		delete_transient(self::TRANSIENT_PREFIX . $cacheKey);
	}

	/**
	 * @param array<string, mixed> $params
	 */
	public static function stripInternalParams( array $params ): array {
		unset($params[ self::PARAM_KEY ]);

		return $params;
	}

	/**
	 * @param array<string, mixed> $params
	 */
	private static function extractCacheKey( array $params ): string {
		if (! isset($params[ self::PARAM_KEY ])) {
			return '';
		}

		$key = trim((string) $params[ self::PARAM_KEY ]);

		return $key;
	}

	/**
	 * @param array<string, mixed> $params
	 */
	private static function fingerprint( string $actionId, array $params ): string {
		$params = self::stripInternalParams($params);

		$relevant = match ($actionId) {
			'create_plugin'       => array(
				'description' => isset($params['description']) ? trim((string) $params['description']) : '',
				'name'        => isset($params['name']) ? sanitize_text_field((string) $params['name']) : '',
			),
			'update_plugin_code'  => array(
				'post_id'     => isset($params['post_id']) ? (int) $params['post_id'] : 0,
				'instruction' => isset($params['instruction']) ? trim((string) $params['instruction']) : '',
			),
			default               => $params,
		};

		return hash('sha256', wp_json_encode($relevant) ?: '');
	}
}

