<?php
/**
 * Custom Fields — format stored values for display and integrations.
 *
 * @package CustomFields
 * @since 1.4.0
 */

namespace CustomFields;

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

/**
 * Resolves and formats field values for templates, shortcodes, and builders.
 */
final class FieldOutput {

	/**
	 * Field types that must never be rendered via shortcodes/tags/builders.
	 *
	 * @return array<int, string>
	 */
	public static function sensitiveTypes(): array {
		return [ 'password' ];
	}

	/**
	 * Field types excluded from builder/tag pickers.
	 *
	 * @return array<int, string>
	 */
	public static function integratableTypes(): array {
		return array_values(
			array_diff(
				array_keys( Config::fieldTypes() ),
				array_merge( Config::displayOnlyTypes(), Config::nestedTypes(), self::sensitiveTypes() )
			)
		);
	}

	/**
	 * Whether the current viewer may read custom-field values for a post.
	 */
	public static function viewerCanReadPost( int $post_id ): bool {
		if ( $post_id <= 0 ) {
			return false;
		}

		$post = get_post( $post_id );
		if ( ! $post instanceof \WP_Post ) {
			return false;
		}

		if ( ! current_user_can( 'read_post', $post_id ) ) {
			return false;
		}

		// Password-protected posts: require unlock cookie, or edit access.
		if ( post_password_required( $post ) && ! current_user_can( 'edit_post', $post_id ) ) {
			return false;
		}

		return true;
	}

	/**
	 * @param array<string, mixed> $field Field definition.
	 */
	public static function fieldIsPubliclyRenderable( array $field ): bool {
		$type = (string) ( $field['type'] ?? 'text' );

		return ! in_array( $type, self::sensitiveTypes(), true );
	}

	/**
	 * All integratable fields from enabled groups.
	 *
	 * @return array<int, array<string, string>>
	 */
	public static function integratableFields(): array {
		$storage = new Storage();
		$fields  = [];

		foreach ( $storage->all() as $group_key => $group ) {
			if ( ! is_array( $group ) || empty( $group['enabled'] ) ) {
				continue;
			}

			$group_key   = (string) $group_key;
			$group_title = (string) ( $group['title'] ?? $group_key );
			$list        = isset( $group['fields'] ) && is_array( $group['fields'] ) ? $group['fields'] : [];

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

				$type = (string) ( $field['type'] ?? 'text' );
				if ( ! in_array( $type, self::integratableTypes(), true ) ) {
					continue;
				}

				$name = (string) ( $field['name'] ?? '' );
				if ( $name === '' ) {
					continue;
				}

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

				$fields[] = [
					'group_key'   => $group_key,
					'group_label' => $group_title,
					'field_name'  => $name,
					'field_label' => (string) ( $field['label'] ?? $name ),
					'meta_key'    => $meta_key,
					'type'        => $type,
					'selection'   => $group_key . ':' . $name,
				];
			}
		}

