<?php
/**
 * WooCommerce tools — products, orders, stock, coupons, customers, reports.
 *
 * Only registered when WooCommerce is active. Wraps the WC PHP API so the AI
 * works with the same data model wp-admin shows, not a separate REST shape.
 *
 * Destructive operations (refund, status change, stock mutation, product
 * update) snapshot prior state via PCD_PC_Snapshot so they can be undone.
 */

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

class PCD_PC_Tools_Woo {

	public static function register(): void {
		if ( ! class_exists( 'WooCommerce' ) ) {
			return;
		}

		// ---- PRODUCTS ----

		PCD_PC_Tools::register(
			'woo_product_list',
			'List WooCommerce products. Filter by status, type, search, category, stock status, on-sale flag. Returns id, name, sku, type, price, stock_quantity, stock_status, status.',
			[
				'type' => 'object',
				'properties' => [
					'search'        => [ 'type' => 'string' ],
					'status'        => [ 'type' => 'string', 'enum' => [ 'any', 'publish', 'draft', 'private', 'pending' ], 'default' => 'publish' ],
					'type'          => [ 'type' => 'string', 'description' => 'simple | variable | grouped | external' ],
					'category'      => [ 'type' => 'string', 'description' => 'Category slug or ID.' ],
					'stock_status'  => [ 'type' => 'string', 'enum' => [ 'instock', 'outofstock', 'onbackorder' ] ],
					'on_sale'       => [ 'type' => 'boolean' ],
					'per_page'      => [ 'type' => 'integer', 'default' => 20, 'minimum' => 1, 'maximum' => 100 ],
					'page'          => [ 'type' => 'integer', 'default' => 1, 'minimum' => 1 ],
				],
			],
			[ __CLASS__, 'product_list' ],
			[ 'cap' => 'edit_products', 'category' => 'woo' ]
		);

		PCD_PC_Tools::register(
			'woo_product_get',
			'Get a single WooCommerce product with full details — pricing, stock, images, categories, attributes, and variations (if a variable product).',
			[
				'type' => 'object',
				'properties' => [
					'id'  => [ 'type' => 'integer' ],
					'sku' => [ 'type' => 'string', 'description' => 'Look up by SKU when id is not provided.' ],
				],
			],
			[ __CLASS__, 'product_get' ],
			[ 'cap' => 'edit_products', 'category' => 'woo' ]
		);

		PCD_PC_Tools::register(
			'woo_product_create',
			'Create a new WooCommerce product. For variable products pass attributes + variations[]; each variation gets its own row with its own price/stock/SKU.',
			[
				'type' => 'object',
				'required' => [ 'name' ],
				'properties' => [
					'name'              => [ 'type' => 'string' ],
					'type'              => [ 'type' => 'string', 'enum' => [ 'simple', 'variable', 'grouped', 'external' ], 'default' => 'simple' ],
					'sku'               => [ 'type' => 'string' ],
					'description'       => [ 'type' => 'string' ],
					'short_description' => [ 'type' => 'string' ],
					'regular_price'     => [ 'type' => 'string', 'description' => 'Price as a string e.g. "29.99". WC stores as string.' ],
					'sale_price'        => [ 'type' => 'string' ],
					'status'            => [ 'type' => 'string', 'enum' => [ 'publish', 'draft', 'private', 'pending' ], 'default' => 'publish' ],
					'categories'        => [ 'type' => 'array', 'items' => [ 'type' => 'integer' ], 'description' => 'Term IDs in product_cat.' ],
					'tags'              => [ 'type' => 'array', 'items' => [ 'type' => 'string' ] ],
					'featured_image_id' => [ 'type' => 'integer', 'description' => 'Media library attachment_id (use wp_media_upload first if you need to add a new image).' ],
					'gallery_image_ids' => [ 'type' => 'array', 'items' => [ 'type' => 'integer' ] ],
					'manage_stock'      => [ 'type' => 'boolean', 'default' => false ],
					'stock_quantity'    => [ 'type' => 'integer' ],
					'stock_status'      => [ 'type' => 'string', 'enum' => [ 'instock', 'outofstock', 'onbackorder' ], 'default' => 'instock' ],
					'weight'            => [ 'type' => 'string' ],
					'attributes'        => [ 'type' => 'array', 'description' => 'For variable products: [{name, options: [str], visible: bool, variation: bool}].' ],
					'variations'        => [ 'type' => 'array', 'description' => 'For variable products: [{attributes: {pa_color: "red"}, regular_price, sku, stock_quantity}].' ],
				],
			],
			[ __CLASS__, 'product_create' ],
			[ 'cap' => 'edit_products', 'category' => 'woo' ]
		);

		PCD_PC_Tools::register(
			'woo_product_update',
			'Update an existing WooCommerce product. Same fields as woo_product_create; only the ones you pass are changed. Snapshots prior state.',
			[
				'type' => 'object',
				'required' => [ 'id' ],
				'properties' => [
					'id'                => [ 'type' => 'integer' ],
					'name'              => [ 'type' => 'string' ],
					'sku'               => [ 'type' => 'string' ],
					'description'       => [ 'type' => 'string' ],
					'short_description' => [ 'type' => 'string' ],
					'regular_price'     => [ 'type' => 'string' ],
					'sale_price'        => [ 'type' => 'string' ],
					'status'            => [ 'type' => 'string', 'enum' => [ 'publish', 'draft', 'private', 'pending' ] ],
					'categories'        => [ 'type' => 'array', 'items' => [ 'type' => 'integer' ] ],
					'tags'              => [ 'type' => 'array', 'items' => [ 'type' => 'string' ] ],
					'featured_image_id' => [ 'type' => 'integer' ],
					'gallery_image_ids' => [ 'type' => 'array', 'items' => [ 'type' => 'integer' ] ],
					'manage_stock'      => [ 'type' => 'boolean' ],
					'stock_quantity'    => [ 'type' => 'integer' ],
					'stock_status'      => [ 'type' => 'string', 'enum' => [ 'instock', 'outofstock', 'onbackorder' ] ],
					'weight'            => [ 'type' => 'string' ],
				],
			],
			[ __CLASS__, 'product_update' ],
			[ 'cap' => 'edit_products', 'category' => 'woo' ]
		);

		// ---- ORDERS ----

		PCD_PC_Tools::register(
			'woo_order_list',
			'List WooCommerce orders. Filter by status, customer, date range, search. Returns id, status, total, customer, date_created, item_count.',
			[
				'type' => 'object',
				'properties' => [
					'status'       => [ 'type' => 'string', 'description' => 'Single status (e.g. processing, completed) or "any".' ],
					'customer_id'  => [ 'type' => 'integer' ],
					'after'        => [ 'type' => 'string', 'description' => 'ISO date — orders created on/after.' ],
					'before'       => [ 'type' => 'string', 'description' => 'ISO date — orders created on/before.' ],
					'search'       => [ 'type' => 'string' ],
					'per_page'     => [ 'type' => 'integer', 'default' => 20, 'minimum' => 1, 'maximum' => 100 ],
					'page'         => [ 'type' => 'integer', 'default' => 1, 'minimum' => 1 ],
				],
			],
			[ __CLASS__, 'order_list' ],
			[ 'cap' => 'edit_shop_orders', 'category' => 'woo' ]
		);

		PCD_PC_Tools::register(
			'woo_order_get',
			'Get full details for one WooCommerce order — line items, totals, customer, billing/shipping addresses, status history, order notes.',
			[
				'type' => 'object',
				'required' => [ 'id' ],
				'properties' => [
					'id'             => [ 'type' => 'integer' ],
					'include_notes'  => [ 'type' => 'boolean', 'default' => true ],
				],
			],
			[ __CLASS__, 'order_get' ],
			[ 'cap' => 'edit_shop_orders', 'category' => 'woo' ]
		);

		PCD_PC_Tools::register(
			'woo_order_refund',
			'Refund a WooCommerce order — partial or full. Set restock=true to put items back in stock. Two-phase: first call returns refund preview; pass confirm=true to actually process.',
			[
				'type' => 'object',
				'required' => [ 'order_id' ],
				'properties' => [
					'order_id' => [ 'type' => 'integer' ],
					'amount'   => [ 'type' => 'string', 'description' => 'Amount to refund as string. Omit for full refund of remaining refundable total.' ],
					'reason'   => [ 'type' => 'string' ],
					'restock'  => [ 'type' => 'boolean', 'default' => false ],
					'confirm'  => [ 'type' => 'boolean', 'default' => false ],
				],
			],
			[ __CLASS__, 'order_refund' ],
			[ 'cap' => 'manage_woocommerce', 'category' => 'woo', 'destructive' => true ]
		);

		PCD_PC_Tools::register(
			'woo_order_update_status',
			'Change a WooCommerce order\'s status (e.g. processing → completed). Optionally add a customer-visible note. Snapshots prior status.',
			[
				'type' => 'object',
				'required' => [ 'id', 'status' ],
				'properties' => [
					'id'        => [ 'type' => 'integer' ],
					'status'    => [ 'type' => 'string', 'description' => 'pending | processing | on-hold | completed | cancelled | refunded | failed (no wc- prefix).' ],
					'note'      => [ 'type' => 'string', 'description' => 'Optional note added to the order.' ],
					'customer_note' => [ 'type' => 'boolean', 'default' => false, 'description' => 'If true, note is visible to the customer and triggers a notification.' ],
				],
			],
			[ __CLASS__, 'order_update_status' ],
			[ 'cap' => 'edit_shop_orders', 'category' => 'woo' ]
		);

		// ---- STOCK ----

		PCD_PC_Tools::register(
			'woo_stock_check',
			'How many of this product are in stock? Pass id, sku, or search. For variable products, returns the per-variation breakdown.',
			[
				'type' => 'object',
				'properties' => [
					'id'     => [ 'type' => 'integer' ],
					'sku'    => [ 'type' => 'string' ],
					'search' => [ 'type' => 'string', 'description' => 'Name or partial SKU search if id/sku not provided.' ],
				],
			],
			[ __CLASS__, 'stock_check' ],
			[ 'cap' => 'edit_products', 'category' => 'woo' ]
		);

		PCD_PC_Tools::register(
			'woo_stock_update',
			'Adjust stock for a product or variation. Pass action=set (absolute), increase, or decrease. Enables manage_stock automatically if not already on.',
			[
				'type' => 'object',
				'required' => [ 'id', 'quantity' ],
				'properties' => [
					'id'       => [ 'type' => 'integer', 'description' => 'Product or variation ID.' ],
					'quantity' => [ 'type' => 'integer' ],
					'action'   => [ 'type' => 'string', 'enum' => [ 'set', 'increase', 'decrease' ], 'default' => 'set' ],
				],
			],
			[ __CLASS__, 'stock_update' ],
			[ 'cap' => 'edit_products', 'category' => 'woo' ]
		);

		// ---- BULK IMAGE ATTACH ----

		PCD_PC_Tools::register(
			'woo_bulk_set_product_image',
			'Bulk-attach images to WooCommerce products in one tool call. Built for batch workflows like "match a folder of images to products by filename" — instead of calling wp_media_upload + woo_product_update for every product (which hammers host bot-protection like SiteGround/Cloudflare and trips CAPTCHAs around the ~50-100-call mark), this batches up to 50 attachments into a single MCP round-trip. Each item downloads its `source_url`, sideloads into the media library attached to the target product, and either sets it as the featured image (default) or appends to the product gallery. Snapshots prior `image_id` + `gallery_image_ids` per product so the entire batch can be reverted via pcd_snapshot_restore. For folders larger than 50, split into multiple calls.',
			[
				'type' => 'object',
				'required' => [ 'items' ],
				'properties' => [
					'items' => [
						'type' => 'array',
						'maxItems' => 50,
						'minItems' => 1,
						'items' => [
							'type' => 'object',
							'required' => [ 'post_id', 'source_url' ],
							'properties' => [
								'post_id'    => [ 'type' => 'integer', 'description' => 'WooCommerce product ID.' ],
								'source_url' => [ 'type' => 'string', 'description' => 'Public HTTPS URL of the image to download.' ],
								'alt_text'   => [ 'type' => 'string' ],
								'title'      => [ 'type' => 'string', 'description' => 'Attachment title (defaults to filename).' ],
								'as_gallery' => [ 'type' => 'boolean', 'default' => false, 'description' => 'True = append to product gallery. False (default) = set as featured image.' ],
							],
						],
					],
					'continue_on_error' => [ 'type' => 'boolean', 'default' => true, 'description' => 'If false, stop on first failure. Default true — collect per-item errors and keep going.' ],
				],
			],
			[ __CLASS__, 'bulk_set_product_image' ],
			[ 'cap' => 'edit_products', 'category' => 'woo' ]
		);

		// ---- COUPONS ----

		PCD_PC_Tools::register(
			'woo_coupon_create',
			'Create a WooCommerce coupon code.',
			[
				'type' => 'object',
				'required' => [ 'code', 'discount_type', 'amount' ],
				'properties' => [
					'code'                  => [ 'type' => 'string' ],
					'discount_type'         => [ 'type' => 'string', 'enum' => [ 'percent', 'fixed_cart', 'fixed_product' ] ],
					'amount'                => [ 'type' => 'string', 'description' => 'Discount amount. For percent it\'s the percentage (e.g. "10"); for fixed it\'s the currency amount.' ],
					'description'           => [ 'type' => 'string' ],
					'expiry_date'           => [ 'type' => 'string', 'description' => 'YYYY-MM-DD.' ],
					'usage_limit'           => [ 'type' => 'integer' ],
					'individual_use'        => [ 'type' => 'boolean', 'default' => false ],
					'exclude_sale_items'    => [ 'type' => 'boolean', 'default' => false ],
					'minimum_amount'        => [ 'type' => 'string' ],
					'maximum_amount'        => [ 'type' => 'string' ],
					'product_ids'           => [ 'type' => 'array', 'items' => [ 'type' => 'integer' ] ],
					'excluded_product_ids'  => [ 'type' => 'array', 'items' => [ 'type' => 'integer' ] ],
				],
			],
			[ __CLASS__, 'coupon_create' ],
			[ 'cap' => 'manage_woocommerce', 'category' => 'woo' ]
		);

		// ---- REPORTS ----

		PCD_PC_Tools::register(
			'woo_sales_report',
			'Summary of sales over a date range. Returns gross sales, order count, average order value, refund total, and top-selling products. Use range shortcuts or custom dates.',
			[
				'type' => 'object',
				'properties' => [
					'range'      => [ 'type' => 'string', 'enum' => [ 'today', 'yesterday', 'week', 'month', 'year', 'custom' ], 'default' => 'week' ],
					'start_date' => [ 'type' => 'string', 'description' => 'YYYY-MM-DD — only when range=custom.' ],
					'end_date'   => [ 'type' => 'string', 'description' => 'YYYY-MM-DD — only when range=custom.' ],
					'top_n'      => [ 'type' => 'integer', 'default' => 5, 'description' => 'Number of top products to include.' ],
				],
			],
			[ __CLASS__, 'sales_report' ],
			[ 'cap' => 'view_woocommerce_reports', 'category' => 'woo' ]
		);

		// ---- CUSTOMERS ----

		PCD_PC_Tools::register(
			'woo_customer_get',
			'Get a WooCommerce customer\'s profile + recent order history. Pass id or email.',
			[
				'type' => 'object',
				'properties' => [
					'id'           => [ 'type' => 'integer' ],
					'email'        => [ 'type' => 'string' ],
					'order_limit'  => [ 'type' => 'integer', 'default' => 10 ],
				],
			],
			[ __CLASS__, 'customer_get' ],
			[ 'cap' => 'manage_woocommerce', 'category' => 'woo' ]
		);
	}

