<?php
/**
 * The class manages various admin action links, feedback submission and text overrides in footer.
 *
 * @package AdvancedAds
 * @author  Advanced Ads <info@wpadvancedads.com>
 * @since   1.47.0
 */

namespace AdvancedAds\Admin;

use WP_Term;
use WP_Query;
use WP_Terms_List_Table;
use Advanced_Ads;
use Advanced_Ads_Group;
use Advanced_Ads_Modal;
use AdvancedAds\Entities;
use Advanced_Ads\Group_Repository;
use AdvancedAds\Interfaces\Group_Type;
use AdvancedAds\Utilities\Groups;

defined( 'ABSPATH' ) || exit;

/**
 * Action Links.
 */
class Groups_List_Table extends WP_Terms_List_Table {

	/**
	 * Current row item.
	 *
	 * @var Advanced_Ads_Group
	 */
	private $current = null;

	/**
	 * Current group type.
	 *
	 * @var Group_Type
	 */
	private $group_type = null;

	/**
	 * Missing type error.
	 *
	 * @var string
	 */
	private $type_error = '';

	/**
	 * Array with all ads.
	 *
	 * @var $all_ads
	 */
	private $all_ads = [];

	/**
	 * Array of group ads info.
	 *
	 * @var array
	 */
	private $group_ads_info = [];

	/**
	 * Hints html.
	 *
	 * @var string
	 */
	private $hints_html = null;

	/**
	 * Construct the current list
	 */
	public function __construct() {
		parent::__construct();
		$this->prepare_items();

		// TODO: replace with Ad repository.
		$this->all_ads = $this->ads_for_select();
	}

	/**
	 * Load groups
	 *
	 * @return void
	 */
	public function prepare_items(): void {
		parent::prepare_items();

		$taxonomy = $this->screen->taxonomy;

		$this->callback_args['taxonomy']   = $taxonomy;
		$this->callback_args['hide_empty'] = 0;
		$this->callback_args['offset']     = ( $this->callback_args['page'] - 1 ) * $this->callback_args['number'];

		$this->items = get_terms( $this->callback_args );

		$this->items = array_map(
			function ( WP_Term $term ) {
				return Group_Repository::get( $term );
			},
			$this->items ?? []
		);

		$this->_column_headers = [ $this->get_columns(), [], [], 'name' ];
	}

	/**
	 * Gets the number of items to display on a single page.
	 *
	 * @param string $option        User option name.
	 * @param int    $default_value The number of items to display.
	 *
	 * @return int
	 */
	protected function get_items_per_page( $option, $default_value = 20 ): int {
		return 0;
	}

	/**
	 * Get columns
	 *
	 * @return array
	 */
	public function get_columns(): array {
		return [
			'type'    => __( 'Type', 'advanced-ads' ),
			'name'    => _x( 'Name', 'term name', 'advanced-ads' ),
			'details' => __( 'Details', 'advanced-ads' ),
			'ads'     => __( 'Ads', 'advanced-ads' ),
		];
	}

	/**
	 * Displays the table.
	 *
	 * @since 3.1.0
	 */
	public function display() {
		$singular = $this->_args['singular'];

		$this->screen->render_screen_reader_content( 'heading_list' );
		?>
		<table class="<?php echo esc_attr( implode( ' ', $this->get_table_classes() ) ); ?>">
			<?php $this->print_table_description(); ?>
			<thead>
				<tr>
					<?php $this->print_column_headers(); ?>
				</tr>
			</thead>

			<tbody id="the-list"
				<?php
				if ( $singular ) {
					echo " data-wp-lists='list:$singular'"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
				}
				?>
				>
				<?php $this->display_rows_or_placeholder(); ?>
			</tbody>
		</table>
		<?php
	}

	/**
	 * Display rows or placeholder
	 *
	 * @return void
	 */
	public function display_rows_or_placeholder(): void {
		if ( empty( $this->items ) || ! is_array( $this->items ) ) {
			echo '<tr class="no-items"><td class="colspanchange" colspan="' . $this->get_column_count() . '">'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
			$this->no_items();
			echo '</td></tr>';
			return;
		}

		foreach ( $this->items as $term ) {
			$this->single_row( $term );
		}
	}

