<?php
namespace Pay4All\Pro\Integrations;

use Pay4All\Pro\PostTypes;
use Pay4All\Pro\License\LicenseManager;
use Pay4All\Pro\Domain\Category;
use Pay4All\Pro\Domain\Brand;

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

/**
 * Glue layer between Pay4All Pro and the free Pay4All Shop plugin.
 *
 * Responsibilities:
 *
 *  1. **Menu swap** — the free plugin registers Pro-teaser submenu entries
 *     `SLUG_CATS_PRO` / `SLUG_BRANDS_PRO` (rendered by AddOns\Page). When Pro
 *     is active AND the licence allows the feature, we replace them with
 *     WordPress-native edit-tags.php links so clicking « Catégories » lands
 *     on the real taxonomy management screen.
 *
 *  2. **Feature unlocks** — a small set of filters on the free plugin lets
 *     us unlock the colour tab, the multilingual language picker and the
 *     product-filter widget without a code fork.
 *
 *  3. **JS-data enrichment** — inject the serialised `categories` / `brands`
 *     rule bags into the shortcode's `data-config` so the front-end JS can
 *     validate quantity rules client-side.
 */
final class Pay4AllForm {

	public static function register() : void {
		// 1. Feature unlocks — silent no-op when the trial has expired and the
		//    licence is inactive, so the free version's guardrails re-engage.
		add_filter( 'p4all_form_pro_active', [ self::class, 'is_licensed' ] );

		// 2. Menu swap (runs late so we see the final submenu ordering).
		add_action( 'admin_menu', [ self::class, 'swap_teaser_menus' ], 10000 );

		// 3. Right after the swap, drop the leftover teaser entries whose URL
		//    now collides with a real Pro submenu page, so the sidebar shows
		//    one link per feature instead of two. See dedupe_swapped_entries().
		add_action( 'admin_menu', [ self::class, 'dedupe_swapped_entries' ], 10001 );

		// 4. JS-data injection for the shortcode.
		add_filter( 'p4all_form_js_data', [ self::class, 'inject_rules_bundle' ], 10, 3 );

		// 5. Adds the « Billet » entry to the free plugin's product-type
		//    select. Guarded by the licence so an expired trial hides the
		//    option — existing ticket products stay saved as `ticket` in
		//    meta thanks to Product::TYPES already declaring the constant.
		add_filter( 'p4all_product_type_options', [ self::class, 'add_ticket_type_option' ] );
	}

	/**
	 * @param array<string,string> $options
	 * @return array<string,string>
	 */
	public static function add_ticket_type_option( array $options ) : array {
		if ( ! LicenseManager::is_feature_allowed() ) { return $options; }
		$options[ \Pay4All\Form\Domain\Product::TYPE_TICKET ]   = __( 'Billet (QR code envoyé au client)', 'pay4all-pro' );
		$options[ \Pay4All\Form\Domain\Product::TYPE_DONATION ] = __( 'Collect donations', 'pay4all-pro' );
		return $options;
	}

	/**
	 * Both Billets and Abandon panier are now served by real Pro submenu
	 * pages (Admin\Tickets\Page + Admin\AbandonedCarts\Page), each registered
	 * under Menu::SLUG_TOP via add_submenu_page(). After swap_teaser_menus()
	 * rewrote the teaser URLs to match the real page slugs, the SHOP submenu
	 * ends up with two entries pointing at the same slug: the (visually
	 * rebranded) teaser sitting in its natural position and the real Page's
	 * submenu appended at the bottom of the sidebar.
	 *
	 * We want the teaser slot to keep the sidebar position and the real
	 * Page's callback to serve requests. Since each Page::add_submenu()
	 * already registered `add_action( pay4all-shop_page_<slug> )` and
	 * `$_registered_pages[...]`, routing works regardless of which $submenu
	 * entry survives — we only strip the *appended* one so the sidebar
	 * shows a single link in the right place. remove_submenu_page() would
	 * remove the first match (the teaser), so we walk the array and drop
	 * later duplicates by hand.
	 */
	public static function dedupe_swapped_entries() : void {
		if ( ! LicenseManager::is_feature_allowed() ) { return; }

		global $submenu;
		$parent = defined( '\\Pay4All\\Form\\Admin\\Menu::SLUG_TOP' )
			? \Pay4All\Form\Admin\Menu::SLUG_TOP
			: 'pay4all-form-dashboard';

		if ( empty( $submenu[ $parent ] ) || ! is_array( $submenu[ $parent ] ) ) { return; }

		$dedupe_slugs = [ 'pay4all-form-tickets', 'pay4all-form-abandoned-carts', \Pay4All\Pro\Admin\Newsletter\Page::SLUG ];
		$seen         = [];
		foreach ( $submenu[ $parent ] as $index => $item ) {
			if ( ! isset( $item[2] ) ) { continue; }
			$slug = (string) $item[2];
			if ( ! in_array( $slug, $dedupe_slugs, true ) ) { continue; }
			if ( isset( $seen[ $slug ] ) ) {
				unset( $submenu[ $parent ][ $index ] );
			} else {
				$seen[ $slug ] = true;
			}
		}
	}

