<?php
/**
 * Settings class.
 *
 * Manages default values, retrieval, sanitisation and migration from the legacy
 * option key used by plugin versions prior to 5.0.
 *
 * @package IGIT_RPWT
 */

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

class IGIT_RPWT_Settings {

	/**
	 * Current option key (v5+).
	 */
	const OPTION_KEY = 'igit_rpwt_settings';

	/**
	 * Legacy option key (v4 and below).
	 */
	const LEGACY_OPTION_KEY = 'igit_rpwt';

	/**
	 * Cached settings array.
	 *
	 * @var array|null
	 */
	private static $cache = null;

	/**
	 * Default settings.
	 *
	 * All keys for every feature phase are declared here so wp_parse_args always
	 * produces a complete options array regardless of which features are active.
	 *
	 * @return array
	 */
	public static function defaults() {
		$sanitised = array(

			// ── Display ──────────────────────────────────────────────────────────
			'heading_text'          => __( 'Related Posts', 'igit-related-posts-with-thumb-images-after-posts' ),
			'auto_show'             => true,
			'post_count'            => 5,
			'layout'                => 'grid',   // 'grid', 'list', 'compact', 'masonry', 'carousel', or 'chip'
			'no_posts_text'         => __( 'No related posts found.', 'igit-related-posts-with-thumb-images-after-posts' ),
			'heading_level'         => 'h2',     // 'h2', 'h3', or 'h4'
			'show_excerpt'          => false,
			'excerpt_length'        => 20,       // words (10–30)
			'show_date'             => false,
			'show_reading_time'     => false,
			'show_comment_count'    => false,
			'link_target'           => 'same',   // 'same' or 'new'
			'link_nofollow'         => false,
			'show_category_badge'   => false,
			'custom_css'            => '',
			'grid_columns_desktop'  => 3,        // 2–6
			'grid_columns_tablet'   => 2,        // 1–3
			'grid_columns_mobile'   => 1,        // 1–2

			// ── Thumbnails ────────────────────────────────────────────────────────
			'show_thumbnail'        => true,
			'thumbnail_size'        => 'igit-rpwt-thumb',
			'default_image'         => '',

			// ── Query ─────────────────────────────────────────────────────────────
			'relate_by'             => 'both',              // 'category', 'tag', 'both', 'fulltext', 'author', or 'series'
			'post_types'            => array( 'post' ),
			'relate_taxonomies'     => array( 'category', 'post_tag' ),
			'exclude_categories'    => array(),
			'exclude_posts'         => array(),
			'min_term_overlap'      => 1,                   // 1–5
			'max_age_days'          => 0,                   // 0 = no limit
			'fallback_mode'         => 'random',            // 'recent', 'popular', 'random', 'none'
			'rotate_results'        => false,               // shuffle candidate pool on each request
			'show_query_time'       => false,               // display query execution time in the heading
			'show_title'            => true,

			// ── Cache ─────────────────────────────────────────────────────────────
			'cache_ttl'             => 43200,               // 12 hours in seconds

			// ── Performance ───────────────────────────────────────────────────────
			'load_async'            => true,

			// ── Multisite ────────────────────────────────────────────────────────
			'multisite_search'      => false,

			// ── Analytics ─────────────────────────────────────────────────────────
			'enable_click_tracking'    => false,
			'analytics_retention_days' => 0,       // 0 = keep forever; Pro cron auto-purges when > 0

			// ── Post Nudges (Pro) ─────────────────────────────────────────────────
			'nudge_enabled'              => true,
			'nudge_style'               => 'compact',   // 'compact', 'card', 'minimal'
			'nudge_position'            => 'bottom-right', // 'bottom-right','bottom-left','top-right','top-left'
			'nudge_trigger'             => 'immediate',  // 'immediate', 'scroll-50', 'exit-intent'
			'nudge_animation'           => 'slide',      // 'slide', 'fade', 'bounce'
			'nudge_duration'            => 7,            // seconds notification stays visible
			'nudge_interval'            => 10,           // seconds between nudges
			'nudge_max_count'           => 5,            // max nudges shown per page visit
			'nudge_show_thumbnail'      => true,
			'nudge_show_excerpt'        => false,
			'nudge_show_date'           => false,
			'nudge_mobile'              => false,
			'nudge_post_count'          => 5,            // pool size fetched server-side
			'nudge_link_target'         => 'same',       // 'same' or 'new'
			'nudge_on_posts'            => true,
			'nudge_on_pages'            => false,
			'nudge_on_homepage'         => true,
			'nudge_homepage_source'     => 'random',     // 'latest','popular','random','category','handpicked'
			'nudge_homepage_category'   => '',      // 'taxonomy:term_id' format, e.g. 'category:5' or 'product_cat:12'
			'nudge_homepage_post_ids'   => '',
			'nudge_pause_on_hover'      => true,
			'nudge_post_types'          => array( 'post' ), // CPTs to include in nudge pool
		);

		return $sanitised;
	}

