<?php
/**
 * Conversations REST API controller.
 *
 * Persists AI chat conversation history per post in wp_postmeta.
 * Conversations are stored as a JSON array under the meta key
 * `_ambld_conversations`. Each conversation contains an id, title,
 * messages array, createdAt, updatedAt, and pageScope.
 *
 * Routes:
 *   GET    /ambld/v1/conversations?post_id=123   — list conversations
 *   POST   /ambld/v1/conversations               — upsert a conversation
 *   DELETE /ambld/v1/conversations/{id}?post_id  — delete a conversation
 *
 * @package Antimanual_Builder
 * @since   1.1.0
 */

defined( 'ABSPATH' ) || exit;

/**
 * Class AMBLD_API_Conversations_Api
 *
 * @since 1.1.0
 */
class AMBLD_API_Conversations_Api {

	/**
	 * REST namespace.
	 *
	 * @var string
	 */
	private $namespace = 'ambld/v1';

	/**
	 * Post meta key used to store conversations.
	 *
	 * @var string
	 */
	const META_KEY = '_ambld_conversations';

	/**
	 * Maximum conversations stored per post.
	 *
	 * @var int
	 */
	const MAX_PER_POST = 50;

	/**
	 * Register REST routes.
	 *
	 * @since  1.1.0
	 * @return void
	 */
	public function register_routes() {
		register_rest_route(
			$this->namespace,
			'/conversations',
			array(
				array(
					'methods'             => \WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_conversations' ),
					'permission_callback' => array( $this, 'check_permission' ),
					'args'                => array(
						'post_id' => array(
							'required'          => true,
							'type'              => 'integer',
							'sanitize_callback' => 'absint',
							'validate_callback' => function ( $value ) {
								return absint( $value ) > 0;
							},
						),
					),
				),
				array(
					'methods'             => \WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'save_conversation' ),
					'permission_callback' => array( $this, 'check_permission' ),
					'args'                => array(
						'post_id'      => array(
							'required'          => true,
							'type'              => 'integer',
							'sanitize_callback' => 'absint',
							'validate_callback' => function ( $value ) {
								return absint( $value ) > 0;
							},
						),
						'conversation' => array(
							'required' => true,
							'type'     => 'object',
						),
					),
				),
			)
		);

