<?php
/**
 * Settings access and sanitization.
 *
 * @package RedirectZen
 */

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

class RDZ_Settings {

	const OPTION_KEY = 'rdz_settings';

	/**
	 * Redirect status codes the plugin supports.
	 *
	 * @var int[]
	 */
	public static $redirect_types = array( 301, 302, 307 );

	/**
	 * Re-verification schedules available on the Pro plan.
	 *
	 * @var string[]
	 */
	public static $verify_schedules = array( 'off', 'daily', 'weekly' );

	/**
	 * @return array Default settings.
	 */
	public static function defaults() {
		return array(
			'default_redirect_type' => 301,
			'log_404'               => 1,
			'retention_days'        => 30,
			'max_404_rows'          => 10000,
			'ignore_patterns'       => array(
				'/.env*',
				'/.git*',
				'*.asp',
				'*.aspx',
				'/cgi-bin/*',
				'*wlwmanifest.xml',
				'/phpmyadmin*',
				'/wp-content/cache/*',
			),
			'request_timeout'       => 10,
			'verify_batch'          => 10,
			'keep_data'             => 0,
			// Pro.
			'verify_schedule'       => 'weekly',
			'digest_enabled'        => 0,
			'digest_recipient'      => '',
			'updates_optin'         => 0,
		);
	}

	/**
	 * Returns all settings merged with defaults.
	 *
	 * @return array
	 */
	public static function all() {
		$saved = get_option( self::OPTION_KEY, array() );
		if ( ! is_array( $saved ) ) {
			$saved = array();
		}

		$settings = wp_parse_args( $saved, self::defaults() );

		// Scheduled re-verification and the digest are Pro features.
		if ( ! rdz_can_use_premium() ) {
			$settings['verify_schedule'] = 'off';
			$settings['digest_enabled']  = 0;
		}

		return $settings;
	}

	/**
	 * Returns a single setting.
	 *
	 * @param string $key Setting key.
	 * @return mixed
	 */
	public static function get( $key ) {
		$settings = self::all();

		return isset( $settings[ $key ] ) ? $settings[ $key ] : null;
	}

	/**
	 * Sanitizes settings submitted from the settings screen.
	 *
	 * @param array $input Raw input.
	 * @return array Clean settings.
	 */
	public static function sanitize( $input ) {
		$defaults = self::defaults();
		$clean    = array();

		if ( ! is_array( $input ) ) {
			return $defaults;
		}

		$type = isset( $input['default_redirect_type'] ) ? absint( $input['default_redirect_type'] ) : $defaults['default_redirect_type'];
		if ( ! in_array( $type, self::$redirect_types, true ) ) {
			$type = 301;
		}
		$clean['default_redirect_type'] = $type;

		$clean['log_404']   = empty( $input['log_404'] ) ? 0 : 1;
		$clean['keep_data'] = empty( $input['keep_data'] ) ? 0 : 1;

		$retention               = isset( $input['retention_days'] ) ? absint( $input['retention_days'] ) : $defaults['retention_days'];
		$clean['retention_days'] = max( 1, min( 365, $retention ) );

		$max_rows              = isset( $input['max_404_rows'] ) ? absint( $input['max_404_rows'] ) : $defaults['max_404_rows'];
		$clean['max_404_rows'] = max( 100, min( 100000, $max_rows ) );

		$clean['ignore_patterns'] = array();
		if ( isset( $input['ignore_patterns'] ) ) {
			$raw_patterns = is_array( $input['ignore_patterns'] )
				? $input['ignore_patterns']
				: explode( "\n", (string) $input['ignore_patterns'] );
			foreach ( $raw_patterns as $pattern ) {
				$pattern = trim( sanitize_text_field( $pattern ) );
				if ( '' !== $pattern ) {
					$clean['ignore_patterns'][] = $pattern;
				}
			}
			$clean['ignore_patterns'] = array_slice( array_unique( $clean['ignore_patterns'] ), 0, 200 );
		}

		$timeout                  = isset( $input['request_timeout'] ) ? absint( $input['request_timeout'] ) : $defaults['request_timeout'];
		$clean['request_timeout'] = max( 3, min( 30, $timeout ) );

		$batch                 = isset( $input['verify_batch'] ) ? absint( $input['verify_batch'] ) : $defaults['verify_batch'];
		$clean['verify_batch'] = max( 5, min( 50, $batch ) );

		$schedule = isset( $input['verify_schedule'] ) ? sanitize_key( $input['verify_schedule'] ) : 'weekly';
		if ( ! in_array( $schedule, self::$verify_schedules, true ) || ! rdz_can_use_premium() ) {
			$schedule = rdz_can_use_premium() ? 'weekly' : 'off';
		}
		$clean['verify_schedule'] = $schedule;

		$clean['digest_enabled'] = ( empty( $input['digest_enabled'] ) || ! rdz_can_use_premium() ) ? 0 : 1;

		$recipient                 = isset( $input['digest_recipient'] ) ? sanitize_email( $input['digest_recipient'] ) : '';
		$clean['digest_recipient'] = $recipient;

		// Product-updates co-consent: explicit, unchecked by default. Record
		// the opt-in only on the transition from unchecked to checked.
		$clean['updates_optin'] = empty( $input['updates_optin'] ) ? 0 : 1;
		$previous               = get_option( self::OPTION_KEY, array() );
		$was_opted              = is_array( $previous ) && ! empty( $previous['updates_optin'] );
		if ( $clean['updates_optin'] && ! $was_opted && class_exists( 'RDZ_Optin' ) ) {
			$optin_email = $recipient ? $recipient : get_option( 'admin_email' );
			RDZ_Optin::record( $optin_email, 'settings' );
		}

		return $clean;
	}

	/**
	 * Whether a 404 path matches any ignore pattern. Patterns support a
	 * leading/trailing/inner '*' wildcard; a pattern without '*' matches as
	 * a substring.
	 *
	 * @param string $path Normalized request path.
	 * @return bool
	 */
	public static function is_ignored_404( $path ) {
		$patterns = self::get( 'ignore_patterns' );
		if ( ! is_array( $patterns ) ) {
			return false;
		}

		foreach ( $patterns as $pattern ) {
			if ( false === strpos( $pattern, '*' ) ) {
				if ( false !== stripos( $path, $pattern ) ) {
					return true;
				}
				continue;
			}

			$regex = '#^' . str_replace( '\*', '.*', preg_quote( $pattern, '#' ) ) . '$#i';
			if ( preg_match( $regex, $path ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Digest recipient with fallback to the site admin email.
	 *
	 * @return string
	 */
	public static function digest_recipient() {
		$recipient = self::get( 'digest_recipient' );

		return $recipient ? $recipient : get_option( 'admin_email' );
	}
}
