<?php
/**
 * Notification persistence.
 *
 * The plugin displays one configurable notification, so its configuration is
 * stored as a single site option rather than as a record in a multi-row table.
 *
 * @package VoidPulse
 */

namespace VoidPulse\Infrastructure;

use VoidPulse\Shared\CampaignSchema;

defined( 'ABSPATH' ) || exit;

final class NotificationSettings {
	const OPTION = 'voidpulse_notification';

	const CACHE_KEY = 'notification';

	/**
	 * Return the stored notification, or null when none has been saved yet.
	 *
	 * @return array<string,mixed>|null
	 */
	public function get() {
		$cached = wp_cache_get( self::CACHE_KEY, 'voidpulse' );
		if ( false !== $cached ) {
			return 'none' === $cached ? null : $cached;
		}
		$stored = get_option( self::OPTION, null );
		$value  = is_array( $stored ) && isset( $stored['campaign'] ) && is_array( $stored['campaign'] ) ? $this->hydrate( $stored ) : null;
		wp_cache_set( self::CACHE_KEY, null === $value ? 'none' : $value, 'voidpulse', 60 );
		return $value;
	}

	/**
	 * Validate and store the notification.
	 *
	 * @param array<string,mixed> $campaign Campaign data.
	 * @param int                 $user_id User ID.
	 * @return true|\WP_Error
	 */
	public function save( $campaign, $user_id ) {
		$validated = CampaignSchema::validate( CampaignSchema::strip_metadata( $campaign ) );
		if ( is_wp_error( $validated ) ) {
			return $validated;
		}
		$now      = gmdate( 'Y-m-d H:i:s' );
		$existing = get_option( self::OPTION, null );
		$existing = is_array( $existing ) ? $existing : array();
		$record   = array(
			'campaign'     => $validated,
			'created_by'   => isset( $existing['created_by'] ) ? absint( $existing['created_by'] ) : absint( $user_id ),
			'created_at'   => isset( $existing['created_at'] ) ? (string) $existing['created_at'] : $now,
			'updated_at'   => $now,
			'published_at' => 'active' === $validated['status'] ? $now : ( isset( $existing['published_at'] ) ? $existing['published_at'] : null ),
		);
		/*
		 * update_option() also returns false when the stored value is already
		 * identical, so compare the stored record before reporting a failure.
		 */
		$written = update_option( self::OPTION, $record, false );
		wp_cache_delete( self::CACHE_KEY, 'voidpulse' );
		if ( ! $written && get_option( self::OPTION, null ) !== $record ) {
			return new \WP_Error( 'voidpulse_save_failed', __( 'The campaign could not be saved.', 'voidpulse-social-proof-for-woocommerce' ) );
		}
		return true;
	}

	/**
	 * Merge stored values over current defaults.
	 *
	 * @param array<string,mixed> $stored Stored record.
	 * @return array<string,mixed>
	 */
	private function hydrate( $stored ) {
		$defaults = CampaignSchema::defaults();

		/*
		 * Surface only the settings this plugin actually defines. Stored data
		 * written by a different edition of the notification editor can carry
		 * settings that have no meaning here, and those must never reach the
		 * editor or the storefront.
		 */
		$configuration = array_intersect_key( $stored['campaign'], $defaults );

		foreach ( array( 'timing', 'orders', 'style', 'display' ) as $group ) {
			$configuration[ $group ] = array_replace(
				$defaults[ $group ],
				isset( $configuration[ $group ] ) && is_array( $configuration[ $group ] )
					? array_intersect_key( $configuration[ $group ], $defaults[ $group ] )
					: array()
			);
		}

		$performance = isset( $configuration['performance'] ) && is_array( $configuration['performance'] )
			? array_intersect_key( $configuration['performance'], $defaults['performance'] )
			: array();
		if ( ! array_key_exists( 'show_to_admins', $performance ) && array_key_exists( 'disable_for_admins', $performance ) ) {
			$performance['show_to_admins'] = ! rest_sanitize_boolean( $performance['disable_for_admins'] );
		}
		$configuration['performance'] = array_replace( $defaults['performance'], $performance );

		return array_merge(
			$defaults,
			$configuration,
			array(
				'created_at'   => isset( $stored['created_at'] ) ? (string) $stored['created_at'] : '',
				'updated_at'   => isset( $stored['updated_at'] ) ? (string) $stored['updated_at'] : '',
				'published_at' => isset( $stored['published_at'] ) ? $stored['published_at'] : null,
			)
		);
	}
}
