<?php

namespace Limb_Chatbot\Includes\Data_Objects;

use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Services\Helper;
use WP_Post;
use WP_Term;

/**
 * Class Vector
 *
 * Represents a vector data object with embedding values and related metadata.
 *
 * @since 1.0.0
 */
class Vector extends WPDB_Data_Object {

	/**
	 * Table name in the database.
	 *
	 * @since 1.0.0
	 */
	const TABLE_NAME = 'lbaic_vectors';

	/**
	 * Fields fillable during mass assignment.
	 *
	 * @since 1.0.0
	 */
	const FILLABLE = array( 'uuid', 'values', 'status', 'dimension', 'dataset_entry_id', 'ai_model_id', 'config_id', 'access_roles', 'created_at', 'updated_at' );

	/**
	 * Active status
	 *
	 * @since 1.0.0
	 */
	const STATUS_ACTIVE = 1;

	/**
	 * Inactive status
	 *
	 * @since 1.0.0
	 */
	const STATUS_INACTIVE = 2;

	/**
	 * A parameter for keeping the current input temporary
	 *
	 * @var
	 */
	public $input;

	/**
	 * Unique identifier for the vector.
	 *
	 * @var string
	 */
	public string $uuid;

	/**
	 * Vector embedding values.
	 *
	 * @var array|null
	 * @json_excluded
	 * @since 1.0.0
	 */
	public ?array $values = null;


	/**
	 * Dimension of the vector.
	 *
	 * @var int|null
	 */
	public ?int $dimension = null;


	/**
	 * Dataset entry ID associated with this vector.
	 *
	 * @var int|null
	 *
	 * @since 1.0.0
	 */
	public ?int $dataset_entry_id = null;

	/**
	 * AI model ID associated with this vector.
	 *
	 * @var int|null
	 */
	public ?int $ai_model_id = null;

	/**
	 * Configuration ID related to this vector.
	 *
	 * @var int|null
	 */
	public ?int $config_id = null;
	/**
	 * Access role slugs allowed to use this vector.
	 *
	 * @var array|null
	 * @since 2.0.0
	 */
	public ?array $access_roles = null;

	/**
	 * Creation timestamp.
	 *
	 * @var string
	 */
	public string $created_at;

	/**
	 * Update timestamp.
	 *
	 * @var string
	 */
	public string $updated_at;
	/**
	 * Similarity or relevance score.
	 *
	 * @var float|null
	 * @json_excluded
	 * @since 1.0.0
	 */
	public ?float $score = null;

	/**
	 * Token usage associated with this vector.
	 *
	 * @var Token_Usage|null
	 * @json_excluded
	 * @since 1.0.0
	 */
	protected ?Token_Usage $usage = null;

	/**
	 * List of meta properties to be excluded from JSON serialization.
	 *
	 * @var array
	 */
	protected array $meta_properties = [ 'vector_index', 'dataset_entry' ];

	/**
	 * Included properties for serialization exclusion.
	 *
	 * @var array
	 * @json_excluded
	 */
	public array $included = [];

	/**
	 * Vector status (active/inactive)
	 *
	 * @var int|null
	 */
	public ?int $status = null;

	/**
	 * Vector constructor.
	 *
	 * @param  mixed|null  $instance  array or object to initialize from.
	 *
	 * @since 1.0.0
	 */
	public function __construct( $instance = null ) {
		if ( is_array( $instance ) && ! empty( $instance['values'] ) && $this->isJson( $instance['values'] ) ) {
			$instance['values'] = Helper::maybe_json_decode( $instance['values'] );
		} elseif ( is_object( $instance ) && ! empty( $instance->values ) && $this->isJson( $instance->values ) ) {
			$instance->values = Helper::maybe_json_decode( $instance->values );
		}
		if ( is_array( $instance ) && array_key_exists( 'access_roles', $instance ) ) {
			$instance['access_roles'] = $this->normalize_access_roles_value( $instance['access_roles'] );
		} elseif ( is_object( $instance ) && property_exists( $instance, 'access_roles' ) ) {
			$instance->access_roles = $this->normalize_access_roles_value( $instance->access_roles );
		}
		parent::__construct( $instance );
	}

