<?php

/**
 * MetaHandler — Domain handler for post meta read/write actions.
 *
 * Owns: get_post_meta, update_post_meta
 *
 * @package LolaCore\Actions\Handlers
 * @license GPL-2.0-or-later
 */

// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Dynamic meta access required for post meta inspection/update tool.

declare( strict_types=1 );

namespace LolaCore\Actions\Handlers;

// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Diagnostic exception messages; escaped at REST/chat presentation layer.

use LolaCore\Actions\Contracts\ActionHandlerInterface;

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

/**
 * Handles get_post_meta and update_post_meta actions.
 */
class MetaHandler implements ActionHandlerInterface {

	/**
	 * Meta keys that should never be exposed or modified by the AI.
	 *
	 * @var array<string>
	 */
	private const BLOCKED_READ = array( '_wp_attached_file', '_edit_lock', '_edit_last' );

	/**
	 * Meta keys that should never be modified by the AI.
	 *
	 * @var array<string>
	 */
	private const BLOCKED_WRITE = array( '_wp_attached_file', '_edit_lock', '_edit_last', '_wp_page_template' );

	/** @inheritDoc */
	public function getSupportedActions(): array {
		return array( 'get_post_meta', 'update_post_meta' );
	}

	/** @inheritDoc */
	public function handle( string $actionId, array $params, bool $dryRun ): array {
		return match ( $actionId ) {
			'get_post_meta'    => $this->getPostMeta( $params ),
			'update_post_meta' => $this->updatePostMeta( $params, $dryRun ),
			default            => throw new \Exception( esc_html( "MetaHandler: unknown action '{$actionId}'." ) ),
		};
	}

	/**
	 * Read meta values for a specific post/page/product.
	 *
	 * @param array $params Expects: post_id (int), keys (array, optional).
	 * @return array
	 * @throws \Exception On invalid post_id or post not found.
	 */
	private function getPostMeta( array $params ): array {
		$post_id = (int) ( $params['post_id'] ?? 0 );
		$keys    = $params['keys'] ?? array();

		if ( $post_id <= 0 ) {
			throw new \Exception( __( 'A valid post_id is required.', 'lolacore' ) );
		}

		$post = get_post( $post_id );
		if ( ! $post ) {
			// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
			/* translators: 1: number */
			throw new \Exception( esc_html( sprintf( __( 'Post with ID %d not found.', 'lolacore' ), intval( $post_id ) ) ) );
		}

		if ( ! empty( $keys ) ) {
			// Return specific keys only.
			$data = array();
			foreach ( $keys as $k ) {
				if ( in_array( $k, self::BLOCKED_READ, true ) ) {
					continue;
				}
				$data[ $k ] = get_post_meta( $post_id, $k, true );
			}
		} else {
			// Return all non-internal meta.
			$all_meta = get_post_meta( $post_id );
			$data     = array();
			foreach ( $all_meta as $k => $values ) {
				if ( in_array( $k, self::BLOCKED_READ, true ) ) {
					continue;
				}
				// Return single value if only one, array if multiple.
				$data[ $k ] = count( $values ) === 1 ? $values[0] : $values;
			}
		}

		return array(
			'post_id'    => $post_id,
			'post_title' => $post->post_title,
			'post_type'  => $post->post_type,
			'meta'       => $data,
		);
	}

	/**
	 * Update a meta value on a specific post/page/product.
	 *
	 * @param array $params  Expects: post_id (int), meta_key (string), value (mixed).
	 * @param bool  $dryRun  If true, returns a preview without executing.
	 * @return array
	 * @throws \Exception On validation failure.
	 */
	private function updatePostMeta( array $params, bool $dryRun ): array {
		$post_id  = (int) ( $params['post_id'] ?? 0 );
		$meta_key = $params['meta_key'] ?? '';
		$value    = $params['value'] ?? '';

		if ( $post_id <= 0 ) {
			throw new \Exception( __( 'A valid post_id is required.', 'lolacore' ) );
		}
		if ( empty( $meta_key ) ) {
			throw new \Exception( __( 'meta_key is required.', 'lolacore' ) );
		}

		$post = get_post( $post_id );
		if ( ! $post ) {
			// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
			/* translators: 1: number */
			throw new \Exception( esc_html( sprintf( __( 'Post with ID %d not found.', 'lolacore' ), intval( $post_id ) ) ) );
		}

		if ( in_array( $meta_key, self::BLOCKED_WRITE, true ) ) {
			// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
			/* translators: 1: value */
			throw new \Exception( sprintf( __( "Updating meta key '%s' is blocked for security.", 'lolacore' ), $meta_key ) );
		}

		$current = get_post_meta( $post_id, $meta_key, true );

		if ( $dryRun ) {
			return array(
				'post_id'       => $post_id,
				'post_title'    => $post->post_title,
				'post_type'     => $post->post_type,
				'meta_key'      => $meta_key,
				'current_value' => is_string( $current ) ? mb_substr( $current, 0, 200 ) : $current,
				'new_value'     => is_string( $value ) ? mb_substr( $value, 0, 200 ) : $value,
			);
		}

		update_post_meta( $post_id, $meta_key, $value );

		return array(
			'success'  => true,
			'post_id'  => $post_id,
			'meta_key' => $meta_key,
			'updated'  => true,
			'message'  => sprintf( __( "Meta '%1\$s' updated on post ID %2\$d.", 'lolacore' ), $meta_key, $post_id ),
		);
	}
}

