<?php
/**
 * Custom Fields admin list columns.
 *
 * @package CustomFields
 * @since 1.2.0
 */

namespace CustomFields;

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

/**
 * Adds optional post list columns for fields flagged with show_in_list.
 */
final class AdminColumns {

	private Locations $locations;

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

	/**
	 * Register hooks for every active post type.
	 */
	public function register(): void {
		foreach ( $this->locations->activePostTypes() as $post_type ) {
			add_filter( 'manage_' . $post_type . '_posts_columns', [ $this, 'addColumns' ] );
			add_action( 'manage_' . $post_type . '_posts_custom_column', [ $this, 'renderColumn' ], 10, 2 );
		}
		foreach ( $this->locations->activeTaxonomies() as $taxonomy ) {
			add_filter( 'manage_edit-' . $taxonomy . '_columns', [ $this, 'addTermColumns' ] );
			add_filter( 'manage_' . $taxonomy . '_custom_column', [ $this, 'renderTermColumn' ], 10, 3 );
		}
	}

	/**
	 * @param array<string, string> $columns Existing columns.
	 * @return array<string, string>
	 */
	public function addColumns( array $columns ): array {
		$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
		$post_type = $screen && isset( $screen->post_type ) ? (string) $screen->post_type : '';
		if ( $post_type === '' ) {
			return $columns;
		}

		foreach ( $this->listFieldsForPostType( $post_type ) as $meta_key => $field ) {
			$label = (string) ( $field['label'] ?? $meta_key );
			$columns[ 'wpps_cf_' . $meta_key ] = $label;
		}

		return $columns;
	}

	/**
	 * @param string $column  Column key.
	 * @param int    $post_id Post ID.
	 */
	public function renderColumn( string $column, int $post_id ): void {
		if ( ! str_starts_with( $column, 'wpps_cf_' ) ) {
			return;
		}

		$meta_key = substr( $column, 8 );
		$post_type = get_post_type( $post_id );
		if ( ! is_string( $post_type ) ) {
			return;
		}

		$field = $this->listFieldsForPostType( $post_type )[ $meta_key ] ?? null;
		if ( ! is_array( $field ) ) {
			return;
		}

		$value = get_post_meta( $post_id, $meta_key, true );
		echo esc_html( $this->formatListValue( $field, $value ) );
	}

	/**
	 * @param array<string, string> $columns Existing columns.
	 * @return array<string, string>
	 */
	public function addTermColumns( array $columns ): array {
		$screen   = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
		$taxonomy = $screen && isset( $screen->taxonomy ) ? (string) $screen->taxonomy : '';
		if ( $taxonomy === '' ) {
			return $columns;
		}

		foreach ( $this->listFieldsForTaxonomy( $taxonomy ) as $meta_key => $field ) {
			$label = (string) ( $field['label'] ?? $meta_key );
			$columns[ 'wpps_cf_' . $meta_key ] = $label;
		}

		return $columns;
	}

	/**
	 * @param string $content Current column content.
	 * @param string $column  Column key.
	 * @param int    $term_id Term ID.
	 */
	public function renderTermColumn( string $content, string $column, int $term_id ): string {
		if ( ! str_starts_with( $column, 'wpps_cf_' ) ) {
			return $content;
		}

		$screen   = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
		$taxonomy = $screen && isset( $screen->taxonomy ) ? (string) $screen->taxonomy : '';
		if ( $taxonomy === '' ) {
			return $content;
		}

		$meta_key = substr( $column, 8 );
		$field    = $this->listFieldsForTaxonomy( $taxonomy )[ $meta_key ] ?? null;
		if ( ! is_array( $field ) ) {
			return $content;
		}

		$value = get_term_meta( $term_id, $meta_key, true );
		return esc_html( $this->formatListValue( $field, $value ) );
	}

