<?php
/**
 * MCP Abilities — registers ClearPost capabilities via the WordPress Abilities API.
 *
 * Hooks into wp_abilities_api_init and exposes ClearPost-specific read/write
 * abilities to any MCP client connected via the official WordPress/mcp-adapter
 * plugin. Falls back gracefully (no-op) when the Abilities API is not installed.
 *
 * Abilities registered (14 total):
 *   - clearpost/list-posts            (read, supports post_type: post/page/any)
 *   - clearpost/get-post              (read)
 *   - clearpost/update-post           (write)
 *   - clearpost/bulk-find-replace     (write, dry-run by default)
 *   - clearpost/get-post-performance  (read, premium: GSC data per post)
 *   - clearpost/get-content-calendar  (read, premium: planned content calendar)
 *   - clearpost/get-keywords          (read, premium: keyword research/tracking)
 *   - clearpost/get-site-metrics      (read, premium: site-wide GSC metrics)
 *   - clearpost/get-strategy-context  (read, premium: site context & strategy)
 *   - clearpost/generate-post         (write, premium: AI generation, supports post_type)
 *   - clearpost/add-to-calendar       (write, premium: add a calendar item)
 *   - clearpost/set-keyword-tracking  (write, premium: track/untrack keywords)
 *   - clearpost/set-head-code         (write: inject code into wp_head)
 *   - clearpost/get-head-code         (read: current custom head code)
 *
 * @package Simple_Ai_Auto_Post
 * @since 2.0.49
 */

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

/**
 * Register the ClearPost ability category.
 *
 * The Abilities API requires categories to be registered (on their own hook)
 * before any ability can reference them.
 */
function saiap_register_mcp_ability_category() {
	if ( ! function_exists( 'wp_register_ability_category' ) ) {
		return;
	}

	wp_register_ability_category(
		'clearpost',
		array(
			'label'       => __( 'ClearPost', 'clearpost-simple-ai-auto-post' ),
			'description' => __( 'Abilities for managing and measuring ClearPost SEO content.', 'clearpost-simple-ai-auto-post' ),
		)
	);
}
add_action( 'wp_abilities_api_categories_init', 'saiap_register_mcp_ability_category' );

/**
 * Register all ClearPost MCP abilities when the Abilities API is initialized.
 */