	// ------------------------------------------------------------------
	// Helpers
	// ------------------------------------------------------------------

	private static function product_summary( $product ): array {
		if ( ! $product || ! is_object( $product ) ) return [];
		return [
			'id'             => $product->get_id(),
			'name'           => $product->get_name(),
			'sku'            => $product->get_sku(),
			'type'           => $product->get_type(),
			'status'         => $product->get_status(),
			'price'          => $product->get_price(),
			'regular_price'  => $product->get_regular_price(),
			'sale_price'     => $product->get_sale_price(),
			'on_sale'        => $product->is_on_sale(),
			'stock_status'   => $product->get_stock_status(),
			'stock_quantity' => $product->get_stock_quantity(),
			'manage_stock'   => $product->get_manage_stock(),
			'permalink'      => get_permalink( $product->get_id() ),
		];
	}

	private static function order_summary( $order ): array {
		if ( ! $order || ! is_object( $order ) ) return [];
		return [
			'id'             => $order->get_id(),
			'number'         => $order->get_order_number(),
			'status'         => $order->get_status(),
			'total'          => $order->get_total(),
			'currency'       => $order->get_currency(),
			'customer_id'    => $order->get_customer_id(),
			'customer_email' => $order->get_billing_email(),
			'item_count'     => $order->get_item_count(),
			'date_created'   => $order->get_date_created() ? $order->get_date_created()->date( 'c' ) : null,
		];
	}

