<?php
/** Shared validation, state and locking for WooCommerce global product attributes. */

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

define( 'WPAGENT_WC_ATTRIBUTE_LOCK_OPTION', 'wp_agent_wc_attribute_lock' );
define( 'WPAGENT_WC_ATTRIBUTE_LOCK_TTL', 60 );

function wpagent_perm_wc_manage_product_terms( $input = null ): bool {
	unset( $input );
	return current_user_can( 'manage_product_terms' );
}

/** @return true|WP_Error */
function wpagent_wc_require_attribute_runtime() {
	$woocommerce = wpagent_require_woocommerce();
	if ( is_wp_error( $woocommerce ) ) {
		return $woocommerce;
	}
	foreach ( array( 'wc_get_attribute_taxonomies', 'wc_create_attribute', 'wc_update_attribute' ) as $function ) {
		if ( ! function_exists( $function ) ) {
			return new WP_Error( 'wpagent_wc_attributes_unavailable', __( 'Diese WooCommerce-Version stellt die öffentliche Produktattribut-API nicht bereit.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
	}
	return true;
}

/** @return stdClass|null */
function wpagent_wc_attribute_record( int $attribute_id ) {
	$attributes = wc_get_attribute_taxonomies();
	$record     = $attributes[ 'id:' . $attribute_id ] ?? null;
	return is_object( $record ) ? $record : null;
}

function wpagent_wc_refresh_attribute_cache(): void {
	delete_transient( 'wc_attribute_taxonomies' );
	if ( class_exists( 'WC_Cache_Helper' ) ) {
		WC_Cache_Helper::invalidate_cache_group( 'woocommerce-attributes' );
	}
}

function wpagent_wc_attribute_id_by_slug( string $slug ): int {
	wpagent_wc_refresh_attribute_cache();
	foreach ( wc_get_attribute_taxonomies() as $record ) {
		if ( is_object( $record ) && hash_equals( $slug, (string) ( $record->attribute_name ?? '' ) ) ) {
			return absint( $record->attribute_id ?? 0 );
		}
	}
	return 0;
}


/**
 * Count dependent attribute terms in one query so list operations do not
 * introduce an N+1 query. The count participates in the conflict hash because
 * deleting an attribute also deletes all of its terms.
 *
 * @param string[] $taxonomies Attribute taxonomy names including the pa_ prefix.
 * @return array<string,int>
 */
function wpagent_wc_attribute_term_counts( array $taxonomies ): array {
	$taxonomies = array_values( array_unique( array_filter( array_map( 'strval', $taxonomies ) ) ) );
	$counts     = array_fill_keys( $taxonomies, 0 );
	if ( array() === $taxonomies ) {
		return $counts;
	}

	global $wpdb;
	$placeholders = implode( ', ', array_fill( 0, count( $taxonomies ), '%s' ) );
	$query        = $wpdb->prepare(
		"SELECT taxonomy, COUNT(*) AS term_count FROM {$wpdb->term_taxonomy} WHERE taxonomy IN ({$placeholders}) GROUP BY taxonomy", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		...$taxonomies
	);
	$rows         = $wpdb->get_results( $query, ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
	foreach ( $rows as $row ) {
		$taxonomy = (string) ( $row['taxonomy'] ?? '' );
		if ( array_key_exists( $taxonomy, $counts ) ) {
			$counts[ $taxonomy ] = max( 0, (int) ( $row['term_count'] ?? 0 ) );
		}
	}
	return $counts;
}

/** @return array<string,mixed> */
function wpagent_wc_attribute_payload( object $record, ?int $term_count = null ): array {
	$taxonomy = wc_attribute_taxonomy_name( (string) ( $record->attribute_name ?? '' ) );
	if ( null === $term_count ) {
		$counts     = wpagent_wc_attribute_term_counts( array( $taxonomy ) );
		$term_count = $counts[ $taxonomy ] ?? 0;
	}
	$base = array(
		'attribute_id' => absint( $record->attribute_id ?? 0 ),
		'name'         => (string) ( $record->attribute_label ?? '' ),
		'slug'         => (string) ( $record->attribute_name ?? '' ),
		'taxonomy'     => $taxonomy,
		'type'         => (string) ( $record->attribute_type ?? 'select' ),
		'order_by'     => (string) ( $record->attribute_orderby ?? 'menu_order' ),
		'has_archives' => (bool) ( $record->attribute_public ?? false ),
		'term_count'   => max( 0, $term_count ),
	);
	$base['state_hash'] = hash( 'sha256', (string) wp_json_encode( $base, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) );
	return $base;
}

/** @return array<string,mixed> */
function wpagent_wc_attribute_provider_args( array $payload ): array {
	return array(
		'name'         => $payload['name'],
		'slug'         => $payload['slug'],
		'type'         => $payload['type'],
		'order_by'     => $payload['order_by'],
		'has_archives' => $payload['has_archives'],
	);
}

/** @return array<string,mixed>|WP_Error */
function wpagent_wc_validate_attribute_fields( array $input, bool $creating, ?array $previous = null ) {
	$allowed = array( 'name', 'slug', 'type', 'order_by', 'has_archives' );
	$fields  = array_intersect_key( $input, array_flip( $allowed ) );
	if ( ! $creating && array() === $fields ) {
		return new WP_Error( 'wpagent_wc_attribute_no_fields', __( 'Kein zu aktualisierendes Attributfeld übergeben.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$next = $creating
		? array( 'name' => '', 'slug' => '', 'type' => 'select', 'order_by' => 'menu_order', 'has_archives' => false )
		: wpagent_wc_attribute_provider_args( $previous ?? array() );

	if ( array_key_exists( 'name', $fields ) ) {
		$next['name'] = trim( (string) wc_clean( $fields['name'] ) );
	}
	if ( '' === $next['name'] ) {
		return new WP_Error( 'wpagent_wc_attribute_name_required', __( 'Ein globales Produktattribut benötigt einen Namen.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( array_key_exists( 'slug', $fields ) ) {
		$raw_slug     = preg_replace( '/^pa_/', '', strtolower( trim( (string) $fields['slug'] ) ) );
		$next['slug'] = wc_sanitize_taxonomy_name( (string) $raw_slug );
		if ( '' === $next['slug'] ) {
			return new WP_Error( 'wpagent_wc_attribute_slug_required', __( 'Der Attribut-Slug darf nach der Bereinigung nicht leer sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
	}
	if ( $creating && '' === $next['slug'] ) {
		$next['slug'] = wc_sanitize_taxonomy_name( $next['name'] );
	}
	if ( strlen( $next['slug'] ) > 28 ) {
		return new WP_Error( 'wpagent_wc_attribute_slug_too_long', __( 'Der Attribut-Slug darf höchstens 28 Zeichen lang sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( array_key_exists( 'type', $fields ) ) {
		$type = sanitize_key( (string) $fields['type'] );
		if ( ! array_key_exists( $type, wc_get_attribute_types() ) ) {
			return new WP_Error( 'wpagent_wc_attribute_type_invalid', __( 'Der angeforderte Attributtyp ist in WooCommerce nicht registriert.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$next['type'] = $type;
	}
	if ( array_key_exists( 'order_by', $fields ) ) {
		$order_by = (string) $fields['order_by'];
		if ( ! in_array( $order_by, array( 'menu_order', 'name', 'name_num', 'id' ), true ) ) {
			return new WP_Error( 'wpagent_wc_attribute_order_invalid', __( 'order_by ist für WooCommerce-Produktattribute ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$next['order_by'] = $order_by;
	}
	if ( array_key_exists( 'has_archives', $fields ) ) {
		$next['has_archives'] = rest_sanitize_boolean( $fields['has_archives'] );
	}
	return $next;
}

/** @return string|WP_Error */
function wpagent_wc_attribute_acquire_lock() {
	$token = wpagent_companion_acquire_option_lock( WPAGENT_WC_ATTRIBUTE_LOCK_OPTION, WPAGENT_WC_ATTRIBUTE_LOCK_TTL );
	if ( is_string( $token ) ) {
		return $token;
	}
	return new WP_Error( 'wpagent_wc_attribute_locked', __( 'Die globalen WooCommerce-Produktattribute werden gerade durch einen anderen Vorgang geändert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
}

function wpagent_wc_attribute_release_lock( string $token ): void {
	wpagent_companion_release_option_lock( WPAGENT_WC_ATTRIBUTE_LOCK_OPTION, $token );
}

/** @return array<string,mixed> */
function wpagent_wc_attribute_schema(): array {
	return array(
		'type'                 => 'object',
		'properties'           => array(
			'attribute_id' => array( 'type' => 'integer' ),
			'name'         => array( 'type' => 'string' ),
			'slug'         => array( 'type' => 'string' ),
			'taxonomy'     => array( 'type' => 'string' ),
			'type'         => array( 'type' => 'string' ),
			'order_by'     => array( 'type' => 'string', 'enum' => array( 'menu_order', 'name', 'name_num', 'id' ) ),
			'has_archives' => array( 'type' => 'boolean' ),
			'term_count'   => array( 'type' => 'integer', 'minimum' => 0 ),
			'state_hash'   => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
		),
		'required'             => array( 'attribute_id', 'name', 'slug', 'taxonomy', 'type', 'order_by', 'has_archives', 'term_count', 'state_hash' ),
		'additionalProperties' => false,
	);
}