function saiap_register_mcp_abilities() {
	if ( ! function_exists( 'wp_register_ability' ) ) {
		return;
	}

	// 1. clearpost/list-posts
	wp_register_ability(
		'clearpost/list-posts',
		array(
			'label'               => __( 'List posts', 'clearpost-simple-ai-auto-post' ),
			'description'         => __( 'Returns a paginated list of posts or pages on this site with their metadata.', 'clearpost-simple-ai-auto-post' ),
			'category'            => 'clearpost',
			'input_schema'        => array(
				'type'                 => 'object',
				'properties'           => array(
					'post_type' => array(
						'type'        => 'string',
						'enum'        => array( 'post', 'page', 'any' ),
						'default'     => 'post',
						'description' => 'Content type to list: post, page, or any',
					),
					'per_page' => array(
						'type'        => 'integer',
						'minimum'     => 1,
						'maximum'     => 100,
						'default'     => 20,
						'description' => 'Number of posts to return',
					),
					'page'     => array(
						'type'        => 'integer',
						'minimum'     => 1,
						'default'     => 1,
						'description' => 'Page number for pagination',
					),
					'status'   => array(
						'type'        => 'string',
						'enum'        => array( 'publish', 'draft', 'any' ),
						'default'     => 'publish',
						'description' => 'Post status to filter by',
					),
				),
				'additionalProperties' => false,
			),
			'output_schema'       => array(
				'type'        => 'object',
				'description' => 'Paginated list of posts with metadata',
			),
			'execute_callback'    => 'saiap_mcp_list_posts',
			'permission_callback' => 'saiap_mcp_can_read_posts',
			'meta'                => array(
				'mcp' => array( 'public' => true ),
			),
		)
	);

	// 2. clearpost/get-post
	wp_register_ability(
		'clearpost/get-post',
		array(
			'label'               => __( 'Get a post by ID', 'clearpost-simple-ai-auto-post' ),
			'description'         => __( 'Returns the full content and ClearPost-specific metadata for a single post.', 'clearpost-simple-ai-auto-post' ),
			'category'            => 'clearpost',
			'input_schema'        => array(
				'type'                 => 'object',
				'properties'           => array(
					'post_id' => array(
						'type'        => 'integer',
						'description' => 'WordPress post ID',
					),
				),
				'required'             => array( 'post_id' ),
				'additionalProperties' => false,
			),
			'output_schema'       => array(
				'type'        => 'object',
				'description' => 'Post object with content, metadata, and ClearPost flags',
			),
			'execute_callback'    => 'saiap_mcp_get_post',
			'permission_callback' => 'saiap_mcp_can_read_posts',
			'meta'                => array(
				'mcp' => array( 'public' => true ),
			),
		)
	);

	// 3. clearpost/update-post
	$term_ref_schema = array(
		'oneOf' => array(
			array(
				'type'        => 'string',
				'description' => 'Term name. May include hierarchy as a path separated by " > " (e.g. "Direito Imobiliário > Compra e Venda > Compra Residencial") to disambiguate when multiple terms share a name.',
			),
			array(
				'type'                 => 'object',
				'properties'           => array(
					'id' => array( 'type' => 'integer' ),
				),
				'required'             => array( 'id' ),
				'additionalProperties' => false,
			),
			array(
				'type'                 => 'object',
				'properties'           => array(
					'slug' => array( 'type' => 'string' ),
				),
				'required'             => array( 'slug' ),
				'additionalProperties' => false,
			),
		),
	);

	wp_register_ability(
		'clearpost/update-post',
		array(
			'label'               => __( 'Update a post', 'clearpost-simple-ai-auto-post' ),
			'description'         => __( 'Updates fields on an existing post — any subset of title, content, excerpt, status, categories, tags. Replace semantics: pass a value to swap, omit to leave alone, pass an empty array to clear categories/tags. Categories may be referenced by name, by hierarchical path ("Parent > Child"), by {id}, or by {slug}. Tags missing from the site are auto-created; categories must already exist. Status accepts publish/draft/pending/private/future.', 'clearpost-simple-ai-auto-post' ),
			'category'            => 'clearpost',
			'input_schema'        => array(
				'type'                 => 'object',
				'properties'           => array(
					'post_id'           => array(
						'type'        => 'integer',
						'description' => 'WordPress post ID to update',
					),
					'content'           => array(
						'type'        => 'string',
						'description' => 'New post content (HTML). Optional.',
					),
					'title'             => array(
						'type'        => 'string',
						'description' => 'New post title. Optional.',
					),
					'excerpt'           => array(
						'type'        => 'string',
						'description' => 'New post excerpt. Optional.',
					),
					'status'            => array(
						'type'        => 'string',
						'enum'        => array( 'publish', 'draft', 'pending', 'private', 'future' ),
						'description' => 'Set the post status. Optional. WordPress publish flow rules apply (e.g. future requires a future post_date).',
					),
					'categories'        => array(
						'type'        => 'array',
						'items'       => $term_ref_schema,
						'description' => 'Replace the post category list. Pass [] to clear (WordPress will auto-assign the default category). Categories must already exist on the site — no auto-create.',
					),
					'tags'              => array(
						'type'        => 'array',
						'items'       => $term_ref_schema,
						'description' => 'Replace the post tag list. Pass [] to clear all tags. Tag names that do not exist are auto-created.',
					),
					'include_ancestors' => array(
						'type'        => 'boolean',
						'default'     => false,
						'description' => 'When true, each resolved category also pulls in its parent chain so the post appears in ancestor archives too. Applies to categories only.',
					),
				),
				'required'             => array( 'post_id' ),
				'additionalProperties' => false,
			),
			'output_schema'       => array(
				'type'        => 'object',
				'description' => 'Confirmation with the updated post ID and (when applied) the resolved category and tag term IDs and final status.',
			),
			'execute_callback'    => 'saiap_mcp_update_post',
			'permission_callback' => 'saiap_mcp_can_edit_posts',
			'meta'                => array(
				'mcp' => array( 'public' => true ),
			),
		)
	);

	// 4. clearpost/bulk-find-replace
	wp_register_ability(
		'clearpost/bulk-find-replace',
		array(
			'label'               => __( 'Bulk find and replace across posts', 'clearpost-simple-ai-auto-post' ),
			'description'         => __( 'Finds a literal string across published posts and optionally replaces it. Defaults to dry_run mode (returns matches without changing anything). Set dry_run=false to actually update posts.', 'clearpost-simple-ai-auto-post' ),
			'category'            => 'clearpost',
			'input_schema'        => array(
				'type'                 => 'object',
				'properties'           => array(
					'find'      => array(
						'type'        => 'string',
						'description' => 'Literal string to find (not a regex)',
					),
					'replace'   => array(
						'type'        => 'string',
						'description' => 'Replacement string. Required if dry_run is false.',
						'default'     => '',
					),
					'dry_run'   => array(
						'type'        => 'boolean',
						'default'     => true,
						'description' => 'If true (default), report what would change without modifying any posts.',
					),
					'status'    => array(
						'type'        => 'string',
						'enum'        => array( 'publish', 'draft', 'any' ),
						'default'     => 'publish',
						'description' => 'Post status to scan',
					),
					'max_posts' => array(
						'type'        => 'integer',
						'minimum'     => 1,
						'maximum'     => 500,
						'default'     => 200,
						'description' => 'Hard cap on number of posts to scan in one call',
					),
				),
				'required'             => array( 'find' ),
				'additionalProperties' => false,
			),
			'output_schema'       => array(
				'type'        => 'object',
				'description' => 'Counts of matched and updated posts plus per-post detail',
			),
			'execute_callback'    => 'saiap_mcp_bulk_find_replace',
			'permission_callback' => 'saiap_mcp_can_edit_posts',
			'meta'                => array(
				'mcp' => array( 'public' => true ),
			),
		)
	);

	// 4a. clearpost/get-seo-meta
	wp_register_ability(
		'clearpost/get-seo-meta',
		array(
			'label'               => __( 'Read SEO meta for a post', 'clearpost-simple-ai-auto-post' ),
			'description'         => __( 'Returns the meta description, focus keyword, excerpt, and primary category currently set on a post, plus which SEO plugin (Yoast / Rank Math / AIOSEO / none) is active. Use before suggesting changes so the user sees what is being replaced.', 'clearpost-simple-ai-auto-post' ),
			'category'            => 'clearpost',
			'input_schema'        => array(
				'type'                 => 'object',
				'properties'           => array(
					'post_id' => array(
						'type'        => 'integer',
						'description' => 'WordPress post ID',
					),
				),
				'required'             => array( 'post_id' ),
				'additionalProperties' => false,
			),
			'output_schema'       => array(
				'type'        => 'object',
				'description' => 'Current SEO meta state for the post',
			),
			'execute_callback'    => 'saiap_mcp_get_seo_meta',
			'permission_callback' => 'saiap_mcp_can_read_posts',
			'meta'                => array(
				'mcp' => array( 'public' => true ),
			),
		)
	);

	// 4b. clearpost/update-seo-meta
	wp_register_ability(
		'clearpost/update-seo-meta',
		array(
			'label'               => __( 'Update SEO meta on a post', 'clearpost-simple-ai-auto-post' ),
			'description'         => __( 'Sets one or more SEO fields (SEO title, meta description, focus keyword, excerpt, primary category) on an existing post. Only the fields you pass are changed. Writes to whichever SEO plugin is active on the site (Yoast, Rank Math, or AIOSEO). The user should confirm the change before you call this — read first, propose, then write.', 'clearpost-simple-ai-auto-post' ),
			'category'            => 'clearpost',
			'input_schema'        => array(
				'type'                 => 'object',
				'properties'           => array(
					'post_id'             => array(
						'type'        => 'integer',
						'description' => 'WordPress post ID',
					),
					'meta_description'    => array(
						'type'        => 'string',
						'description' => 'New meta description. Omit to leave unchanged.',
					),
					'seo_title'           => array(
						'type'        => 'string',
						'description' => 'New SEO title. Omit to leave unchanged.',
					),
					'focus_keyword'       => array(
						'type'        => 'string',
						'description' => 'New focus keyword. Omit to leave unchanged.',
					),
					'excerpt'             => array(
						'type'        => 'string',
						'description' => 'New excerpt. Omit to leave unchanged.',
					),
					'primary_category_id' => array(
						'type'        => 'integer',
						'description' => 'New primary category term_id. Must already be assigned to the post. Omit to leave unchanged.',
					),
				),
				'required'             => array( 'post_id' ),
				'additionalProperties' => false,
			),
			'output_schema'       => array(
				'type'        => 'object',
				'description' => 'New SEO meta state for the post, plus which fields were updated.',
			),
			'execute_callback'    => 'saiap_mcp_update_seo_meta',
			'permission_callback' => 'saiap_mcp_can_edit_posts',
			'meta'                => array(
				'mcp' => array( 'public' => true ),
			),
		)
	);

	// 5. clearpost/get-post-performance
	wp_register_ability(
		'clearpost/get-post-performance',
		array(
			'label'               => __( 'Get GSC performance for a post', 'clearpost-simple-ai-auto-post' ),
			'description'         => __( 'Returns Google Search Console performance for a specific post: clicks, impressions, CTR, average position, and top ranking queries over the last 28 days. Requires a connected ClearPost license with Google Search Console connected.', 'clearpost-simple-ai-auto-post' ),
			'category'            => 'clearpost',
			'input_schema'        => array(
				'type'                 => 'object',
				'properties'           => array(
					'post_id' => array(
						'type'        => 'integer',
						'description' => 'WordPress post ID',
					),
				),
				'required'             => array( 'post_id' ),
				'additionalProperties' => false,
			),
			'output_schema'       => array(
				'type'        => 'object',
				'description' => 'GSC performance metrics for the post',
			),
			'execute_callback'    => 'saiap_mcp_get_post_performance',
			'permission_callback' => 'saiap_mcp_can_read_performance',
			'meta'                => array(
				'mcp' => array( 'public' => true ),
			),
		)
	);

	// 6. clearpost/get-content-calendar
	wp_register_ability(
		'clearpost/get-content-calendar',
		array(
			'label'               => __( 'Get the content calendar', 'clearpost-simple-ai-auto-post' ),
			'description'         => __( 'Returns the AI content calendar for this site: planned posts with titles, target keywords, content briefs, scheduled dates, and statuses. Requires a connected ClearPost license.', 'clearpost-simple-ai-auto-post' ),
			'category'            => 'clearpost',
			'input_schema'        => array(
				'type'                 => 'object',
				'properties'           => array(
					'start_date' => array(
						'type'        => 'string',
						'description' => 'Filter items scheduled on or after this date (YYYY-MM-DD). Optional.',
					),
					'end_date'   => array(
						'type'        => 'string',
						'description' => 'Filter items scheduled on or before this date (YYYY-MM-DD). Optional.',
					),
					'status'     => array(
						'type'        => 'string',
						'description' => 'Filter by item status, e.g. planned, pending_review, published, skipped. Optional.',
					),
					'limit'      => array(
						'type'        => 'integer',
						'minimum'     => 1,
						'maximum'     => 100,
						'description' => 'Maximum number of items to return. Optional.',
					),
				),
				'additionalProperties' => false,
			),
			'output_schema'       => array(
				'type'        => 'object',
				'description' => 'Calendar items with titles, keywords, briefs, scheduled dates, and statuses',
			),
			'execute_callback'    => 'saiap_mcp_get_content_calendar',
			'permission_callback' => 'saiap_mcp_can_manage_premium',
			'meta'                => array(
				'mcp' => array( 'public' => true ),
			),
		)
	);

	// 7. clearpost/get-keywords
	wp_register_ability(
		'clearpost/get-keywords',
		array(
			'label'               => __( 'Get SEO keywords', 'clearpost-simple-ai-auto-post' ),
			'description'         => __( 'Returns the keywords ClearPost has researched for this site, including tracking status and scoring. Use tracked=true to see only rank-tracked keywords. Requires a connected ClearPost license.', 'clearpost-simple-ai-auto-post' ),
			'category'            => 'clearpost',
			'input_schema'        => array(
				'type'                 => 'object',
				'properties'           => array(
					'tracked' => array(
						'type'        => 'boolean',
						'description' => 'If true, return only rank-tracked keywords; if false, only untracked ones. Omit for all.',
					),
				),
				'additionalProperties' => false,
			),
			'output_schema'       => array(
				'type'        => 'object',
				'description' => 'Keyword list with tracking status and scores',
			),
			'execute_callback'    => 'saiap_mcp_get_keywords',
			'permission_callback' => 'saiap_mcp_can_manage_premium',
			'meta'                => array(
				'mcp' => array( 'public' => true ),
			),
		)
	);

	// 8. clearpost/get-site-metrics
	wp_register_ability(
		'clearpost/get-site-metrics',
		array(
			'label'               => __( 'Get site-wide search metrics', 'clearpost-simple-ai-auto-post' ),
			'description'         => __( 'Returns site-wide Google Search Console metrics: daily clicks and impressions over the requested period. Requires a connected ClearPost license with Google Search Console connected.', 'clearpost-simple-ai-auto-post' ),
			'category'            => 'clearpost',
			'input_schema'        => array(
				'type'                 => 'object',
				'properties'           => array(
					'days' => array(
						'type'        => 'integer',
						'minimum'     => 1,
						'maximum'     => 90,
						'default'     => 28,
						'description' => 'Number of days of history to return (1-90)',
					),
				),
				'additionalProperties' => false,
			),
			'output_schema'       => array(
				'type'        => 'object',
				'description' => 'Daily GSC clicks/impressions, or connected=false if GSC is not connected',
			),
			'execute_callback'    => 'saiap_mcp_get_site_metrics',
			'permission_callback' => 'saiap_mcp_can_manage_premium',
			'meta'                => array(
				'mcp' => array( 'public' => true ),
			),
		)
	);

	// 9. clearpost/get-strategy-context
	wp_register_ability(
		'clearpost/get-strategy-context',
		array(
			'label'               => __( 'Get the site strategy context', 'clearpost-simple-ai-auto-post' ),
			'description'         => __( 'Returns the AI-generated site context ClearPost uses to ground content generation: business summary, audience, services, and taxonomy rules, plus any custom context the site owner added. Requires a connected ClearPost license.', 'clearpost-simple-ai-auto-post' ),
			'category'            => 'clearpost',
			'input_schema'        => array(
				'type'                 => 'object',
				'properties'           => array(),
				'additionalProperties' => false,
			),
			'output_schema'       => array(
				'type'        => 'object',
				'description' => 'Site context document and custom context, or context=null if none generated yet',
			),
			'execute_callback'    => 'saiap_mcp_get_strategy_context',
			'permission_callback' => 'saiap_mcp_can_manage_premium',
			'meta'                => array(
				'mcp' => array( 'public' => true ),
			),
		)
	);

	// 10. clearpost/generate-post
	wp_register_ability(
		'clearpost/generate-post',
		array(
			'label'               => __( 'Generate a new post or page with AI', 'clearpost-simple-ai-auto-post' ),
			'description'         => __( 'Generates a complete new post or page using the ClearPost AI pipeline (content, taxonomy, images) and creates it on this site. Respects the site\'s "review before publish" setting: the post is created as a draft when review-first is enabled, otherwise published. Consumes AI generation credits and can take 1-3 minutes. Requires a connected ClearPost license.', 'clearpost-simple-ai-auto-post' ),
			'category'            => 'clearpost',
			'input_schema'        => array(
				'type'                 => 'object',
				'properties'           => array(
					'prompt'        => array(
						'type'        => 'string',
						'description' => 'What the post should be about. Topic, target keyword, and any angle or instructions.',
					),
					'title'         => array(
						'type'        => 'string',
						'description' => 'Exact post title to use. Optional; the AI generates one if omitted.',
					),
					'content_brief' => array(
						'type'        => 'string',
						'description' => 'Structured content brief (outline, must-cover points). Optional.',
					),
					'post_type'     => array(
						'type'        => 'string',
						'enum'        => array( 'post', 'page' ),
						'default'     => 'post',
						'description' => 'Create a post or a page. Defaults to post.',
					),
				),
				'required'             => array( 'prompt' ),
				'additionalProperties' => false,
			),
			'output_schema'       => array(
				'type'        => 'object',
				'description' => 'Created post ID, title, status, permalink, and edit URL',
			),
			'execute_callback'    => 'saiap_mcp_generate_post',
			'permission_callback' => 'saiap_mcp_can_manage_premium',
			'meta'                => array(
				'mcp' => array( 'public' => true ),
			),
		)
	);

	// 11. clearpost/add-to-calendar
	wp_register_ability(
		'clearpost/add-to-calendar',
		array(
			'label'               => __( 'Add an item to the content calendar', 'clearpost-simple-ai-auto-post' ),
			'description'         => __( 'Adds a planned post to the ClearPost content calendar for a future date. The item is generated automatically by the site\'s daily schedule when its date arrives. Requires a connected ClearPost license.', 'clearpost-simple-ai-auto-post' ),
			'category'            => 'clearpost',
			'input_schema'        => array(
				'type'                 => 'object',
				'properties'           => array(
					'title'         => array(
						'type'        => 'string',
						'description' => 'Working title for the planned post',
					),
					'scheduled_for' => array(
						'type'        => 'string',
						'description' => 'Date to schedule the post for (YYYY-MM-DD)',
					),
					'prompt'        => array(
						'type'        => 'string',
						'description' => 'Generation prompt for the post. Optional; a sensible default is built from the title.',
					),
					'content_brief' => array(
						'type'        => 'string',
						'description' => 'Structured content brief (outline, must-cover points). Optional.',
					),
				),
				'required'             => array( 'title', 'scheduled_for' ),
				'additionalProperties' => false,
			),
			'output_schema'       => array(
				'type'        => 'object',
				'description' => 'The created calendar item',
			),
			'execute_callback'    => 'saiap_mcp_add_to_calendar',
			'permission_callback' => 'saiap_mcp_can_manage_premium',
			'meta'                => array(
				'mcp' => array( 'public' => true ),
			),
		)
	);

	// 12. clearpost/set-keyword-tracking
	wp_register_ability(
		'clearpost/set-keyword-tracking',
		array(
			'label'               => __( 'Track or untrack a keyword', 'clearpost-simple-ai-auto-post' ),
			'description'         => __( 'Adds a keyword to rank tracking or toggles tracking for an existing keyword. Pass keyword (text) to add a brand-new keyword, or keyword_id (from clearpost-get-keywords) with enabled true/false to toggle an existing one. Requires a connected ClearPost license.', 'clearpost-simple-ai-auto-post' ),
			'category'            => 'clearpost',
			'input_schema'        => array(
				'type'                 => 'object',
				'properties'           => array(
					'keyword'    => array(
						'type'        => 'string',
						'description' => 'New keyword text to add and start tracking. Use this OR keyword_id, not both.',
					),
					'keyword_id' => array(
						'type'        => 'string',
						'description' => 'ID of an existing keyword (from clearpost-get-keywords). Use this OR keyword, not both.',
					),
					'enabled'    => array(
						'type'        => 'boolean',
						'default'     => true,
						'description' => 'Whether tracking should be enabled. Only applies with keyword_id.',
					),
				),
				'additionalProperties' => false,
			),
			'output_schema'       => array(
				'type'        => 'object',
				'description' => 'The updated keyword tracking state',
			),
			'execute_callback'    => 'saiap_mcp_set_keyword_tracking',
			'permission_callback' => 'saiap_mcp_can_manage_premium',
			'meta'                => array(
				'mcp' => array( 'public' => true ),
			),
		)
	);
	// 13. clearpost/set-head-code
	wp_register_ability(
		'clearpost/set-head-code',
		array(
			'label'               => __( 'Set custom head code', 'clearpost-simple-ai-auto-post' ),
			'description'         => __( 'Injects arbitrary code into the <head> of every page on this site. Use for structured data (JSON-LD schema), meta tags, analytics scripts, verification codes, or any other head-level markup. Supports replace (overwrite), append (add to existing), and clear modes.', 'clearpost-simple-ai-auto-post' ),
			'category'            => 'clearpost',
			'input_schema'        => array(
				'type'                 => 'object',
				'properties'           => array(
					'code' => array(
						'type'        => 'string',
						'description' => 'HTML/script code to inject into <head>. Required unless mode is "clear".',
					),
					'mode' => array(
						'type'        => 'string',
						'enum'        => array( 'replace', 'append', 'clear' ),
						'default'     => 'replace',
						'description' => 'replace: overwrite all custom head code. append: add to existing code. clear: remove all custom head code.',
					),
				),
				'additionalProperties' => false,
			),
			'output_schema'       => array(
				'type'        => 'object',
				'description' => 'Confirmation message and the current full head code after the operation',
			),
			'execute_callback'    => 'saiap_mcp_set_head_code',
			'permission_callback' => 'saiap_mcp_can_edit_posts',
			'meta'                => array(
				'mcp' => array( 'public' => true ),
			),
		)
	);

	// 14. clearpost/get-head-code
	wp_register_ability(
		'clearpost/get-head-code',
		array(
			'label'               => __( 'Get custom head code', 'clearpost-simple-ai-auto-post' ),
			'description'         => __( 'Returns the current custom code injected into <head>. Use to inspect what structured data, meta tags, or scripts are installed before modifying.', 'clearpost-simple-ai-auto-post' ),
			'category'            => 'clearpost',
			'input_schema'        => array(
				'type'                 => 'object',
				'properties'           => array(),
				'additionalProperties' => false,
			),
			'output_schema'       => array(
				'type'        => 'object',
				'description' => 'Current custom head code',
			),
			'execute_callback'    => 'saiap_mcp_get_head_code',
			'permission_callback' => 'saiap_mcp_can_read_posts',
			'meta'                => array(
				'mcp' => array( 'public' => true ),
			),
		)
	);
}
add_action( 'wp_abilities_api_init', 'saiap_register_mcp_abilities' );