	// ------------------------------------------------------------------
	// Product handlers
	// ------------------------------------------------------------------

	public static function product_list( array $args ): array {
		$query = [
			'limit'    => (int) ( $args['per_page'] ?? 20 ),
			'page'     => (int) ( $args['page'] ?? 1 ),
			'status'   => $args['status'] ?? 'publish',
			'paginate' => true,
		];
		if ( ! empty( $args['type'] ) )         $query['type']         = $args['type'];
		if ( ! empty( $args['search'] ) )       $query['s']            = sanitize_text_field( $args['search'] );
		if ( ! empty( $args['stock_status'] ) ) $query['stock_status'] = $args['stock_status'];
		if ( ! empty( $args['on_sale'] ) )      $query['on_sale']      = true;
		if ( ! empty( $args['category'] ) )     $query['category']     = [ $args['category'] ];

		if ( 'any' === $query['status'] ) {
			$query['status'] = [ 'publish', 'draft', 'private', 'pending' ];
		}

		$result = wc_get_products( $query );
		$rows   = [];
		foreach ( $result->products as $product ) {
			$rows[] = self::product_summary( $product );
		}
		return [
			'total'       => $result->total,
			'total_pages' => $result->max_num_pages,
			'page'        => (int) ( $args['page'] ?? 1 ),
			'count'       => count( $rows ),
			'products'    => $rows,
		];
	}

