<?php
/**
 * Ability: wp-agent/list-terms
 *
 * Read-only. Listet Terme einer Taxonomie mit Such-, hide_empty- und
 * Eltern-Term-Filter. number begrenzt die Trefferzahl (Default 100, maximal
 * 500), analog zum per_page-Deckel in wp-agent/list-posts: get_terms() liefert
 * ohne eigenes Limit sonst potenziell die gesamte Taxonomie in einer Antwort.
 *
 * @package WPAgentCompanion
 */

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

function wpagent_ability_list_terms(): array {
	return array(
		'label'               => __( 'Terme auflisten', 'wp-agent-companion' ),
		'description'         => __( 'Listet Terme einer Taxonomie mit optionaler Suche, hide_empty- und Eltern-Term-Filter sowie kanonischem state_hash für konfliktgeschützte Folgeänderungen.', 'wp-agent-companion' ),
		'category'            => 'wp-agent',
		'input_schema'        => array(
			'type'                 => 'object',
			'properties'           => array(
				'taxonomy'   => array(
					'type'        => 'string', 'maxLength' => 32, 'pattern' => '^[a-z0-9_-]{1,32}$',
					'description' => __( 'Muss eine registrierte Taxonomie sein (z. B. category, post_tag).', 'wp-agent-companion' ),
				),
				'search'     => array( 'type' => 'string', 'minLength' => 1, 'maxLength' => 100 ),
				'hide_empty' => array(
					'type'        => 'boolean',
					'default'     => false,
					'description' => __( 'true blendet Terme ohne zugeordnete Objekte aus.', 'wp-agent-companion' ),
				),
				'parent'     => array(
					'type'        => 'integer', 'minimum' => 0, 'maximum' => 2147483647,
					'description' => __( 'Nur direkte Kind-Terme dieses Eltern-Terms (nur bei hierarchischen Taxonomien sinnvoll).', 'wp-agent-companion' ),
				),
				'number'     => array(
					'type'        => 'integer', 'minimum' => 1, 'maximum' => 100,
					'default'     => 100,
					'description' => __( 'Maximale Trefferzahl, gedeckelt auf 100.', 'wp-agent-companion' ),
				),
			),
			'required'   => array( 'taxonomy' ),
			'additionalProperties' => false,
		),
		'output_schema'       => array(
			'type'                 => 'object',
			'properties'           => array(
				'items' => array(
					'type'  => 'array', 'maxItems' => 100,
					'items' => array(
						'type'                 => 'object',
						'properties'           => array(
							'id'          => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => 2147483647 ),
							'name'        => array( 'type' => 'string', 'maxLength' => 200 ),
							'slug'        => array( 'type' => 'string', 'maxLength' => 200 ),
							'taxonomy'    => array( 'type' => 'string', 'maxLength' => 32 ),
							'parent'      => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => 2147483647 ),
							'count'       => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => 2147483647 ),
							'description' => array( 'type' => 'string', 'maxLength' => 10000 ),
							'state_hash'  => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
						),
						'required'             => array( 'id', 'name', 'slug', 'taxonomy', 'parent', 'count', 'description', 'state_hash' ),
						'additionalProperties' => false,
					),
				),
				'count' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => 100 ),
			),
			'required'             => array( 'items', 'count' ),
			'additionalProperties' => false,
		),
		'execute_callback'    => 'wpagent_exec_list_terms',
		'permission_callback' => 'wpagent_perm_edit_posts',
		'meta'                => array(
			'show_in_rest' => true,
			'annotations'  => array(
				'readonly'    => true,
				'destructive' => false,
				'idempotent'  => true,
			),
		),
	);
}

/**
 * @param mixed $input
 * @return array|WP_Error
 */
