<?php
/**
 * Rule schema, defaults, and migration.
 *
 * @package EasySearchReplace
 */

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

/**
 * Canonical rule schema.
 *
 * Defines the complete shape of a rule and handles forward-migration of
 * rules saved by older plugin versions (which may be missing newer fields).
 */
class ESRN_Rule_Schema {

	/**
	 * Schema version. Bump when the rule shape changes.
	 */
	const VERSION = '2.0';

	/**
	 * Option key that stores the last-migrated schema version.
	 */
	const VERSION_OPTION = 'esrn_schema_version';

	/**
	 * Return the canonical defaults for a single rule.
	 *
	 * @return array
	 */
	public static function defaults() {
		return array(
			/* Free fields */
			'enabled'            => 1,      // NEW in 2.0 - defaults to enabled so existing rules keep working.
			'search'             => '',
			'replace'            => '',
			'ignore_case'        => 0,
			'post_types'         => array(),
			'post_ids'           => '',
			'urls'               => '',

			/* Pro fields (present in schema, gated at runtime) */
			'is_regex'           => 0,
			'between_active'     => 0,      // NEW in 2.1 - mutually exclusive with is_regex.
			'between_start'      => '',
			'between_end'        => '',
			'selector'           => '',     // Moved from free to Pro in 2.0.
			'schedule'           => array(
				'enabled'    => 0,
				'start_date' => '',
				'end_date'   => '',
				'start_time' => '',
				'end_time'   => '',
				'days'       => array(),    // 0..6 = Sun..Sat. Empty = all days.
			),
			'roles'              => array(),// Empty = any role (including guests).
			'logged_in'          => 'any',  // any | in | out.
			'device'             => 'any',  // any | desktop | mobile | tablet.
			'exclude_post_types' => array(),
			'exclude_post_ids'   => '',
			'exclude_urls'       => '',
		);
	}

	/**
	 * Merge an arbitrary rule array against the defaults so every field exists.
	 *
	 * Non-recursive for the top level; schedule sub-array is merged explicitly.
	 *
	 * @param mixed $rule Raw rule (may be from an old version).
	 * @return array
	 */
	public static function normalize( $rule ) {
		$defaults = self::defaults();

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

		$out = array_merge( $defaults, $rule );

		// Schedule needs a deeper merge because it's an associative array.
		$schedule_defaults = $defaults['schedule'];
		$schedule_value    = isset( $rule['schedule'] ) && is_array( $rule['schedule'] ) ? $rule['schedule'] : array();
		$out['schedule']   = array_merge( $schedule_defaults, $schedule_value );

		// Ensure array-typed fields are really arrays.
		foreach ( array( 'post_types', 'roles', 'exclude_post_types' ) as $array_key ) {
			if ( ! is_array( $out[ $array_key ] ) ) {
				$out[ $array_key ] = array();
			}
		}
		if ( ! is_array( $out['schedule']['days'] ) ) {
			$out['schedule']['days'] = array();
		}

		return $out;
	}

	/**
	 * Run one-time migration of stored rules to the current schema.
	 *
	 * Safe to call on every request - a version check short-circuits after the
	 * first successful migration. Uses update_option so no noise in options table
	 * when nothing changes.
	 */
	public static function maybe_migrate() {
		$current = get_option( self::VERSION_OPTION, '1.0' );

		if ( version_compare( $current, self::VERSION, '>=' ) ) {
			return;
		}

		$options = get_option( ESRN_OPTION_KEY );

		// Grandfather CSS selector for sites upgrading from pre-1.2.
		// The plugin option key exists only if the plugin was previously
		// installed and used, so fresh 1.2 installs are not affected.
		if ( version_compare( $current, '2.0', '<' ) && false !== $options ) {
			update_option( ESRN_Freemius_Gate::GRANDFATHER_OPTION, 1, 'yes' );
		}

		if ( is_array( $options ) && ! empty( $options['fields'] ) && is_array( $options['fields'] ) ) {
			$migrated = array();
			foreach ( $options['fields'] as $rule ) {
				$migrated[] = self::normalize( $rule );
			}
			$options['fields'] = $migrated;
			update_option( ESRN_OPTION_KEY, $options );
		}

		update_option( self::VERSION_OPTION, self::VERSION );
	}
}