	public static function product_get( array $args ) {
		$id = (int) ( $args['id'] ?? 0 );
		if ( ! $id && ! empty( $args['sku'] ) ) {
			$id = (int) wc_get_product_id_by_sku( sanitize_text_field( $args['sku'] ) );
		}
		if ( ! $id ) return new WP_Error( 'not_found', 'Product not found.' );

		$product = wc_get_product( $id );
		if ( ! $product ) return new WP_Error( 'not_found', "Product {$id} not found." );

		$out = self::product_summary( $product );
		$out['description']       = $product->get_description();
		$out['short_description'] = $product->get_short_description();
		$out['weight']            = $product->get_weight();
		$out['featured_image_id'] = $product->get_image_id();
		$out['gallery_image_ids'] = $product->get_gallery_image_ids();
		$out['category_ids']      = $product->get_category_ids();
		$out['tag_ids']           = $product->get_tag_ids();
		$out['attributes']        = [];
		foreach ( $product->get_attributes() as $attr ) {
			$out['attributes'][] = [
				'name'      => $attr->get_name(),
				'options'   => $attr->get_options(),
				'visible'   => $attr->get_visible(),
				'variation' => $attr->get_variation(),
			];
		}

		if ( $product->is_type( 'variable' ) ) {
			$out['variations'] = [];
			foreach ( $product->get_children() as $variation_id ) {
				$v = wc_get_product( $variation_id );
				if ( $v ) {
					$out['variations'][] = [
						'id'             => $v->get_id(),
						'sku'            => $v->get_sku(),
						'attributes'     => $v->get_attributes(),
						'regular_price'  => $v->get_regular_price(),
						'sale_price'     => $v->get_sale_price(),
						'stock_status'   => $v->get_stock_status(),
						'stock_quantity' => $v->get_stock_quantity(),
					];
				}
			}
		}
		return $out;
	}

	public static function product_create( array $args ) {
		$type = $args['type'] ?? 'simple';
		switch ( $type ) {
			case 'variable': $product = new WC_Product_Variable(); break;
			case 'grouped':  $product = new WC_Product_Grouped(); break;
			case 'external': $product = new WC_Product_External(); break;
			default:         $product = new WC_Product_Simple();
		}
		self::apply_product_fields( $product, $args );
		$product_id = $product->save();
		if ( is_wp_error( $product_id ) ) return $product_id;

		// Variable: create variations after parent saved.
		if ( 'variable' === $type && ! empty( $args['variations'] ) && is_array( $args['variations'] ) ) {
			self::create_variations( $product_id, $args['variations'] );
		}

		return self::product_get( [ 'id' => $product_id ] );
	}

	public static function product_update( array $args ) {
		$id = (int) $args['id'];
		$product = wc_get_product( $id );
		if ( ! $product ) return new WP_Error( 'not_found', "Product {$id} not found." );

		PCD_PC_Snapshot::capture( 'woo_product', $id, $product->get_data(), "Product {$id} before update" );
		self::apply_product_fields( $product, $args );
		$product->save();
		return self::product_get( [ 'id' => $id ] );
	}

	private static function apply_product_fields( $product, array $args ): void {
		if ( isset( $args['name'] ) )              $product->set_name( sanitize_text_field( $args['name'] ) );
		if ( isset( $args['sku'] ) )               $product->set_sku( sanitize_text_field( $args['sku'] ) );
		if ( isset( $args['description'] ) )       $product->set_description( $args['description'] );
		if ( isset( $args['short_description'] ) ) $product->set_short_description( $args['short_description'] );
		if ( isset( $args['regular_price'] ) )     $product->set_regular_price( (string) $args['regular_price'] );
		if ( isset( $args['sale_price'] ) )        $product->set_sale_price( (string) $args['sale_price'] );
		if ( isset( $args['status'] ) )            $product->set_status( $args['status'] );
		if ( isset( $args['categories'] ) )        $product->set_category_ids( array_map( 'intval', (array) $args['categories'] ) );
		if ( isset( $args['tags'] ) )              $product->set_tag_ids( self::resolve_tag_ids( (array) $args['tags'] ) );
		if ( isset( $args['featured_image_id'] ) ) $product->set_image_id( (int) $args['featured_image_id'] );
		if ( isset( $args['gallery_image_ids'] ) ) $product->set_gallery_image_ids( array_map( 'intval', (array) $args['gallery_image_ids'] ) );
		if ( isset( $args['manage_stock'] ) )      $product->set_manage_stock( (bool) $args['manage_stock'] );
		if ( isset( $args['stock_quantity'] ) ) {
			$product->set_manage_stock( true );
			$product->set_stock_quantity( (int) $args['stock_quantity'] );
		}
		if ( isset( $args['stock_status'] ) )      $product->set_stock_status( $args['stock_status'] );
		if ( isset( $args['weight'] ) )            $product->set_weight( (string) $args['weight'] );
		if ( isset( $args['attributes'] ) )        $product->set_attributes( self::build_attributes( (array) $args['attributes'] ) );
	}

