<?php

namespace Limb_Chatbot\Includes\Services;

use Limb_Chatbot\Includes\Data_Objects\Setting;
use Limb_Chatbot\Includes\Services\Helper;

/**
 * Represents and manages the global chatbot preview state.
 *
 * Stored as a single option:
 * - key: lbaic.utilities.chatbot.preview_state
 * - value: {
 *     "version": 1,
 *     "parameters": { "config_id": 4, ... },
 *     "widgets": [ ... ]
 *   }
 *
 * @since 1.0.0
 */
class Preview_State {

	const OPTION_KEY = 'utilities.chatbot.preview_state';

	/**
	 * Current state payload.
	 *
	 * @var array<string,mixed>
	 */
	private array $state;

	/**
	 * @param array<string,mixed> $state
	 */
	private function __construct( array $state ) {
		$defaults     = array(
			'version'    => 1,
			'parameters' => array(),
			'widgets'    => array(),
			// Whether widgets were explicitly overridden by admin preview UI.
			// If false, Chatbot_Payload will fall back to published widgets from DB.
			'widgets_override' => false,
		);
		$this->state = array_merge( $defaults, $state );
		if ( ! is_array( $this->state['parameters'] ) ) {
			$this->state['parameters'] = array();
		}
		if ( ! is_array( $this->state['widgets'] ) ) {
			$this->state['widgets'] = array();
		}
	}

	/**
	 * Load current preview state from options.
	 *
	 * @return self
	 */
	public static function load(): self {
		$setting = Setting::find( self::OPTION_KEY );
		$value   = $setting ? $setting->get_value() : null;

		if ( is_array( $value ) ) {
			return new self( $value );
		}

		if ( is_string( $value ) && ! empty( $value ) ) {
			$decoded = Helper::maybe_json_decode( $value );
			if ( is_array( $decoded ) ) {
				return new self( $decoded );
			}
		}

		return new self( array() );
	}

	/**
	 * Returns the option name used to store the state.
	 *
	 * @return string
	 */
	public static function get_option_name(): string {
		return Setting::SETTING_PREFIX . self::OPTION_KEY;
	}

	/**
	 * Get raw state array.
	 *
	 * @return array<string,mixed>
	 */
	public function to_array(): array {
		return $this->state;
	}

	/**
	 * Get all preview parameters.
	 *
	 * @return array<string,mixed>
	 */
	public function get_parameters(): array {
		return $this->state['parameters'];
	}

	/**
	 * Get a single parameter value if present.
	 *
	 * @param string $key
	 *
	 * @return mixed|null
	 */
	public function get_parameter( string $key ) {
		return $this->state['parameters'][ $key ] ?? null;
	}

	/**
	 * Merge parameters into the current state.
	 *
	 * @param array<string,mixed> $parameters
	 *
	 * @return self
	 */
	public function with_merged_parameters( array $parameters ): self {
		$new               = $this->state;
		$new['parameters'] = array_merge( $new['parameters'], $parameters );

		return new self( $new );
	}

	/**
	 * Replace widgets array in the current state.
	 *
	 * @param array<int,mixed> $widgets
	 *
	 * @return self
	 */
	public function with_widgets( array $widgets ): self {
		$new           = $this->state;
		$new['widgets'] = array_values( $widgets );

		return new self( $new );
	}

	/**
	 * Mark widgets override flag.
	 *
	 * @param bool $override
	 *
	 * @return self
	 */
	public function with_widgets_override( bool $override ): self {
		$new                      = $this->state;
		$new['widgets_override'] = $override;
		return new self( $new );
	}

	/**
	 * Whether widgets should override DB widgets for preview payload.
	 *
	 * @return bool
	 */
	public function widgets_override_enabled(): bool {
		return (bool) ( $this->state['widgets_override'] ?? false );
	}

	/**
	 * Get widgets payload.
	 *
	 * @return array<int,mixed>
	 */
	public function get_widgets(): array {
		return $this->state['widgets'];
	}

	/**
	 * Persist the current state into options table.
	 *
	 * @return void
	 */
	public function save(): void {
		$key   = self::get_option_name();
		$value = $this->state;

		if ( Setting::exists( $key ) ) {
			Setting::update( array( 'key' => $key ), array( 'value' => $value ) );
		} else {
			Setting::create( array( 'key' => $key, 'value' => $value ) );
		}
	}

	/**
	 * Remove preview state option entirely.
	 *
	 * @return void
	 */
	public static function clear(): void {
		$key = self::get_option_name();
		if ( Setting::exists( $key ) ) {
			Setting::delete( array( 'key' => $key ) );
		}
	}
}

