<?php
/**
 * CPT Builder taxonomy registrar.
 *
 * Builds register_taxonomy() arguments from stored definitions and registers
 * enabled taxonomies on `init`, before post types, so post type/taxonomy
 * connections resolve correctly.
 *
 * @package CptBuilder
 * @since 1.1.0
 */

namespace CptBuilder;

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

/**
 * Registers stored custom taxonomies with WordPress.
 */
final class TaxonomyRegistrar {

	private TaxonomyStorage $storage;

	public function __construct( TaxonomyStorage $storage ) {
		$this->storage = $storage;
	}

	/**
	 * Register all enabled, valid taxonomies. Hooked to `init` (priority 8).
	 */
	public function registerAll(): void {
		/**
		 * Filter the full map of stored taxonomy definitions before registration.
		 *
		 * @param array<string, array<string, mixed>> $definitions Definitions keyed by taxonomy key.
		 */
		$definitions = apply_filters( 'wps_cpt_builder_taxonomies', $this->storage->all() );

		if ( ! is_array( $definitions ) ) {
			return;
		}

		foreach ( $definitions as $key => $definition ) {
			if ( ! is_string( $key ) || ! is_array( $definition ) ) {
				continue;
			}

			if ( empty( $definition['enabled'] ) ) {
				continue;
			}

			if ( taxonomy_exists( $key ) ) {
				continue;
			}

			if ( empty( $definition['labels']['singular_name'] ) || empty( $definition['labels']['name'] ) ) {
				continue;
			}

			$object_types = isset( $definition['object_types'] ) && is_array( $definition['object_types'] )
				? array_values( array_filter( array_map( 'strval', $definition['object_types'] ) ) )
				: [];

			$args = $this->buildArgs( $key, $definition );

			/**
			 * Filter the final register_taxonomy() arguments.
			 *
			 * @param array<string, mixed> $args         Arguments passed to register_taxonomy().
			 * @param string               $key          Taxonomy key.
			 * @param array<string, mixed> $definition   Stored definition.
			 * @param array<int, string>   $object_types Object types to attach to.
			 */
			$args = apply_filters( 'wps_cpt_builder_register_taxonomy_args', $args, $key, $definition, $object_types );

			/**
			 * Fires before a CPT Builder taxonomy is registered.
			 *
			 * @param string               $key  Taxonomy key.
			 * @param array<string, mixed> $args Final arguments.
			 */
			do_action( 'wps_cpt_builder_before_register_taxonomy', $key, $args );

			$result = register_taxonomy( $key, $object_types, $args );

			/**
			 * Fires after a CPT Builder taxonomy is registered.
			 *
			 * @param string                  $key    Taxonomy key.
			 * @param \WP_Taxonomy|\WP_Error  $result Registration result.
			 */
			do_action( 'wps_cpt_builder_after_register_taxonomy', $key, $result );
		}
	}

	/**
	 * Build register_taxonomy() arguments from a stored definition.
	 *
	 * @param string               $key        Taxonomy key.
	 * @param array<string, mixed> $definition Stored definition.
	 * @return array<string, mixed>
	 */
	public function buildArgs( string $key, array $definition ): array {
		$src    = isset( $definition['args'] ) && is_array( $definition['args'] ) ? $definition['args'] : [];
		$args   = wp_parse_args( $src, Config::defaultTaxonomyArgs() );
		$labels = $this->buildLabels( $definition );

		$built = [
			'labels'             => $labels,
			'hierarchical'       => (bool) $args['hierarchical'],
			'public'             => (bool) $args['public'],
			'publicly_queryable' => (bool) $args['publicly_queryable'],
			'show_ui'            => (bool) $args['show_ui'],
			'show_in_menu'       => (bool) $args['show_in_menu'],
			'show_in_nav_menus'  => (bool) $args['show_in_nav_menus'],
			'show_tagcloud'      => (bool) $args['show_tagcloud'],
			'show_in_quick_edit' => (bool) $args['show_in_quick_edit'],
			'show_admin_column'  => (bool) $args['show_admin_column'],
			'sort'               => (bool) $args['sort'],
			'show_in_rest'       => (bool) $args['show_in_rest'],
		];

		if ( ! empty( $args['description'] ) ) {
			$built['description'] = (string) $args['description'];
		}

		if ( ! empty( $args['rest_base'] ) ) {
			$built['rest_base'] = (string) $args['rest_base'];
		}
		if ( ! empty( $args['rest_namespace'] ) ) {
			$built['rest_namespace'] = (string) $args['rest_namespace'];
		}

		// Rewrite rules.
		if ( ! empty( $args['rewrite'] ) ) {
			$built['rewrite'] = [
				'slug'         => ! empty( $args['rewrite_slug'] ) ? (string) $args['rewrite_slug'] : $key,
				'with_front'   => ! empty( $args['rewrite_with_front'] ),
				'hierarchical' => ! empty( $args['rewrite_hierarchical'] ),
			];
		} else {
			$built['rewrite'] = false;
		}

		// Query var.
		if ( ! empty( $args['query_var'] ) ) {
			$built['query_var'] = ! empty( $args['query_var_slug'] )
				? (string) $args['query_var_slug']
				: true;
		} else {
			$built['query_var'] = false;
		}

		return $built;
	}

	/**
	 * Build the labels array.
	 *
	 * Labels left empty in the UI are auto-generated from the singular and
	 * plural labels (e.g. "Edit Genre", "No genres found."). Explicit values
	 * always win.
	 *
	 * @param array<string, mixed> $definition Stored definition.
	 * @return array<string, string>
	 */
	private function buildLabels( array $definition ): array {
		$labels   = isset( $definition['labels'] ) && is_array( $definition['labels'] ) ? $definition['labels'] : [];
		$singular = (string) ( $labels['singular_name'] ?? '' );
		$plural   = (string) ( $labels['name'] ?? '' );
		$clean    = [];

		foreach ( Config::allTaxonomyLabelKeys() as $label_key ) {
			if ( isset( $labels[ $label_key ] ) && (string) $labels[ $label_key ] !== '' ) {
				$clean[ $label_key ] = (string) $labels[ $label_key ];
			}
		}

		// Auto-generate any label the user did not override.
		foreach ( Config::taxonomyLabelTemplates() as $label_key => $meta ) {
			if ( isset( $clean[ $label_key ] ) ) {
				continue;
			}

			$generated = Config::generateLabel( $meta, $singular, $plural );
			if ( $generated !== '' ) {
				$clean[ $label_key ] = $generated;
			}
		}

		return $clean;
	}
}