		return $fields;
	}

	/**
	 * Locate a field definition by group key and short field name.
	 *
	 * @return array{group: array<string, mixed>, field: array<string, mixed>}|null
	 */
	public static function locateField( string $group_key, string $field_name ): ?array {
		$storage = new Storage();
		$group   = $storage->get( $group_key );
		if ( ! is_array( $group ) ) {
			return null;
		}

		foreach ( (array) ( $group['fields'] ?? [] ) as $field ) {
			if ( ! is_array( $field ) ) {
				continue;
			}
			if ( (string) ( $field['name'] ?? '' ) === $field_name || (string) ( $field['key'] ?? '' ) === $field_name ) {
				return [
					'group' => $group,
					'field' => $field,
				];
			}
		}

		return null;
	}

	/**
	 * Get a human-friendly display value for a post field.
	 *
	 * @param int|null $post_id Post ID (defaults to current post).
	 */
	public static function getDisplay( string $group_key, string $field_name, ?int $post_id = null, mixed $default = '' ): mixed {
		$post_id = $post_id ?? (int) get_the_ID();
		if ( $post_id <= 0 || ! self::viewerCanReadPost( $post_id ) ) {
			return $default;
		}

		$located = self::locateField( $group_key, $field_name );
		if ( $located === null ) {
			return $default;
		}

		$field = $located['field'];
		if ( ! self::fieldIsPubliclyRenderable( $field ) ) {
			return $default;
		}

		$meta = MetaKeys::resolve( $group_key, $field );
		if ( $meta === '' ) {
			return $default;
		}

		$value = get_post_meta( $post_id, $meta, true );
		if ( $value === '' || $value === null || $value === [] ) {
			return $default;
		}

		return self::formatValue( $field, $value, 'display' );
	}

	/**
	 * Render a field value for shortcodes, blocks, and page builders.
	 *
	 * @param array<string, mixed> $args format: display|raw|html|url|text, default
	 */
	public static function render( string $group_key, string $field_name, ?int $post_id = null, array $args = [] ): string {
		$post_id = $post_id ?? (int) get_the_ID();
		if ( $post_id <= 0 || ! self::viewerCanReadPost( $post_id ) ) {
			return '';
		}

		$located = self::locateField( $group_key, $field_name );
		if ( $located === null ) {
			return '';
		}

		$format  = sanitize_key( (string) ( $args['format'] ?? 'display' ) );
		$default = $args['default'] ?? '';
		$field   = $located['field'];
		if ( ! self::fieldIsPubliclyRenderable( $field ) ) {
			return '';
		}

		$meta = MetaKeys::resolve( $group_key, $field );
		if ( $meta === '' ) {
			return '';
		}

		$raw = get_post_meta( $post_id, $meta, true );
		if ( $raw === '' || $raw === null || $raw === [] ) {
			if ( $default === '' || $default === null ) {
				return '';
			}
			$raw = $default;
		}

		if ( $format === 'raw' ) {
			if ( is_array( $raw ) ) {
				return esc_html( wp_json_encode( $raw ) );
			}

			return esc_html( (string) $raw );
		}

		$type  = (string) ( $field['type'] ?? 'text' );
		$value = self::formatValue( $field, $raw, $format === 'display' ? 'display' : $format );

		if ( $format === 'display' ) {
			if ( in_array( $type, [ 'wysiwyg', 'oembed' ], true ) ) {
				$format = 'html';
			} elseif ( in_array( $type, [ 'image', 'file', 'gallery' ], true ) ) {
				$format = 'url';
			} elseif ( $type === 'link' ) {
				$link = is_array( $value ) ? $value : [];
				$href = (string) ( $link['url'] ?? '' );
				$text = (string) ( $link['title'] ?? $href );
				if ( $href === '' ) {
					return '';
				}
				$target = (string) ( $link['target'] ?? '' ) === '_blank' ? ' target="_blank" rel="noopener noreferrer"' : '';

				return '<a href="' . esc_url( $href ) . '"' . $target . '>' . esc_html( $text !== '' ? $text : $href ) . '</a>';
			} elseif ( in_array( $type, [ 'post_object', 'term', 'user' ], true ) ) {
				$format = 'text';
			} else {
				$format = 'text';
			}
		}

		if ( is_array( $value ) ) {
			$value = implode( ', ', array_map( 'strval', $value ) );
		}

		switch ( $format ) {
			case 'html':
				return wp_kses_post( (string) $value );
			case 'url':
				return esc_url( (string) $value );
			case 'text':
			default:
				return esc_html( (string) $value );
		}
	}

	/**
	 * Render by stored meta key (when group/field names are unknown).
	 *
	 * @param array<string, mixed> $args
	 */
	public static function renderByMetaKey( string $meta_key, ?int $post_id = null, array $args = [] ): string {
		$post_id = $post_id ?? (int) get_the_ID();
		if ( $post_id <= 0 || $meta_key === '' ) {
			return '';
		}

		// Only registered integratable CF fields — never arbitrary post meta.
		foreach ( self::integratableFields() as $field ) {
			if ( (string) ( $field['meta_key'] ?? '' ) === $meta_key ) {
				return self::render(
					(string) $field['group_key'],
					(string) $field['field_name'],
					$post_id,
					$args
				);
			}
		}

		return '';
	}

	/**
	 * @param array<string, mixed> $field  Field definition.
	 * @param mixed                $value  Stored value.
	 * @param string               $format display|url|text
	 * @return mixed
	 */
	public static function formatValue( array $field, mixed $value, string $format = 'display' ): mixed {
		$type = (string) ( $field['type'] ?? 'text' );

		if ( $value === '' || $value === null || $value === [] ) {
			return $format === 'url' ? '' : $value;
		}

		switch ( $type ) {
			case 'password':
				return $format === 'url' ? '' : '';

			case 'toggle':
				return (string) $value === '1' ? __( 'Yes', 'wp-powersuite' ) : __( 'No', 'wp-powersuite' );

			case 'image':
			case 'file':
				$id = (int) $value;
				if ( $id <= 0 ) {
					return $format === 'url' ? '' : '—';
				}
				if ( $format === 'url' ) {
					$return = (string) ( $field['return'] ?? 'url' );
					if ( $return === 'id' ) {
						return (string) $id;
					}

					return wps_cf_get_image_url( $id );
				}
				$title = get_the_title( $id );

				return $title !== '' ? $title : (string) $id;

			case 'gallery':
				if ( ! is_array( $value ) ) {
					$value = array_filter( array_map( 'absint', explode( ',', (string) $value ) ) );
				}
				if ( $format === 'url' ) {
					$first = (int) ( $value[0] ?? 0 );

					return $first > 0 ? wps_cf_get_image_url( $first ) : '';
				}

				return (string) count( $value );

			case 'link':
				if ( ! is_array( $value ) ) {
					return $format === 'url' ? '' : $value;
				}
				if ( $format === 'url' ) {
					return (string) ( $value['url'] ?? '' );
				}

				return $value;

			case 'post_object':
				return self::formatObjectList( $value, 'post' );

			case 'term':
				return self::formatObjectList( $value, 'term' );

			case 'user':
				return self::formatObjectList( $value, 'user' );

			case 'checkbox':
			case 'select':
				if ( is_array( $value ) ) {
					return implode( ', ', array_map( 'strval', $value ) );
				}

				return (string) $value;

			default:
				return $value;
		}
	}

	/**
	 * @param mixed  $value Stored ID or list of IDs.
	 * @param string $kind  post|term|user
	 */
	private static function formatObjectList( mixed $value, string $kind ): string {
		$ids = is_array( $value ) ? array_map( 'absint', $value ) : [ absint( $value ) ];
		$ids = array_values( array_filter( $ids ) );
		if ( $ids === [] ) {
			return '';
		}

		$labels = [];
		foreach ( $ids as $id ) {
			if ( $kind === 'post' ) {
				$labels[] = get_the_title( $id ) ?: (string) $id;
			} elseif ( $kind === 'term' ) {
				$term = get_term( $id );
				$labels[] = ( $term instanceof \WP_Term && ! is_wp_error( $term ) ) ? $term->name : (string) $id;
			} else {
				$user = get_userdata( $id );
				$labels[] = $user instanceof \WP_User ? $user->display_name : (string) $id;
			}
		}

		return implode( ', ', $labels );
	}
}