	/**
	 * Return all settings, merged with defaults.
	 *
	 * @return array
	 */
	public static function get_all() {
		if ( null === self::$cache ) {
			self::maybe_migrate();
			$saved        = get_option( self::OPTION_KEY, array() );
			self::$cache  = wp_parse_args( $saved, self::defaults() );
		}
		return self::$cache;
	}

	/**
	 * Return a single setting value.
	 *
	 * @param string $key     Setting key.
	 * @param mixed  $default Fallback when key is absent.
	 * @return mixed
	 */
	public static function get( $key, $default = null ) {
		$all = self::get_all();
		if ( array_key_exists( $key, $all ) ) {
			return $all[ $key ];
		}
		return null !== $default ? $default : ( self::defaults()[ $key ] ?? null );
	}

	/**
	 * Persist sanitised settings.
	 *
	 * @param array $raw Raw input (e.g. from $_POST or Settings API).
	 * @return bool
	 */
	public static function save( array $raw ) {
		$sanitised   = self::sanitise( $raw );
		self::$cache = null; // bust cache
		return update_option( self::OPTION_KEY, $sanitised );
	}

	/**
	 * Sanitise raw input values.
	 *
	 * @param array $raw
	 * @return array
	 */
	public static function sanitise( array $raw ) {
		$defaults = self::defaults();

		$kses_heading = array( 'h1' => array(), 'h2' => array(), 'h3' => array(), 'h4' => array(), 'h5' => array(), 'strong' => array(), 'em' => array() );

		$sanitised = array(

			// ── Display ──────────────────────────────────────────────────────────
			'heading_text'         => isset( $raw['heading_text'] )
				? wp_kses( $raw['heading_text'], $kses_heading )
				: $defaults['heading_text'],

			'auto_show'            => ! empty( $raw['auto_show'] ),

			'post_count'           => isset( $raw['post_count'] )
				? min( 20, max( 1, absint( $raw['post_count'] ) ) )
				: $defaults['post_count'],

			'layout'               => in_array( $raw['layout'] ?? '', array( 'grid', 'list', 'compact', 'masonry', 'carousel', 'chip' ), true )
				? $raw['layout']
				: $defaults['layout'],

			'no_posts_text'        => isset( $raw['no_posts_text'] )
				? sanitize_text_field( $raw['no_posts_text'] )
				: $defaults['no_posts_text'],

			'heading_level'        => in_array( $raw['heading_level'] ?? '', array( 'h2', 'h3', 'h4' ), true )
				? $raw['heading_level']
				: $defaults['heading_level'],

			'show_excerpt'         => ! empty( $raw['show_excerpt'] ),

			'excerpt_length'       => isset( $raw['excerpt_length'] )
				? min( 30, max( 10, absint( $raw['excerpt_length'] ) ) )
				: $defaults['excerpt_length'],

			'show_date'            => ! empty( $raw['show_date'] ),
			'show_reading_time'    => ! empty( $raw['show_reading_time'] ),
			'show_comment_count'   => ! empty( $raw['show_comment_count'] ),

			'link_target'          => in_array( $raw['link_target'] ?? '', array( 'same', 'new' ), true )
				? $raw['link_target']
				: $defaults['link_target'],

			'link_nofollow'        => ! empty( $raw['link_nofollow'] ),
			'show_category_badge'  => ! empty( $raw['show_category_badge'] ),

			'custom_css'           => isset( $raw['custom_css'] )
				? wp_strip_all_tags( $raw['custom_css'] )
				: $defaults['custom_css'],

			'grid_columns_desktop' => isset( $raw['grid_columns_desktop'] )
				? min( 6, max( 2, absint( $raw['grid_columns_desktop'] ) ) )
				: $defaults['grid_columns_desktop'],

			'grid_columns_tablet'  => isset( $raw['grid_columns_tablet'] )
				? min( 3, max( 1, absint( $raw['grid_columns_tablet'] ) ) )
				: $defaults['grid_columns_tablet'],

			'grid_columns_mobile'  => isset( $raw['grid_columns_mobile'] )
				? min( 2, max( 1, absint( $raw['grid_columns_mobile'] ) ) )
				: $defaults['grid_columns_mobile'],

			// ── Thumbnails ────────────────────────────────────────────────────────
			'show_thumbnail'       => ! empty( $raw['show_thumbnail'] ),

			'thumbnail_size'       => isset( $raw['thumbnail_size'] )
				? sanitize_key( $raw['thumbnail_size'] )
				: $defaults['thumbnail_size'],

			'default_image'        => isset( $raw['default_image'] )
				? esc_url_raw( $raw['default_image'] )
				: $defaults['default_image'],

			// ── Query ─────────────────────────────────────────────────────────────
			'relate_by'            => in_array( $raw['relate_by'] ?? '', array( 'category', 'tag', 'both', 'fulltext', 'author', 'series' ), true )
				? $raw['relate_by']
				: $defaults['relate_by'],

			'post_types'           => isset( $raw['post_types'] ) && is_array( $raw['post_types'] )
				? array_values( array_filter( array_map( 'sanitize_key', $raw['post_types'] ) ) )
				: $defaults['post_types'],

			'relate_taxonomies'    => isset( $raw['relate_taxonomies'] ) && is_array( $raw['relate_taxonomies'] )
				? array_values( array_filter( array_map( 'sanitize_key', $raw['relate_taxonomies'] ) ) )
				: $defaults['relate_taxonomies'],

			'exclude_categories'   => isset( $raw['exclude_categories'] ) && is_array( $raw['exclude_categories'] )
				? array_values( array_map( 'absint', $raw['exclude_categories'] ) )
				: array(),

			'exclude_posts'        => isset( $raw['exclude_posts'] )
				? ( is_array( $raw['exclude_posts'] )
					? array_values( array_filter( array_map( 'absint', $raw['exclude_posts'] ) ) )
					: array_values( array_filter( array_map( 'absint', array_map( 'trim', explode( ',', $raw['exclude_posts'] ) ) ) ) ) )
				: $defaults['exclude_posts'],

			'min_term_overlap'     => isset( $raw['min_term_overlap'] )
				? min( 5, max( 1, absint( $raw['min_term_overlap'] ) ) )
				: $defaults['min_term_overlap'],

			'max_age_days'         => isset( $raw['max_age_days'] )
				? max( 0, absint( $raw['max_age_days'] ) )
				: $defaults['max_age_days'],

			'fallback_mode'        => in_array( $raw['fallback_mode'] ?? '', array( 'recent', 'popular', 'random', 'none' ), true )
				? $raw['fallback_mode']
				: $defaults['fallback_mode'],
			'rotate_results'       => ! empty( $raw['rotate_results'] ),
			'show_query_time'      => ! empty( $raw['show_query_time'] ),

			'show_title'           => ! empty( $raw['show_title'] ),

			// ── Cache ─────────────────────────────────────────────────────────────
			'cache_ttl'            => isset( $raw['cache_ttl'] )
				? min( 604800, max( 0, absint( $raw['cache_ttl'] ) ) )
				: $defaults['cache_ttl'],

			// ── Performance ───────────────────────────────────────────────────────
			'load_async'           => ! empty( $raw['load_async'] ),

			// ── Multisite ────────────────────────────────────────────────────────
			'multisite_search'      => ! empty( $raw['multisite_search'] ),

			// ── Analytics ─────────────────────────────────────────────────────────
			'enable_click_tracking'    => ! empty( $raw['enable_click_tracking'] ),
			'analytics_retention_days' => isset( $raw['analytics_retention_days'] )
				? min( 3650, max( 0, absint( $raw['analytics_retention_days'] ) ) )
				: $defaults['analytics_retention_days'],

			// ── Post Nudges (Pro) ─────────────────────────────────────────────────
			'nudge_enabled'            => ! empty( $raw['nudge_enabled'] ),

			'nudge_style'              => in_array( $raw['nudge_style'] ?? '', array( 'compact', 'card', 'minimal' ), true )
				? $raw['nudge_style']
				: $defaults['nudge_style'],

			'nudge_position'           => in_array( $raw['nudge_position'] ?? '', array( 'bottom-right', 'bottom-left', 'top-right', 'top-left' ), true )
				? $raw['nudge_position']
				: $defaults['nudge_position'],

			'nudge_trigger'            => in_array( $raw['nudge_trigger'] ?? '', array( 'immediate', 'scroll-50', 'exit-intent' ), true )
				? $raw['nudge_trigger']
				: $defaults['nudge_trigger'],

			'nudge_animation'          => in_array( $raw['nudge_animation'] ?? '', array( 'slide', 'fade', 'bounce', 'flip', 'zoom', 'swing', 'pop', 'rubber', 'roll', 'drop' ), true )
				? $raw['nudge_animation']
				: $defaults['nudge_animation'],

			'nudge_duration'           => isset( $raw['nudge_duration'] )
				? min( 30, max( 1, absint( $raw['nudge_duration'] ) ) )
				: $defaults['nudge_duration'],

			'nudge_interval'           => isset( $raw['nudge_interval'] )
				? min( 120, max( 5, absint( $raw['nudge_interval'] ) ) )
				: $defaults['nudge_interval'],

			'nudge_max_count'          => isset( $raw['nudge_max_count'] )
				? min( 10, max( 1, absint( $raw['nudge_max_count'] ) ) )
				: $defaults['nudge_max_count'],

			'nudge_show_thumbnail'     => ! empty( $raw['nudge_show_thumbnail'] ),
			'nudge_show_excerpt'       => ! empty( $raw['nudge_show_excerpt'] ),
			'nudge_show_date'          => ! empty( $raw['nudge_show_date'] ),
			'nudge_mobile'             => ! empty( $raw['nudge_mobile'] ),

			'nudge_post_count'         => isset( $raw['nudge_post_count'] )
				? min( 20, max( 1, absint( $raw['nudge_post_count'] ) ) )
				: $defaults['nudge_post_count'],

			'nudge_link_target'        => in_array( $raw['nudge_link_target'] ?? '', array( 'same', 'new' ), true )
				? $raw['nudge_link_target']
				: $defaults['nudge_link_target'],

			'nudge_on_posts'           => ! empty( $raw['nudge_on_posts'] ),
			'nudge_on_pages'           => ! empty( $raw['nudge_on_pages'] ),
			'nudge_on_homepage'        => ! empty( $raw['nudge_on_homepage'] ),

			'nudge_homepage_source'    => in_array( $raw['nudge_homepage_source'] ?? '', array( 'latest', 'popular', 'random', 'category', 'handpicked' ), true )
				? $raw['nudge_homepage_source']
				: $defaults['nudge_homepage_source'],

			'nudge_homepage_category'  => isset( $raw['nudge_homepage_category'] )
				? ( function ( $v ) {
						// Accept 'taxonomy:term_id' format or empty string. Reject anything else.
						$v = sanitize_text_field( (string) $v );
						if ( '' === $v ) { return ''; }
						// Backward-compat: plain integer means category taxonomy.
						if ( is_numeric( $v ) ) { return (int) $v > 0 ? (string) absint( $v ) : ''; }
						// New format: taxonomy:term_id
						if ( preg_match( '/^[a-z0-9_-]+:\d+$/', $v ) ) { return $v; }
						return '';
				} )( $raw['nudge_homepage_category'] )
				: $defaults['nudge_homepage_category'],

			'nudge_homepage_post_ids'  => isset( $raw['nudge_homepage_post_ids'] )
				? sanitize_text_field( $raw['nudge_homepage_post_ids'] )
				: $defaults['nudge_homepage_post_ids'],

			'nudge_pause_on_hover'     => ! empty( $raw['nudge_pause_on_hover'] ),

			'nudge_post_types'         => isset( $raw['nudge_post_types'] ) && is_array( $raw['nudge_post_types'] )
				? array_values( array_filter( array_map( 'sanitize_key', $raw['nudge_post_types'] ) ) )
				: $defaults['nudge_post_types'],
		);
		// Pro-only settings: force safe defaults when the Pro add-on is not active.
		if ( ! igit_rpwt_is_pro() ) {
			$sanitised['load_async']            = false;
			$sanitised['enable_click_tracking'] = false;
			$sanitised['multisite_search']      = false;
			$sanitised['rotate_results']        = false;
			// Nudges are entirely Pro — disable everything when Pro is not active.
			$sanitised['nudge_enabled'] = false;
			// Strip CPT post types — free users may only select built-in WP post types.
			$builtin_types = array( 'post', 'page', 'attachment', 'revision', 'nav_menu_item' );
			$sanitised['post_types'] = array_values( array_filter(
				$sanitised['post_types'],
				function ( $pt ) use ( $builtin_types ) {
					return in_array( $pt, $builtin_types, true );
				}
			) );
			if ( empty( $sanitised['post_types'] ) ) {
				$sanitised['post_types'] = array( 'post' ); // safe fallback
			}
			// Strip CPT-only taxonomies — keep only those registered to built-in post types.
			$sanitised['relate_taxonomies'] = array_values( array_filter(
				$sanitised['relate_taxonomies'],
				function ( $tax_slug ) use ( $builtin_types ) {
					$tax_obj = get_taxonomy( $tax_slug );
					if ( ! $tax_obj ) {
						return false;
					}
					return ! empty( array_intersect( (array) $tax_obj->object_type, $builtin_types ) );
				}
			) );
		}

		return $sanitised;	}

