<?php
/**
 * @package ACF
 * @author  WP Engine
 *
 * © 2026 Advanced Custom Fields (ACF®). All rights reserved.
 * "ACF" is a trademark of WP Engine.
 * Licensed under the GNU General Public License v2 or later.
 * https://www.gnu.org/licenses/gpl-2.0.html
 */

namespace ACF\AI\Abilities;

use WP_Error;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

/**
 * ACF Taxonomy Abilities
 *
 * Handles ACF custom taxonomy related abilities for the WordPress Abilities API.
 */
class Taxonomy extends AbstractAbilityGroup {

	/**
	 * Register taxonomy related abilities.
	 *
	 * @since 6.8.0
	 *
	 * @return void
	 */
	public function register_abilities() {
		if ( ! $this->is_abilities_api_available() ) {
			return;
		}

		// Register ACF Custom Taxonomies resource.
		$this->register_ability(
			'acf/custom-taxonomies',
			array(
				'label'               => __( 'ACF Custom Taxonomies', 'acf' ),
				'description'         => __( 'Get all ACF registered custom taxonomies', 'acf' ),
				'category'            => 'acf-field-management',
				'input_schema'        => array(
					'type' => 'null',
				),
				'output_schema'       => array(
					'type'       => 'object',
					'properties' => array(
						'custom_taxonomies' => array(
							'type'  => 'array',
							'items' => array( 'type' => 'object' ),
						),
						'count'             => array( 'type' => 'integer' ),
						'message'           => array( 'type' => 'string' ),
					),
				),
				'execute_callback'    => array( $this, 'get_custom_taxonomies' ),
				'permission_callback' => '__return_true',
				'meta'                => array(
					'annotations'  => array(
						'readonly'    => true,
						'destructive' => false,
						'idempotent'  => true,
					),
					'show_in_rest' => true,
				),
			)
		);

		// Register ACF Custom Taxonomy creation tool.
		$this->register_ability(
			'acf/create-custom-taxonomy',
			array(
				'label'               => __( 'Create ACF Custom Taxonomy', 'acf' ),
				'description'         => __( 'Create a new custom taxonomy in ACF', 'acf' ),
				'category'            => 'acf-field-management',
				'input_schema'        => array(
					'type'       => 'object',
					'properties' => array(
						'taxonomy'          => array(
							'type'        => 'string',
							'pattern'     => '^[a-z0-9_-]*$',
							'maxLength'   => 32,
							'description' => 'The taxonomy key (slug)',
							'required'    => true,
						),
						'label'             => array(
							'type'        => 'string',
							'description' => 'The singular label for the taxonomy',
							'required'    => true,
						),
						'plural_label'      => array(
							'type'        => 'string',
							'description' => 'The plural label for the taxonomy',
							'required'    => true,
						),
						'description'       => array(
							'type'        => 'string',
							'description' => 'Description of the taxonomy',
							'required'    => false,
						),
						'public'            => array(
							'type'        => 'boolean',
							'description' => 'Whether the taxonomy is public',
							'required'    => false,
						),
						'hierarchical'      => array(
							'type'        => 'boolean',
							'description' => 'Whether the taxonomy is hierarchical (like categories) or flat (like tags)',
							'required'    => false,
						),
						'post_types'        => array(
							'type'        => 'array',
							'description' => 'Array of post types this taxonomy applies to',
							'required'    => false,
							'items'       => array(
								'type' => 'string',
							),
						),
						'show_in_rest'      => array(
							'type'        => 'boolean',
							'description' => 'Whether to show this taxonomy in the REST API (required for AI abilities)',
							'required'    => false,
						),
						'rest_base'         => array(
							'type'        => 'string',
							'description' => 'Custom REST API base path (defaults to taxonomy key)',
							'required'    => false,
						),
						'allow_ai_access'   => array(
							'type'        => 'boolean',
							'description' => 'Whether to allow AI access to this taxonomy',
							'required'    => false,
						),
						'ai_description'    => array(
							'type'        => 'string',
							'description' => 'Description to help AI understand the purpose of this taxonomy',
							'required'    => false,
						),
						'show_ui'           => array(
							'type'        => 'boolean',
							'description' => 'Whether to generate a default UI for managing this taxonomy in the admin',
							'required'    => false,
						),
						'show_admin_column' => array(
							'type'        => 'boolean',
							'description' => 'Whether to display a column for the taxonomy on its post type listing screens',
							'required'    => false,
						),
					),
				),
				'output_schema'       => array(
					'type'       => 'object',
					'properties' => array(
						'success'  => array( 'type' => 'boolean' ),
						'taxonomy' => array( 'type' => 'object' ),
						'message'  => array( 'type' => 'string' ),
					),
				),
				'execute_callback'    => array( $this, 'create_custom_taxonomy' ),
				'permission_callback' => function () {
					return current_user_can( acf_get_setting( 'capability' ) );
				},
				'meta'                => array(
					'annotations'  => array(
						'readonly'    => false,
						'destructive' => false,
						'idempotent'  => false,
					),
					'show_in_rest' => true,
				),
			)
		);

		// Register abilities for each ACF custom taxonomy that has REST API enabled.
		$this->register_acf_taxonomy_term_abilities();
	}