	/**
	 * Answered by LicenseManager::is_feature_allowed() so the free plugin can
	 * flip its display / language / filter-widget gates in one call. During
	 * the 30-day trial this returns true unconditionally; afterwards it
	 * mirrors the licence state.
	 */
	public static function is_licensed( bool $active ) : bool {
		return $active || LicenseManager::is_feature_allowed();
	}

	/**
	 * Rewrites the two Pro-teaser submenu entries in the « Pay4All SHOP » menu
	 * so they point at WordPress's native taxonomy edit screen. WP core still
	 * anchors the submenu highlight via `parent_file` so we mirror that here
	 * for the two edit-tags.php destinations we bind to.
	 */
	public static function swap_teaser_menus() : void {
		if ( ! LicenseManager::is_feature_allowed() ) { return; }

		global $submenu;
		$parent = defined( '\\Pay4All\\Form\\Admin\\Menu::SLUG_TOP' )
			? \Pay4All\Form\Admin\Menu::SLUG_TOP
			: 'pay4all-form-dashboard';
		if ( empty( $submenu[ $parent ] ) || ! is_array( $submenu[ $parent ] ) ) { return; }

		$product_pt = defined( '\\Pay4All\\Form\\PostTypes::PRODUCT' )
			? \Pay4All\Form\PostTypes::PRODUCT
			: 'p4all_product';

		// Categories + Brands → real WordPress taxonomy admin URLs (the Pro
		// PostTypes registered them so edit-tags.php is a legitimate landing).
		// Billets + Abandon panier → real Pro admin pages served by
		// Admin\Tickets\Page and Admin\AbandonedCarts\Page. Duplicates are
		// stripped by dedupe_swapped_entries() at priority 10001.
		// All four variants also strip the amber "PRO" pill.
		$swap = [
			'pay4all-form-categories-pro'   => 'edit-tags.php?taxonomy=' . PostTypes::TAXONOMY       . '&post_type=' . $product_pt,
			'pay4all-form-brands-pro'       => 'edit-tags.php?taxonomy=' . PostTypes::TAXONOMY_BRAND . '&post_type=' . $product_pt,
			'pay4all-form-tickets-pro'      => 'pay4all-form-tickets',
			'pay4all-form-carts-pro'        => 'pay4all-form-abandoned-carts',
			// Newsletter teaser → real submenu registered by
			// \Pay4All\Pro\Admin\Newsletter\Page::add_menu(). Duplicate
			// entries are stripped by dedupe_swapped_entries() below.
			'pay4all-form-newsletters-pro'  => \Pay4All\Pro\Admin\Newsletter\Page::SLUG,
		];

		foreach ( $submenu[ $parent ] as &$item ) {
			if ( ! isset( $item[2] ) ) { continue; }
			$from = (string) $item[2];
			if ( isset( $swap[ $from ] ) ) {
				// Strip the amber "PRO" pill markup — the feature is now real
				// (or at least present as a placeholder under the Pro plugin).
				$item[0] = preg_replace( '#\\s*<span[^>]*>PRO</span>#', '', (string) $item[0] );
				$item[2] = $swap[ $from ];
			}
		}
		unset( $item );

		// Hide the free plugin's « Passer à la PRO » submenu entry — the
		// merchant IS on Pro, showing them a marketing page for it would
		// only be confusing. remove_submenu_page() is idempotent + safe
		// when the slug is missing (older Free versions).
		remove_submenu_page( $parent, 'pay4all-form-go-pro' );
	}