	/**
	 * Normalize stored access_roles payload into a plain array.
	 *
	 * Handles native arrays, JSON arrays, serialized PHP arrays, JSON strings,
	 * and comma-separated fallbacks from older rows.
	 *
	 * @param  mixed  $value
	 *
	 * @return array|null
	 */
	private function normalize_access_roles_value( $value ): ?array {
		if ( null === $value || false === $value || '' === $value ) {
			return null;
		}

		if ( is_string( $value ) ) {
			$value = maybe_unserialize( $value );
		}

		if ( is_string( $value ) ) {
			$decoded = Helper::maybe_json_decode( $value );
			if ( is_array( $decoded ) ) {
				$value = $decoded;
			} elseif ( is_string( $decoded ) && '' !== trim( $decoded ) ) {
				$value = str_contains( $decoded, ',' ) ? array_map( 'trim', explode( ',', $decoded ) ) : [ $decoded ];
			}
		}

		if ( ! is_array( $value ) ) {
			return null;
		}

		$value = array_values(
			array_filter(
				array_unique(
					array_map(
						static function ( $role ): string {
							return is_string( $role ) ? sanitize_key( $role ) : '';
						},
						$value
					)
				)
			)
		);

		return empty( $value ) ? null : $value;
	}

	/**
	 * Find a vector by its UUID.
	 *
	 * @param  string  $uuid  UUID string to find.
	 *
	 * @return static|null Vector object or null if not found.
	 * @since 1.0.0
	 */
	public static function find_by_uuid( string $uuid ): ?Vector {
		return self::where( [ 'uuid' => $uuid ] )->first();
	}

	/**
	 * Save the vector to the database.
	 *
	 * If object_id is set, input will be set to null before saving.
	 *
	 * @return Vector
	 * @throws \Exception
	 * @since 1.0.0
	 */
	public function save() {
		return parent::save();
	}

	/**
	 * Get UUID.
	 *
	 * @return string UUID string.
	 * @since 1.0.0
	 */
	public function get_uuid(): string {
		return $this->uuid;
	}

	/**
	 * Set UUID.
	 *
	 * @param  string  $uuid  UUID string.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	public function set_uuid( string $uuid ): void {
		$this->uuid = $uuid;
	}

	/**
	 * Get embedding values.
	 *
	 * @return array|null Array of float values or null.
	 * @since 1.0.0
	 */
	public function get_values(): ?array {
		return $this->values;
	}

	/**
	 * Set embedding values.
	 *
	 * @param  array|null  $values  Array of floats or null.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	public function set_values( ?array $values ): void {
		$this->values = $values;
	}

	/**
	 * Get vector dimension.
	 *
	 * @return int|null Dimension or null.
	 * @since 1.0.0
	 */
	public function get_dimension(): ?int {
		return $this->dimension;
	}

	/**
	 * Set vector dimension.
	 *
	 * @param  int|null  $dimension  Dimension.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	public function set_dimension( ?int $dimension ): void {
		$this->dimension = $dimension;
	}

	/**
	 * Get input text/data.
	 *
	 * @return string|null Input string or null.
	 * @since 1.0.0
	 */
	public function get_input(): ?string {
		if ( $this->input && empty( $this->id ) ) {
			return $this->input;
		}
		$dataset_entry = $this->dataset_entry();

		return $dataset_entry->stringify();
	}

	public function set_input($input){
		$this->input = $input;
	}

	/**
	 * Get associated dataset entry.
	 *
	 * @return Dataset_Entry|null The dataset entry object or null.
	 * @since 1.0.0
	 */
	public function dataset_entry(){
		return Dataset_Entry::find($this->get_dataset_entry_id());
	}


	/**
	 * Get AI model ID.
	 *
	 * @return int|null AI model ID or null.
	 * @since 1.0.0
	 */
	public function get_ai_model_id(): ?int {
		return $this->ai_model_id;
	}