	/**
	 * Register CRUD abilities for taxonomy terms.
	 *
	 * @since 6.8.0
	 *
	 * @return void
	 */
	private function register_acf_taxonomy_term_abilities() {
		$acf_taxonomies = acf_get_acf_taxonomies();

		foreach ( $acf_taxonomies as $acf_taxonomy ) {
			$taxonomy_name = $acf_taxonomy['taxonomy'] ?? '';
			if ( ! $taxonomy_name ) {
				continue;
			}

			// Check if AI access is enabled for this taxonomy.
			if ( empty( $acf_taxonomy['allow_ai_access'] ) || empty( $acf_taxonomy['active'] ) ) {
				continue;
			}

			// Sanitize taxonomy name for feature ID (convert underscores to hyphens, ensure lowercase)
			$sanitized_taxonomy_name = str_replace( '_', '-', strtolower( $taxonomy_name ) );

			// Skip if we can't retrieve the taxonomy object or if it isn't configured with REST API access.
			$taxonomy_object = get_taxonomy( $taxonomy_name );
			if ( ! $taxonomy_object || empty( $taxonomy_object->show_in_rest ) ) {
				continue;
			}

			$rest_base             = acf_get_object_type_rest_base( $taxonomy_object );
			$taxonomy_label        = $taxonomy_object->labels->singular_name ?? $taxonomy_name;
			$taxonomy_label_plural = $taxonomy_object->labels->name ?? $taxonomy_name . 's';

			// Get AI description for enhanced ability descriptions
			$ai_description     = $acf_taxonomy['ai_description'] ?? '';
			$description_suffix = $ai_description ? ' ' . $ai_description : '';

			// Get ACF fields for this taxonomy.
			$acf_fields = $this->get_acf_fields_for_object( 'taxonomy', $taxonomy_name );

			// Get schemas from REST controller.
			$item_schema       = $this->get_rest_item_output_schema( $acf_fields, $taxonomy_name );
			$collection_schema = $this->get_rest_item_output_schema( $acf_fields, $taxonomy_name, 'collection' );

			// Register query/list feature for this taxonomy
			$this->register_ability(
				'acf/' . $sanitized_taxonomy_name . 's',
				array(
					/* translators: %s The plural label for the custom taxonomy. */
					'label'               => sprintf( __( 'Query %s', 'acf' ), $taxonomy_label_plural ),
					/* translators: %s The plural label for the custom taxonomy. */
					'description'         => sprintf( __( 'Get a list of %s terms that match the query parameters.', 'acf' ), strtolower( $taxonomy_label_plural ) ) . $description_suffix,
					'category'            => 'wordpress-content-discovery',
					'input_schema'        => array(
						'type'                 => array( 'object', 'null' ),
						'properties'           => array(
							'per_page'   => array(
								'type'    => 'integer',
								'default' => 10,
								'minimum' => 1,
								'maximum' => 100,
							),
							'page'       => array(
								'type'    => 'integer',
								'default' => 1,
								'minimum' => 1,
							),
							'search'     => array(
								'type'        => 'string',
								'default'     => '',
								'description' => 'Search terms by name.',
							),
							'post'       => array(
								'type'        => 'integer',
								'default'     => null,
								'description' => 'Search terms assigned to a specific post.',
							),
							'slug'       => array(
								'type'        => 'array',
								'items'       => array(
									'type' => 'string',
								),
								'description' => 'Search terms with specific slugs.',
							),
							'parent'     => array(
								'type'        => 'integer',
								'description' => 'Filter by parent term ID for hierarchical taxonomies. Use 0 for top-level terms only.',
								'required'    => false,
							),
							'orderby'    => array(
								'type'        => 'string',
								'enum'        => array( 'id', 'name', 'slug', 'description', 'count' ),
								'default'     => 'name',
								'description' => 'Sort collection by term attribute.',
							),
							'order'      => array(
								'type'        => 'string',
								'enum'        => array( 'asc', 'desc' ),
								'default'     => 'asc',
								'description' => 'Order sort attribute ascending or descending.',
							),
							'hide_empty' => array(
								'type'        => 'boolean',
								'default'     => false,
								'description' => 'Whether to hide terms not assigned to any posts.',
							),
						),
						'additionalProperties' => false,
					),
					'output_schema'       => $collection_schema,
					'execute_callback'    => function ( $input = array() ) use ( $rest_base ) {
						return $this->execute_rest_request( 'GET', $rest_base, $input );
					},
					'permission_callback' => function () {
						return current_user_can( 'read' );
					},
					'meta'                => array(
						'annotations'  => array(
							'readonly'    => true,
							'destructive' => false,
							'idempotent'  => true,
						),
						'show_in_rest' => true,
					),
					'ability_class'       => self::REST_ABILITY_CLASS,
				)
			);

			// Register create ability for this taxonomy
			$this->register_ability(
				'acf/create-' . $sanitized_taxonomy_name,
				array(
					/* translators: %s The singular label for the custom taxonomy. */
					'label'               => sprintf( __( 'Create %s Term', 'acf' ), $taxonomy_label ),
					/* translators: %s The singular label for the custom taxonomy. */
					'description'         => sprintf( __( 'Create a new "%s" term.', 'acf' ), strtolower( $taxonomy_label ) ) . $description_suffix,
					'category'            => 'wordpress-content-discovery',
					'input_schema'        => $this->get_rest_item_input_schema( $acf_fields, $taxonomy_label, $taxonomy_object->hierarchical ),
					'output_schema'       => $item_schema,
					'execute_callback'    => function ( $input = array() ) use ( $rest_base ) {
						return $this->execute_rest_request( 'POST', $rest_base, $input );
					},
					'permission_callback' => function () use ( $taxonomy_object ) {
						return current_user_can( $taxonomy_object->cap->manage_terms );
					},
					'meta'                => array(
						'annotations'  => array(
							'readonly'    => false,
							'destructive' => false,
							'idempotent'  => false,
						),
						'show_in_rest' => true,
					),
					'ability_class'       => self::REST_ABILITY_CLASS,
				)
			);

			// Register view single ability for this taxonomy
			$this->register_ability(
				'acf/view-' . $sanitized_taxonomy_name,
				array(
					/* translators: %s The singular label for the custom taxonomy. */
					'label'               => sprintf( __( 'View a %s Term', 'acf' ), $taxonomy_label ),
					/* translators: %s The singular label for the custom taxonomy. */
					'description'         => sprintf( __( 'Get a %s term by its ID.', 'acf' ), strtolower( $taxonomy_label ) ) . $description_suffix,
					'category'            => 'wordpress-content-discovery',
					'input_schema'        => array(
						'type'       => 'object',
						'properties' => array(
							'id' => array(
								'type'        => 'integer',
								'description' => sprintf( 'The ID of the %s term to view.', strtolower( $taxonomy_label ) ),
								'required'    => true,
							),
						),
					),
					'output_schema'       => $item_schema,
					'execute_callback'    => function ( $input = array() ) use ( $rest_base ) {
						$item_id = $input['id'] ?? null;
						return $this->execute_rest_request( 'GET', $rest_base, $input, $item_id );
					},
					'permission_callback' => function () {
						return current_user_can( 'read' );
					},
					'meta'                => array(
						'annotations'  => array(
							'readonly'    => true,
							'destructive' => false,
							'idempotent'  => true,
						),
						'show_in_rest' => true,
					),
					'ability_class'       => self::REST_ABILITY_CLASS,
				)
			);

			// Register update ability for this taxonomy
			$this->register_ability(
				'acf/update-' . $sanitized_taxonomy_name,
				array(
					/* translators: %s The singular label for the custom taxonomy. */
					'label'               => sprintf( __( 'Update a %s Term', 'acf' ), $taxonomy_label ),
					/* translators: %s The singular label for the custom taxonomy. */
					'description'         => sprintf( __( 'Update a %s term by its ID.', 'acf' ), strtolower( $taxonomy_label ) ) . $description_suffix,
					'category'            => 'wordpress-content-discovery',
					'input_schema'        => $this->get_rest_item_input_schema( $acf_fields, $taxonomy_label, $taxonomy_object->hierarchical, 'update' ),
					'output_schema'       => $item_schema,
					'execute_callback'    => function ( $input = array() ) use ( $rest_base ) {
						$item_id = $input['id'] ?? null;
						return $this->execute_rest_request( 'PUT', $rest_base, $input, $item_id );
					},
					'permission_callback' => function () use ( $taxonomy_object ) {
						return current_user_can( $taxonomy_object->cap->edit_terms );
					},
					'meta'                => array(
						'annotations'  => array(
							'readonly'    => false,
							'destructive' => false,
							'idempotent'  => true,
						),
						'show_in_rest' => true,
					),
					'ability_class'       => self::REST_ABILITY_CLASS,
				)
			);

			// Register delete ability for this taxonomy.
			$this->register_ability(
				'acf/delete-' . $sanitized_taxonomy_name,
				array(
					/* translators: %s The singular label for the custom taxonomy. */
					'label'               => sprintf( __( 'Delete a %s Term', 'acf' ), $taxonomy_label ),
					/* translators: %s The singular label for the custom taxonomy. */
					'description'         => sprintf( __( 'Delete a %s term by its ID.', 'acf' ), strtolower( $taxonomy_label ) ) . $description_suffix,
					'category'            => 'wordpress-content-discovery',
					'input_schema'        => array(
						'type'       => 'object',
						'properties' => array(
							'id'    => array(
								'type'        => 'integer',
								'description' => sprintf( 'The ID of the %s term to delete.', strtolower( $taxonomy_label ) ),
								'required'    => true,
							),
							'force' => array(
								'type'        => 'boolean',
								'description' => 'Whether to permanently delete the term (required, as terms cannot be trashed)',
								'required'    => false,
								'default'     => false,
							),
						),
					),
					'output_schema'       => $item_schema,
					'execute_callback'    => function ( $input = array() ) use ( $rest_base ) {
						$item_id = $input['id'] ?? null;
						return $this->execute_rest_request( 'DELETE', $rest_base, $input, $item_id );
					},
					'permission_callback' => function () use ( $taxonomy_object ) {
						return current_user_can( $taxonomy_object->cap->delete_terms );
					},
					'meta'                => array(
						'annotations'  => array(
							'readonly'    => false,
							'destructive' => true,
							'idempotent'  => true,
						),
						'show_in_rest' => true,
					),
					'ability_class'       => self::REST_ABILITY_CLASS,
				)
			);
		}
	}