// -----------------------------------------------------------------------------
// Permission callbacks
// -----------------------------------------------------------------------------

function saiap_mcp_bearer_authenticated() {
	$key = get_option( 'saiap_license_key', '' );
	if ( ! $key ) {
		return false;
	}
	$header = isset( $_SERVER['HTTP_AUTHORIZATION'] ) ? $_SERVER['HTTP_AUTHORIZATION'] : '';
	if ( ! $header && function_exists( 'getallheaders' ) ) {
		$all = getallheaders();
		$header = isset( $all['Authorization'] ) ? $all['Authorization'] : '';
	}
	return preg_match( '/^Bearer\s+(.+)$/i', $header, $m ) && hash_equals( $key, $m[1] );
}

function saiap_mcp_authenticate_bearer( $user_id ) {
	if ( $user_id ) {
		return $user_id;
	}
	if ( ! saiap_mcp_bearer_authenticated() ) {
		return $user_id;
	}
	$admins = get_users( array( 'role' => 'administrator', 'number' => 1, 'fields' => 'ID' ) );
	if ( empty( $admins ) ) {
		return $user_id;
	}
	// Fire-and-forget heartbeat to the premium backend so we can track which
	// sites have an MCP client connected. Throttled to once per 6 hours per
	// PHP-FPM worker via a transient so high-volume customers don't spam the
	// backend.
	saiap_mcp_maybe_send_heartbeat();
	return (int) $admins[0];
}
add_filter( 'determine_current_user', 'saiap_mcp_authenticate_bearer', 30 );