	/**
	 * Gets a list of CSS classes for the WP_List_Table table tag.
	 *
	 * @return array
	 */
	protected function get_table_classes(): array {
		$mode = get_user_setting( 'posts_list_mode', 'list' );

		$mode_class = esc_attr( 'table-view-' . $mode );

		return [ 'wp-list-table', 'widefat', 'fixed', 'advads-table', $mode_class, $this->_args['plural'] ];
	}

	/**
	 * No groups found
	 *
	 * @return void
	 */
	public function no_items(): void {
		echo esc_html_e( 'No Ad Group found', 'advanced-ads' );
	}

	/**
	 * Render single row.
	 *
	 * @param Advanced_Ads_Group $group Term object.
	 * @param int                $level Depth level.
	 */
	public function single_row( $group, $level = 0 ) {
		$this->type_error = '';
		$this->hints_html = '';

		// Set the group to behave as default, if the original type is not available.
		if ( ! wp_advads()->group_manager->has_type( $group->type ) ) {
			$this->type_error = sprintf(
			/* translators: %s is the group type string */
				__( 'The originally selected group type “%s” is not enabled.', 'advanced-ads' ),
				$group->type
			);
			$group->type = 'default';
		}

		$this->hints_html = Groups::build_hints_html( $group );
		$this->current    = $group;
		$this->group_type = wp_advads()->group_manager->get_type( $group->type );

		parent::single_row( $group, $level );
		$this->current = null;
	}

	/**
	 * Column type
	 *
	 * @param Advanced_Ads_Group $group Group Instance.
	 *
	 * @return void
	 */
	public function column_type( $group ) {
		?>
		<div class="advads-form-type">
			<?php if ( ! $this->type_error ) : ?>
				<img src="<?php echo esc_url( $this->group_type->get_image() ); ?>" alt="<?php echo esc_attr( $this->group_type->get_title() ); ?>">
			<?php endif; ?>
			<p class="advads-form-description">
				<strong><?php echo esc_html( $this->group_type->get_title() ); ?></strong>
			</p>
		</div>
		<?php
	}

	/**
	 * Column name
	 *
	 * @param Advanced_Ads_Group $group Group Instance.
	 *
	 * @return void
	 */
	public function column_name( $group ) {
		?>
		<div class="advads-table-name">
			<a class="row-title" href="#modal-group-edit-<?php echo absint( $group->id ); ?>"><?php echo esc_html( $group->name ); ?></a>
		</div>
		<?php if ( $this->type_error ) : ?>
		<p class="advads-notice-inline advads-error"><?php echo esc_html( $this->type_error ); ?></p>
			<?php
		endif;
		$this->render_edit_modal( $group );
		$this->render_usage_modal( $group );
	}

	/**
	 * Column details
	 *
	 * @param Advanced_Ads_Group $group Group Instance.
	 *
	 * @return void
	 */
	public function column_details( $group ) {
		?>
		<ul>
			<li>
				<strong>
				<?php
				/* translators: %s is the name of a group type */
				printf( esc_html__( 'Type: %s', 'advanced-ads' ), esc_html( $this->group_type->get_title() ) );
				?>
				</strong>
			</li>
			<li>
			<?php
			/*
			 * translators: %s is the ID of an ad group
			 */
			printf( esc_attr__( 'ID: %s', 'advanced-ads' ), absint( $group->id ) );
			?>
			</li>
		</ul>
		<?php
	}

	/**
	 * Column ads
	 *
	 * @param Advanced_Ads_Group $group Group Instance.
	 *
	 * @return void
	 */
	public function column_ads( $group ) {
		$group_ads_info = $this->get_group_ads_info( $group );
		$ads            = $group_ads_info['ads'] ?? null;
		$weights        = $group_ads_info['weights'] ?? null;
		$weight_sum     = $group_ads_info['weight_sum'] ?? null;

		if ( $ads->have_posts() ) {
			include ADVADS_ABSPATH . 'views/admin/tables/groups/list-row-loop.php';
		} else {
			include ADVADS_ABSPATH . 'views/admin/tables/groups/list-row-loop-none.php';
		}

		wp_reset_postdata();
	}

