<?php
/**
 * Omnibus active-sales list table.
 *
 * @package Kramar
 * @since   1.0.0
 */

declare( strict_types=1 );

namespace Kramar;

// Prevent direct access.
defined( 'ABSPATH' ) || exit;

if ( ! class_exists( '\WP_List_Table' ) ) {
	require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}

/**
 * Renders all on-sale products with their Omnibus compliance status.
 *
 * @since 1.0.0
 */
final class Omnibus_Table extends \WP_List_Table {

	/**
	 * Constructor.
	 *
	 * @since 1.0.0
	 */
	public function __construct() {
		parent::__construct(
			[
				'singular' => 'product',
				'plural'   => 'products',
				'ajax'     => false,
			]
		);
	}

	/**
	 * {@inheritDoc}
	 *
	 * @return array<string, string>
	 */
	public function get_columns(): array {
		return [
			'cb'            => '<input type="checkbox" />',
			'product'       => __( 'Product', 'kramar' ),
			'sale_start'    => __( 'Sale Start', 'kramar' ),
			'days_active'   => __( 'Days Active', 'kramar' ),
			'regular_price' => __( 'Regular Price', 'kramar' ),
			'sale_price'    => __( 'Sale Price', 'kramar' ),
			'status'        => __( 'Status', 'kramar' ),
			'actions'       => __( 'Actions', 'kramar' ),
		];
	}

	/**
	 * {@inheritDoc}
	 *
	 * @return array<string, array{0:string,1:bool}>
	 */
	protected function get_sortable_columns(): array {
		return [
			'sale_start'  => [ 'sale_start', false ],
			'days_active' => [ 'days_active', true ],
		];
	}

	/**
	 * {@inheritDoc}
	 *
	 * @return array<string, string>
	 */
	protected function get_bulk_actions(): array {
		return [ 'end_sale_bulk' => __( 'End sale', 'kramar' ) ];
	}

	/**
	 * Checkbox column.
	 *
	 * @since 1.0.0
	 *
	 * @param array<string, mixed> $item Row data.
	 * @return string
	 */
	protected function column_cb( $item ): string {
		return sprintf(
			'<input type="checkbox" name="product_ids[]" value="%d" />',
			(int) $item['product_id']
		);
	}

	/**
	 * Default column fallback.
	 *
	 * @since 1.0.0
	 *
	 * @param array<string, mixed> $item        Row data.
	 * @param string               $column_name Column key.
	 * @return string
	 */
	protected function column_default( $item, $column_name ): string {
		return esc_html( (string) ( $item[ $column_name ] ?? '' ) );
	}

	/**
	 * Product name linked to its edit screen.
	 *
	 * @since 1.0.0
	 *
	 * @param array<string, mixed> $item Row data.
	 * @return string
	 */
	protected function column_product( $item ): string {
		$edit_url = get_edit_post_link( (int) $item['product_id'] );

		if ( $edit_url ) {
			return sprintf(
				'<strong><a href="%s">%s</a></strong>',
				esc_url( $edit_url ),
				esc_html( (string) $item['product_name'] )
			);
		}

		return '<strong>' . esc_html( (string) $item['product_name'] ) . '</strong>';
	}

	/**
	 * Sale start date, formatted in site timezone.
	 *
	 * @since 1.0.0
	 *
	 * @param array<string, mixed> $item Row data.
	 * @return string
	 */
	protected function column_sale_start( $item ): string {
		$timestamp = (int) $item['sale_start_ts'];

		if ( ! $timestamp ) {
			// Sale predates price tracking — a subtle placeholder reads better than
			// a stark "Unknown" / "0". See the Omnibus admin-UX improvement.
			return sprintf(
				'<span class="kramar-omnibus-muted" title="%s">&mdash;</span>',
				esc_attr__( 'This sale started before price tracking began.', 'kramar' )
			);
		}

		return esc_html( date_i18n( get_option( 'date_format' ), $timestamp ) );
	}