/**
 * Fire-and-forget heartbeat to the premium backend on a successful MCP bearer
 * authentication. Throttled to one call per 6h via a transient. Errors are
 * swallowed — heartbeat failure must not affect the MCP request.
 */
function saiap_mcp_maybe_send_heartbeat() {
	$last = (int) get_transient( 'saiap_mcp_heartbeat_sent' );
	if ( $last && ( time() - $last ) < 6 * HOUR_IN_SECONDS ) {
		return;
	}
	set_transient( 'saiap_mcp_heartbeat_sent', time(), 6 * HOUR_IN_SECONDS );

	if ( ! defined( 'SAIAP_PREMIUM_API_URL' ) || ! function_exists( 'saiap_get_server_host' ) ) {
		return;
	}

	$license_key = get_option( 'saiap_license_key', '' );
	if ( empty( $license_key ) ) {
		return;
	}

	// blocking=false so this returns immediately; we don't read the response.
	wp_remote_post(
		trailingslashit( SAIAP_PREMIUM_API_URL ) . 'api/mcp/heartbeat',
		array(
			'method'   => 'POST',
			'timeout'  => 0.1,
			'blocking' => false,
			'headers'  => array( 'Content-Type' => 'application/json' ),
			'body'     => wp_json_encode(
				array(
					'license_key' => $license_key,
					'domain'      => saiap_get_server_host(),
				)
			),
		)
	);
}

function saiap_mcp_can_read_posts() {
	return current_user_can( 'edit_posts' ) || saiap_mcp_bearer_authenticated();
}

function saiap_mcp_can_edit_posts() {
	return current_user_can( 'edit_posts' ) || saiap_mcp_bearer_authenticated();
}

function saiap_mcp_can_read_performance() {
	return current_user_can( 'manage_options' ) || saiap_mcp_bearer_authenticated();
}

function saiap_mcp_can_manage_premium() {
	return current_user_can( 'manage_options' ) || saiap_mcp_bearer_authenticated();
}

// -----------------------------------------------------------------------------
// Premium backend helpers
// -----------------------------------------------------------------------------

/**
 * Common preconditions for abilities that call the premium backend.
 *
 * @return array|WP_Error array( 'license_key' => ..., 'domain' => ... ) or WP_Error.
 */
function saiap_mcp_premium_context() {
	if ( ! defined( 'SAIAP_PREMIUM_API_URL' ) || ! function_exists( 'saiap_get_server_host' ) ) {
		return new WP_Error( 'plugin_not_loaded', __( 'ClearPost plugin internals not loaded yet.', 'clearpost-simple-ai-auto-post' ) );
	}

	$license_key = get_option( 'saiap_license_key', '' );
	if ( ! $license_key ) {
		return new WP_Error( 'no_license', __( 'No ClearPost license configured. Connect a ClearPost account in the plugin settings to use this ability.', 'clearpost-simple-ai-auto-post' ) );
	}

	return array(
		'license_key' => $license_key,
		'domain'      => saiap_get_server_host(),
	);
}

/**
 * Decode a premium backend response, mapping HTTP errors to WP_Error.
 *
 * @param array|WP_Error $response wp_remote_* response.
 * @param string         $label    Human label for error messages.
 * @return array|WP_Error
 */
function saiap_mcp_premium_decode( $response, $label ) {
	if ( is_wp_error( $response ) ) {
		return $response;
	}

	$code = wp_remote_retrieve_response_code( $response );
	$body = json_decode( wp_remote_retrieve_body( $response ), true );

	if ( $code >= 400 ) {
		$message = ( is_array( $body ) && isset( $body['error'] ) )
			? (string) $body['error']
			: sprintf( '%s backend returned HTTP %d', $label, $code );
		return new WP_Error( 'backend_error', $message );
	}

	if ( ! is_array( $body ) ) {
		return new WP_Error( 'invalid_response', __( 'Malformed backend response', 'clearpost-simple-ai-auto-post' ) );
	}

	return $body;
}

// -----------------------------------------------------------------------------
// Handlers
// -----------------------------------------------------------------------------

/**
 * Handler: clearpost/list-posts
 */
function saiap_mcp_list_posts( $input = array() ) {
	$per_page  = isset( $input['per_page'] ) ? (int) $input['per_page'] : 20;
	$page      = isset( $input['page'] ) ? (int) $input['page'] : 1;
	$status    = isset( $input['status'] ) ? (string) $input['status'] : 'publish';
	$post_type = isset( $input['post_type'] ) ? (string) $input['post_type'] : 'post';
	if ( ! in_array( $post_type, array( 'post', 'page', 'any' ), true ) ) {
		$post_type = 'post';
	}

	$query_args = array(
		'post_type'      => ( 'any' === $post_type ) ? array( 'post', 'page' ) : $post_type,
		'post_status'    => ( 'any' === $status ) ? 'any' : $status,
		'posts_per_page' => $per_page,
		'paged'          => $page,
		'orderby'        => 'date',
		'order'          => 'DESC',
		'no_found_rows'  => false,
	);

	$query = new WP_Query( $query_args );
	$out   = array();
	foreach ( $query->posts as $p ) {
		$out[] = array(
			'id'                     => $p->ID,
			'title'                  => $p->post_title,
			'slug'                   => $p->post_name,
			'status'                 => $p->post_status,
			'date'                   => $p->post_date,
			'permalink'              => get_permalink( $p->ID ),
			'is_clearpost_generated' => (bool) get_post_meta( $p->ID, '_saiap_generated', true ),
		);
	}

	return array(
		'posts'       => $out,
		'page'        => $page,
		'per_page'    => $per_page,
		'total'       => (int) $query->found_posts,
		'total_pages' => (int) $query->max_num_pages,
	);
}

/**
 * Handler: clearpost/get-post
 */