	/**
	 * @param string $post_type Post type key.
	 * @return array<string, array<string, mixed>> meta_key => field
	 */
	private function listFieldsForPostType( string $post_type ): array {
		$out = [];
		foreach ( $this->locations->groupsForPost( 0, $post_type ) as $group ) {
			$group_key = (string) ( $group['key'] ?? '' );
			$fields = isset( $group['fields'] ) && is_array( $group['fields'] ) ? $group['fields'] : [];
			foreach ( $fields as $field ) {
				if ( ! is_array( $field ) || empty( $field['show_in_list'] ) ) {
					continue;
				}
				if ( ! FieldOutput::fieldIsPubliclyRenderable( $field ) ) {
					continue;
				}
				$meta_key = MetaKeys::resolve( $group_key, $field );
				if ( $meta_key !== '' ) {
					$out[ $meta_key ] = $field;
				}
				if ( count( $out ) >= Config::MAX_ADMIN_LIST_COLUMNS ) {
					return $out;
				}
			}
		}

		return $out;
	}

	/**
	 * @param string $taxonomy Taxonomy key.
	 * @return array<string, array<string, mixed>> meta_key => field
	 */
	private function listFieldsForTaxonomy( string $taxonomy ): array {
		$out = [];
		foreach ( $this->locations->groupsForTaxonomy( $taxonomy ) as $group ) {
			$group_key = (string) ( $group['key'] ?? '' );
			$fields    = isset( $group['fields'] ) && is_array( $group['fields'] ) ? $group['fields'] : [];
			foreach ( $fields as $field ) {
				if ( ! is_array( $field ) || empty( $field['show_in_list'] ) ) {
					continue;
				}
				if ( ! FieldOutput::fieldIsPubliclyRenderable( $field ) ) {
					continue;
				}
				$meta_key = MetaKeys::resolve( $group_key, $field );
				if ( $meta_key !== '' ) {
					$out[ $meta_key ] = $field;
				}
				if ( count( $out ) >= Config::MAX_ADMIN_LIST_COLUMNS ) {
					return $out;
				}
			}
		}

		return $out;
	}

	/**
	 * @param array<string, mixed> $field Field definition.
	 * @param mixed                $value Stored value.
	 */
	private function formatListValue( array $field, mixed $value ): string {
		$type = (string) ( $field['type'] ?? 'text' );

		if ( $value === '' || $value === null || $value === [] ) {
			return '—';
		}

		if ( $type === 'toggle' ) {
			return (string) $value === '1' ? __( 'Yes', 'wppowersuite-lite' ) : __( 'No', 'wppowersuite-lite' );
		}

		if ( in_array( $type, [ 'image', 'file' ], true ) ) {
			$id = (int) $value;
			if ( $id <= 0 ) {
				return '—';
			}
			$title = get_the_title( $id );

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

		if ( $type === 'gallery' && is_array( $value ) ) {
			return (string) count( $value );
		}

		if ( in_array( $type, [ 'post_object', 'relationship' ], true ) ) {
			return $this->formatObjectList( $value, 'post' );
		}

		if ( $type === 'page_link' ) {
			return $this->formatPageLinkList( $value );
		}

		if ( $type === 'group' && is_array( $value ) ) {
			$json = wp_json_encode( $value );
			return is_string( $json ) ? self::trimText( $json ) : '—';
		}

		if ( $type === 'google_map' && is_array( $value ) ) {
			$address = trim( (string) ( $value['address'] ?? '' ) );
			$lat     = trim( (string) ( $value['lat'] ?? '' ) );
			$lng     = trim( (string) ( $value['lng'] ?? '' ) );

			return $address !== '' ? $address : ( $lat !== '' && $lng !== '' ? $lat . ', ' . $lng : '—' );
		}

		if ( $type === 'term' ) {
			return $this->formatObjectList( $value, 'term' );
		}

		if ( $type === 'user' ) {
			return $this->formatObjectList( $value, 'user' );
		}

		if ( is_array( $value ) ) {
			if ( isset( $value['url'] ) ) {
				return self::trimText( (string) ( $value['url'] ?? '' ) );
			}

			return self::joinLabels( array_map( 'strval', $value ), count( $value ) );
		}

		$text = wp_strip_all_tags( (string) $value );

		return self::trimText( $text );
	}

	/**
	 * @param mixed  $value Stored ID or list of IDs.
	 * @param string $kind  post|term|user.
	 */
	private 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 ( count( $labels ) >= Config::MAX_ADMIN_LIST_ITEMS ) {
				break;
			}

			if ( $kind === 'post' ) {
				if ( ! current_user_can( 'read_post', $id ) ) {
					continue;
				}
				$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 {
				if ( ! RelationalSearch::canListUsers() ) {
					continue;
				}
				$user     = get_userdata( $id );
				$labels[] = $user instanceof \WP_User ? $user->display_name : (string) $id;
			}
		}

		return $labels !== [] ? self::joinLabels( $labels, count( $ids ) ) : '—';
	}