	/**
	 * Get REST input schema for taxonomy terms.
	 *
	 * @since 6.8.0
	 *
	 * @param array   $acf_fields     ACF fields for this taxonomy.
	 * @param string  $taxonomy_label Taxonomy label for descriptions.
	 * @param boolean $hierarchical   Whether taxonomy is hierarchical.
	 * @param string  $action         Action type ('create' or 'update').
	 * @return array
	 */
	private function get_rest_item_input_schema( array $acf_fields, string $taxonomy_label, bool $hierarchical = false, string $action = 'create' ): array {
		$schema = array(
			'type'       => 'object',
			'properties' => array(),
		);

		if ( 'update' === $action ) {
			$schema['properties']['id'] = array(
				'type'        => 'integer',
				'description' => sprintf( 'The ID of the %s term to update.', strtolower( $taxonomy_label ) ),
				'required'    => true,
			);
		}

		$schema['properties']['name'] = array(
			'type'        => 'string',
			'description' => sprintf( 'The name of the %s term.', strtolower( $taxonomy_label ) ),
			'required'    => 'update' !== $action,
		);

		$schema['properties']['description'] = array(
			'type'        => 'string',
			'description' => sprintf( 'The description of the %s term.', strtolower( $taxonomy_label ) ),
			'required'    => false,
		);

		$schema['properties']['slug'] = array(
			'type'        => 'string',
			'description' => sprintf( 'The slug of the %s term (auto-generated from name if not provided).', strtolower( $taxonomy_label ) ),
			'required'    => false,
		);

		if ( $hierarchical ) {
			$schema['properties']['parent'] = array(
				'type'        => 'integer',
				'description' => 'Parent term ID for hierarchical taxonomies. Use 0 or omit for top-level terms. For child terms, provide the ID of the parent term.',
				'required'    => false,
				'default'     => 0,
			);
		}

		return $this->add_acf_fields_to_schema( $schema, $acf_fields );
	}