	private static function resolve_tag_ids( array $tags ): array {
		$out = [];
		foreach ( $tags as $tag ) {
			$term = is_numeric( $tag ) ? get_term( (int) $tag, 'product_tag' ) : get_term_by( 'name', $tag, 'product_tag' );
			if ( ! $term || is_wp_error( $term ) ) {
				$created = wp_insert_term( $tag, 'product_tag' );
				if ( ! is_wp_error( $created ) ) $out[] = (int) $created['term_id'];
			} else {
				$out[] = (int) $term->term_id;
			}
		}
		return $out;
	}

	private static function build_attributes( array $attrs ): array {
		$out = [];
		foreach ( $attrs as $a ) {
			if ( empty( $a['name'] ) ) continue;
			$obj = new WC_Product_Attribute();
			$obj->set_name( (string) $a['name'] );
			$obj->set_options( (array) ( $a['options'] ?? [] ) );
			$obj->set_visible( ! empty( $a['visible'] ) );
			$obj->set_variation( ! empty( $a['variation'] ) );
			$out[] = $obj;
		}
		return $out;
	}

	private static function create_variations( int $parent_id, array $variations ): void {
		foreach ( $variations as $v ) {
			$variation = new WC_Product_Variation();
			$variation->set_parent_id( $parent_id );
			if ( ! empty( $v['attributes'] ) ) $variation->set_attributes( (array) $v['attributes'] );
			if ( isset( $v['sku'] ) )           $variation->set_sku( sanitize_text_field( $v['sku'] ) );
			if ( isset( $v['regular_price'] ) ) $variation->set_regular_price( (string) $v['regular_price'] );
			if ( isset( $v['sale_price'] ) )    $variation->set_sale_price( (string) $v['sale_price'] );
			if ( isset( $v['stock_quantity'] ) ) {
				$variation->set_manage_stock( true );
				$variation->set_stock_quantity( (int) $v['stock_quantity'] );
			}
			$variation->save();
		}
	}

	// ------------------------------------------------------------------
	// Order handlers
	// ------------------------------------------------------------------

	public static function order_list( array $args ): array {
		$query = [
			'limit'    => (int) ( $args['per_page'] ?? 20 ),
			'page'     => (int) ( $args['page'] ?? 1 ),
			'paginate' => true,
		];
		if ( ! empty( $args['status'] ) && 'any' !== $args['status'] ) {
			$query['status'] = (array) $args['status'];
		}
		if ( ! empty( $args['customer_id'] ) ) $query['customer_id'] = (int) $args['customer_id'];
		if ( ! empty( $args['after'] ) )       $query['date_created'] = '>=' . sanitize_text_field( $args['after'] );
		if ( ! empty( $args['before'] ) )      $query['date_created'] = ( $query['date_created'] ?? '' ) . ( $query['date_created'] ? '...' : '' ) . '<=' . sanitize_text_field( $args['before'] );
		if ( ! empty( $args['search'] ) )      $query['s'] = sanitize_text_field( $args['search'] );

		$result = wc_get_orders( $query );
		$rows   = [];
		foreach ( $result->orders as $order ) {
			$rows[] = self::order_summary( $order );
		}
		return [
			'total'       => $result->total,
			'total_pages' => $result->max_num_pages,
			'page'        => (int) ( $args['page'] ?? 1 ),
			'count'       => count( $rows ),
			'orders'      => $rows,
		];
	}

	public static function order_get( array $args ) {
		$order = wc_get_order( (int) $args['id'] );
		if ( ! $order ) return new WP_Error( 'not_found', 'Order not found.' );

		$items = [];
		foreach ( $order->get_items() as $item ) {
			$items[] = [
				'id'        => $item->get_id(),
				'name'      => $item->get_name(),
				'product_id' => $item->get_product_id(),
				'variation_id' => $item->get_variation_id(),
				'quantity'  => $item->get_quantity(),
				'subtotal'  => $item->get_subtotal(),
				'total'     => $item->get_total(),
			];
		}

		$out = self::order_summary( $order );
		$out['items']    = $items;
		$out['subtotal'] = $order->get_subtotal();
		$out['tax_total'] = $order->get_total_tax();
		$out['shipping_total'] = $order->get_shipping_total();
		$out['discount_total'] = $order->get_discount_total();
		$out['payment_method'] = $order->get_payment_method_title();
		$out['billing']  = $order->get_address( 'billing' );
		$out['shipping'] = $order->get_address( 'shipping' );

		if ( ! isset( $args['include_notes'] ) || ! empty( $args['include_notes'] ) ) {
			$notes = wc_get_order_notes( [ 'order_id' => $order->get_id(), 'limit' => 50 ] );
			$out['notes'] = array_map( function ( $n ) {
				return [
					'id'             => $n->id,
					'date'           => $n->date_created->date( 'c' ),
					'author'         => $n->added_by,
					'is_customer'    => $n->customer_note,
					'content'        => $n->content,
				];
			}, $notes );
		}

		$refunds = $order->get_refunds();
		$out['refunds'] = array_map( function ( $r ) {
			return [
				'id'     => $r->get_id(),
				'amount' => $r->get_amount(),
				'reason' => $r->get_reason(),
				'date'   => $r->get_date_created() ? $r->get_date_created()->date( 'c' ) : null,
			];
		}, $refunds );

		return $out;
	}

