<?php

/**
 * Detects file-scope early returns that skip plugin bootstrap when WooCommerce is absent.
 *
 * @package LolaCore\Builder\Safety\Rules
 * @license GPL-2.0-or-later
 */

declare(strict_types=1);

namespace LolaCore\Builder\Safety\Rules;

use LolaCore\Builder\Safety\SafetyContext;
use LolaCore\Builder\Safety\SafetyRuleInterface;

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

/**
 * Flags a recurring codegen pattern that prevents hooks from registering at file scope.
 *
 * Severity is warning (non-blocking): without WooCommerce the plugin simply skips hook
 * registration; with WooCommerce active this is not an activation fatal.
 */
class EarlyWooCommerceExitRule implements SafetyRuleInterface {

	public function id(): string {
		return 'early_woocommerce_exit';
	}

	/**
	 * @inheritDoc
	 */
	public function analyze( string $php, SafetyContext $context ): array {
		unset($context);

		if (! $this->hasWooCommerceClassExistsGuard($php)) {
			return array();
		}

		if (! $this->hasFileScopeEarlyExit($php)) {
			return array();
		}

		$line = $this->lineNumberForPattern(
			$php,
			'/class_exists\s*\(\s*[\'"]WooCommerce[\'"]\s*\)/i'
		);

		return array(
			array(
				'severity' => 'warning',
				'code'     => $this->id(),
				'message'  => __('A file-scope return/exit after class_exists("WooCommerce") prevents the plugin from registering hooks when WooCommerce is inactive. Move dependency checks into an init hook instead of exiting at file scope.', 'lolacore'),
				'line'     => $line,
			),
		);
	}

	private function hasWooCommerceClassExistsGuard( string $php ): bool {
		return preg_match('/class_exists\s*\(\s*[\'"]WooCommerce[\'"]\s*\)/i', $php) === 1;
	}

	private function hasFileScopeEarlyExit( string $php ): bool {
		$tokens = token_get_all($php);
		$depth  = 0;
		$count  = count($tokens);

		for ($i = 0; $i < $count; $i++) {
			$token = $tokens[ $i ];

			if ($token === '{') {
				++$depth;
				continue;
			}

			if ($token === '}') {
				$depth = max(0, $depth - 1);
				continue;
			}

			if ($depth !== 0 || ! is_array($token) || $token[0] !== T_IF) {
				continue;
			}

			if (! $this->ifConditionMentionsWooCommerce($tokens, $i)) {
				continue;
			}

			$bodyStart = $this->findOpeningBrace($tokens, $i + 1);
			if ($bodyStart === null) {
				continue;
			}

			if ($this->blockContainsEarlyExit($tokens, $bodyStart + 1)) {
				return true;
			}
		}

		return false;
	}

	/**
	 * @param array<int, array{0: int, 1: string, 2: int}|string> $tokens
	 */
	private function ifConditionMentionsWooCommerce( array $tokens, int $ifIndex ): bool {
		$depth  = 0;
		$count  = count($tokens);
		$buffer = '';

		for ($i = $ifIndex + 1; $i < $count; $i++) {
			$token = $tokens[ $i ];

			if ($token === '(') {
				++$depth;
			} elseif ($token === ')') {
				--$depth;
				if ($depth === 0) {
					break;
				}
			}

			if (is_array($token)) {
				$buffer .= $token[1];
			} elseif (is_string($token) && strlen($token) === 1) {
				$buffer .= $token;
			}
		}

		return preg_match('/class_exists\s*\(\s*[\'"]WooCommerce[\'"]\s*\)/i', $buffer) === 1;
	}

	/**
	 * @param array<int, array{0: int, 1: string, 2: int}|string> $tokens
	 */
	private function findOpeningBrace( array $tokens, int $start ): ?int {
		$count = count($tokens);

		for ($i = $start; $i < $count; $i++) {
			if ($tokens[ $i ] === '{') {
				return $i;
			}
		}

		return null;
	}

	/**
	 * @param array<int, array{0: int, 1: string, 2: int}|string> $tokens
	 */
	private function blockContainsEarlyExit( array $tokens, int $start ): bool {
		$depth = 1;
		$count = count($tokens);

		for ($i = $start; $i < $count && $depth > 0; $i++) {
			$token = $tokens[ $i ];

			if ($token === '{') {
				++$depth;
				continue;
			}

			if ($token === '}') {
				--$depth;
				continue;
			}

			if ($depth !== 1 || ! is_array($token)) {
				continue;
			}

			if (in_array($token[0], array( T_RETURN, T_EXIT ), true)) {
				return true;
			}
		}

		return false;
	}

	private function lineNumberForPattern( string $php, string $pattern ): int {
		if (preg_match($pattern, $php, $matches, PREG_OFFSET_CAPTURE) !== 1) {
			return 0;
		}

		$offset = $matches[0][1];

		return substr_count(substr($php, 0, $offset), "\n") + 1;
	}
}
