<?php
/**
 * Custom Fields taxonomy term fields.
 *
 * Renders and saves field groups attached to taxonomy terms. Values are stored
 * as native term meta keyed by each field's meta name.
 *
 * @package CustomFields
 * @since 1.0.0
 */

namespace CustomFields;

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

/**
 * Handles taxonomy term meta fields for custom field groups.
 */
final class TermFields {

	private const NONCE_ACTION = 'wpps_cf_save_term';
	private const NONCE_NAME   = 'wpps_cf_term_nonce';

	private static bool $nonce_printed = false;

	private Locations $locations;

	private Fields $fields;

	public function __construct( Locations $locations, Fields $fields ) {
		$this->locations = $locations;
		$this->fields    = $fields;
	}

	/**
	 * Render fields on the "Add new term" screen (no term yet).
	 *
	 * @param string $taxonomy Taxonomy key.
	 */
	public function renderAddFields( string $taxonomy ): void {
		$groups = $this->locations->groupsForTaxonomy( $taxonomy );
		if ( $groups === [] ) {
			return;
		}

		$this->printNonceOnce();

		foreach ( $groups as $group ) {
			echo '<div class="form-field cf-term-group">';
			$this->renderGroupHeading( $group );
			$this->renderGroupGrid( $group, $taxonomy, 0 );
			echo '</div>';
		}
	}

	/**
	 * Render fields on the "Edit term" screen.
	 *
	 * @param \WP_Term $term     Term being edited.
	 * @param string   $taxonomy Taxonomy key.
	 */
	public function renderEditFields( \WP_Term $term, string $taxonomy ): void {
		$groups = $this->locations->groupsForTaxonomy( $taxonomy );
		if ( $groups === [] ) {
			return;
		}

		$this->printNonceOnce();

		foreach ( $groups as $group ) {
			echo '<tr class="cf-term-group"><th scope="row">';
			$this->renderGroupHeading( $group, true );
			echo '</th><td>';
			$this->renderGroupGrid( $group, $taxonomy, (int) $term->term_id );
			echo '</td></tr>';
		}
	}

	/**
	 * Print the save nonce once per term screen.
	 */
	private function printNonceOnce(): void {
		if ( self::$nonce_printed ) {
			return;
		}

		wp_nonce_field( self::NONCE_ACTION, self::NONCE_NAME );
		self::$nonce_printed = true;
	}

	/**
	 * @param array<string, mixed> $group   Field group definition.
	 * @param bool                 $inline  Whether the heading is in a table header cell.
	 */
	private function renderGroupHeading( array $group, bool $inline = false ): void {
		$title = trim( (string) ( $group['title'] ?? '' ) );
		if ( $title === '' ) {
			return;
		}

		if ( $inline ) {
			echo esc_html( $title );
			return;
		}

		echo '<label>' . esc_html( $title ) . '</label>';
	}

	/**
	 * Render one field group's grid on a term screen.
	 *
	 * @param array<string, mixed> $group    Field group definition.
	 * @param string               $taxonomy Taxonomy key.
	 * @param int                  $term_id  Term ID (0 on add screen).
	 */
	private function renderGroupGrid( array $group, string $taxonomy, int $term_id ): void {
		unset( $taxonomy );

		$fields = isset( $group['fields'] ) && is_array( $group['fields'] ) ? $group['fields'] : [];
		if ( $fields === [] ) {
			return;
		}

		echo '<div class="cf-meta-box cf-meta-box--grid cf-meta-box--normal cf-term-fields">';

		foreach ( Config::packFieldRows( $fields ) as $row ) {
			echo '<div class="cf-meta-box__row">';
			foreach ( $row as $field ) {
				if ( ! is_array( $field ) ) {
					continue;
				}

				$default = $field['default'] ?? '';
				[ $name, $value, $id_attr ] = $this->fieldRenderContext( $field, $group, $term_id, $default );
				echo '<div class="cf-meta-box__cell ' . esc_attr( Config::fieldWidthClass( $field ) ) . '">';
				$this->fields->render( $field, $name, $value, $id_attr, $group );
				echo '</div>';
			}
			echo '</div>';
		}

		echo '</div>';
	}

	/**
	 * Build render context for a field on a term screen.
	 *
	 * @param array<string, mixed> $field    Field definition.
	 * @param array<string, mixed> $group    Group definition.
	 * @param int                  $term_id  Term ID (0 on add screen).
	 * @param mixed                $default  Default when adding.
	 * @return array{0: string, 1: mixed, 2: string}
	 */
	private function fieldRenderContext( array $field, array $group, int $term_id, mixed $default = '' ): array {
		$type     = (string) ( $field['type'] ?? 'text' );
		$meta_key = MetaKeys::resolve( (string) ( $group['key'] ?? '' ), $field );
		$field_id = (string) ( $field['key'] ?? $meta_key );
		$id_attr  = 'wpps-cf-' . sanitize_key( (string) ( $group['key'] ?? '' ) ) . '-' . sanitize_key( $field_id );

		if ( ! Config::storesValue( $type ) ) {
			return [ '', '', $id_attr ];
		}

		if ( $meta_key === '' ) {
			return [ '', '', $id_attr ];
		}

		$value = $term_id > 0 ? $this->readMeta( $term_id, $meta_key, $field ) : $default;
		$name  = 'wpps_cf[' . $meta_key . ']';

		return [ $name, $value, $id_attr ];
	}