	public static function order_refund( array $args ) {
		$order = wc_get_order( (int) $args['order_id'] );
		if ( ! $order ) return new WP_Error( 'not_found', 'Order not found.' );

		$max     = (float) ( $order->get_total() - $order->get_total_refunded() );
		$request = isset( $args['amount'] ) ? (float) $args['amount'] : $max;
		if ( $request <= 0 || $request > $max + 0.001 ) {
			return new WP_Error( 'invalid_amount', "Refund amount must be > 0 and ≤ {$max} (remaining refundable)." );
		}

		if ( empty( $args['confirm'] ) ) {
			return [
				'requires_confirmation' => true,
				'order_id'              => $order->get_id(),
				'order_total'           => $order->get_total(),
				'already_refunded'      => $order->get_total_refunded(),
				'refund_amount'         => number_format( $request, 2, '.', '' ),
				'restock'               => ! empty( $args['restock'] ),
				'message'               => "Will refund {$request} on order {$order->get_id()}. Re-call with confirm:true.",
			];
		}

		$refund = wc_create_refund( [
			'amount'         => $request,
			'reason'         => sanitize_text_field( $args['reason'] ?? '' ),
			'order_id'       => $order->get_id(),
			'restock_items'  => ! empty( $args['restock'] ),
		] );
		if ( is_wp_error( $refund ) ) return $refund;

		return [
			'refunded'     => true,
			'refund_id'    => $refund->get_id(),
			'amount'       => $refund->get_amount(),
			'order_status' => $order->get_status(),
		];
	}

	public static function order_update_status( array $args ) {
		$order = wc_get_order( (int) $args['id'] );
		if ( ! $order ) return new WP_Error( 'not_found', 'Order not found.' );

		PCD_PC_Snapshot::capture( 'woo_order_status', $order->get_id(), $order->get_status(), "Order {$order->get_id()} status before update" );

		$new = preg_replace( '/^wc-/', '', sanitize_text_field( $args['status'] ) );
		$note = isset( $args['note'] ) ? wp_kses_post( $args['note'] ) : '';
		$ok  = $order->update_status( $new, $note, false );
		if ( $note ) {
			$order->add_order_note( $note, ! empty( $args['customer_note'] ) ? 1 : 0 );
		}

		return [
			'updated'    => (bool) $ok,
			'order_id'   => $order->get_id(),
			'new_status' => $order->get_status(),
		];
	}

	// ------------------------------------------------------------------
	// Stock
	// ------------------------------------------------------------------

	public static function stock_check( array $args ) {
		$id = (int) ( $args['id'] ?? 0 );
		if ( ! $id && ! empty( $args['sku'] ) ) {
			$id = (int) wc_get_product_id_by_sku( sanitize_text_field( $args['sku'] ) );
		}
		if ( ! $id && ! empty( $args['search'] ) ) {
			$found = wc_get_products( [ 's' => sanitize_text_field( $args['search'] ), 'limit' => 1 ] );
			if ( ! empty( $found[0] ) ) $id = $found[0]->get_id();
		}
		if ( ! $id ) return new WP_Error( 'not_found', 'Product not found.' );

		$product = wc_get_product( $id );
		if ( ! $product ) return new WP_Error( 'not_found', "Product {$id} not found." );

		$out = [
			'id'             => $product->get_id(),
			'name'           => $product->get_name(),
			'sku'            => $product->get_sku(),
			'manage_stock'   => $product->get_manage_stock(),
			'stock_status'   => $product->get_stock_status(),
			'stock_quantity' => $product->get_stock_quantity(),
		];

		if ( $product->is_type( 'variable' ) ) {
			$out['variations'] = [];
			foreach ( $product->get_children() as $vid ) {
				$v = wc_get_product( $vid );
				if ( $v ) {
					$out['variations'][] = [
						'id'             => $v->get_id(),
						'sku'            => $v->get_sku(),
						'attributes'     => $v->get_attributes(),
						'stock_status'   => $v->get_stock_status(),
						'stock_quantity' => $v->get_stock_quantity(),
					];
				}
			}
		}
		return $out;
	}

	public static function stock_update( array $args ) {
		$id      = (int) $args['id'];
		$product = wc_get_product( $id );
		if ( ! $product ) return new WP_Error( 'not_found', "Product {$id} not found." );

		PCD_PC_Snapshot::capture( 'woo_stock', $id, [
			'manage_stock'   => $product->get_manage_stock(),
			'stock_quantity' => $product->get_stock_quantity(),
			'stock_status'   => $product->get_stock_status(),
		], "Stock for product {$id} before update" );

		$qty    = (int) $args['quantity'];
		$action = $args['action'] ?? 'set';
		$current = (int) ( $product->get_stock_quantity() ?? 0 );
		$next = $current;
		switch ( $action ) {
			case 'increase': $next = $current + $qty; break;
			case 'decrease': $next = $current - $qty; break;
			default:         $next = $qty;
		}

		$product->set_manage_stock( true );
		$product->set_stock_quantity( $next );
		$product->set_stock_status( $next > 0 ? 'instock' : 'outofstock' );
		$product->save();

		return [
			'updated'         => true,
			'product_id'      => $id,
			'previous'        => $current,
			'new_quantity'    => $next,
			'stock_status'    => $product->get_stock_status(),
		];
	}