	/**
	 * Number of days the sale has been active.
	 *
	 * @since 1.0.0
	 *
	 * @param array<string, mixed> $item Row data.
	 * @return string
	 */
	protected function column_days_active( $item ): string {
		// Without a known sale-start, days-active can't be computed — show the same
		// subtle placeholder instead of a misleading "0".
		if ( ! (int) $item['sale_start_ts'] ) {
			return sprintf(
				'<span class="kramar-omnibus-muted" title="%s">&mdash;</span>',
				esc_attr__( 'This sale started before price tracking began.', 'kramar' )
			);
		}

		return esc_html( (string) (int) $item['days_active'] );
	}

	/**
	 * Regular price formatted with WooCommerce currency.
	 *
	 * @since 1.0.0
	 *
	 * @param array<string, mixed> $item Row data.
	 * @return string
	 */
	protected function column_regular_price( $item ): string {
		if ( '' === (string) $item['regular_price'] ) {
			return '—';
		}

		return wp_kses_post( wc_price( (float) $item['regular_price'] ) );
	}

	/**
	 * Sale price formatted with WooCommerce currency.
	 *
	 * @since 1.0.0
	 *
	 * @param array<string, mixed> $item Row data.
	 * @return string
	 */
	protected function column_sale_price( $item ): string {
		if ( '' === (string) $item['sale_price'] ) {
			return '—';
		}

		return wp_kses_post( wc_price( (float) $item['sale_price'] ) );
	}

	/**
	 * Status badge: green <25 days, yellow 25-29, red 30+.
	 *
	 * @since 1.0.0
	 *
	 * @param array<string, mixed> $item Row data.
	 * @return string
	 */
	protected function column_status( $item ): string {
		// When the sale start is unknown (predates tracking), days_active is 0 and
		// would otherwise read as a green "Active" — show a neutral "Unknown" badge
		// so these ambiguous rows aren't masked as healthy.
		if ( ! (int) $item['sale_start_ts'] ) {
			return sprintf(
				'<span style="display:inline-block;padding:2px 8px;border-radius:3px;background:%s;color:#fff;font-weight:600;font-size:12px;" title="%s">%s</span>',
				'#787c82',
				esc_attr__( 'This sale started before price tracking began, so its duration is unknown.', 'kramar' ),
				esc_html__( 'Unknown', 'kramar' )
			);
		}

		$days = (int) $item['days_active'];

		if ( $days >= 30 ) {
			$color = '#d63638';
			$label = __( 'Overdue', 'kramar' );
		} elseif ( $days >= 25 ) {
			$color = '#dba617';
			$label = __( 'Expiring', 'kramar' );
		} else {
			$color = '#008a20';
			$label = __( 'Active', 'kramar' );
		}

		return sprintf(
			'<span style="display:inline-block;padding:2px 8px;border-radius:3px;background:%s;color:#fff;font-weight:600;font-size:12px;">%s</span>',
			esc_attr( $color ),
			esc_html( $label )
		);
	}

	/**
	 * End Sale action link.
	 *
	 * @since 1.0.0
	 *
	 * @param array<string, mixed> $item Row data.
	 * @return string
	 */
	protected function column_actions( $item ): string {
		$product_id = (int) $item['product_id'];
		$url        = wp_nonce_url(
			add_query_arg(
				[
					'page'       => 'kramar-omnibus',
					'action'     => 'end_sale',
					'product_id' => $product_id,
				],
				admin_url( 'admin.php' )
			),
			'kramar_end_sale_' . $product_id
		);

		return sprintf(
			'<a href="%s" onclick="return confirm(\'%s\');">%s</a>',
			esc_url( $url ),
			esc_js( __( 'End the sale for this product?', 'kramar' ) ),
			esc_html__( 'End sale', 'kramar' )
		);
	}