	/**
	 * Generates and displays row action links.
	 *
	 * @param Advanced_Ads_Group $group       Group Instance.
	 * @param string             $column_name Column name.
	 * @param string             $primary     Primary column name.
	 *
	 * @return string
	 */
	protected function handle_row_actions( $group, $column_name, $primary ): string {
		global $tax;

		if ( $primary !== $column_name ) {
			return '';
		}

		$actions = [];
		if ( current_user_can( $tax->cap->edit_terms ) ) {
			$actions['edit']  = '<a href="#modal-group-edit-' . $group->id . '" class="edits">' . esc_html__( 'Edit', 'advanced-ads' ) . '</a>';
			$actions['usage'] = '<a href="#modal-' . (int) $group->id . '-usage" class="usage-modal-link">' . esc_html__( 'show usage', 'advanced-ads' ) . '</a>';
		}

		if ( current_user_can( $tax->cap->delete_terms ) ) {
			$args              = [
				'action'   => 'delete',
				'group_id' => $group->id,
				'page'     => 'advanced-ads-groups',
			];
			$delete_link       = add_query_arg( $args, admin_url( 'admin.php' ) );
			$actions['delete'] = "<a class='delete-tag' href='" . wp_nonce_url( $delete_link, 'delete-tag_' . $group->id ) . "'>" . __( 'Delete', 'advanced-ads' ) . '</a>';
		}

		return $this->row_actions( $actions );
	}

	/**
	 * Render edit form modal
	 *
	 * @param Advanced_Ads_Group $group Group Instance.
	 *
	 * @return void
	 */
	private function render_edit_modal( $group ): void {
		$group_ads_info = $this->get_group_ads_info( $group );
		$ads            = $group_ads_info['ads'] ?? null;
		$weights        = $group_ads_info['weights'] ?? null;
		$ad_form_rows   = $this->get_weighted_ad_order( $weights );
		$max_weight     = Advanced_Ads_Group::get_max_ad_weight( $ads->post_count );

		// The Loop.
		if ( $ads->post_count ) {
			foreach ( $ads->posts as $ad ) {
				$row       = '';
				$row      .= '<tr data-ad-id="' . absint( $ad->ID ) . '" data-group-id="' . absint( $group->id ) . '"><td>' . esc_html( $ad->post_title ) . '</td><td>';
				$row      .= '<select name="advads-groups[' . absint( $group->id ) . '][ads][' . absint( $ad->ID ) . ']">';
				$ad_weight = ( isset( $weights[ $ad->ID ] ) ) ? $weights[ $ad->ID ] : Advanced_Ads_Group::MAX_AD_GROUP_DEFAULT_WEIGHT;
				for ( $i = 0; $i <= $max_weight; $i++ ) {
					$row .= '<option ' . selected( $ad_weight, $i, false ) . '>' . $i . '</option>';
				}

				$row                    .= '</select</td><td><button type="button" class="advads-remove-ad-from-group button">x</button></td></tr>';
				$ad_form_rows[ $ad->ID ] = $row;
			}
		}
		$ad_form_rows = $this->remove_empty_weights( $ad_form_rows );

		ob_start();
		require ADVADS_ABSPATH . 'views/admin/tables/groups/edit-form-modal.php';
		$modal_content = ob_get_clean();

		Advanced_Ads_Modal::create(
			[
				'modal_slug'       => 'group-edit-' . $this->current->id,
				'modal_content'    => $modal_content,
				'modal_title'      => __( 'Edit', 'advanced-ads' ) . ' ' . $this->current->name,
				'close_action'     => __( 'Save', 'advanced-ads' ) . ' ' . $this->current->name,
				'close_form'       => 'advads-form-groups',
				'close_validation' => 'advads_group_edit_submit',
			]
		);
	}

	/**
	 * Render usage modal
	 *
	 * @param Advanced_Ads_Group $group Group Instance.
	 *
	 * @return void
	 */
	private function render_usage_modal( $group ): void {
		ob_start();
		?>
		<div class="advads-usage">
			<h2><?php esc_html_e( 'shortcode', 'advanced-ads' ); ?></h2>
			<code><input type="text" onclick="this.select();" value='[the_ad_group id="<?php echo absint( $group->id ); ?>"]' readonly /></code>
			<h2><?php esc_html_e( 'template (PHP)', 'advanced-ads' ); ?></h2>
			<code><input type="text" onclick="this.select();" value="the_ad_group(<?php echo absint( $group->id ); ?>);" readonly /></code>
		</div>

		<?php
		$modal_content = ob_get_clean();
		Advanced_Ads_Modal::create(
			[
				'modal_slug'    => $group->id . '-usage',
				'modal_content' => $modal_content,
				'modal_title'   => __( 'Usage', 'advanced-ads' ),
			]
		);
	}

