<?php

namespace Limb_Chatbot\Includes\Data_Objects;

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

/**
 * Class Meta_Data_Object
 *
 * Represents a metadata object with key-value pairs linked to some parent entity.
 *
 * @since 1.0.0
 */
class Meta_Data_Object extends WPDB_Data_Object {

	/**
	 * @var int|null Primary ID of the meta object.
	 * @since 1.0.0
	 */
	public ?int $id;

	/**
	 * @var string Meta key name.
	 * @since 1.0.0
	 */
	public string $meta_key;

	/**
	 * @var mixed Meta value. Can be string, array, object, etc.
	 * @since 1.0.0
	 */
	public $meta_value;

	/**
	 * Meta_Data_Object constructor.
	 *
	 * @param array|object|null $instance Optional data to initialize the object.
	 * @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 );
	}


	/**
	 * Find a meta object by its ID.
	 *
	 * @param int $id
	 * @return static|null
	 * @since 1.0.0
	 */
	public static function find( $id ) {
		return static::get_db_strategy()->find( $id, static::TABLE_NAME );
	}

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

	/**
	 * Delete meta records matching the given criteria.
	 *
	 * @param array $where Conditions to delete by.
	 * @return bool True if deletion succeeded.
	 * @since 1.0.0
	 */
	public static function delete( $where ): bool {
		return static::get_db_strategy()->delete( $where, static::TABLE_NAME );
	}

	/**
	 * 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( $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;
	}

	/**
	 * Save the meta object to the database.
	 *
	 * @return Meta_Data_Object
	 * @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 );
		$relation_key  = array_values( array_filter( static::FILLABLE, function ( $item ) {
			return $item !== 'id' && $item !== 'meta_key' && $item !== 'meta_value';
		} ) );
		$relation_key  = $relation_key ? $relation_key[0] : null;
		if ( $relation_key ) {
			$should_create = self::where( [
					$relation_key => $this->{$relation_key},
					'meta_key'    => $this->meta_key
				] )->is_empty()
			                 && $should_create;
		}
		if ( $should_create ) {
			$this->set_data( static::get_db_strategy()->create( $data, static::TABLE_NAME, static::FILLABLE ) );
		} else {
			$where = [ $relation_key => $this->{$relation_key}, 'meta_key' => $this->meta_key ];
			$this->set_data( static::get_db_strategy()->update( $where, $data, static::TABLE_NAME, static::FILLABLE ) );
		}
		$this->hydrate();

		return $this;
	}

	/**
	 * Create a new meta object.
	 *
	 * @param  array  $data  Data to create.
	 *
	 * @return static
	 * @throws Exception
	 * @since 1.0.0
	 */
	public static function create( $data ): Meta_Data_Object {
		if ( is_array( $data['meta_value'] ) || is_object( $data['meta_value'] ) ) {
			$data['meta_value'] = json_encode( $data['meta_value'] );
		}

		return static::make( static::get_db_strategy()->create( $data, static::TABLE_NAME, static::FILLABLE ) );
	}

	/**
	 * Update existing meta or create if none found.
	 *
	 * @param  array  $where  Conditions to find existing meta.
	 * @param  array  $data  Data to update or create.
	 *
	 * @return static
	 * @throws Exception
	 * @since 1.0.0
	 */
	public static function update( $where, $data ) {
		// Serialize meta_value if it's an array or object
		if ( isset( $data['meta_value'] ) && ( is_array( $data['meta_value'] ) || is_object( $data['meta_value'] ) ) ) {
			$data['meta_value'] = json_encode( $data['meta_value'] );
		}

		$metas = self::where( $where );
		if ( ! $metas->is_empty() ) {
			return static::make( static::get_db_strategy()->update( $where, $data, static::TABLE_NAME ) );
		} else {
			unset( $where['id'] );
			$data = array_merge( $where, $data );

			return static::make( static::get_db_strategy()->create( $data, static::TABLE_NAME, static::FILLABLE ) );
		}
	}
}