	// ------------------------------------------------------------------
	// Bulk image attach
	// ------------------------------------------------------------------

	public static function bulk_set_product_image( array $args ) {
		$items = is_array( $args['items'] ?? null ) ? $args['items'] : [];
		if ( ! $items ) {
			return new WP_Error( 'no_items', 'items array is required.' );
		}
		if ( count( $items ) > 50 ) {
			return new WP_Error( 'too_many_items', 'Max 50 items per call — split the batch.' );
		}
		if ( ! function_exists( 'media_handle_sideload' ) ) {
			require_once ABSPATH . 'wp-admin/includes/media.php';
			require_once ABSPATH . 'wp-admin/includes/file.php';
			require_once ABSPATH . 'wp-admin/includes/image.php';
		}

		$continue_on_error = ! isset( $args['continue_on_error'] ) || ! empty( $args['continue_on_error'] );
		$results = [];

		foreach ( $items as $i => $item ) {
			$post_id    = (int) ( $item['post_id'] ?? 0 );
			$url        = trim( (string) ( $item['source_url'] ?? '' ) );
			$as_gallery = ! empty( $item['as_gallery'] );

			if ( ! $post_id || ! $url ) {
				$results[] = [ 'index' => $i, 'post_id' => $post_id, 'status' => 'error', 'error' => 'post_id and source_url required' ];
				if ( ! $continue_on_error ) break;
				continue;
			}

			$product = wc_get_product( $post_id );
			if ( ! $product ) {
				$results[] = [ 'index' => $i, 'post_id' => $post_id, 'status' => 'error', 'error' => 'Not a WooCommerce product.' ];
				if ( ! $continue_on_error ) break;
				continue;
			}

			// Snapshot prior image state so the whole batch can be undone.
			PCD_PC_Snapshot::capture(
				'woo_product_images',
				(string) $post_id,
				[
					'image_id'          => (int) $product->get_image_id(),
					'gallery_image_ids' => $product->get_gallery_image_ids(),
				],
				"Product {$post_id} images before bulk attach"
			);

			// Download → sideload into media library, attached to the product.
			$tmp = download_url( $url, 30 );
			if ( is_wp_error( $tmp ) ) {
				$results[] = [ 'index' => $i, 'post_id' => $post_id, 'status' => 'error', 'error' => 'download_url: ' . $tmp->get_error_message() ];
				if ( ! $continue_on_error ) break;
				continue;
			}
			$title = isset( $item['title'] ) ? (string) $item['title'] : null;

			// Derive a filename WITH an extension. URLs like Google Drive's
			// `uc?export=download&id=...` have no extension in the path, so
			// media_handle_sideload can't determine the file type and WP
			// rejects it ("not allowed to upload this file type"). Mirror
			// wp_media_upload: prefer the title, fall back to the URL path,
			// and if there's still no extension, sniff the downloaded file's
			// MIME and append the right one.
			$base = $title ? sanitize_file_name( $title ) : basename( (string) wp_parse_url( $url, PHP_URL_PATH ) );
			if ( ! $base || ! pathinfo( $base, PATHINFO_EXTENSION ) ) {
				$mime    = function_exists( 'mime_content_type' ) ? mime_content_type( $tmp ) : '';
				$ext_map = [
					'image/jpeg'    => '.jpg',
					'image/png'     => '.png',
					'image/gif'     => '.gif',
					'image/webp'    => '.webp',
					'image/svg+xml' => '.svg',
				];
				$base = ( $base ?: "image-{$post_id}" ) . ( $ext_map[ $mime ] ?? '.jpg' );
			}
			$file_array = [ 'name' => $base, 'tmp_name' => $tmp ];

			$attach_id = media_handle_sideload( $file_array, $post_id, $title );
			if ( is_wp_error( $attach_id ) ) {
				wp_delete_file( $tmp );
				$results[] = [ 'index' => $i, 'post_id' => $post_id, 'status' => 'error', 'error' => 'media_handle_sideload: ' . $attach_id->get_error_message() ];
				if ( ! $continue_on_error ) break;
				continue;
			}

			if ( ! empty( $item['alt_text'] ) ) {
				update_post_meta( $attach_id, '_wp_attachment_image_alt', sanitize_text_field( $item['alt_text'] ) );
			}

			if ( $as_gallery ) {
				$existing = $product->get_gallery_image_ids();
				$existing[] = (int) $attach_id;
				$product->set_gallery_image_ids( array_values( array_unique( array_map( 'intval', $existing ) ) ) );
				$mode = 'gallery';
			} else {
				$product->set_image_id( (int) $attach_id );
				$mode = 'featured';
			}
			$product->save();

			$results[] = [
				'index'         => $i,
				'post_id'       => $post_id,
				'attachment_id' => (int) $attach_id,
				'url'           => wp_get_attachment_url( $attach_id ),
				'mode'          => $mode,
				'status'        => 'ok',
			];
		}

		$ok_count = count( array_filter( $results, function ( $r ) { return ( $r['status'] ?? '' ) === 'ok'; } ) );
		return [
			'total'      => count( $items ),
			'successful' => $ok_count,
			'failed'     => count( $items ) - $ok_count,
			'results'    => $results,
		];
	}

	// ------------------------------------------------------------------
	// Coupons
	// ------------------------------------------------------------------

