<?php
/**
 * Google Shopping Feed module.
 *
 * @package Kramar
 * @since   1.0.0
 */

declare( strict_types=1 );

namespace Kramar\Modules\Marketing;

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

/**
 * Google Shopping Feed module.
 *
 * Generates a token-secured, cached Google Shopping RSS2 XML feed and
 * rebuilds it on a configurable WP Cron schedule.
 *
 * @since 1.0.0
 */
final class Google_Shopping_Feed extends \Kramar\Module {

	/**
	 * Cron hook that rebuilds the feed file.
	 *
	 * A named constant rather than a local string: deactivation and uninstall
	 * clear the plugin's scheduled hooks from a list, and this one was written
	 * as a literal inside schedule_cron(), so it never made either list and
	 * survived both — leaving a WP-Cron row firing at a hook nothing listened
	 * to any more.
	 *
	 * @since 1.0.2
	 */
	public const CRON_HOOK = 'kramar_google_feed_generate';

	/**
	 * {@inheritDoc}
	 */
	public function get_slug(): string {
		return 'google-shopping-feed';
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_category(): string {
		return 'marketing';
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_name(): string {
		return __( 'Google Shopping Feed', 'kramar' );
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_description(): string {
		return __( 'Generate a Google Shopping product feed (RSS2 XML) for Google Merchant Center. Cached and token-secured; rebuilt automatically on a schedule.', 'kramar' );
	}

	/**
	 * {@inheritDoc}
	 *
	 * Keys MUST match sanitize_settings() exactly.
	 */
	public function get_default_settings(): array {
		return [
			'enabled'                 => false,
			'token'                   => '',
			'schedule'                => '6h',
			'brand_attribute'         => 'pa_brand',
			'mpn_meta_key'            => '_mpn',
			'google_product_category' => '',
			'category_map'            => [],
			'variation_mode'          => 'all',
			'include_out_of_stock'    => false,
		];
	}

	/**
	 * {@inheritDoc}
	 */
	public function sanitize_settings( array $input ): array {
		$token = sanitize_text_field( $input['token'] ?? '' );
		// Auto-generate token on first save if empty.
		if ( '' === $token ) {
			$token = bin2hex( random_bytes( 16 ) );
		}

		$valid_schedules = [ '6h', '12h', 'daily' ];
		$schedule        = sanitize_key( $input['schedule'] ?? '6h' );
		if ( ! in_array( $schedule, $valid_schedules, true ) ) {
			$schedule = '6h';
		}

		$valid_modes    = [ 'all', 'default', 'parent_only' ];
		$variation_mode = sanitize_key( $input['variation_mode'] ?? 'all' );
		if ( ! in_array( $variation_mode, $valid_modes, true ) ) {
			$variation_mode = 'all';
		}

		// WC category term id => Google category (numeric id or full path). Empty
		// values are dropped so the map only holds real mappings.
		$category_map = [];
		if ( isset( $input['category_map'] ) && is_array( $input['category_map'] ) ) {
			foreach ( $input['category_map'] as $term_id => $gcat ) {
				$tid = absint( $term_id );
				$val = sanitize_text_field( (string) $gcat );
				if ( $tid > 0 && '' !== $val ) {
					$category_map[ $tid ] = $val;
				}
			}
		}

		return [
			'enabled'                 => ! empty( $input['enabled'] ),
			'token'                   => $token,
			'schedule'                => $schedule,
			'brand_attribute'         => sanitize_key( $input['brand_attribute'] ?? 'pa_brand' ),
			'mpn_meta_key'            => sanitize_text_field( $input['mpn_meta_key'] ?? '_mpn' ),
			'google_product_category' => sanitize_text_field( $input['google_product_category'] ?? '' ),
			'category_map'            => $category_map,
			'variation_mode'          => $variation_mode,
			'include_out_of_stock'    => ! empty( $input['include_out_of_stock'] ),
		];
	}

	/**
	 * {@inheritDoc}
	 */
	public function render_extra_settings( string $option_name ): void {
		$cfg  = new Feed_Config( $this->plugin );
		$slug = $this->get_slug();
		$cat  = $this->get_category();
		$base = "{$option_name}[{$cat}][{$slug}]";
		?>
		<div class="kramar-module__extra">
			<div class="kramar-settings-stack">

				<p>
					<label>
						<?php esc_html_e( 'Refresh schedule', 'kramar' ); ?><br>
						<select name="<?php echo esc_attr( $base ); ?>[schedule]">
							<?php
							foreach ( [
								'6h'    => __( 'Every 6 hours', 'kramar' ),
								'12h'   => __( 'Every 12 hours', 'kramar' ),
								'daily' => __( 'Once a day', 'kramar' ),
							] as $val => $label ) :
								?>
								<option value="<?php echo esc_attr( $val ); ?>" <?php selected( $cfg->get_schedule(), $val ); ?>>
									<?php echo esc_html( $label ); ?>
								</option>
							<?php endforeach; ?>
						</select>
					</label>
				</p>

				<p>
					<label>
						<?php esc_html_e( 'Variation handling', 'kramar' ); ?><br>
						<select name="<?php echo esc_attr( $base ); ?>[variation_mode]">
							<?php
							foreach ( [
								'all'         => __( 'All variations (recommended)', 'kramar' ),
								'default'     => __( 'Default variation only', 'kramar' ),
								'parent_only' => __( 'Parent product only', 'kramar' ),
							] as $val => $label ) :
								?>
								<option value="<?php echo esc_attr( $val ); ?>" <?php selected( $cfg->get_variation_mode(), $val ); ?>>
									<?php echo esc_html( $label ); ?>
								</option>
							<?php endforeach; ?>
						</select>
					</label>
				</p>

				<p>
					<label>
						<?php esc_html_e( 'Brand attribute slug', 'kramar' ); ?><br>
						<input
							type="text"
							name="<?php echo esc_attr( $base ); ?>[brand_attribute]"
							value="<?php echo esc_attr( $cfg->get_brand_attribute() ); ?>"
							class="regular-text"
							placeholder="pa_brand"
						>
					</label>
					<span class="description"><?php esc_html_e( 'WooCommerce attribute slug used for brand (e.g. pa_brand). Leave empty to fall back to site name.', 'kramar' ); ?></span>
				</p>

				<p>
					<label>
						<?php esc_html_e( 'MPN meta key', 'kramar' ); ?><br>
						<input
							type="text"
							name="<?php echo esc_attr( $base ); ?>[mpn_meta_key]"
							value="<?php echo esc_attr( $cfg->get_mpn_meta_key() ); ?>"
							class="regular-text"
							placeholder="_mpn"
						>
					</label>
				</p>

				<p>
					<label>
						<?php esc_html_e( 'Default Google product category (numeric ID)', 'kramar' ); ?><br>
						<input
							type="text"
							name="<?php echo esc_attr( $base ); ?>[google_product_category]"
							value="<?php echo esc_attr( $cfg->get_google_product_category() ); ?>"
							class="regular-text"
							placeholder="166"
						>
					</label>
					<span class="description">
						<?php
						printf(
							/* translators: %s: Google taxonomy URL */
							esc_html__( 'Optional. Find your category ID in the %s.', 'kramar' ),
							'<a href="https://www.google.com/basepages/producttype/taxonomy-with-ids.en-US.txt" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Google taxonomy file', 'kramar' ) . '</a>'
						);
						?>
					</span>
				</p>

				<?php $this->render_category_map( $base, $cfg ); ?>

				<p>
					<label>
						<input
							type="checkbox"
							name="<?php echo esc_attr( $base ); ?>[include_out_of_stock]"
							value="1"
							<?php checked( $cfg->include_out_of_stock() ); ?>
						>
						<?php esc_html_e( 'Include out-of-stock products', 'kramar' ); ?>
					</label>
					<span class="description"><?php esc_html_e( 'When checked, out-of-stock products appear in the feed with availability="out_of_stock". Google allows this but may limit impressions.', 'kramar' ); ?></span>
				</p>

				<input
					type="hidden"
					name="<?php echo esc_attr( $base ); ?>[token]"
					value="<?php echo esc_attr( $cfg->get_token() ); ?>"
				>

				<?php if ( $this->is_enabled() && '' !== $cfg->get_token() ) : ?>
					<p>
						<strong><?php esc_html_e( 'Feed URL', 'kramar' ); ?></strong><br>
						<code style="display:inline-block;padding:4px 8px;background:#f3f4f6;border-radius:3px;user-select:all;"><?php echo esc_html( $cfg->get_feed_url() ); ?></code>
					</p>
					<?php
					$stats = get_option( 'kramar_google_feed_stats' );
					if ( is_array( $stats ) ) :
						$generated = (int) ( $stats['generated'] ?? 0 );
						$no_image  = (int) ( $stats['skipped_no_image'] ?? 0 );
						$no_price  = (int) ( $stats['skipped_no_price'] ?? 0 );
						$when      = (int) ( $stats['generated_at'] ?? 0 );
						?>
						<p class="description" style="margin:0 0 12px;">
							<?php
							printf(
								/* translators: 1: number of items in the feed, 2: number skipped for no image, 3: number skipped for no price */
								esc_html__( 'Last build: %1$d items in the feed, %2$d skipped (no image), %3$d skipped (no price).', 'kramar' ),
								(int) $generated,
								(int) $no_image,
								(int) $no_price
							);
							if ( $when > 0 ) {
								echo ' ';
								printf(
									/* translators: %s: human-readable time difference, e.g. "2 hours" */
									esc_html__( 'Generated %s ago.', 'kramar' ),
									esc_html( human_time_diff( $when, time() ) )
								);
							}
							?>
						</p>
					<?php endif; ?>
					<div style="padding:12px 14px;margin:4px 0 12px;background:#eff6ff;border:1px solid #bfdbfe;border-radius:4px;font-size:13px;line-height:1.6;color:#1e3a5f;">
						<strong><?php esc_html_e( 'How to use this feed in Google Merchant Center:', 'kramar' ); ?></strong>
						<ol style="margin:6px 0 0;padding-left:20px;">
							<li><?php esc_html_e( 'Sign in to Google Merchant Center and open Products → Feeds.', 'kramar' ); ?></li>
							<li><?php esc_html_e( 'Click the + button to add a primary feed, choose your country and language.', 'kramar' ); ?></li>
							<li><?php esc_html_e( 'For the input method, choose “Scheduled fetch”.', 'kramar' ); ?></li>
							<li><?php esc_html_e( 'Paste the Feed URL above into the file URL field and set a fetch schedule (e.g. daily).', 'kramar' ); ?></li>
						</ol>
						<p style="margin:8px 0 0;"><?php esc_html_e( 'The URL contains a private token — keep it secret. Regenerate the feed manually below after large catalogue changes; otherwise it rebuilds on the schedule set above.', 'kramar' ); ?></p>
					</div>
					<p>
						<a href="<?php echo esc_url( admin_url( 'admin-post.php?action=kramar_regenerate_feed&_wpnonce=' . wp_create_nonce( 'kramar_regenerate_feed' ) ) ); ?>" class="button">
							<?php esc_html_e( 'Regenerate feed now', 'kramar' ); ?>
						</a>
					</p>
				<?php else : ?>
					<p class="description"><?php esc_html_e( 'Enable and save this module to generate your private feed URL for Google Merchant Center.', 'kramar' ); ?></p>
				<?php endif; ?>

			</div>
		</div>
		<?php
	}

	/**
	 * Render the WooCommerce-category -> Google-category mapping table.
	 *
	 * Lists product categories in tree order; a value on a parent covers every
	 * child that has no value of its own (resolution walks up the tree), so only
	 * top-level categories usually need filling. Accepts a numeric Google id or
	 * the full path string.
	 *
	 * @since 1.0.0
	 *
	 * @param string      $base The settings field-name base for this module.
	 * @param Feed_Config $cfg  Feed config accessor.
	 */
	private function render_category_map( string $base, Feed_Config $cfg ): void {
		if ( ! taxonomy_exists( 'product_cat' ) ) {
			return;
		}

		$terms = get_terms(
			[
				'taxonomy'   => 'product_cat',
				'hide_empty' => false,
				'orderby'    => 'name',
			]
		);

		if ( is_wp_error( $terms ) || empty( $terms ) ) {
			return;
		}

		// Index by parent so we can render the tree depth-first.
		$by_parent = [];
		foreach ( $terms as $term ) {
			$by_parent[ (int) $term->parent ][] = $term;
		}

		$map = $cfg->get_category_map();
		?>
		<div style="margin:16px 0;">
			<strong><?php esc_html_e( 'Google category by product category', 'kramar' ); ?></strong>
			<p class="description" style="margin:4px 0 8px;">
				<?php esc_html_e( 'Map each product category to a Google product category (numeric ID or full path). A value on a parent category applies to its children unless they have their own — so usually only the top-level categories need filling. Anything left blank uses the default above.', 'kramar' ); ?>
			</p>
			<table class="widefat striped" style="max-width:680px;">
				<thead>
					<tr>
						<th style="width:55%;"><?php esc_html_e( 'Product category', 'kramar' ); ?></th>
						<th><?php esc_html_e( 'Google category (ID or path)', 'kramar' ); ?></th>
					</tr>
				</thead>
				<tbody>
					<?php $this->render_category_rows( $by_parent, 0, 0, $base, $map ); ?>
				</tbody>
			</table>
		</div>
		<?php
	}

	/**
	 * Recursively render category-map rows, indented by depth.
	 *
	 * @since 1.0.0
	 *
	 * @param array<int, \WP_Term[]> $by_parent Terms indexed by parent id.
	 * @param int                    $parent    Parent term id to render children of.
	 * @param int                    $depth     Current tree depth.
	 * @param string                 $base      Settings field-name base.
	 * @param array<int, string>     $map       Saved term_id => Google category.
	 */
	private function render_category_rows( array $by_parent, int $parent, int $depth, string $base, array $map ): void {
		if ( empty( $by_parent[ $parent ] ) ) {
			return;
		}

		foreach ( $by_parent[ $parent ] as $term ) {
			$tid    = (int) $term->term_id;
			$indent = str_repeat( '&mdash; ', $depth );
			?>
			<tr>
				<td><?php echo wp_kses_post( $indent ) . esc_html( $term->name ); ?></td>
				<td>
					<input
						type="text"
						name="<?php echo esc_attr( $base ); ?>[category_map][<?php echo esc_attr( (string) $tid ); ?>]"
						value="<?php echo esc_attr( $map[ $tid ] ?? '' ); ?>"
						class="regular-text"
						style="width:100%;"
						placeholder="<?php echo 0 === $depth ? esc_attr__( 'e.g. 187', 'kramar' ) : ''; ?>"
					>
				</td>
			</tr>
			<?php
			$this->render_category_rows( $by_parent, $tid, $depth + 1, $base, $map );
		}
	}

	/**
	 * {@inheritDoc}
	 */
	protected function run(): void {
		$cfg = new Feed_Config( $this->plugin );

		// Register custom cron schedules.
		add_filter( 'cron_schedules', [ $this, 'register_cron_schedules' ] );

		// Schedule feed generation cron.
		$this->schedule_cron( $cfg );

		// Handle cron-triggered generation.
		add_action( self::CRON_HOOK, [ $this, 'generate_feed' ] );

		// Register rewrite rule for feed URL delivery.
		( new Feed_Delivery( $cfg ) )->register();

		// Handle "Regenerate now" admin-post action.
		if ( is_admin() ) {
			add_action( 'admin_post_kramar_regenerate_feed', [ $this, 'handle_regenerate_now' ] );
		}
	}

	/**
	 * Register custom cron intervals.
	 *
	 * @since 1.0.0
	 *
	 * @param array<string, array<string, mixed>> $schedules Existing schedules.
	 * @return array<string, array<string, mixed>> Updated schedules.
	 */
	public function register_cron_schedules( array $schedules ): array {
		$schedules['kramar_6h'] = [
			'interval' => 6 * HOUR_IN_SECONDS,
			'display'  => __( 'Every 6 hours', 'kramar' ),
		];
		$schedules['kramar_12h'] = [
			'interval' => 12 * HOUR_IN_SECONDS,
			'display'  => __( 'Every 12 hours', 'kramar' ),
		];
		return $schedules;
	}

	/**
	 * Schedule the feed generation cron event.
	 *
	 * @since 1.0.0
	 *
	 * @param Feed_Config $cfg Module configuration.
	 */
	private function schedule_cron( Feed_Config $cfg ): void {
		$hook = self::CRON_HOOK;
		$map  = [
			'6h'    => 'kramar_6h',
			'12h'   => 'kramar_12h',
			'daily' => 'daily',
		];
		$recurrence = $map[ $cfg->get_schedule() ] ?? 'kramar_6h';

		// Reschedule when the interval changed — wp_next_scheduled() alone would
		// keep the old cadence until the stale event fired.
		$existing = wp_get_scheduled_event( $hook );
		if ( ! $existing ) {
			wp_schedule_event( time(), $recurrence, $hook );
		} elseif ( $existing->schedule !== $recurrence ) {
			wp_clear_scheduled_hook( $hook );
			wp_schedule_event( time(), $recurrence, $hook );
		}
	}

	/**
	 * Cron callback — generate the feed file.
	 *
	 * @since 1.0.0
	 */
	public function generate_feed(): void {
		$cfg = new Feed_Config( $this->plugin );
		( new Feed_Generator( $cfg ) )->generate();
	}

	/**
	 * Handle the "Regenerate now" admin-post action.
	 *
	 * @since 1.0.0
	 */
	public function handle_regenerate_now(): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'Insufficient permissions.', 'kramar' ) );
		}

		check_admin_referer( 'kramar_regenerate_feed' );
		$this->generate_feed();

		wp_safe_redirect(
			add_query_arg(
				[ 'page' => 'kramar-modules', 'feed_regenerated' => '1' ],
				admin_url( 'admin.php' )
			)
		);
		exit;
	}
}