function saiap_mcp_get_post( $input = array() ) {
	$id = isset( $input['post_id'] ) ? (int) $input['post_id'] : 0;
	if ( $id <= 0 ) {
		return new WP_Error( 'invalid_input', __( 'post_id is required and must be a positive integer', 'clearpost-simple-ai-auto-post' ) );
	}

	$p = get_post( $id );
	if ( ! $p ) {
		return new WP_Error( 'not_found', __( 'Post not found', 'clearpost-simple-ai-auto-post' ) );
	}

	return array(
		'id'                     => $p->ID,
		'title'                  => $p->post_title,
		'slug'                   => $p->post_name,
		'status'                 => $p->post_status,
		'date'                   => $p->post_date,
		'modified'               => $p->post_modified,
		'content'                => $p->post_content,
		'excerpt'                => $p->post_excerpt,
		'permalink'              => get_permalink( $p->ID ),
		'is_clearpost_generated' => (bool) get_post_meta( $p->ID, '_saiap_generated', true ),
		'target_keyword'         => (string) get_post_meta( $p->ID, '_saiap_target_keyword', true ),
	);
}

/**
 * Handler: clearpost/update-post
 */
function saiap_mcp_update_post( $input = array() ) {
	$id = isset( $input['post_id'] ) ? (int) $input['post_id'] : 0;
	if ( $id <= 0 ) {
		return new WP_Error( 'invalid_input', __( 'post_id is required and must be a positive integer', 'clearpost-simple-ai-auto-post' ) );
	}

	$p = get_post( $id );
	if ( ! $p ) {
		return new WP_Error( 'not_found', __( 'Post not found', 'clearpost-simple-ai-auto-post' ) );
	}

	$has_categories    = array_key_exists( 'categories', $input );
	$has_tags          = array_key_exists( 'tags', $input );
	$include_ancestors = ! empty( $input['include_ancestors'] );

	$update = array( 'ID' => $id );
	if ( isset( $input['content'] ) ) {
		$update['post_content'] = wp_kses_post( (string) $input['content'] );
	}
	if ( isset( $input['title'] ) ) {
		$update['post_title'] = sanitize_text_field( (string) $input['title'] );
	}
	if ( isset( $input['excerpt'] ) ) {
		$update['post_excerpt'] = sanitize_textarea_field( (string) $input['excerpt'] );
	}
	if ( isset( $input['status'] ) ) {
		$allowed_statuses = array( 'publish', 'draft', 'pending', 'private', 'future' );
		$req_status       = sanitize_key( (string) $input['status'] );
		if ( ! in_array( $req_status, $allowed_statuses, true ) ) {
			return new WP_Error( 'invalid_status', sprintf( __( 'status must be one of: %s', 'clearpost-simple-ai-auto-post' ), implode( ', ', $allowed_statuses ) ) );
		}
		$update['post_status'] = $req_status;
	}

	if ( count( $update ) === 1 && ! $has_categories && ! $has_tags ) {
		return new WP_Error( 'no_changes', __( 'At least one of content, title, excerpt, status, categories, or tags must be provided', 'clearpost-simple-ai-auto-post' ) );
	}

	$response = array( 'id' => $id, 'updated' => false );

	if ( count( $update ) > 1 ) {
		$result = wp_update_post( $update, true );
		if ( is_wp_error( $result ) ) {
			return $result;
		}
		$response['updated'] = true;
		if ( isset( $update['post_status'] ) ) {
			$response['status'] = $update['post_status'];
		}
	}

	if ( $has_categories ) {
		$refs    = is_array( $input['categories'] ) ? $input['categories'] : array();
		$cat_ids = saiap_mcp_resolve_term_refs( $refs, 'category', false );
		if ( is_wp_error( $cat_ids ) ) {
			return $cat_ids;
		}
		if ( $include_ancestors && ! empty( $cat_ids ) ) {
			$cat_ids = saiap_mcp_add_ancestors( $cat_ids, 'category' );
		}
		$set = wp_set_post_categories( $id, $cat_ids );
		if ( is_wp_error( $set ) ) {
			return $set;
		}
		$response['categories'] = array_map( 'intval', $cat_ids );
		$response['updated']    = true;
	}

	if ( $has_tags ) {
		$refs    = is_array( $input['tags'] ) ? $input['tags'] : array();
		$tag_ids = saiap_mcp_resolve_term_refs( $refs, 'post_tag', true );
		if ( is_wp_error( $tag_ids ) ) {
			return $tag_ids;
		}
		$set = wp_set_post_tags( $id, $tag_ids );
		if ( is_wp_error( $set ) ) {
			return $set;
		}
		$response['tags']    = array_map( 'intval', $tag_ids );
		$response['updated'] = true;
	}

	return $response;
}

/**
 * Resolve an array of TermRefs to term IDs in a given taxonomy.
 *
 * A TermRef is one of:
 *   - a string: term name, or hierarchical path with " > " separator
 *     (e.g. "Direito Imobiliário > Compra e Venda > Compra Residencial")
 *   - { id: int }
 *   - { slug: string }
 *
 * @param array  $refs        TermRefs to resolve.
 * @param string $taxonomy    Taxonomy slug (e.g. 'category', 'post_tag').
 * @param bool   $auto_create If true, create missing string-name terms (tag semantics).
 *                            If false, missing terms return WP_Error (category semantics).
 * @return int[]|WP_Error Array of unique term IDs, or WP_Error on first failure.
 */
function saiap_mcp_resolve_term_refs( $refs, $taxonomy, $auto_create = false ) {
	$ids = array();
	if ( empty( $refs ) ) {
		return $ids;
	}
	foreach ( $refs as $ref ) {
		$resolved = saiap_mcp_resolve_one_term_ref( $ref, $taxonomy, $auto_create );
		if ( is_wp_error( $resolved ) ) {
			return $resolved;
		}
		$ids[] = (int) $resolved;
	}
	return array_values( array_unique( $ids ) );
}

function saiap_mcp_resolve_one_term_ref( $ref, $taxonomy, $auto_create ) {
	if ( is_array( $ref ) ) {
		if ( isset( $ref['id'] ) ) {
			$id = (int) $ref['id'];
			$t  = get_term( $id, $taxonomy );
			if ( ! $t || is_wp_error( $t ) ) {
				return new WP_Error( 'term_not_found', sprintf( __( 'No %1$s term with id %2$d', 'clearpost-simple-ai-auto-post' ), $taxonomy, $id ) );
			}
			return (int) $t->term_id;
		}
		if ( isset( $ref['slug'] ) && '' !== $ref['slug'] ) {
			$slug = sanitize_title( (string) $ref['slug'] );
			$t    = get_term_by( 'slug', $slug, $taxonomy );
			if ( ! $t ) {
				return new WP_Error( 'term_not_found', sprintf( __( 'No %1$s term with slug "%2$s"', 'clearpost-simple-ai-auto-post' ), $taxonomy, $slug ) );
			}
			return (int) $t->term_id;
		}
		return new WP_Error( 'invalid_term_ref', __( 'Term reference object must contain "id" or "slug"', 'clearpost-simple-ai-auto-post' ) );
	}

	if ( ! is_string( $ref ) || '' === trim( $ref ) ) {
		return new WP_Error( 'invalid_term_ref', __( 'Term reference must be a non-empty string or object', 'clearpost-simple-ai-auto-post' ) );
	}

	if ( false !== strpos( $ref, '>' ) ) {
		return saiap_mcp_resolve_term_path( $ref, $taxonomy, $auto_create );
	}
	return saiap_mcp_resolve_term_name( $ref, $taxonomy, $auto_create );
}

function saiap_mcp_resolve_term_name( $name, $taxonomy, $auto_create ) {
	$name    = trim( $name );
	$matches = get_terms(
		array(
			'taxonomy'   => $taxonomy,
			'name'       => $name,
			'hide_empty' => false,
		)
	);
	if ( is_wp_error( $matches ) ) {
		return $matches;
	}
	if ( count( $matches ) === 1 ) {
		return (int) $matches[0]->term_id;
	}
	if ( count( $matches ) > 1 ) {
		$paths = array();
		foreach ( $matches as $m ) {
			$paths[] = '"' . saiap_mcp_build_term_path( $m, $taxonomy ) . '"';
		}
		return new WP_Error(
			'ambiguous_term',
			sprintf(
				__( 'Term "%1$s" is ambiguous in %2$s. Candidates: %3$s. Use the path syntax ("Parent > Child") or pass {id} / {slug} to disambiguate.', 'clearpost-simple-ai-auto-post' ),
				$name,
				$taxonomy,
				implode( ', ', $paths )
			)
		);
	}
	if ( ! $auto_create ) {
		return new WP_Error( 'term_not_found', sprintf( __( 'No %1$s term named "%2$s". Create it in WordPress first, or reference an existing term.', 'clearpost-simple-ai-auto-post' ), $taxonomy, $name ) );
	}
	$created = wp_insert_term( $name, $taxonomy );
	if ( is_wp_error( $created ) ) {
		return $created;
	}
	return (int) $created['term_id'];
}