	/**
	 * One-time migration from the v4 legacy option.
	 *
	 * Maps old array keys to new ones and saves under the new key.
	 * The old option is intentionally left in place for rollback safety.
	 */
	public static function maybe_migrate() {
		if ( get_option( self::OPTION_KEY ) ) {
			return; // already migrated or fresh install
		}

		$legacy = get_option( self::LEGACY_OPTION_KEY );
		if ( ! $legacy || ! is_array( $legacy ) ) {
			return; // nothing to migrate
		}

		$defaults = self::defaults();

		// Map old related_post_style (1=horizontal/grid, 2=vertical/list, 3=raw/list) → layout
		$layout_map = array( '1' => 'grid', '2' => 'list', '3' => 'list' );
		$old_style  = strval( $legacy['related_post_style'] ?? '1' );

		$migrated = array(
			'heading_text'       => ! empty( $legacy['text_show'] )
				? wp_kses( $legacy['text_show'], array( 'h1' => array(), 'h2' => array(), 'h3' => array(), 'h4' => array() ) )
				: $defaults['heading_text'],

			'auto_show'          => ( isset( $legacy['auto_show'] ) && '1' === strval( $legacy['auto_show'] ) ),

			'post_count'         => ! empty( $legacy['related_post_num'] )
				? min( 20, max( 1, absint( $legacy['related_post_num'] ) ) )
				: $defaults['post_count'],

			'relate_by'          => $defaults['relate_by'],

			'exclude_categories' => isset( $legacy['exclude_cat_arr'] ) && is_array( $legacy['exclude_cat_arr'] )
				? array_values( array_map( 'absint', $legacy['exclude_cat_arr'] ) )
				: array(),

			'layout'             => $layout_map[ $old_style ] ?? $defaults['layout'],

			'show_thumbnail'     => ( ! isset( $legacy['display_thumb'] ) || '1' === strval( $legacy['display_thumb'] ) ),

			'thumbnail_size'     => $defaults['thumbnail_size'],

			'no_posts_text'      => ! empty( $legacy['no_related_post_text'] )
				? sanitize_text_field( $legacy['no_related_post_text'] )
				: $defaults['no_posts_text'],

			'default_image'      => ! empty( $legacy['default_no_image'] )
				? esc_url_raw( $legacy['default_no_image'] )
				: $defaults['default_image'],

			'cache_ttl'          => $defaults['cache_ttl'],
		);

		update_option( self::OPTION_KEY, $migrated );
	}

	/**
	 * Flush the in-memory cache (call after saving).
	 */
	public static function flush_cache() {
		self::$cache = null;
	}
}