	/**
	 * @param mixed $value Stored post ID or list of IDs.
	 */
	private function formatPageLinkList( mixed $value ): string {
		$values = is_array( $value ) ? $value : [ $value ];
		$values = array_values( array_filter( array_map( static fn( mixed $item ): string => trim( (string) $item ), $values ) ) );
		if ( $values === [] ) {
			return '—';
		}

		$labels = [];
		foreach ( $values as $item ) {
			if ( count( $labels ) >= Config::MAX_ADMIN_LIST_ITEMS ) {
				break;
			}

			if ( str_starts_with( $item, 'archive:' ) ) {
				$label = $this->formatPageLinkArchiveLabel( $item );
				if ( $label !== '' ) {
					$labels[] = $label;
				}
				continue;
			}

			$id = absint( $item );
			if ( $id <= 0 ) {
				continue;
			}
			if ( ! current_user_can( 'read_post', $id ) ) {
				continue;
			}

			$title = get_the_title( $id );
			$url   = get_permalink( $id );
			if ( $title !== '' ) {
				$labels[] = $title;
			} elseif ( is_string( $url ) && $url !== '' ) {
				$labels[] = $url;
			} else {
				$labels[] = (string) $id;
			}
		}

		return $labels !== [] ? self::joinLabels( $labels, count( $values ) ) : '—';
	}

	/**
	 * @param array<int, string> $labels      Labels to join.
	 * @param int                $total_count Total source item count before limits.
	 */
	private static function joinLabels( array $labels, int $total_count ): string {
		$labels = array_values( array_filter( array_map( 'wp_strip_all_tags', $labels ), static fn( string $label ): bool => $label !== '' ) );
		if ( $labels === [] ) {
			return '—';
		}

		$text = implode( ', ', array_slice( $labels, 0, Config::MAX_ADMIN_LIST_ITEMS ) );
		if ( $total_count > Config::MAX_ADMIN_LIST_ITEMS ) {
			$text .= sprintf(
				/* translators: %d: number of hidden field values. */
				_n( ' +%d more', ' +%d more', $total_count - Config::MAX_ADMIN_LIST_ITEMS, 'wppowersuite-lite' ),
				$total_count - Config::MAX_ADMIN_LIST_ITEMS
			);
		}

		return self::trimText( $text );
	}

	private static function trimText( string $text ): string {
		$text   = trim( $text );
		$length = function_exists( 'mb_strlen' ) ? mb_strlen( $text ) : strlen( $text );
		if ( $length <= Config::MAX_ADMIN_LIST_TEXT_LENGTH ) {
			return $text;
		}

		$cut = Config::MAX_ADMIN_LIST_TEXT_LENGTH - 1;
		$out = function_exists( 'mb_substr' ) ? mb_substr( $text, 0, $cut ) : substr( $text, 0, $cut );

		return rtrim( $out ) . '…';
	}

	private function formatPageLinkArchiveLabel( string $token ): string {
		if ( $token === 'archive:home' ) {
			return __( 'Home', 'wppowersuite-lite' );
		}

		if ( ! preg_match( '/^archive:post_type:([a-z0-9_-]+)$/', $token, $matches ) ) {
			return '';
		}

		$object = get_post_type_object( $matches[1] );
		if ( ! $object ) {
			return '';
		}

		return sprintf(
			/* translators: %s: post type label. */
			__( '%s archive', 'wppowersuite-lite' ),
			(string) $object->labels->name
		);
	}
}