function wpagent_exec_list_terms( $input ) {
	$input    = is_array( $input ) ? $input : array();
	$taxonomy = wpagent_core_mutator_bounded_text( $input['taxonomy'] ?? null, 32 );

	if ( is_wp_error( $taxonomy ) || 1 !== preg_match( '/^[a-z0-9_-]{1,32}$/D', $taxonomy ) || ! taxonomy_exists( $taxonomy ) ) {
		return new WP_Error( 'wpagent_bad_taxonomy', sprintf( __( 'Unbekannte Taxonomie "%s".', 'wp-agent-companion' ), $taxonomy ), array( 'status' => 400 ) );
	}

	// Feingranulare Capability der konkreten Taxonomie (get_taxonomy()->cap),
	// fail-closed analog zu wpagent_post_type_cap() in helpers.php.
	$tax_object = get_taxonomy( $taxonomy );
	$read_cap   = ( $tax_object && isset( $tax_object->cap->assign_terms ) && '' !== (string) $tax_object->cap->assign_terms )
		? (string) $tax_object->cap->assign_terms
		: 'do_not_allow';
	if ( ! current_user_can( $read_cap ) ) {
		return new WP_Error( 'wpagent_forbidden', __( 'Keine Berechtigung für diese Taxonomie.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}

	$number = 100;
	if ( array_key_exists( 'number', $input ) ) {
		$number = wpagent_core_schema_input_integer( $input['number'], 1, 100, 'number' );
		if ( is_wp_error( $number ) ) {
			return $number;
		}
	}

	// KRITISCH: Bei GET-Aufrufen (readonly-Abilities laufen als GET mit
	// verschachtelten Query-Parametern, siehe WpClient::runAbility()) validiert
	// die Abilities API den Input laut Schema, wandelt ihn aber NICHT in echte
	// PHP-Typen um: WP_Ability::execute() ruft normalize_input() und
	// validate_input() auf, übergibt do_execute() aber weiterhin den
	// UNVERÄNDERTEN Rohwert (siehe wp-includes/abilities-api/class-wp-ability.php).
	// input[hide_empty]=false kommt also als der PHP-STRING "false" an, und
	// dieser ist truthy (! empty( 'false' ) === true). Ein einfaches
	// ! empty( $input['hide_empty'] ) würde hide_empty=false fälschlich als
	// wahr behandeln. rest_sanitize_boolean() (WordPress-Kernfunktion, immer
	// verfügbar) übernimmt exakt die gleiche String-zu-Bool-Konvertierung wie
	// die REST-API selbst und behandelt 'false'/'0' korrekt als falsch.
	$hide_empty = isset( $input['hide_empty'] ) ? rest_sanitize_boolean( $input['hide_empty'] ) : false;

	$query_args = array(
		'taxonomy'   => $taxonomy,
		'hide_empty' => $hide_empty,
		'number'     => $number,
	);
	if ( array_key_exists( 'search', $input ) ) {
		$search = wpagent_core_mutator_bounded_text( $input['search'], 100 );
		if ( is_wp_error( $search ) ) {
			return $search;
		}
		$query_args['search'] = $search;
	}
	if ( array_key_exists( 'parent', $input ) ) {
		$parent = wpagent_core_schema_input_integer( $input['parent'], 0, 2147483647, 'parent' );
		if ( is_wp_error( $parent ) ) {
			return $parent;
		}
		$query_args['parent'] = $parent;
	}

	$terms = get_terms( $query_args );
	if ( is_wp_error( $terms ) ) {
		return $terms;
	}

	$items = array();
	foreach ( $terms as $term ) {
		$item = array(
			'id'          => (int) $term->term_id,
			'name'        => wpagent_core_schema_output_text( $term->name, 200, 'items.name' ),
			'slug'        => wpagent_core_schema_output_text( $term->slug, 200, 'items.slug' ),
			'taxonomy'    => wpagent_core_schema_output_text( $term->taxonomy, 32, 'items.taxonomy' ),
			'parent'      => (int) $term->parent,
			'count'       => (int) $term->count,
			'description' => wpagent_core_schema_output_text( $term->description, 10000, 'items.description' ),
			'state_hash'  => wpagent_term_state_hash( $term ),
		);
		foreach ( $item as $value ) {
			if ( is_wp_error( $value ) ) {
				return $value;
			}
		}
		$items[] = $item;
	}
	$items = wpagent_core_schema_output_list( $items, 100, 'items' );
	if ( is_wp_error( $items ) ) {
		return $items;
	}

	return array(
		'items' => $items,
		'count' => count( $items ),
	);
}