	public static function coupon_create( array $args ) {
		$coupon = new WC_Coupon();
		$coupon->set_code( sanitize_text_field( $args['code'] ) );
		$coupon->set_discount_type( sanitize_text_field( $args['discount_type'] ) );
		$coupon->set_amount( (string) $args['amount'] );
		if ( isset( $args['description'] ) )          $coupon->set_description( wp_kses_post( $args['description'] ) );
		if ( isset( $args['expiry_date'] ) )          $coupon->set_date_expires( strtotime( $args['expiry_date'] ) );
		if ( isset( $args['usage_limit'] ) )          $coupon->set_usage_limit( (int) $args['usage_limit'] );
		if ( isset( $args['individual_use'] ) )       $coupon->set_individual_use( (bool) $args['individual_use'] );
		if ( isset( $args['exclude_sale_items'] ) )   $coupon->set_exclude_sale_items( (bool) $args['exclude_sale_items'] );
		if ( isset( $args['minimum_amount'] ) )       $coupon->set_minimum_amount( (string) $args['minimum_amount'] );
		if ( isset( $args['maximum_amount'] ) )       $coupon->set_maximum_amount( (string) $args['maximum_amount'] );
		if ( isset( $args['product_ids'] ) )          $coupon->set_product_ids( array_map( 'intval', (array) $args['product_ids'] ) );
		if ( isset( $args['excluded_product_ids'] ) ) $coupon->set_excluded_product_ids( array_map( 'intval', (array) $args['excluded_product_ids'] ) );

		$id = $coupon->save();
		if ( is_wp_error( $id ) ) return $id;
		return [
			'created' => true,
			'id'      => $id,
			'code'    => $coupon->get_code(),
		];
	}

	// ------------------------------------------------------------------
	// Reports
	// ------------------------------------------------------------------

	public static function sales_report( array $args ): array {
		$range = $args['range'] ?? 'week';
		[ $start, $end ] = self::resolve_range( $range, $args['start_date'] ?? null, $args['end_date'] ?? null );

		$orders = wc_get_orders( [
			'limit'        => -1,
			'status'       => [ 'completed', 'processing' ],
			'date_created' => $start . '...' . $end,
		] );

		$gross     = 0.0;
		$refunded  = 0.0;
		$count     = 0;
		$item_count = 0;
		$top       = [];
		foreach ( $orders as $order ) {
			$gross    += (float) $order->get_total();
			$refunded += (float) $order->get_total_refunded();
			$count++;
			$item_count += $order->get_item_count();
			foreach ( $order->get_items() as $item ) {
				$pid = $item->get_product_id();
				if ( ! isset( $top[ $pid ] ) ) {
					$prod = $item->get_product();
					$top[ $pid ] = [
						'product_id' => $pid,
						'name'       => $prod ? $prod->get_name() : '(deleted)',
						'units'      => 0,
						'revenue'    => 0.0,
					];
				}
				$top[ $pid ]['units']   += $item->get_quantity();
				$top[ $pid ]['revenue'] += (float) $item->get_total();
			}
		}

		usort( $top, function ( $a, $b ) { return $b['revenue'] <=> $a['revenue']; } );
		$top_n = (int) ( $args['top_n'] ?? 5 );

		return [
			'range'              => $range,
			'start'              => $start,
			'end'                => $end,
			'order_count'        => $count,
			'gross_sales'        => number_format( $gross, 2, '.', '' ),
			'refunded_total'     => number_format( $refunded, 2, '.', '' ),
			'net_sales'          => number_format( $gross - $refunded, 2, '.', '' ),
			'items_sold'         => $item_count,
			'average_order_value'=> $count ? number_format( $gross / $count, 2, '.', '' ) : '0.00',
			'top_products'       => array_slice( $top, 0, $top_n ),
			'currency'           => get_woocommerce_currency(),
		];
	}

	private static function resolve_range( string $range, ?string $start, ?string $end ): array {
		$today = current_time( 'Y-m-d' );
		switch ( $range ) {
			case 'today':
				return [ $today, $today ];
			case 'yesterday':
				$y = gmdate( 'Y-m-d', strtotime( '-1 day', current_time( 'timestamp' ) ) );
				return [ $y, $y ];
			case 'week':
				return [ gmdate( 'Y-m-d', strtotime( '-7 days', current_time( 'timestamp' ) ) ), $today ];
			case 'month':
				return [ gmdate( 'Y-m-d', strtotime( '-30 days', current_time( 'timestamp' ) ) ), $today ];
			case 'year':
				return [ gmdate( 'Y-m-d', strtotime( '-365 days', current_time( 'timestamp' ) ) ), $today ];
			case 'custom':
				return [ $start ?: $today, $end ?: $today ];
		}
		return [ $today, $today ];
	}

	// ------------------------------------------------------------------
	// Customers
	// ------------------------------------------------------------------

	public static function customer_get( array $args ) {
		$id = (int) ( $args['id'] ?? 0 );
		if ( ! $id && ! empty( $args['email'] ) ) {
			$user = get_user_by( 'email', sanitize_email( $args['email'] ) );
			if ( $user ) $id = $user->ID;
		}
		if ( ! $id ) return new WP_Error( 'not_found', 'Customer not found.' );

		$customer = new WC_Customer( $id );
		if ( ! $customer->get_id() ) return new WP_Error( 'not_found', "Customer {$id} not found." );

		$orders = wc_get_orders( [
			'customer_id' => $id,
			'limit'       => (int) ( $args['order_limit'] ?? 10 ),
			'orderby'     => 'date',
			'order'       => 'DESC',
		] );

		return [
			'id'              => $customer->get_id(),
			'email'           => $customer->get_email(),
			'first_name'      => $customer->get_first_name(),
			'last_name'       => $customer->get_last_name(),
			'username'        => $customer->get_username(),
			'date_created'    => $customer->get_date_created() ? $customer->get_date_created()->date( 'c' ) : null,
			'order_count'     => $customer->get_order_count(),
			'total_spent'     => $customer->get_total_spent(),
			'billing'         => $customer->get_billing(),
			'shipping'        => $customer->get_shipping(),
			'recent_orders'   => array_map( [ __CLASS__, 'order_summary' ], $orders ),
		];
	}
}