	/**
	 * Adds a serialised categories + brands bag to the shortcode's JS data so
	 * the front-end validator sees the same rule tree the server enforces.
	 * Silent no-op when the licence is inactive — the front-end filter widget
	 * simply doesn't render in that state.
	 *
	 * @param array<string,mixed> $data
	 * @param int                 $form_id
	 * @param array<\WP_Post>     $products
	 */
	public static function inject_rules_bundle( array $data, int $form_id, array $products ) : array {
		if ( ! LicenseManager::is_feature_allowed() ) { return $data; }

		// Walk products once, keeping both the flat set of term ids we
		// need for the rule map AND a per-product cats/brands array we
		// stitch back into $data['products']['products'][pid] below —
		// otherwise the JS validator has no way to know which category
		// or brand each product belongs to.
		$cat_ids     = [];
		$brand_ids   = [];
		$per_product = [];
		foreach ( $products as $p ) {
			if ( ! ( $p instanceof \WP_Post ) ) { continue; }
			$pid       = (int) $p->ID;
			$p_cats    = array_values( array_unique( array_map( 'intval', (array) wp_get_object_terms( $pid, PostTypes::TAXONOMY,       [ 'fields' => 'ids' ] ) ) ) );
			$p_brands  = array_values( array_unique( array_map( 'intval', (array) wp_get_object_terms( $pid, PostTypes::TAXONOMY_BRAND, [ 'fields' => 'ids' ] ) ) ) );
			$per_product[ $pid ] = [ 'cats' => $p_cats, 'brands' => $p_brands ];
			$cat_ids   = array_merge( $cat_ids,   $p_cats );
			$brand_ids = array_merge( $brand_ids, $p_brands );
		}
		$cat_ids   = array_values( array_unique( array_map( 'intval', $cat_ids ) ) );
		$brand_ids = array_values( array_unique( array_map( 'intval', $brand_ids ) ) );

		$categories = [];
		foreach ( $cat_ids as $tid ) {
			$categories[ $tid ] = [
				'name'     => Category::display_name( $tid ),
				'type'     => Category::rule_type( $tid ),
				'multiple' => Category::multiple( $tid ),
				'min'      => Category::min_total( $tid ),
				'max'      => Category::max_total( $tid ),
				'exact'    => Category::exact_total( $tid ),
				'message'  => Category::error_message( $tid ),
			];
		}
		$brands = [];
		foreach ( $brand_ids as $tid ) {
			$brands[ $tid ] = [
				'name'     => Brand::display_name( $tid ),
				'type'     => Brand::rule_type( $tid ),
				'multiple' => Brand::multiple( $tid ),
				'min'      => Brand::min_total( $tid ),
				'max'      => Brand::max_total( $tid ),
				'exact'    => Brand::exact_total( $tid ),
				'message'  => Brand::error_message( $tid ),
			];
		}

		if ( ! isset( $data['products'] ) || ! is_array( $data['products'] ) ) { $data['products'] = []; }
		$data['products']['categories'] = $categories;
		$data['products']['brands']     = $brands;

		// Enrich the base product map already built by Shortcode::form_data_for_js()
		// with the term ids each product belongs to. Products missing from the
		// base map (edge case : products injected by a filter without going
		// through form_data_for_js) get a minimal entry so the JS validator can
		// still sum their quantities against the rule tree.
		$product_map = isset( $data['products']['products'] ) && is_array( $data['products']['products'] )
			? $data['products']['products']
			: [];
		foreach ( $per_product as $pid => $bag ) {
			if ( isset( $product_map[ $pid ] ) && is_array( $product_map[ $pid ] ) ) {
				$product_map[ $pid ]['cats']   = $bag['cats'];
				$product_map[ $pid ]['brands'] = $bag['brands'];
			} else {
				$product_map[ $pid ] = $bag;
			}
		}
		$data['products']['products'] = $product_map;

		return $data;
	}
}
