<?php
/**
 * Thread.
 *
 * Represents a single thread instance with its state operations.
 * Created by Thread_Manager via get_or_create_thread() or get_thread().
 *
 * @package   Agenistrator
 * @since     1.0.0
 * @author    Alex Ruban <alex@maximum.guru>
 * @author    Maximum Software Inc <contact@maximum.software>
 * @license   GPL-3.0-only
 * @link      https://maximum.software/
 *
 * SPDX-FileCopyrightText: 2025-2026 Alex Ruban <alex@maximum.guru>
 * SPDX-FileCopyrightText: 2025-2026 Maximum Software Inc <contact@maximum.software>
 * SPDX-License-Identifier: GPL-3.0-only
 */

namespace Agenistrator;

if ( ! defined( 'ABSPATH' ) ) {
	return;
}

/**
 * Thread class.
 */
class Thread {

	/**
	 * Thread ID (primary key)
	 *
	 * @var int
	 */
	private $id;

	/**
	 * User ID (owner of the thread)
	 *
	 * @var int
	 */
	private $user_id;

	/**
	 * Thread title
	 *
	 * @var string
	 */
	private $title;

	/**
	 * Constructor
	 *
	 * @param int    $id      The thread ID.
	 * @param int    $user_id The user ID.
	 * @param string $title   The thread title.
	 * @since     1.0.0
	 */
	public function __construct( $id, $user_id, $title ) {
		$this->id      = (int) $id;
		$this->user_id = (int) $user_id;
		$this->title   = $title;
	}

	/**
	 * Get the thread ID
	 *
	 * @return int
	 * @since     1.0.0
	 */
	public function get_id() {
		return $this->id;
	}

	/**
	 * Get the user ID
	 *
	 * @return int
	 * @since     1.0.0
	 */
	public function get_user_id() {
		return $this->user_id;
	}

	/**
	 * Get the thread title
	 *
	 * @return string
	 * @since     1.0.0
	 */
	public function get_title() {
		return $this->title;
	}

	/**
	 * Get the database table name
	 *
	 * @return string
	 */
	private function get_table() {
		return Thread_Manager::get_instance()->get_table();
	}

	/**
	 * Get imported tools for this thread
	 *
	 * @return array Array of imported tool IDs.
	 * @since     1.0.0
	 */
	public function get_imported_tools() {
		global $wpdb;
		$table_name = $this->get_table();

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table; caching not applicable; table name is a hardcoded string literal with a trusted prefix, not user input.
		$value = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT imported_tools FROM {$table_name} WHERE id = %d", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is hardcoded
				$this->id
			)
		);

		if ( empty( $value ) ) {
			return array();
		}

		$tools = json_decode( $value, true );
		return is_array( $tools ) ? $tools : array();
	}

	/**
	 * Add imported tools to this thread
	 *
	 * @param array $tool_ids Array of tool IDs to add.
	 * @return bool Success.
	 * @since     1.0.0
	 */
	public function add_imported_tools( $tool_ids ) {
		$current = $this->get_imported_tools();
		$merged  = array_values( array_unique( array_merge( $current, $tool_ids ) ) );
		return $this->set_imported_tools( $merged );
	}

	/**
	 * Remove imported tools from this thread
	 *
	 * @param array $tool_ids Array of tool IDs to remove.
	 * @return bool Success.
	 * @since     1.0.0
	 */
	public function remove_imported_tools( $tool_ids ) {
		$current  = $this->get_imported_tools();
		$filtered = array_values( array_diff( $current, $tool_ids ) );
		return $this->set_imported_tools( $filtered );
	}

	/**
	 * Set imported tools for this thread
	 *
	 * @param array $tool_ids Array of tool IDs.
	 * @return bool Success.
	 */
	private function set_imported_tools( $tool_ids ) {
		global $wpdb;
		$table_name = $this->get_table();

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table; caching not applicable.
		$result = $wpdb->update(
			$table_name,
			array( 'imported_tools' => wp_json_encode( $tool_ids ) ),
			array( 'id' => $this->id ),
			array( '%s' ),
			array( '%d' )
		);

		return false !== $result;
	}

	/**
	 * Get content guard for this thread
	 *
	 * Returns existing guard or generates and stores a new one.
	 * The guard persists for the thread lifetime to enable LLM token caching.
	 *
	 * @return string Content guard string.
	 * @since     1.0.0
	 */
	public function get_content_guard() {
		global $wpdb;
		$table_name = $this->get_table();

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table; caching not applicable; table name is a hardcoded string literal with a trusted prefix, not user input.
		$value = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT content_guard FROM {$table_name} WHERE id = %d", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is hardcoded
				$this->id
			)
		);

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

		return $this->reset_content_guard();
	}

	/**
	 * Reset content guard for this thread
	 *
	 * Generates and stores a new content guard. Called when thread is cleared
	 * to ensure the guard changes, preventing prompt injection attacks.
	 *
	 * @return string The new content guard.
	 * @since     1.0.0
	 */
	public function reset_content_guard() {
		global $wpdb;
		$table_name = $this->get_table();

		$guard = Content_Guard::generate_guard();

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table; caching not applicable.
		$wpdb->update(
			$table_name,
			array( 'content_guard' => $guard ),
			array( 'id' => $this->id ),
			array( '%s' ),
			array( '%d' )
		);

		return $guard;
	}

	/**
	 * Get loop state for this thread
	 *
	 * @return array|null Loop state array or null if no state exists.
	 * @since     1.0.0
	 */
	public function get_loop_state() {
		global $wpdb;
		$table_name = $this->get_table();

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table; caching not applicable; table name is a hardcoded string literal with a trusted prefix, not user input.
		$value = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT loop_state FROM {$table_name} WHERE id = %d", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is hardcoded
				$this->id
			)
		);

		if ( null === $value || '' === $value ) {
			return null;
		}

		$state = json_decode( $value, true );
		return is_array( $state ) ? $state : null;
	}

	/**
	 * Set loop state for this thread
	 *
	 * @param array $state Loop state array (ttl, iteration, consecutive_empty_responses).
	 * @return bool Success.
	 * @since     1.0.0
	 */
	public function set_loop_state( $state ) {
		global $wpdb;
		$table_name = $this->get_table();

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table; caching not applicable.
		$result = $wpdb->update(
			$table_name,
			array( 'loop_state' => wp_json_encode( $state ) ),
			array( 'id' => $this->id ),
			array( '%s' ),
			array( '%d' )
		);

		return false !== $result;
	}

	/**
	 * Clear loop state for this thread
	 *
	 * @return bool Success.
	 * @since     1.0.0
	 */
	public function clear_loop_state() {
		global $wpdb;
		$table_name = $this->get_table();

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table; caching not applicable.
		$result = $wpdb->update(
			$table_name,
			array( 'loop_state' => null ),
			array( 'id' => $this->id ),
			array( '%s' ),
			array( '%d' )
		);

		return false !== $result;
	}

	/**
	 * Clear thread state
	 *
	 * Resets imported_tools and loop_state, regenerates content_guard.
	 *
	 * @return bool Success.
	 * @since     1.0.0
	 */
	public function clear() {
		global $wpdb;
		$table_name = $this->get_table();

		$guard = Content_Guard::generate_guard();

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table; caching not applicable.
		$result = $wpdb->update(
			$table_name,
			array(
				'imported_tools' => null,
				'loop_state'     => null,
				'content_guard'  => $guard,
			),
			array( 'id' => $this->id ),
			array( '%s', '%s', '%s' ),
			array( '%d' )
		);

		return false !== $result;
	}
}