	/**
	 * Query all published products with an active sale and build row data.
	 *
	 * @since 1.0.0
	 */
	public function prepare_items(): void {
		$this->_column_headers = [ $this->get_columns(), [], $this->get_sortable_columns() ];

		$rows = $this->query_on_sale_products();

		// Sort by days_active or sale_start if requested.
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$orderby = sanitize_key( $_GET['orderby'] ?? '' );
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$order   = strtolower( sanitize_key( $_GET['order'] ?? 'desc' ) ) === 'asc' ? 1 : -1;

		if ( in_array( $orderby, [ 'days_active', 'sale_start' ], true ) ) {
			$key = 'sale_start' === $orderby ? 'sale_start_ts' : 'days_active';
			usort(
				$rows,
				static function ( array $a, array $b ) use ( $key, $order ): int {
					return ( (int) $a[ $key ] - (int) $b[ $key ] ) * $order;
				}
			);
		} else {
			// Default: overdue first.
			usort(
				$rows,
				static function ( array $a, array $b ): int {
					return (int) $b['days_active'] - (int) $a['days_active'];
				}
			);
		}

		$this->items = $rows;

		$this->set_pagination_args(
			[
				'total_items' => count( $rows ),
				'per_page'    => count( $rows ),
				'total_pages' => 1,
			]
		);
	}

	/**
	 * Fetch all published products (simple + variations) with a non-empty sale price.
	 *
	 * @since 1.0.0
	 *
	 * @return array<int, array<string, mixed>>
	 */
	private function query_on_sale_products(): array {
		global $wpdb;

		// Collect product and variation IDs that have a sale price set.
		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$meta_rows = $wpdb->get_results(
			"SELECT post_id, meta_value as sale_price_val
			FROM {$wpdb->postmeta}
			WHERE meta_key = '_sale_price'
			AND meta_value != ''
			AND post_id IN (
				SELECT ID FROM {$wpdb->posts}
				WHERE post_status = 'publish'
				AND post_type IN ('product','product_variation')
			)",
			ARRAY_A
		);

		$regular_prices  = [];
		$sale_start_rows = [];
		if ( ! empty( $meta_rows ) ) {
			$ids          = array_map( 'intval', array_column( $meta_rows, 'post_id' ) );
			$placeholders = implode( ', ', array_fill( 0, count( $ids ), '%d' ) );
			// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $placeholders/$sale_key_placeholders contain only %%d/%%s tokens; table name is trusted
			$rp_rows = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT post_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key = %s AND post_id IN ({$placeholders})",
					array_merge( [ '_regular_price' ], $ids )
				),
				ARRAY_A
			);
			foreach ( $rp_rows as $rp ) {
				$regular_prices[ (int) $rp['post_id'] ] = $rp['meta_value'];
			}

			$sale_key_placeholders = implode( ', ', array_fill( 0, 2, '%s' ) );
			$sale_start_rows       = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT post_id, meta_value, meta_key FROM {$wpdb->postmeta} WHERE meta_key IN ({$sale_key_placeholders}) AND post_id IN ({$placeholders})",
					array_merge( [ '_date_on_sale_from', '_kramar_sale_started' ], $ids )
				),
				ARRAY_A
			);
			// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
		}
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

		// Build sale start timestamp map.
		$sale_start_map = [];
		if ( ! empty( $sale_start_rows ) ) {
			foreach ( $sale_start_rows as $ss ) {
				$pid = (int) $ss['post_id'];
				$val = $ss['meta_value'];
				// Prefer _date_on_sale_from (a UTC timestamp) over _kramar_sale_started.
				if ( ! isset( $sale_start_map[ $pid ] ) || '_date_on_sale_from' === $ss['meta_key'] ) {
					$sale_start_map[ $pid ] = (int) $val;
				}
			}
		}

		$rows = [];

		foreach ( $meta_rows as $meta ) {
			$post_id  = (int) $meta['post_id'];
			$product  = wc_get_product( $post_id );
			if ( ! $product ) {
				continue;
			}

			$start_ts   = $sale_start_map[ $post_id ] ?? 0;
			$days       = $start_ts ? (int) ( ( time() - $start_ts ) / DAY_IN_SECONDS ) : 0;
			$parent_id  = $product->get_parent_id() ?: $post_id;

			$rows[] = [
				'product_id'    => $post_id,
				'product_name'  => $product->get_name(),
				'sale_start_ts' => $start_ts,
				'days_active'   => $days,
				'regular_price' => $regular_prices[ $post_id ] ?? $product->get_regular_price(),
				'sale_price'    => $meta['sale_price_val'],
			];
		}

		return $rows;
	}
}