	/**
	 * Get REST output schema for taxonomy terms.
	 *
	 * @since 6.8.0
	 *
	 * @param array  $acf_fields ACF fields for this taxonomy.
	 * @param string $taxonomy   Taxonomy name.
	 * @param string $type       Schema type ('item' or 'collection').
	 * @return array|null
	 */
	private function get_rest_item_output_schema( array $acf_fields, string $taxonomy, string $type = 'item' ) {
		$taxonomy_object = get_taxonomy( $taxonomy );

		if ( ! $taxonomy_object ) {
			return null;
		}

		$controller = $taxonomy_object->get_rest_controller();
		if ( ! $controller || ! method_exists( $controller, 'get_public_item_schema' ) ) {
			return null;
		}

		$schema = $controller->get_public_item_schema();
		$schema = $this->add_acf_fields_to_schema( $schema, $acf_fields );

		if ( $type === 'collection' ) {
			return array(
				'type'  => 'array',
				'items' => $schema,
			);
		}

		return $schema;
	}

	/**
	 * Callback for the "acf/get-custom-taxonomies" ability.
	 *
	 * @since 6.8.0
	 *
	 * @param array $input Input args (unused).
	 * @return array
	 */
	public function get_custom_taxonomies( $input ) {
		unset( $input ); // Not used, but required by interface.

		$custom_taxonomies = array();

		// Get ACF custom taxonomies.
		$acf_taxonomies = acf_get_acf_taxonomies();

		foreach ( $acf_taxonomies as $acf_taxonomy ) {
			$taxonomy_name = $acf_taxonomy['taxonomy'] ?? '';
			if ( ! $taxonomy_name ) {
				continue;
			}

			if ( empty( $acf_taxonomy['active'] ) || empty( $acf_taxonomy['allow_ai_access'] ) ) {
				continue;
			}

			$taxonomy_object = get_taxonomy( $taxonomy_name );
			if ( $taxonomy_object ) {
				$taxonomy_data = array(
					'taxonomy'     => $taxonomy_name,
					'label'        => $taxonomy_object->label,
					'labels'       => (array) $taxonomy_object->labels,
					'description'  => $taxonomy_object->description,
					'public'       => $taxonomy_object->public,
					'hierarchical' => $taxonomy_object->hierarchical,
					'object_type'  => $taxonomy_object->object_type,
					'acf_settings' => $acf_taxonomy,
				);

				// Add ACF field groups information
				$acf_fields = $this->get_acf_fields_for_object( 'taxonomy', $taxonomy_name );
				if ( ! empty( $acf_fields ) ) {
					$taxonomy_data['acf_field_groups'] = $acf_fields;
				}

				$custom_taxonomies[] = $taxonomy_data;
			}
		}

		$count = count( $custom_taxonomies );

		return array(
			'custom_taxonomies' => $custom_taxonomies,
			'count'             => $count,
			'message'           => sprintf(
				/* translators: %d: Number of ACF custom taxonomies */
				_n( 'Found %d ACF custom taxonomy', 'Found %d ACF custom taxonomies', $count, 'acf' ),
				$count
			),
		);
	}