	/**
	 * Set AI model ID.
	 *
	 * @param  int|null  $ai_model_id  AI model ID.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	public function set_model_id( ?int $ai_model_id ): void {
		$this->ai_model_id = $ai_model_id;
	}

	/**
	 * Get config ID.
	 *
	 * @return int|null Config ID or null.
	 * @since 1.0.0
	 */
	public function get_config_id(): ?int {
		return $this->config_id;
	}

	/**
	 * Set config ID.
	 *
	 * @param  int|null  $config_id  Config ID.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	public function set_config_id( ?int $config_id ): void {
		$this->config_id = $config_id;
	}

	/**
	 * Get access role slugs.
	 *
	 * @return array|null
	 * @since 2.0.0
	 */
	public function get_access_roles(): ?array {
		return $this->access_roles;
	}

	/**
	 * Set access role slugs.
	 *
	 * @param  array|null  $access_roles
	 *
	 * @return void
	 * @since 2.0.0
	 */
	public function set_access_roles( ?array $access_roles ): void {
		$this->access_roles = $access_roles;
	}

	/**
	 * Get created at timestamp.
	 *
	 * @return string Creation datetime.
	 * @since 1.0.0
	 */
	public function get_created_at(): string {
		return $this->created_at;
	}

	/**
	 * Set created at timestamp.
	 *
	 * @param  string  $created_at  Creation datetime.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	public function set_created_at( $created_at ): void {
		$this->created_at = $created_at;
	}

	/**
	 * Get updated at timestamp.
	 *
	 * @return string Update datetime.
	 * @since 1.0.0
	 */
	public function get_updated_at(): string {
		return $this->updated_at;
	}

	/**
	 * Set updated at timestamp.
	 *
	 * @param  string  $updated_at  Update datetime.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	/**
	 * Get related AI model.
	 *
	 * @return AI_Model|null The AI model object or null.
	 * @since 1.0.0
	 */
	public function get_ai_model(): ?AI_Model {
		return AI_Model::find( $this->ai_model_id );
	}

	/**
	 * Get related config.
	 *
	 * @return Config|null The config object or null.
	 * @since 1.0.0
	 */
	public function get_config(): ?Config {
		return Config::find( $this->config_id );
	}

	/**
	 * Get associated vector indexes.
	 *
	 * @return array|null List of Vector_Index objects or null.
	 * @since 1.0.0
	 */
	public function vector_index(): ?array {
		$ids = Vector_Index_Vector::where( [ 'vector_id' => $this->get_id() ] );
		if ( $ids->is_empty() ) {
			return null;
		}
		$ids = $ids->map( function ( $item ) {
			return $item->get_vector_index_id();
		} )->get();

		return Vector_Index::where( [ 'id' => $ids ] )->get();
	}

	/**
	 * Set usage stats.
	 *
	 * @param  Token_Usage|null  $usage  Token usage object.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	public function set_usage( ?Token_Usage $usage ): void {
		$this->usage = $usage;
	}

	/**
	 * Get usage stats.
	 *
	 * @return Token_Usage|null Token usage object or null.
	 * @since 1.0.0
	 */
	public function get_usage(): ?Token_Usage {
		return $this->usage;
	}

	/**
	 * Get the ID of the dataset entry associated with this object.
	 *
	 * @since 1.0.0
	 *
	 * @return int|null The dataset entry ID, or null if not set.
	 */
	public function get_dataset_entry_id(): ?int {
		return $this->dataset_entry_id;
	}

	/**
	 * Set the ID of the dataset entry associated with this object.
	 *
	 * @since 1.0.0
	 *
	 * @param  int|null  $dataset_entry_id  The dataset entry ID to set.
	 * @return void
	 */
	public function set_dataset_entry_id( ?int $dataset_entry_id ): void {
		$this->dataset_entry_id = $dataset_entry_id;
	}

	/**
	 * Set the current status of this object.
	 *
	 * @param  mixed  $status  The status value to set.
	 *
	 * @return void
	 * @since 1.0.0
	 *
	 */
	public function set_status( $status ) {
		$this->status = $status;
	}

	/**
	 * Get the current status of this object.
	 *
	 * @return int|null The current status value.
	 * @since 1.0.0
	 *
	 */
	public function get_status() {
		return $this->status;
	}
}