	/**
	 * List of all ads to display in select dropdown
	 *
	 * @return array
	 */
	private function ads_for_select() {
		$model = Advanced_Ads::get_instance()->get_model();

		// load all ads.
		$ads = $model->get_ads(
			[
				'orderby' => 'title',
				'order'   => 'ASC',
			]
		);

		return wp_list_pluck( $ads, 'post_title', 'ID' );
	}

	/**
	 * Get ads information for this group
	 *
	 * @param Advanced_Ads_Group $group group object.
	 *
	 * @return WP_Query
	 */
	private function get_group_ads_info( $group ) {
		if ( isset( $this->group_ads_info[ $group->id ] ) ) {
			return $this->group_ads_info[ $group->id ];
		}

		$weights = $group->get_ad_weights();
		arsort( $weights );

		$sorted_ad_ids = array_keys( $weights );
		$args          = [
			'post_type'      => Entities::POST_TYPE_AD,
			'post_status'    => [ 'publish', 'future', 'draft' ],
			'taxonomy'       => $group->taxonomy,
			'term'           => $group->slug,
			'posts_per_page' => - 1,
			'post__in'       => $sorted_ad_ids,
		];

		$ads        = new WP_Query( $args );
		$ad_ids     = wp_list_pluck( $ads->posts, 'ID' );
		$weight_sum = array_sum( array_intersect_key( $weights, array_flip( $ad_ids ) ) );

		$this->group_ads_info[ $group->id ] = compact( 'weights', 'ads', 'weight_sum' );

		return $this->group_ads_info[ $group->id ];
	}

	/**
	 * Return the displayed ad count string
	 *
	 * @param Advanced_Ads_Group $group     the ad group.
	 * @param WP_Query           $ads_query list of ads in group.
	 *
	 * @return string
	 */
	private function get_ad_count_string( $group, $ads_query ) {
		$ad_count = 'all' === $group->ad_count ? $ads_query->post_count : $group->ad_count;

		/**
		 * Filters the displayed ad count on the ad groups page.
		 *
		 * @param int                $ad_count the amount of displayed ads.
		 * @param Advanced_Ads_Group $group    the current ad group.
		 */
		$ad_count = (int) apply_filters( 'advanced-ads-group-displayed-ad-count', $ad_count, $group );

		/* translators: amount of ads displayed */
		return sprintf( _n( 'Up to %d ad displayed.', 'Up to %d ads displayed', $ad_count, 'advanced-ads' ), $ad_count );
	}

	/**
	 * Function to calculate weight percentage
	 *
	 * @param int $weight     Ad weight.
	 * @param int $weight_sum Sum of all ad weights.
	 *
	 * @return string
	 */
	private function calculate_weight_percentage( $weight, $weight_sum ): string {
		$percentage = ( $weight / $weight_sum ) * 100;
		return number_format( $percentage, 2 ) . '%';
	}

	/**
	 * Remove entries from the ad weight array that are just id
	 *
	 * @param array $ads_output array with any output other that an integer.
	 *
	 * @return array $ads_output array with ad output.
	 * @since 1.5.1
	 */
	private function remove_empty_weights( array $ads_output ) {
		foreach ( $ads_output as $key => $value ) {
			if ( is_int( $value ) ) {
				unset( $ads_output[ $key ] );
			}
		}

		return $ads_output;
	}

	/**
	 * Order the ad list by weight first and then by title.
	 *
	 * @param array<int, int> $weights indexed by ad_id, weight as value.
	 *
	 * @return array<int, int>
	 */
	private function get_weighted_ad_order( array $weights ) {
		arsort( $weights );
		$ad_title_weights = [];

		// index ads with the same weight by weight.
		foreach ( $weights as $ad_id => $weight ) {
			$ad_title_weights[ $weight ][ $ad_id ] = get_the_title( $ad_id );
		}

		// Order them by title.
		array_walk(
			$ad_title_weights,
			function ( &$weight_group ) {
				natsort( $weight_group );
			}
		);

		// Flatten the array with the ad_id as key and the weight as value.
		$ad_order = [];
		foreach ( $ad_title_weights as $weight => $ad_array ) {
			$ad_order += array_fill_keys( array_keys( $ad_array ), $weight );
		}

		return $ad_order;
	}
}
