<?php
/**
 * NotFound settings sanitizer.
 *
 * @package SEOvaultSEO\NotFound
 */

namespace SEOvaultSEO\NotFound;

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

/**
 * Sanitizes NotFound option values.
 */
class Sanitizer {

	/**
	 * Allowed fallback modes.
	 *
	 * @var array<int, string>
	 */
	const FALLBACK_MODES = array( 'theme', 'home', 'custom' );

	/**
	 * Allowed fallback HTTP status codes.
	 *
	 * @var array<int, int>
	 */
	const FALLBACK_STATUSES = array( 301, 302, 307 );

	/**
	 * Allowed recovery content modes.
	 *
	 * @var array<int, string>
	 */
	const RECOVERY_CONTENT_MODES = array( 'builtin', 'page' );

	/**
	 * Initialize sanitizer hooks if needed.
	 */
	public static function init() {
	}

	/**
	 * Sanitize fallback mode.
	 *
	 * @param mixed $value Raw mode.
	 * @return string theme|home|custom
	 */
	public static function sanitize_fallback_mode( $value ) {
		$mode = is_string( $value ) ? strtolower( trim( $value ) ) : '';
		if ( in_array( $mode, self::FALLBACK_MODES, true ) ) {
			return $mode;
		}
		return 'theme';
	}

	/**
	 * Sanitize fallback redirect status.
	 *
	 * @param mixed $value Raw status.
	 * @return int 301|302|307
	 */
	public static function sanitize_fallback_status( $value ) {
		$status = (int) $value;
		if ( in_array( $status, self::FALLBACK_STATUSES, true ) ) {
			return $status;
		}
		return 302;
	}

	/**
	 * Sanitize a boolean setting using explicit accepted values.
	 *
	 * @param mixed $value Raw boolean value.
	 * @return bool
	 */
	public static function sanitize_boolean( $value ) {
		if ( is_string( $value ) ) {
			$value = strtolower( trim( $value ) );
		}

		return in_array( $value, array( true, 1, 1.0, '1', 'true', 'yes', 'on' ), true );
	}

	/**
	 * Sanitize a plain-text recovery field.
	 *
	 * Recovery copy deliberately does not allow HTML in v1.
	 *
	 * @param mixed $value Raw text.
	 * @return string
	 */
	public static function sanitize_recovery_text( $value ) {
		return is_string( $value ) ? sanitize_text_field( $value ) : '';
	}

	/**
	 * Sanitize the recovery call-to-action URL.
	 *
	 * @param mixed $value Raw URL.
	 * @return string
	 */
	public static function sanitize_recovery_cta_url( $value ) {
		if ( ! is_string( $value ) ) {
			return '';
		}

		return esc_url_raw( trim( $value ), array( 'http', 'https' ) );
	}

	/**
	 * Sanitize the recovery recent-post count.
	 *
	 * @param mixed $value Raw count.
	 * @return int
	 */
	public static function sanitize_recovery_recent_count( $value ) {
		return max( 1, min( 6, (int) $value ) );
	}

	/**
	 * Sanitize the recovery accent color.
	 *
	 * @param mixed $value Raw CSS hex color.
	 * @return string
	 */
	public static function sanitize_recovery_accent( $value ) {
		if ( ! is_string( $value ) ) {
			return '#1a1a1a';
		}

		$accent = trim( $value );
		return preg_match( '/^#(?:[a-fA-F0-9]{3}|[a-fA-F0-9]{6})$/', $accent )
			? strtolower( $accent )
			: '#1a1a1a';
	}

	/**
	 * Sanitize the recovery content mode.
	 *
	 * @param mixed $value Raw mode.
	 * @return string builtin|page
	 */
	public static function sanitize_recovery_content_mode( $value ) {
		$mode = is_string( $value ) ? strtolower( trim( $value ) ) : '';
		return in_array( $mode, self::RECOVERY_CONTENT_MODES, true ) ? $mode : 'builtin';
	}

	/**
	 * Sanitize the recovery content page ID.
	 *
	 * @param mixed $value Raw page ID.
	 * @return int
	 */
	public static function sanitize_recovery_page_id( $value ) {
		return self::sanitize_custom_page_id( $value );
	}

	/**
	 * Sanitize custom fallback URL.
	 *
	 * Empty string and relative URLs are allowed. Absolute URLs must use HTTP(S)
	 * and match the site's home host before WordPress safe-redirect validation.
	 *
	 * @param mixed $value Raw URL.
	 * @return string
	 */
	public static function sanitize_custom_url( $value ) {
		if ( ! is_string( $value ) ) {
			return '';
		}

		$value = trim( $value );
		if ( '' === $value ) {
			return '';
		}

		if ( self::has_dangerous_scheme( $value ) ) {
			return '';
		}

		$url = esc_url_raw( $value, array( 'http', 'https' ) );
		if ( '' === $url || self::has_dangerous_scheme( $url ) ) {
			return '';
		}

		$target_host = wp_parse_url( $url, PHP_URL_HOST );
		$home_host   = wp_parse_url( home_url(), PHP_URL_HOST );
		if (
			is_string( $target_host ) &&
			'' !== $target_host &&
			( ! is_string( $home_host ) || strtolower( $target_host ) !== strtolower( $home_host ) )
		) {
			return '';
		}

		return wp_validate_redirect( $url, '' );
	}

	/**
	 * Sanitize custom fallback page ID.
	 *
	 * Accepts only published pages. Invalid / draft / trash / non-page → 0.
	 *
	 * @param mixed $value Raw page ID.
	 * @return int
	 */
	public static function sanitize_custom_page_id( $value ) {
		if ( is_string( $value ) ) {
			$value = trim( $value );
		}

		if ( ! is_numeric( $value ) ) {
			return 0;
		}

		// Reject negatives / zero before absint (absint(-5) === 5).
		if ( (int) $value <= 0 ) {
			return 0;
		}

		$id = absint( $value );
		if ( $id <= 0 ) {
			return 0;
		}

		return self::is_published_page( $id ) ? $id : 0;
	}

	/**
	 * Whether a post ID is a published page.
	 *
	 * @param int $page_id Page ID.
	 * @return bool
	 */
	public static function is_published_page( $page_id ) {
		$page_id = absint( $page_id );
		if ( $page_id <= 0 ) {
			return false;
		}

		$post = get_post( $page_id );
		if ( ! $post instanceof \WP_Post ) {
			return false;
		}

		return 'page' === $post->post_type && 'publish' === $post->post_status;
	}

	/**
	 * Detect dangerous URL schemes.
	 *
	 * @param string $value URL candidate.
	 * @return bool
	 */
	private static function has_dangerous_scheme( $value ) {
		return (bool) preg_match( '#^\s*(javascript|data|vbscript)\s*:#i', $value );
	}
}
