<?php

namespace Limb_Chatbot\Includes\Data_Objects;

use Exception;
use Limb_Chatbot\Includes\Database_Strategies\WP_Meta_Query;
use Limb_Chatbot\Includes\Services\Helper;

/**
 * Class WP_Meta_Data_Object
 *
 * Represents metadata stored against WordPress posts.
 *
 * @since 1.0.0
 */
class WP_Meta_Data_Object extends Data_Object {

	/**
	 * The unique ID of the meta record.
	 *
	 * @var mixed
	 * @since 1.0.0
	 */
	public ?int $id;

	/**
	 * The post ID this meta is associated with.
	 *
	 * @var int
	 * @since 1.0.0
	 */
	public int $post_id;

	/**
	 * The meta key.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	public string $meta_key;

	/**
	 * The meta value.
	 *
	 * @var mixed
	 * @since 1.0.0
	 */
	public $meta_value;

	/**
	 * Constructor.
	 *
	 * @param mixed|null $instance Optional instance array or object to hydrate from.
	 * @since 1.0.0
	 */
	public function __construct( $instance = null ) {
		if ( is_array( $instance ) && ! empty( $instance['meta_value'] ) && $this->isJson( $instance['meta_value'] ) ) {
			$instance['meta_value'] = Helper::maybe_json_decode( $instance['meta_value'] );
		}
		parent::__construct( $instance );
	}

	/**
	 * Save the current meta data object to the database.
	 *
	 * @return void
	 * @throws Exception
	 * @since 1.0.0
	 */
    public function save() {
		$data          = (array) $this;
		$data          = array_map( function ( $item ) {
			if ( ! is_array( $item ) && ! is_object( $item ) ) {
				return $item;
			} else {
				return json_encode( $item );
			}
		}, $data );
		$should_create = empty( $this->id );
		$should_create = self::where( [
				'post_id'  => $this->post_id,
				'meta_key' => $this->meta_key
			] )->is_empty() && $should_create;
		if ( $should_create ) {
			$this->set_data( static::get_db_strategy()->create( $data ) );
		} else {
			$where = [ 'post_id' => $this->post_id, 'meta_key' => $this->meta_key ];
			$this->set_data( static::get_db_strategy()->update( $where, $data ) );
		}
		$this->hydrate();
	}

	/**
	 * Create a new meta record or update the existing one.
	 *
	 * @param array $data Meta data to save.
	 * @return static
	 * @throws Exception
	 * @since 1.0.0
	 */
	public static function create( $data ) {
		return self::update( [ 'post_id' => $data['post_id'], 'meta_key' => $data['meta_key'] ], $data );
	}

	/**
	 * Update an existing meta record or create it if not found.
	 *
	 * @param array $where Conditions to identify the record.
	 * @param array $data  Data to update.
	 * @return static
	 * @throws Exception If the update fails and the record is not found.
	 * @since 1.0.0
	 */
	public static function update( $where, $data ) {
		static::get_db_strategy()->update( $where, $data );
		// Basically it's hard to count on update method result, cause when item is failed to update or duplication sent it's the same case
		$metas = self::where( [ 'post_id' => $where['post_id'], 'meta_key' => $where['meta_key'] ] );
		if ( ! $metas->is_empty() ) {
			return $metas->first();
		}
		throw new Exception( esc_html__( 'Failed to update meta!', 'limb-chatbot' ) );
	}


	/**
	 * Get the database strategy.
	 *
	 * @return WP_Meta_Query
	 * @since 1.0.0
	 */
	public static function get_db_strategy(): WP_Meta_Query {
		return WP_Meta_Query::instance();
	}

	/**
	 * Get the meta key.
	 *
	 * @return string
	 * @since 1.0.0
	 */
	public function get_meta_key(): string {
		return $this->meta_key;
	}

	/**
	 * Set the meta key.
	 *
	 * @param string $meta_key
	 * @return void
	 * @since 1.0.0
	 */
	public function set_meta_key( string $meta_key ): void {
		$this->meta_key = $meta_key;
	}

	/**
	 * Get the meta value.
	 *
	 * @return mixed
	 * @since 1.0.0
	 */
	public function get_meta_value() {
		return $this->meta_value;
	}

	/**
	 * Set the meta value.
	 *
	 * @param mixed $meta_value
	 * @return void
	 * @since 1.0.0
	 */
	public function set_meta_value( $meta_value ): void {
		$this->meta_value = $meta_value;
	}

	/**
	 * Convert the object to an array representation.
	 *
	 * @param int $depth Optional depth for nested objects.
	 * @return array
	 * @since 1.0.0
	 */
	public function toArray(int $depth = 0): array {
		$parentToArray = parent::toArray();
		unset( $parentToArray['id'] );

		return $parentToArray + [
				'post_id' => $this->get_post_id()
			];
	}

	/**
	 * Get the post ID.
	 *
	 * @return int
	 * @since 1.0.0
	 */
	public function get_post_id(): int {
		return $this->post_id;
	}

	/**
	 * Set the post ID.
	 *
	 * @param int $post_id
	 * @return void
	 * @since 1.0.0
	 */
	public function set_post_id( int $post_id ): void {
		$this->post_id = $post_id;
	}
}