function saiap_mcp_resolve_term_path( $path, $taxonomy, $auto_create ) {
	$segments = array_map( 'trim', explode( '>', $path ) );
	$segments = array_values( array_filter( $segments, 'strlen' ) );
	if ( empty( $segments ) ) {
		return new WP_Error( 'invalid_term_ref', __( 'Empty path', 'clearpost-simple-ai-auto-post' ) );
	}
	$leaf_name = $segments[ count( $segments ) - 1 ];
	$candidates = get_terms(
		array(
			'taxonomy'   => $taxonomy,
			'name'       => $leaf_name,
			'hide_empty' => false,
		)
	);
	if ( is_wp_error( $candidates ) ) {
		return $candidates;
	}
	if ( empty( $candidates ) ) {
		return new WP_Error( 'term_not_found', sprintf( __( 'No %1$s term named "%2$s" (leaf of path "%3$s")', 'clearpost-simple-ai-auto-post' ), $taxonomy, $leaf_name, $path ) );
	}
	$ancestor_segments = array_slice( $segments, 0, -1 );
	$matched = array();
	foreach ( $candidates as $cand ) {
		if ( saiap_mcp_term_path_matches( $cand, $ancestor_segments, $taxonomy ) ) {
			$matched[] = $cand;
		}
	}
	if ( count( $matched ) === 1 ) {
		return (int) $matched[0]->term_id;
	}
	if ( count( $matched ) > 1 ) {
		$paths = array();
		foreach ( $matched as $m ) {
			$paths[] = '"' . saiap_mcp_build_term_path( $m, $taxonomy ) . '"';
		}
		return new WP_Error( 'ambiguous_term', sprintf( __( 'Path "%1$s" still matches multiple terms: %2$s. Add more ancestor segments or pass {id}/{slug}.', 'clearpost-simple-ai-auto-post' ), $path, implode( ', ', $paths ) ) );
	}
	return new WP_Error( 'term_not_found', sprintf( __( 'No %1$s term matches path "%2$s". Found %3$d term(s) named "%4$s" but none has the ancestor chain you specified.', 'clearpost-simple-ai-auto-post' ), $taxonomy, $path, count( $candidates ), $leaf_name ) );
}

function saiap_mcp_term_path_matches( $term, $segments, $taxonomy ) {
	if ( empty( $segments ) ) {
		return true;
	}
	$parent_id = (int) $term->parent;
	$ancestor_names = array();
	while ( $parent_id > 0 ) {
		$pterm = get_term( $parent_id, $taxonomy );
		if ( ! $pterm || is_wp_error( $pterm ) ) {
			break;
		}
		$ancestor_names[] = $pterm->name;
		$parent_id        = (int) $pterm->parent;
	}
	$want = array_reverse( $segments );
	$i = 0;
	foreach ( $ancestor_names as $aname ) {
		if ( $i >= count( $want ) ) {
			break;
		}
		if ( 0 === strcasecmp( $aname, $want[ $i ] ) ) {
			$i++;
		}
	}
	return $i === count( $want );
}

function saiap_mcp_build_term_path( $term, $taxonomy ) {
	$names = array( $term->name );
	$parent_id = (int) $term->parent;
	while ( $parent_id > 0 ) {
		$pterm = get_term( $parent_id, $taxonomy );
		if ( ! $pterm || is_wp_error( $pterm ) ) {
			break;
		}
		array_unshift( $names, $pterm->name );
		$parent_id = (int) $pterm->parent;
	}
	return implode( ' > ', $names );
}

function saiap_mcp_add_ancestors( $term_ids, $taxonomy ) {
	$expanded = array();
	foreach ( $term_ids as $tid ) {
		$tid        = (int) $tid;
		$expanded[] = $tid;
		$ancestors  = get_ancestors( $tid, $taxonomy, 'taxonomy' );
		foreach ( $ancestors as $a ) {
			$expanded[] = (int) $a;
		}
	}
	return array_values( array_unique( $expanded ) );
}

/**
 * Handler: clearpost/get-seo-meta
 *
 * Delegates to saiap_read_seo_meta() in api-seo-meta.php so the REST endpoint
 * and this MCP ability return identical shape.
 */
function saiap_mcp_get_seo_meta( $input = array() ) {
	$id = isset( $input['post_id'] ) ? (int) $input['post_id'] : 0;
	if ( $id <= 0 ) {
		return new WP_Error( 'invalid_input', __( 'post_id is required and must be a positive integer', 'clearpost-simple-ai-auto-post' ) );
	}
	if ( ! get_post( $id ) ) {
		return new WP_Error( 'not_found', __( 'Post not found', 'clearpost-simple-ai-auto-post' ) );
	}
	if ( ! function_exists( 'saiap_read_seo_meta' ) ) {
		return new WP_Error( 'plugin_not_loaded', __( 'SEO meta endpoint not loaded.', 'clearpost-simple-ai-auto-post' ) );
	}
	return saiap_read_seo_meta( $id );
}

/**
 * Handler: clearpost/update-seo-meta
 *
 * Routes through the same dispatcher and read function as the REST endpoint
 * so behavior is identical across consumer surfaces. Only fields present in
 * the input are written.
 */
function saiap_mcp_update_seo_meta( $input = array() ) {
	$id = isset( $input['post_id'] ) ? (int) $input['post_id'] : 0;
	if ( $id <= 0 ) {
		return new WP_Error( 'invalid_input', __( 'post_id is required and must be a positive integer', 'clearpost-simple-ai-auto-post' ) );
	}
	if ( ! get_post( $id ) ) {
		return new WP_Error( 'not_found', __( 'Post not found', 'clearpost-simple-ai-auto-post' ) );
	}
	if ( ! function_exists( 'saiap_apply_seo_meta' ) || ! function_exists( 'saiap_read_seo_meta' ) ) {
		return new WP_Error( 'plugin_not_loaded', __( 'SEO meta endpoint not loaded.', 'clearpost-simple-ai-auto-post' ) );
	}

	$meta_description    = isset( $input['meta_description'] ) ? sanitize_textarea_field( (string) $input['meta_description'] ) : null;
	$seo_title           = isset( $input['seo_title'] ) ? sanitize_text_field( (string) $input['seo_title'] ) : null;
	$focus_keyword       = isset( $input['focus_keyword'] ) ? sanitize_text_field( (string) $input['focus_keyword'] ) : null;
	$excerpt             = isset( $input['excerpt'] ) ? sanitize_textarea_field( (string) $input['excerpt'] ) : null;
	$primary_category_id = isset( $input['primary_category_id'] ) ? (int) $input['primary_category_id'] : null;

	$updated = array();

	if ( null !== $excerpt && '' !== $excerpt ) {
		wp_update_post(
			array(
				'ID'           => $id,
				'post_excerpt' => $excerpt,
			)
		);
		$updated[] = 'excerpt';
	}

	// Verify primary_category_id is actually assigned to the post; silently
	// drop if not, the GET diff will show the mismatch.
	$primary_for_dispatcher = null;
	if ( null !== $primary_category_id ) {
		if ( $primary_category_id > 0 ) {
			$post_cats = wp_get_post_categories( $id, array( 'fields' => 'ids' ) );
			if ( is_array( $post_cats ) && in_array( $primary_category_id, array_map( 'intval', $post_cats ), true ) ) {
				$primary_for_dispatcher = $primary_category_id;
				$updated[]              = 'primary_category';
			}
		} else {
			$primary_for_dispatcher = 0;
		}
	}

	if ( ( null !== $meta_description && '' !== $meta_description ) ||
		( null !== $seo_title && '' !== $seo_title ) ||
		( null !== $focus_keyword && '' !== $focus_keyword ) ||
		null !== $primary_for_dispatcher ) {
		saiap_apply_seo_meta(
			$id,
			(string) ( $meta_description ?? '' ),
			(string) ( $focus_keyword ?? '' ),
			$primary_for_dispatcher,
			null !== $seo_title ? (string) $seo_title : false
		);
		if ( null !== $meta_description && '' !== $meta_description ) {
			$updated[] = 'meta_description';
		}
		if ( null !== $seo_title && '' !== $seo_title ) {
			$updated[] = 'seo_title';
		}
		if ( null !== $focus_keyword && '' !== $focus_keyword ) {
			$updated[] = 'focus_keyword';
		}
	}

	$response                   = saiap_read_seo_meta( $id );
	$response['updated_fields'] = $updated;
	return $response;
}

