<?php
/**
 * BitForgeNet Guest Tickets - Settings store.
 *
 * @package BitForgeNet_Guest_Tickets
 * @since   1.0.0
 */

defined( 'ABSPATH' ) || exit;

/**
 * Typed access to the plugin option array.
 */
class BFNET_Settings {

	const OPTION = 'bfnet_settings';

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

	/**
	 * Default settings.
	 *
	 * @return array
	 */
	public static function defaults() {
		return array(
			// Branding.
			'brand_name'              => 'BitForgeNet Guest Tickets',
			'brand_primary'           => '#6423E2',
			'brand_secondary'         => '#012254',
			'brand_accent'            => '#B89E5D',
			'logo_attachment_id'      => 0,

			// Commerce.
			'currency'                => 'USD',
			'currency_symbol'         => '$',
			'currency_position'       => 'prefix',
			'thousand_separator'      => ',',
			'decimal_separator'       => '.',
			'decimals'                => 2,

			// Registration.
			'default_max_per_order'   => 10,
			'collect_guest_names'     => 1,
			'require_phone'           => 1,
			'require_terms'           => 1,
			'terms_url'               => '',
			'privacy_url'             => '',
			'terms_page_id'           => 0,
			'regulation_page_id'      => 0,
			'privacy_page_id'         => 0,
			'registration_page_id'    => 0,
			'success_message'         => 'Thank you. Your reservation has been recorded. We will be in touch to confirm your payment, and your tickets are issued the moment it is.',

			// Anti-abuse.
			'rate_limit_per_hour'     => 8,
			'min_form_seconds'        => 3,
			'enable_honeypot'         => 1,
			'block_disposable_email'  => 0,

			// Payments.
			'payment_mode'            => 'manual',
			'whatsapp_number'         => '',
			'payment_instructions'    => 'Get in touch to arrange payment. Your tickets are emailed the moment it is confirmed.',
			'auto_issue_on_paid'      => 1,

			// Email.
			'email_from_name'         => 'BitForgeNet Guest Tickets',
			'email_from_address'      => '',
			'email_reply_to'          => '',
			'email_sender_mode'       => 'inherit',
			'email_bcc'               => '',
			'email_footer'            => 'See you at the door.',
			'send_pending_email'      => 1,
			'send_paid_email'         => 1,
			'attach_individual_pdfs'  => 0,
			'attach_combined_pdf'     => 1,
			'email_download_links'    => 1,
			'email_max_attachment_mb' => 4,
			'download_link_days'      => 30,

			// Tickets.
			'ticket_prefix'           => 'BFNET',
			'qr_ecc'                  => 'Q',
			'qr_endpoint'             => 'bfnet-verify',
			'door_endpoint'           => 'bfnet-door',
			'ticket_width_px'         => 1181,
			'ticket_height_px'        => 2953,
			'ticket_dpi'              => 300,
			'pdf_quality'             => 80,
			'default_template_id'     => 0,

			// Check-in.
			'allow_multi_scan'        => 0,
			'checkin_window_before'   => 240,
			'checkin_window_after'    => 360,
			'gate_names'              => "Main entrance\nVIP entrance",
			'scan_sound'              => 1,

			// System.
			'reset_authorised_email'   => '',
			'delete_data_on_uninstall' => 0,
			'log_retention_days'       => 365,
			'debug_mode'               => 0,
		);
	}

	/**
	 * Retrieve all settings merged with defaults.
	 *
	 * @return array
	 */
	public static function all() {
		if ( null === self::$cache ) {
			$stored      = get_option( self::OPTION, array() );
			self::$cache = wp_parse_args( is_array( $stored ) ? $stored : array(), self::defaults() );
		}
		return self::$cache;
	}

	/**
	 * Retrieve one setting.
	 *
	 * @param string $key     Setting key.
	 * @param mixed  $default Fallback value.
	 * @return mixed
	 */
	public static function get( $key, $default = null ) {
		$all = self::all();
		if ( array_key_exists( $key, $all ) ) {
			return $all[ $key ];
		}
		return $default;
	}

	/**
	 * Persist a set of settings after sanitising.
	 *
	 * @param array $values Raw values.
	 * @return array The stored settings.
	 */
	public static function save( array $values ) {
		$current  = self::all();
		$defaults = self::defaults();
		$clean    = $current;

		foreach ( $values as $key => $value ) {
			if ( ! array_key_exists( $key, $defaults ) ) {
				continue;
			}
			$clean[ $key ] = self::sanitize_value( $key, $value, $defaults[ $key ] );
		}

		update_option( self::OPTION, $clean );
		self::$cache = $clean;

		return $clean;
	}

	/**
	 * Sanitise a single setting value according to its default's type.
	 *
	 * @param string $key     Setting key.
	 * @param mixed  $value   Raw value.
	 * @param mixed  $default Default value (used for type inference).
	 * @return mixed
	 */
	private static function sanitize_value( $key, $value, $default ) {
		$url_keys   = array( 'terms_url', 'privacy_url' );
		$color_keys = array( 'brand_primary', 'brand_secondary', 'brand_accent' );
		$text_areas = array( 'payment_instructions', 'success_message', 'email_footer', 'gate_names' );

		if ( in_array( $key, $url_keys, true ) ) {
			return esc_url_raw( trim( (string) $value ) );
		}
		if ( in_array( $key, $color_keys, true ) ) {
			$color = sanitize_hex_color( trim( (string) $value ) );
			return $color ? $color : $default;
		}
		if ( 'email_sender_mode' === $key ) {
			$mode = sanitize_key( (string) $value );

			/**
			 * The delivery routes on offer.
			 *
			 * Core knows one: let the site send it. Every other route is a module
			 * that adds itself here, so a build without those modules accepts
			 * exactly the one answer it can honour.
			 *
			 * @param array $modes Mode key => label.
			 */
			$modes = apply_filters( 'bfnet_sender_modes', array( 'inherit' => __( 'Let the site decide', 'bitforgenet-guest-tickets' ) ) );

			return array_key_exists( $mode, $modes ) ? $mode : 'inherit';
		}
		if ( 'email_from_address' === $key || 'email_bcc' === $key || 'email_reply_to' === $key || 'reset_authorised_email' === $key ) {
			$email = sanitize_email( trim( (string) $value ) );
			return $email ? $email : '';
		}
		if ( in_array( $key, $text_areas, true ) ) {
			return sanitize_textarea_field( (string) $value );
		}
		if ( 'currency' === $key ) {
			return strtoupper( substr( preg_replace( '/[^A-Za-z]/', '', (string) $value ), 0, 3 ) );
		}
		if ( 'qr_ecc' === $key ) {
			$level = strtoupper( substr( (string) $value, 0, 1 ) );
			return in_array( $level, array( 'L', 'M', 'Q', 'H' ), true ) ? $level : 'Q';
		}
		if ( is_int( $default ) ) {
			return (int) $value;
		}

		return sanitize_text_field( (string) $value );
	}

	/**
	 * Reset the runtime cache (used after direct option writes).
	 *
	 * @return void
	 */
	public static function flush() {
		self::$cache = null;
	}
}
