<?php
/**
 * Stock data collection model.
 *
 * @package flo-sync
 */

namespace NMFS\Sync\Items\Articles\Stock\Models;

use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Abstracts\Hub_API\Hub_Accounts\Models\Hub_Account_Model;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Interfaces\JsonUnserializable;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Traits\Json_Decode_Trait;

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

	/**
	 * Whether stock is kept for the article.
	 *
	 * @var bool
	 */
	public bool $is_stock_kept = false;

	/**
	 * Stock data.
	 *
	 * @var Stock_Data_Model[]
	 */
	private array $stock_data = array();

	/**
	 * Whether hub accounts have been loaded into the Stock data models.
	 *
	 * @var bool
	 */
	private bool $hub_accounts_loaded = false;

	/**
	 * Add stock data.
	 *
	 * @param int                    $hub_account_id        WP post-ID of Hub Account that this stock is for.
	 * @param float                  $quantity              Quantity of the article in stock.
	 * @param bool                   $article_exists_in_hub Whether the article exists in the Hub.
	 * @param Hub_Account_Model|null $hub_account           Hub account that this stock is for.
	 *
	 * @return void
	 */
	public function add_stock_data(
		int $hub_account_id,
		float $quantity,
		bool $article_exists_in_hub,
		?Hub_Account_Model $hub_account = null
	): void {
		$stock_data         = new Stock_Data_Model(
			$hub_account_id,
			$quantity,
			$article_exists_in_hub,
			$hub_account
		);
		$this->stock_data[] = $stock_data;

		if ( $this->hub_accounts_loaded && empty( $hub_account ) ) {
			$stock_data->load_hub_account();
		}
	}

	/**
	 * Load hub accounts for all stock data.
	 *
	 * @return void
	 */
	public function load_hub_accounts(): void {
		foreach ( $this->get_stock_data() as $stock_model ) {
			$stock_model->load_hub_account();
		}
		$this->hub_accounts_loaded = true;
	}

	/**
	 * Check if the stock data has a main account.
	 *
	 * @return bool
	 */
	public function has_main_account(): bool {
		if ( ! $this->hub_accounts_loaded ) {
			$this->load_hub_accounts();
		}

		foreach ( $this->stock_data as $stock_item ) {
			if ( $stock_item->hub_account->is_main() ) {
				return true;
			}
		}

		return true;
	}

	/**
	 * Get stock data.
	 *
	 * @return array
	 */
	public function get_stock_data(): array {
		return $this->stock_data;
	}

	/**
	 * Get all hub account WP IDs.
	 *
	 * @return int[]
	 */
	public function get_all_hub_account_wp_ids(): array {
		$hub_account_wp_ids = array();
		foreach ( $this->stock_data as $stock_item ) {
			$hub_account_wp_ids[] = $stock_item->hub_account->wp_id;
		}
		return $hub_account_wp_ids;
	}

	/**
	 * Get stock data by account ID.
	 *
	 * @param int $account_id Account ID.
	 *
	 * @return Stock_Data_Model|null
	 */
	public function get_stock_data_by_account( int $account_id ): ?Stock_Data_Model {
		foreach ( $this->stock_data as $stock_item ) {
			if ( $stock_item->hub_account_id === $account_id ) {
				return $stock_item;
			}
		}

		return null;
	}

	/**
	 * Check if the article exists in a specific Hub account.
	 *
	 * @param int $account_id Account ID.
	 *
	 * @return bool
	 */
	public function article_exists_in_account( int $account_id ): bool {
		$stock_data = $this->get_stock_data_by_account( $account_id );

		if ( ! $stock_data ) {
			return false;
		}

		return $stock_data->article_exists_in_hub;
	}

	/**
	 * Get total stock from all stock data.
	 *
	 * @return float
	 */
	public function get_total_stock(): float {
		$stock_total = 0;
		foreach ( $this->get_stock_data() as $stock_model ) {
			$stock_total += max( $stock_model->quantity, 0 );
		}

		return $stock_total;
	}

	/**
	 * Get stock for a specific hub account.
	 *
	 * @param int $hub_account_id Hub account ID.
	 *
	 * @return int|null Stock quantity, or null if not found.
	 */
	public function get_stock_quantity_for_account( int $hub_account_id ): ?int {
		$stock_data = $this->get_stock_data_by_account( $hub_account_id );

		if ( ! $stock_data ) {
			return null;
		}

		return max( $stock_data->quantity, 0 );
	}

	/**
	 * Sort stock data array by a property.
	 *
	 * @param string $sort_property Can be property of Stock_Data_Model or Hub_Account_Model.
	 *
	 * @return void
	 */
	public function sort_stock_data_array( string $sort_property ): void {
		if ( empty( $this->stock_data ) ) {
			return;
		}

		usort(
			$this->stock_data,
			function ( $a, $b ) use ( $sort_property ) {
				if ( isset( $a->$sort_property ) && isset( $b->$sort_property ) ) {
					return $a->$sort_property <=> $b->$sort_property;
				} elseif ( isset( $a->backoffice_account->$sort_property ) && isset( $b->backoffice_account->$sort_property ) ) {
					return $a->backoffice_account->$sort_property <=> $b->backoffice_account->$sort_property;
				}
				return 0;
			}
		);
	}

	/**
	 * Specify data which should be serialized to JSON
	 */
	public function jsonSerialize(): array {
		return array(
			'is_stock_kept' => $this->is_stock_kept,
			'stock_data'    => $this->stock_data,
		);
	}

	/**
	 * 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 static|null The instance of the class, or null if the data is invalid.
	 */
	public static function jsonUnserialize( array|string $data ): ?static {
		$data = self::decode_json_data( $data, array( 'stock_data' ) );

		if ( ! $data ) {
			return null;
		}

		$stock_data_models = array_map(
			fn( $stock_data ) => Stock_Data_Model::jsonUnserialize( $stock_data ),
			$data['stock_data']
		);

		$collection                = new static();
		$collection->is_stock_kept = (bool) $data['is_stock_kept'] ?? false;
		$collection->stock_data    = $stock_data_models;

		return $collection;
	}
}