/**
 * Handler: clearpost/bulk-find-replace
 */
function saiap_mcp_bulk_find_replace( $input = array() ) {
	$find      = isset( $input['find'] ) ? (string) $input['find'] : '';
	$replace   = isset( $input['replace'] ) ? (string) $input['replace'] : '';
	$dry_run   = isset( $input['dry_run'] ) ? (bool) $input['dry_run'] : true;
	$status    = isset( $input['status'] ) ? (string) $input['status'] : 'publish';
	$max_posts = isset( $input['max_posts'] ) ? (int) $input['max_posts'] : 200;

	if ( '' === $find ) {
		return new WP_Error( 'invalid_input', __( 'find string is required and must be non-empty', 'clearpost-simple-ai-auto-post' ) );
	}

	$query = new WP_Query(
		array(
			'post_type'      => 'post',
			'post_status'    => ( 'any' === $status ) ? 'any' : $status,
			'posts_per_page' => $max_posts,
			'orderby'        => 'date',
			'order'          => 'DESC',
			's'              => $find,
			'no_found_rows'  => true,
		)
	);

	$matched = array();
	$updated = array();

	foreach ( $query->posts as $p ) {
		if ( false === strpos( $p->post_content, $find ) ) {
			continue; // search picked it up via title/excerpt but not in content
		}

		$count     = substr_count( $p->post_content, $find );
		$matched[] = array(
			'id'      => $p->ID,
			'title'   => $p->post_title,
			'matches' => $count,
		);

		if ( ! $dry_run ) {
			$new_content = str_replace( $find, $replace, $p->post_content );
			$result      = wp_update_post(
				array(
					'ID'           => $p->ID,
					'post_content' => $new_content,
				),
				true
			);
			if ( ! is_wp_error( $result ) ) {
				$updated[] = array(
					'id'       => $p->ID,
					'title'    => $p->post_title,
					'replaced' => $count,
				);
			}
		}
	}

	return array(
		'dry_run'       => $dry_run,
		'find'          => $find,
		'replace'       => $dry_run ? null : $replace,
		'matched_count' => count( $matched ),
		'matched'       => $matched,
		'updated_count' => count( $updated ),
		'updated'       => $updated,
	);
}

/**
 * Handler: clearpost/get-post-performance
 *
 * Calls the premium backend for GSC data tied to this post's permalink path.
 */
function saiap_mcp_get_post_performance( $input = array() ) {
	$id = isset( $input['post_id'] ) ? (int) $input['post_id'] : 0;
	if ( $id <= 0 ) {
		return new WP_Error( 'invalid_input', __( 'post_id is required and must be a positive integer', 'clearpost-simple-ai-auto-post' ) );
	}

	$p = get_post( $id );
	if ( ! $p ) {
		return new WP_Error( 'not_found', __( 'Post not found', 'clearpost-simple-ai-auto-post' ) );
	}

	$permalink = get_permalink( $p->ID );
	$path      = wp_parse_url( $permalink, PHP_URL_PATH );
	if ( ! $path ) {
		return new WP_Error( 'invalid_permalink', __( 'Could not determine post path', 'clearpost-simple-ai-auto-post' ) );
	}

	$license_key = get_option( 'saiap_license_key', '' );
	if ( ! $license_key ) {
		return new WP_Error( 'no_license', __( 'No ClearPost license configured; GSC performance unavailable.', 'clearpost-simple-ai-auto-post' ) );
	}

	if ( ! defined( 'SAIAP_PREMIUM_API_URL' ) || ! function_exists( 'saiap_get_server_host' ) ) {
		return new WP_Error( 'plugin_not_loaded', __( 'ClearPost plugin internals not loaded yet.', 'clearpost-simple-ai-auto-post' ) );
	}

	$domain = saiap_get_server_host();
	$url    = SAIAP_PREMIUM_API_URL . '/api/gsc/post-performance?' . http_build_query(
		array(
			'domain'    => $domain,
			'page_path' => $path,
		)
	);

	$response = wp_remote_get(
		$url,
		array(
			'headers' => array(
				'X-License-Key' => $license_key,
			),
			'timeout' => 15,
		)
	);

	if ( is_wp_error( $response ) ) {
		return $response;
	}

	$code = wp_remote_retrieve_response_code( $response );
	if ( $code >= 400 ) {
		return new WP_Error( 'backend_error', sprintf( 'GSC performance backend returned HTTP %d', $code ) );
	}

	$body = json_decode( wp_remote_retrieve_body( $response ), true );
	if ( ! is_array( $body ) ) {
		return new WP_Error( 'invalid_response', __( 'Malformed backend response', 'clearpost-simple-ai-auto-post' ) );
	}

	$body['post_id']   = $p->ID;
	$body['page_path'] = $path;
	return $body;
}

/**
 * Handler: clearpost/get-content-calendar
 */
function saiap_mcp_get_content_calendar( $input = array() ) {
	$ctx = saiap_mcp_premium_context();
	if ( is_wp_error( $ctx ) ) {
		return $ctx;
	}

	$query = array(
		'domain'      => $ctx['domain'],
		'license_key' => $ctx['license_key'],
	);
	foreach ( array( 'start_date', 'end_date', 'status' ) as $key ) {
		if ( ! empty( $input[ $key ] ) ) {
			$query[ $key ] = sanitize_text_field( (string) $input[ $key ] );
		}
	}
	if ( ! empty( $input['limit'] ) ) {
		$query['limit'] = max( 1, min( 100, (int) $input['limit'] ) );
	}

	$response = wp_remote_get(
		SAIAP_PREMIUM_API_URL . '/api/seo/calendar?' . http_build_query( $query ),
		array(
			'timeout' => 30,
			'headers' => array( 'Content-Type' => 'application/json' ),
		)
	);

	return saiap_mcp_premium_decode( $response, 'Content calendar' );
}

/**
 * Handler: clearpost/get-keywords
 */
function saiap_mcp_get_keywords( $input = array() ) {
	$ctx = saiap_mcp_premium_context();
	if ( is_wp_error( $ctx ) ) {
		return $ctx;
	}

	$query = array(
		'domain'      => $ctx['domain'],
		'license_key' => $ctx['license_key'],
	);
	if ( isset( $input['tracked'] ) ) {
		$query['tracked'] = $input['tracked'] ? 'true' : 'false';
	}

	$response = wp_remote_get(
		SAIAP_PREMIUM_API_URL . '/api/seo/keywords?' . http_build_query( $query ),
		array(
			'timeout' => 30,
			'headers' => array( 'Content-Type' => 'application/json' ),
		)
	);

	return saiap_mcp_premium_decode( $response, 'Keywords' );
}

/**
 * Handler: clearpost/get-site-metrics
 */
function saiap_mcp_get_site_metrics( $input = array() ) {
	$ctx = saiap_mcp_premium_context();
	if ( is_wp_error( $ctx ) ) {
		return $ctx;
	}

	$days = isset( $input['days'] ) ? max( 1, min( 90, (int) $input['days'] ) ) : 28;

	$status_response = wp_remote_get(
		SAIAP_PREMIUM_API_URL . '/api/gsc/status?' . http_build_query( array( 'domain' => $ctx['domain'] ) ),
		array(
			'timeout' => 15,
			'headers' => array( 'X-License-Key' => $ctx['license_key'] ),
		)
	);

	$status = saiap_mcp_premium_decode( $status_response, 'GSC status' );
	if ( is_wp_error( $status ) ) {
		return $status;
	}

	if ( empty( $status['connected'] ) ) {
		return array(
			'connected' => false,
			'message'   => __( 'Google Search Console is not connected for this site. Connect it from the ClearPost dashboard to see search metrics.', 'clearpost-simple-ai-auto-post' ),
		);
	}

	$chart_response = wp_remote_get(
		SAIAP_PREMIUM_API_URL . '/api/gsc/chart?' . http_build_query(
			array(
				'domain' => $ctx['domain'],
				'days'   => $days,
			)
		),
		array(
			'timeout' => 15,
			'headers' => array( 'X-License-Key' => $ctx['license_key'] ),
		)
	);

	$chart = saiap_mcp_premium_decode( $chart_response, 'GSC chart' );
	if ( is_wp_error( $chart ) ) {
		return $chart;
	}

	$chart['connected'] = true;
	$chart['days']      = $days;
	return $chart;
}

/**
 * Handler: clearpost/get-strategy-context
 */