	/**
	 * @param int                  $term_id  Term ID.
	 * @param string               $meta_key Meta key.
	 * @param array<string, mixed> $field    Field definition.
	 * @return mixed
	 */
	private function readMeta( int $term_id, string $meta_key, array $field ): mixed {
		$type     = (string) ( $field['type'] ?? 'text' );
		$is_multi = $type === 'checkbox'
			|| $type === 'gallery'
			|| ( ! empty( $field['multiple'] ) && in_array( $type, [ 'select', 'post_object', 'term', 'user' ], true ) );

		$stored = get_term_meta( $term_id, $meta_key, true );
		if ( $stored === '' || $stored === null ) {
			if ( $type === 'link' ) {
				return [ 'url' => '', 'title' => '', 'target' => '' ];
			}
			return $is_multi ? [] : ( $field['default'] ?? '' );
		}

		if ( $type === 'gallery' && ! is_array( $stored ) ) {
			return array_filter( array_map( 'absint', explode( ',', (string) $stored ) ) );
		}

		return $stored;
	}

	/**
	 * Save submitted term field values.
	 *
	 * @param int    $term_id  Term ID.
	 * @param string $taxonomy Taxonomy key.
	 */
	public function save( int $term_id, string $taxonomy ): void {
		if ( ! isset( $_POST[ self::NONCE_NAME ] ) ) {
			return;
		}
		$nonce = sanitize_text_field( wp_unslash( (string) $_POST[ self::NONCE_NAME ] ) );
		if ( ! wp_verify_nonce( $nonce, self::NONCE_ACTION ) ) {
			return;
		}

		$tax_object = get_taxonomy( $taxonomy );
		$capability = $tax_object->cap->edit_terms ?? 'manage_categories';
		if ( ! current_user_can( $capability ) ) {
			return;
		}

		$groups = $this->locations->groupsForTaxonomy( $taxonomy );
		if ( $groups === [] ) {
			return;
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified above.
		$submitted = isset( $_POST['wpps_cf'] ) && is_array( $_POST['wpps_cf'] ) ? wp_unslash( $_POST['wpps_cf'] ) : [];
		$errors    = [];
		$pending   = [];

		foreach ( $groups as $group_key => $group ) {
			$fields = isset( $group['fields'] ) && is_array( $group['fields'] ) ? $group['fields'] : [];
			$values = [];

			foreach ( $fields as $field ) {
				if ( ! is_array( $field ) ) {
					continue;
				}

				$type = (string) ( $field['type'] ?? 'text' );
				if ( ! Config::storesValue( $type ) ) {
					continue;
				}

				$meta_key = MetaKeys::resolve( (string) $group_key, $field );
				if ( $meta_key === '' ) {
					continue;
				}

				$old_raw = get_term_meta( $term_id, $meta_key, true );
				$raw     = $submitted[ $meta_key ] ?? ( $type === 'toggle' ? '' : null );

				if ( $this->fields->preservesValueWhenEmpty( $field ) && ( $raw === '' || $raw === null ) ) {
					if ( ! empty( $field['required'] ) && $this->fields->isEmpty( $old_raw ) ) {
						$errors[] = sprintf(
							/* translators: %s: field label. */
							__( '“%s” is required.', 'wp-powersuite' ),
							(string) ( $field['label'] ?? $meta_key )
						);
					}
					continue;
				}

				$value = $this->fields->sanitizeValue( $field, $raw, $group );

				if ( ! empty( $field['required'] ) && $this->fields->isEmpty( $value ) ) {
					$errors[] = sprintf(
						/* translators: %s: field label. */
						__( '“%s” is required.', 'wp-powersuite' ),
						(string) ( $field['label'] ?? $meta_key )
					);
					continue;
				}

				if ( ! $this->fields->isEmpty( $value ) && ! $this->fields->matchesPattern( $field, $value ) ) {
					$errors[] = sprintf(
						/* translators: %s: field label. */
						__( '“%s” has an invalid format.', 'wp-powersuite' ),
						(string) ( $field['label'] ?? $meta_key )
					);
					continue;
				}

				$values[ $meta_key ] = $value;
			}

			$pending[ (string) $group_key ] = $values;
		}

		if ( $errors !== [] ) {
			set_transient(
				'wpps_cf_term_save_errors_' . get_current_user_id(),
				$errors,
				60
			);
			return;
		}

		foreach ( $pending as $values ) {
			foreach ( $values as $meta_key => $value ) {
				if ( $this->fields->isEmpty( $value ) ) {
					delete_term_meta( $term_id, $meta_key );
				} else {
					update_term_meta( $term_id, $meta_key, $value );
				}
			}
		}
	}

	/**
	 * Show validation errors from the last term save attempt.
	 */
	public function showSaveErrors(): void {
		$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
		if ( ! $screen || ! in_array( $screen->base, [ 'edit-tags', 'term' ], true ) ) {
			return;
		}

		$errors = get_transient( 'wpps_cf_term_save_errors_' . get_current_user_id() );
		if ( ! is_array( $errors ) || $errors === [] ) {
			return;
		}

		delete_transient( 'wpps_cf_term_save_errors_' . get_current_user_id() );

		echo '<div class="notice notice-error"><p><strong>' . esc_html__( 'Custom Fields could not save:', 'wp-powersuite' ) . '</strong></p><ul>';
		foreach ( $errors as $error ) {
			echo '<li>' . esc_html( (string) $error ) . '</li>';
		}
		echo '</ul><p>' . esc_html__( 'No field values were saved. Fix the errors above and try again.', 'wp-powersuite' ) . '</p></div>';
	}
}