		register_rest_route(
			$this->namespace,
			'/conversations/(?P<id>[a-zA-Z0-9_-]+)',
			array(
				'methods'             => \WP_REST_Server::DELETABLE,
				'callback'            => array( $this, 'delete_conversation' ),
				'permission_callback' => array( $this, 'check_permission' ),
				'args'                => array(
					'id'      => array(
						'required'          => true,
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_text_field',
					),
					'post_id' => array(
						'required'          => true,
						'type'              => 'integer',
						'sanitize_callback' => 'absint',
						'validate_callback' => function ( $value ) {
							return absint( $value ) > 0;
						},
					),
				),
			)
		);
	}

	/**
	 * Permission check — user must be able to edit the target post.
	 *
	 * @since  1.1.0
	 * @param  \WP_REST_Request $request REST request.
	 * @return true|\WP_Error
	 */
	public function check_permission( \WP_REST_Request $request ) {
		if ( ! is_user_logged_in() ) {
			return new \WP_Error(
				'rest_not_logged_in',
				__( 'You must be logged in.', 'antimanual-builder' ),
				array( 'status' => 401 )
			);
		}

		$post_id = absint( $request->get_param( 'post_id' ) );

		if ( $post_id > 0 && ! current_user_can( 'edit_post', $post_id ) ) {
			return new \WP_Error(
				'rest_forbidden',
				__( 'You do not have permission to edit this post.', 'antimanual-builder' ),
				array( 'status' => 403 )
			);
		}

		return true;
	}

	/**
	 * GET /conversations?post_id=123
	 *
	 * @since  1.1.0
	 * @param  \WP_REST_Request $request REST request.
	 * @return \WP_REST_Response
	 */
	public function get_conversations( \WP_REST_Request $request ) {
		$post_id = absint( $request->get_param( 'post_id' ) );
		$conversations = $this->read_conversations( $post_id );
		return new \WP_REST_Response( $conversations, 200 );
	}

	/**
	 * POST /conversations  { post_id, conversation }
	 *
	 * Upserts (inserts or updates) a single conversation.
	 *
	 * @since  1.1.0
	 * @param  \WP_REST_Request $request REST request.
	 * @return \WP_REST_Response|\WP_Error
	 */
	public function save_conversation( \WP_REST_Request $request ) {
		$post_id       = absint( $request->get_param( 'post_id' ) );
		$raw           = $request->get_param( 'conversation' );

		if ( ! is_array( $raw ) ) {
			return new \WP_Error(
				'invalid_conversation',
				__( 'conversation must be an object.', 'antimanual-builder' ),
				array( 'status' => 400 )
			);
		}

		$conversation = $this->sanitize_conversation( $raw );

		if ( empty( $conversation['id'] ) ) {
			return new \WP_Error(
				'missing_id',
				__( 'conversation.id is required.', 'antimanual-builder' ),
				array( 'status' => 400 )
			);
		}

		$all  = $this->read_conversations( $post_id );
		$idx  = -1;

		foreach ( $all as $i => $c ) {
			if ( $c['id'] === $conversation['id'] ) {
				$idx = $i;
				break;
			}
		}

		if ( $idx >= 0 ) {
			// Preserve createdAt from the stored record.
			$conversation['createdAt'] = $all[ $idx ]['createdAt'];
			$all[ $idx ] = $conversation;
		} else {
			array_unshift( $all, $conversation );
			// Cap at maximum.
			if ( count( $all ) > self::MAX_PER_POST ) {
				$all = array_slice( $all, 0, self::MAX_PER_POST );
			}
		}

		// Sort by updatedAt descending.
		usort( $all, function ( $a, $b ) {
			return ( $b['updatedAt'] ?? 0 ) - ( $a['updatedAt'] ?? 0 );
		} );

		$this->write_conversations( $post_id, $all );

		return new \WP_REST_Response( $conversation, 200 );
	}

	/**
	 * DELETE /conversations/{id}?post_id=123
	 *
	 * @since  1.1.0
	 * @param  \WP_REST_Request $request REST request.
	 * @return \WP_REST_Response|\WP_Error
	 */
	public function delete_conversation( \WP_REST_Request $request ) {
		$post_id = absint( $request->get_param( 'post_id' ) );
		$id      = sanitize_text_field( $request->get_param( 'id' ) );

		$all     = $this->read_conversations( $post_id );
		$updated = array_values( array_filter( $all, function ( $c ) use ( $id ) {
			return $c['id'] !== $id;
		} ) );

		$this->write_conversations( $post_id, $updated );

		return new \WP_REST_Response( array( 'deleted' => true ), 200 );
	}

	// ─── Helpers ──────────────────────────────────────────────────────────────

	/**
	 * Read stored conversations for a post.
	 *
	 * @param  int $post_id Post ID.
	 * @return array
	 */
	private function read_conversations( int $post_id ): array {
		$raw = get_post_meta( $post_id, self::META_KEY, true );

		if ( ! $raw ) {
			return array();
		}

		$decoded = json_decode( $raw, true );

		if ( ! is_array( $decoded ) ) {
			return array();
		}

		return $decoded;
	}

	/**
	 * Persist conversations for a post.
	 *
	 * @param  int   $post_id       Post ID.
	 * @param  array $conversations Conversations array.
	 * @return void
	 */
	private function write_conversations( int $post_id, array $conversations ): void {
		update_post_meta( $post_id, self::META_KEY, wp_slash( wp_json_encode( $conversations ) ) );
	}

	/**
	 * Sanitize a raw conversation object from the request.
	 *
	 * Only whitelisted keys are kept. Message content is not altered —
	 * the editor renders it server-side-free.
	 *
	 * @param  array $raw Raw conversation data.
	 * @return array
	 */
	private function sanitize_conversation( array $raw ): array {
		$now = round( microtime( true ) * 1000 );

		$messages = array();
		if ( isset( $raw['messages'] ) && is_array( $raw['messages'] ) ) {
			foreach ( $raw['messages'] as $msg ) {
				if ( ! is_array( $msg ) ) {
					continue;
				}
				$messages[] = array(
					'id'        => sanitize_text_field( $msg['id'] ?? '' ),
					'timestamp' => isset( $msg['timestamp'] ) ? (int) $msg['timestamp'] : $now,
					'role'      => sanitize_text_field( $msg['role'] ?? 'user' ),
					'content'   => wp_kses_post( $msg['content'] ?? '' ),
					// Optional fields preserved as-is (plan data, attachments, etc.).
					...( isset( $msg['toolName'] )          ? array( 'toolName'          => sanitize_text_field( $msg['toolName'] ) )          : array() ),
					...( isset( $msg['isStreaming'] )        ? array( 'isStreaming'        => (bool) $msg['isStreaming'] )                       : array() ),
					...( isset( $msg['isEdited'] )           ? array( 'isEdited'           => (bool) $msg['isEdited'] )                          : array() ),
					...( isset( $msg['plan'] )               ? array( 'plan'               => $msg['plan'] )                                     : array() ),
					...( isset( $msg['planTaskStatuses'] )   ? array( 'planTaskStatuses'   => $msg['planTaskStatuses'] )                         : array() ),
					...( isset( $msg['useKnowledgeBase'] )   ? array( 'useKnowledgeBase'   => (bool) $msg['useKnowledgeBase'] )                  : array() ),
					...( isset( $msg['knowledgeBaseCount'] ) ? array( 'knowledgeBaseCount' => (int) $msg['knowledgeBaseCount'] )                 : array() ),
					// AI Activity snapshot fields — without these, an `activity`
					// role message round-trips through the DB with empty content
					// and its card reloads blank (appears to vanish).
					...( isset( $msg['activityCommentary'] ) && is_array( $msg['activityCommentary'] )       ? array( 'activityCommentary'       => $this->sanitize_activity_commentary( $msg['activityCommentary'] ) )            : array() ),
					...( isset( $msg['activitySources'] ) && is_array( $msg['activitySources'] )             ? array( 'activitySources'          => $this->sanitize_activity_sources( $msg['activitySources'] ) )                  : array() ),
					...( isset( $msg['activityOperationSummary'] ) && is_array( $msg['activityOperationSummary'] ) ? array( 'activityOperationSummary' => $this->sanitize_activity_operation_summary( $msg['activityOperationSummary'] ) ) : array() ),
					// AI clarification card fields — without these, a `clarification`
					// role message reloads blank (same failure mode as activity cards).
					...( isset( $msg['clarificationQuestion'] )                            ? array( 'clarificationQuestion'  => sanitize_text_field( $msg['clarificationQuestion'] ) )                : array() ),
					...( isset( $msg['clarificationOptions'] ) && is_array( $msg['clarificationOptions'] ) ? array( 'clarificationOptions' => $this->sanitize_clarification_options( $msg['clarificationOptions'] ) )  : array() ),
					...( isset( $msg['clarificationPrompt'] )                              ? array( 'clarificationPrompt'    => wp_kses_post( $msg['clarificationPrompt'] ) )                          : array() ),
					...( isset( $msg['clarificationAnswered'] )                            ? array( 'clarificationAnswered'  => (bool) $msg['clarificationAnswered'] )                              : array() ),
				);
			}
		}

		return array(
			'id'        => sanitize_text_field( $raw['id'] ?? '' ),
			'title'     => sanitize_text_field( $raw['title'] ?? 'New Conversation' ),
			'messages'  => $messages,
			'createdAt' => isset( $raw['createdAt'] ) ? (int) $raw['createdAt'] : $now,
			'updatedAt' => isset( $raw['updatedAt'] ) ? (int) $raw['updatedAt'] : $now,
			'pageScope' => sanitize_text_field( $raw['pageScope'] ?? '' ),
		);
	}

	/**
	 * Sanitize the AI activity commentary timeline of an `activity` message.
	 *
	 * @param  array $items Raw commentary items ({ id, phase, message, timestamp }).
	 * @return array
	 */
	private function sanitize_activity_commentary( array $items ): array {
		$now   = round( microtime( true ) * 1000 );
		$clean = array();
		foreach ( $items as $item ) {
			if ( ! is_array( $item ) ) {
				continue;
			}
			$clean[] = array(
				'id'        => sanitize_text_field( $item['id'] ?? '' ),
				'phase'     => sanitize_text_field( $item['phase'] ?? '' ),
				'message'   => sanitize_text_field( $item['message'] ?? '' ),
				'timestamp' => isset( $item['timestamp'] ) ? (int) $item['timestamp'] : $now,
			);
		}
		return $clean;
	}

	/**
	 * Sanitize the RAG sources list of an `activity` message.
	 *
	 * @param  array $sources Raw sources ({ type, id, label, score }).
	 * @return array
	 */
	private function sanitize_activity_sources( array $sources ): array {
		$clean = array();
		foreach ( $sources as $src ) {
			if ( ! is_array( $src ) ) {
				continue;
			}
			$clean[] = array(
				'type'  => sanitize_text_field( $src['type'] ?? '' ),
				'id'    => sanitize_text_field( $src['id'] ?? '' ),
				'label' => sanitize_text_field( $src['label'] ?? '' ),
				'score' => isset( $src['score'] ) ? (float) $src['score'] : 0.0,
			);
		}
		return $clean;
	}

	/**
	 * Sanitize the operation-summary object of an `activity` message.
	 *
	 * @param  array $summary Raw summary ({ operation, title?, summary, details[], elapsed, timestamp }).
	 * @return array
	 */
	private function sanitize_activity_operation_summary( array $summary ): array {
		$now     = round( microtime( true ) * 1000 );
		$details = array();
		if ( isset( $summary['details'] ) && is_array( $summary['details'] ) ) {
			foreach ( $summary['details'] as $detail ) {
				$details[] = sanitize_text_field( (string) $detail );
			}
		}
		return array(
			'operation' => sanitize_text_field( $summary['operation'] ?? '' ),
			'summary'   => sanitize_text_field( $summary['summary'] ?? '' ),
			'details'   => $details,
			'elapsed'   => isset( $summary['elapsed'] ) ? (float) $summary['elapsed'] : 0.0,
			'timestamp' => isset( $summary['timestamp'] ) ? (int) $summary['timestamp'] : $now,
			...( isset( $summary['title'] ) ? array( 'title' => sanitize_text_field( $summary['title'] ) ) : array() ),
		);
	}

	/**
	 * Sanitize the option list of a `clarification` message.
	 *
	 * @param  array $options Raw options ({ label, description? }).
	 * @return array
	 */
	private function sanitize_clarification_options( array $options ): array {
		$clean = array();
		foreach ( $options as $opt ) {
			if ( ! is_array( $opt ) ) {
				continue;
			}
			$label = sanitize_text_field( $opt['label'] ?? '' );
			if ( '' === $label ) {
				continue;
			}
			$clean[] = array(
				'label'       => $label,
				'description' => sanitize_text_field( $opt['description'] ?? '' ),
			);
		}
		return $clean;
	}
}