	/**
	 * Callback for the "acf/create-custom-taxonomy" ability.
	 *
	 * @since 6.8.0
	 *
	 * @param array $input Input args.
	 * @return array|WP_Error
	 */
	public function create_custom_taxonomy( $input ) {
		// Required parameters
		$taxonomy     = $input['taxonomy'];
		$label        = $input['label'];
		$plural_label = $input['plural_label'];

		// Basic optional parameters
		$description  = $input['description'] ?? '';
		$public       = $input['public'] ?? true;
		$hierarchical = $input['hierarchical'] ?? false;
		$post_types   = $input['post_types'] ?? array( 'post' );

		// REST API settings
		$show_in_rest = $input['show_in_rest'] ?? true;
		$rest_base    = $input['rest_base'] ?? '';

		// AI settings
		$allow_ai_access = $input['allow_ai_access'] ?? true;
		$ai_description  = $input['ai_description'] ?? '';

		// UI settings
		$show_ui           = $input['show_ui'] ?? true;
		$show_admin_column = $input['show_admin_column'] ?? false;

		// Check if taxonomy already exists.
		if ( taxonomy_exists( $taxonomy ) ) {
			return new WP_Error(
				'taxonomy_exists',
				__( 'A taxonomy with this key already exists', 'acf' ),
				array( 'status' => 400 )
			);
		}

		// Use ACF's method to create the taxonomy.
		$taxonomy_data = array(
			'key'               => uniqid( 'taxonomy_' ),
			'taxonomy'          => $taxonomy,
			'title'             => $plural_label,
			'labels'            => array(
				'name'          => $plural_label,
				'singular_name' => $label,
			),
			'description'       => $description,
			'public'            => $public ? 1 : 0,
			'hierarchical'      => $hierarchical ? 1 : 0,
			'object_type'       => $post_types,
			'active'            => 1,
			// REST API settings
			'show_in_rest'      => $show_in_rest ? 1 : 0,
			// AI settings
			'allow_ai_access'   => $allow_ai_access ? 1 : 0,
			// UI settings
			'show_ui'           => $show_ui ? 1 : 0,
			'show_admin_column' => $show_admin_column ? 1 : 0,
		);

		// Add optional settings only if provided
		if ( ! empty( $rest_base ) ) {
			$taxonomy_data['rest_base'] = sanitize_text_field( $rest_base );
		}

		if ( ! empty( $ai_description ) ) {
			$taxonomy_data['ai_description'] = sanitize_text_field( $ai_description );
		}

		$result = acf_import_taxonomy( $taxonomy_data );

		if ( empty( $result['ID'] ) || ! is_int( $result['ID'] ) || ! taxonomy_exists( $result['taxonomy'] ) ) {
			return new WP_Error(
				'taxonomy_creation_failed',
				__( 'Failed to create the custom taxonomy', 'acf' )
			);
		}

		return array(
			'success'  => true,
			'taxonomy' => $result,
			'message'  => __( 'ACF custom taxonomy created successfully', 'acf' ),
		);
	}
}
