<?php
/**
 * Output guardrails for AI-generated HTML/CSS.
 *
 * Soft mode (default): records violations and lets the output through so the
 * model can self-correct on the next turn.
 * Strict mode: rejects output when violations are found, surfacing them to
 * the user as a refinement directive.
 *
 * @package Antimanual_Builder
 * @since   1.6.0
 */

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

class AMBLD_Agent_Guardrails {

	const MODE_SOFT   = 'soft';
	const MODE_STRICT = 'strict';

	const OPTION_KEY = 'ambld_guardrails_mode';

	public static function get_mode() {
		$mode = (string) get_option( self::OPTION_KEY, self::MODE_SOFT );
		return self::MODE_STRICT === $mode ? self::MODE_STRICT : self::MODE_SOFT;
	}

	public static function set_mode( $mode ) {
		$mode = self::MODE_STRICT === $mode ? self::MODE_STRICT : self::MODE_SOFT;
		update_option( self::OPTION_KEY, $mode );
	}

	/**
	 * Validate generated HTML.
	 *
	 * Looks for clearly disallowed shapes — raw <script src> referencing
	 * untrusted hosts, PHP open tags, and malformed DOCTYPE inserts. We
	 * lean conservative: flag only patterns that have actually caused real
	 * issues in the editor.
	 *
	 * @return array{ok:bool, violations:string[]}
	 */
	public static function check_html( $html ) {
		$html       = (string) $html;
		$violations = array();

		if ( false !== strpos( $html, '<?php' ) || false !== strpos( $html, '<?=' ) ) {
			$violations[] = 'Output contains PHP tags. The builder renders generated markup as HTML; PHP must come from server-side shortcodes.';
		}
		if ( preg_match( '/<script\b[^>]*\bsrc\s*=\s*["\']https?:\/\/(?!(www\.)?(google|gstatic|jsdelivr|unpkg)\.)/i', $html ) ) {
			$violations[] = 'External <script src> from an unvetted host.';
		}
		if ( substr_count( strtolower( $html ), '<html' ) > 1 ) {
			$violations[] = 'Multiple <html> roots — the page must contain at most one document root.';
		}

		return array(
			'ok'         => empty( $violations ),
			'violations' => $violations,
		);
	}

	/**
	 * Validate generated CSS.
	 *
	 * Checks brace balance and obvious injection vectors. Stops short of a
	 * full CSS parse — soft mode is meant to catch the model returning
	 * "```css ..." fenced output (a common mode failure) rather than to be
	 * a strict validator.
	 */
	public static function check_css( $css ) {
		$css        = (string) $css;
		$violations = array();

		$open  = substr_count( $css, '{' );
		$close = substr_count( $css, '}' );
		if ( $open !== $close ) {
			$violations[] = sprintf( 'Unbalanced CSS braces: %d open, %d close.', $open, $close );
		}
		if ( false !== stripos( $css, '<script' ) || false !== stripos( $css, 'javascript:' ) ) {
			$violations[] = 'CSS contains script-like payload.';
		}
		// "```css" / "```" markdown fences sneaking into the actual CSS string.
		if ( preg_match( '/^\s*```/', $css ) ) {
			$violations[] = 'CSS appears to begin with a markdown code fence.';
		}

		return array(
			'ok'         => empty( $violations ),
			'violations' => $violations,
		);
	}

	/**
	 * Run both checks at once.
	 *
	 * @return array{mode:string, ok:bool, html:array, css:array, violations:string[]}
	 */
	public static function check( $html, $css ) {
		$h = self::check_html( $html );
		$c = self::check_css( $css );
		return array(
			'mode'       => self::get_mode(),
			'ok'         => $h['ok'] && $c['ok'],
			'html'       => $h,
			'css'        => $c,
			'violations' => array_merge( $h['violations'], $c['violations'] ),
		);
	}
}
