<?php
/**
 * Class representing an article availability.
 *
 * @package flo-sync
 */

namespace NMFS\Sync\Items\Article_Availabilities\Models;

use InvalidArgumentException;
use NMFS\Shared;
use NMFS\Sync\Items\Article_Availabilities\Factory_DB;
use NMFS\Sync\Items\Article_Availabilities\Models\Items\Item_Model;
use NMFS\Sync\Items\Article_Availabilities\Models\Schedules\Schedule_Interface;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Interfaces\JsonUnserializable;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Modules\Loggers\Log_Level_Enum;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Traits\Json_Decode_Trait;

/**
 * Class Article_Availability_Model
 */
class Article_Availability_Model implements JsonUnserializable {
	use Json_Decode_Trait;

	/**
	 * ID in Hub.
	 *
	 * @var string $hub_id
	 */
	public string $hub_id;

	/**
	 * ID in WP.
	 * If the article availability is new, this is null.
	 *
	 * @var int|null
	 */
	public ?int $wp_id;

	/**
	 * Name of the article availability.
	 *
	 * @var string
	 */
	public string $name;

	/**
	 * Whether the article availability is enabled.
	 *
	 * @var bool
	 */
	public bool $enabled;

	/**
	 * The times set up for the article availability.
	 *
	 * @var Schedule_Interface[]
	 */
	public array $schedules;

	/**
	 * The items available for the article availability.
	 *
	 * @var Item_Model[]
	 */
	public array $items;

	/**
	 * Get model for JSON encoding.
	 */
	public function jsonSerialize(): array {
		try {
			$schedules = array_map(
				function ( Schedule_Interface $schedule ) {
					return array(
						'key'  => Factory_DB::get_key_from_class( get_class( $schedule ) ),
						'data' => $schedule->jsonSerialize(),
					);
				},
				$this->schedules
			);
		} catch ( InvalidArgumentException $e ) {
			Shared::instance()->logger->log(
				'article_availabilities',
				'Failed to serialize schedules',
				array(
					'hub_id' => $this->hub_id,
					'error'  => $e->getMessage(),
				),
				Log_Level_Enum::ERROR
			);
			return array();
		}

		return array(
			'hub_id'    => $this->hub_id,
			'wp_id'     => $this->wp_id,
			'name'      => $this->name,
			'enabled'   => $this->enabled,
			'schedules' => $schedules,
			'items'     => $this->items,
		);
	}

	/**
	 * Takes a JSON string matching what is returned by jsonSerialize() and returns an instance of the class.
	 *
	 * @param string|array $data JSON data. This is either a JSON string or an already decoded array.
	 *
	 * @return Article_Availability_Model|null The instance of the class, or null if the data is invalid.
	 */
	public static function jsonUnserialize( string|array $data ): ?static {
		$data = self::decode_json_data( $data );
		if ( ! $data ) {
			return null;
		}

		try {
			$schedules = array_map(
				callback: function ( $schedule_data ) {
					$key        = $schedule_data['key'];
					$class_name = Factory_DB::get_class_from_key( $key );

					if ( ! is_subclass_of( $class_name, Schedule_Interface::class ) ) {
						throw new InvalidArgumentException( 'Mapped class does not implement Schedule_Interface: ' . esc_html( $class_name ) );
					}

					return $class_name::jsonUnserialize( $schedule_data['data'] );
				},
				array: $data['schedules']
			);
		} catch ( InvalidArgumentException $e ) {
			Shared::instance()->logger->log(
				'article_availabilities',
				'Failed to unserialize schedules',
				array(
					'hub_id' => $data['hub_id'],
					'error'  => $e->getMessage(),
				),
				Log_Level_Enum::ERROR
			);
			return null;
		}

		$instance            = new self();
		$instance->hub_id    = $data['hub_id'];
		$instance->wp_id     = $data['wp_id'] ?? null;
		$instance->name      = $data['name'];
		$instance->enabled   = $data['enabled'];
		$instance->schedules = $schedules;
		$instance->items     = array_map(
			fn( $item_data ) => Item_Model::jsonUnserialize( $item_data ),
			$data['items']
		);

		return $instance;
	}
}