function saiap_mcp_get_strategy_context( $input = array() ) {
	$ctx = saiap_mcp_premium_context();
	if ( is_wp_error( $ctx ) ) {
		return $ctx;
	}

	$custom_context = (string) get_option( 'saiap_custom_site_context', '' );

	$cached = get_transient( 'saiap_site_context_data' );
	if ( $cached ) {
		return array(
			'context'        => $cached,
			'custom_context' => $custom_context,
			'from_cache'     => true,
		);
	}

	$response = wp_remote_post(
		SAIAP_PREMIUM_API_URL . '/api/context',
		array(
			'timeout' => 30,
			'headers' => array(
				'Content-Type' => 'application/json',
				'Accept'       => 'application/json',
			),
			'body'    => wp_json_encode(
				array(
					'license_key' => $ctx['license_key'],
					'domain'      => $ctx['domain'],
				)
			),
		)
	);

	if ( is_wp_error( $response ) ) {
		return $response;
	}

	$code = wp_remote_retrieve_response_code( $response );
	$body = json_decode( wp_remote_retrieve_body( $response ), true );

	// First-time sites have no context yet; that's a valid answer, not an error.
	if ( 404 === $code && is_array( $body ) && isset( $body['code'] ) && 'NO_CONTEXT' === $body['code'] ) {
		return array(
			'context'        => null,
			'custom_context' => $custom_context,
			'from_cache'     => false,
			'message'        => __( 'No site context has been generated yet. Generate it from the ClearPost Site Context tab.', 'clearpost-simple-ai-auto-post' ),
		);
	}

	if ( $code >= 400 || ! is_array( $body ) || ! isset( $body['context'] ) ) {
		return new WP_Error( 'backend_error', sprintf( 'Site context backend returned HTTP %d', $code ) );
	}

	set_transient( 'saiap_site_context_data', $body['context'], 24 * HOUR_IN_SECONDS );

	return array(
		'context'        => $body['context'],
		'custom_context' => $custom_context,
		'from_cache'     => false,
	);
}

/**
 * Handler: clearpost/generate-post
 *
 * Uses the same fully server-side pipeline as the daily scheduled generation
 * (saiap_orchestrate_post_creation), so taxonomy, images, and the
 * review-before-publish setting all behave exactly as they do for scheduled posts.
 */
function saiap_mcp_generate_post( $input = array() ) {
	$prompt = isset( $input['prompt'] ) ? trim( (string) $input['prompt'] ) : '';
	if ( '' === $prompt ) {
		return new WP_Error( 'invalid_input', __( 'prompt is required and must be non-empty', 'clearpost-simple-ai-auto-post' ) );
	}

	$ctx = saiap_mcp_premium_context();
	if ( is_wp_error( $ctx ) ) {
		return $ctx;
	}

	if ( ! function_exists( 'saiap_orchestrate_post_creation' ) ) {
		return new WP_Error( 'plugin_not_loaded', __( 'ClearPost generation pipeline not loaded.', 'clearpost-simple-ai-auto-post' ) );
	}

	$provider = get_option( 'saiap_default_provider', 'openai' );
	$model    = function_exists( 'saiap_get_model_for_provider' )
		? saiap_get_model_for_provider( $provider )
		: get_option( "saiap_{$provider}_model" );

	$post_type = isset( $input['post_type'] ) ? sanitize_key( (string) $input['post_type'] ) : '';
	if ( ! in_array( $post_type, array( 'post', 'page' ), true ) ) {
		$post_type = get_option( 'saiap_post_type', 'post' );
	}

	$result = saiap_orchestrate_post_creation(
		array(
			'prompt'        => $prompt,
			'provider'      => $provider,
			'model'         => $model,
			'post_type'     => $post_type,
			'title'         => isset( $input['title'] ) ? sanitize_text_field( (string) $input['title'] ) : '',
			'custom_fields' => array(),
			'content_brief' => isset( $input['content_brief'] ) ? sanitize_textarea_field( (string) $input['content_brief'] ) : '',
		)
	);

	if ( is_wp_error( $result ) ) {
		return $result;
	}

	$post_id = isset( $result['post_id'] ) ? (int) $result['post_id'] : 0;

	return array(
		'post_id'     => $post_id,
		'title'       => $post_id ? get_the_title( $post_id ) : '',
		'post_status' => isset( $result['post_status'] ) ? (string) $result['post_status'] : '',
		'permalink'   => $post_id ? get_permalink( $post_id ) : '',
		'edit_url'    => $post_id ? admin_url( 'post.php?post=' . $post_id . '&action=edit' ) : '',
	);
}

/**
 * Handler: clearpost/add-to-calendar
 */
function saiap_mcp_add_to_calendar( $input = array() ) {
	$title         = isset( $input['title'] ) ? sanitize_text_field( (string) $input['title'] ) : '';
	$scheduled_for = isset( $input['scheduled_for'] ) ? sanitize_text_field( (string) $input['scheduled_for'] ) : '';

	if ( '' === $title ) {
		return new WP_Error( 'invalid_input', __( 'title is required and must be non-empty', 'clearpost-simple-ai-auto-post' ) );
	}
	if ( ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', $scheduled_for ) ) {
		return new WP_Error( 'invalid_input', __( 'scheduled_for is required and must be a YYYY-MM-DD date', 'clearpost-simple-ai-auto-post' ) );
	}

	$ctx = saiap_mcp_premium_context();
	if ( is_wp_error( $ctx ) ) {
		return $ctx;
	}

	$prompt = isset( $input['prompt'] ) ? trim( (string) $input['prompt'] ) : '';
	if ( '' === $prompt ) {
		/* translators: %s: post title */
		$prompt = sprintf( 'Write a comprehensive, SEO-optimized blog post titled "%s".', $title );
	}

	$response = wp_remote_post(
		SAIAP_PREMIUM_API_URL . '/api/seo/calendar',
		array(
			'timeout' => 30,
			'headers' => array( 'Content-Type' => 'application/json' ),
			'body'    => wp_json_encode(
				array(
					'domain'        => $ctx['domain'],
					'license_key'   => $ctx['license_key'],
					'title'         => $title,
					'prompt'        => $prompt,
					'content_brief' => isset( $input['content_brief'] ) ? sanitize_textarea_field( (string) $input['content_brief'] ) : '',
					'scheduled_for' => $scheduled_for,
				)
			),
		)
	);

	return saiap_mcp_premium_decode( $response, 'Content calendar' );
}

/**
 * Handler: clearpost/set-keyword-tracking
 */
function saiap_mcp_set_keyword_tracking( $input = array() ) {
	$keyword    = isset( $input['keyword'] ) ? trim( sanitize_text_field( (string) $input['keyword'] ) ) : '';
	$keyword_id = isset( $input['keyword_id'] ) ? trim( sanitize_text_field( (string) $input['keyword_id'] ) ) : '';
	$enabled    = isset( $input['enabled'] ) ? (bool) $input['enabled'] : true;

	if ( '' !== $keyword && '' !== $keyword_id ) {
		return new WP_Error( 'invalid_input', __( 'Provide either keyword or keyword_id, not both', 'clearpost-simple-ai-auto-post' ) );
	}
	if ( '' === $keyword && '' === $keyword_id ) {
		return new WP_Error( 'invalid_input', __( 'One of keyword or keyword_id is required', 'clearpost-simple-ai-auto-post' ) );
	}

	$ctx = saiap_mcp_premium_context();
	if ( is_wp_error( $ctx ) ) {
		return $ctx;
	}

	if ( '' !== $keyword ) {
		// Add a brand-new keyword and start tracking it.
		$response = wp_remote_post(
			SAIAP_PREMIUM_API_URL . '/api/seo/keywords/track',
			array(
				'timeout' => 30,
				'headers' => array( 'Content-Type' => 'application/json' ),
				'body'    => wp_json_encode(
					array(
						'domain'      => $ctx['domain'],
						'license_key' => $ctx['license_key'],
						'is_new'      => true,
						'keyword'     => $keyword,
					)
				),
			)
		);

		return saiap_mcp_premium_decode( $response, 'Keyword tracking' );
	}

	// Toggle tracking on an existing keyword.
	$response = wp_remote_request(
		SAIAP_PREMIUM_API_URL . '/api/seo/keywords/' . rawurlencode( $keyword_id ) . '/tracking',
		array(
			'method'  => 'PUT',
			'timeout' => 30,
			'headers' => array( 'Content-Type' => 'application/json' ),
			'body'    => wp_json_encode(
				array(
					'domain'      => $ctx['domain'],
					'license_key' => $ctx['license_key'],
					'enabled'     => $enabled,
				)
			),
		)
	);

	return saiap_mcp_premium_decode( $response, 'Keyword tracking' );
}
