<?php

namespace Licenzo\Modules\WooCommerce;

use Licenzo\Model\Product;
use Licenzo\Model\ProductVariation;
use Licenzo\Services\Integrations\ProviderAliasService;

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

/**
 * Adds Licenzo mapping controls to Woo product editor and syncs Woo products into Licenzo products.
 */
class WooProductAdminMappingService
{
    const AJAX_ACTION_LOAD_VARIATION_MAPPINGS = 'licenzo_load_product_variation_mappings';
    const AJAX_NONCE_LOAD_VARIATION_MAPPINGS = 'licenzo_load_product_variation_mappings';

    const NONCE_ACTION = 'licenzo_woo_product_mapping_save';
    const NONCE_NAME = 'licenzo_woo_product_mapping_nonce';

    const META_ENABLE_SYNC = '_licenzo_sync_enabled';
    const META_LICENZO_PRODUCT_ID = '_licenzo_product_id';
    const META_LICENZO_PRODUCT_STATUS = '_licenzo_product_status';
    const META_LICENZO_PRODUCT_LINK = '_licenzo_product_link';
    const META_DEFAULT_CYCLE = '_licenzo_default_cycle';
    const META_DEFAULT_MAX_ACTIVATIONS = '_licenzo_default_max_activations';
    const META_DEFAULT_TRIAL_DAYS = '_licenzo_default_trial_days';
    const META_DEFAULT_SUPPORT_FOR = '_licenzo_default_support_for';
    const META_DEFAULT_SUPPORT_UNIT = '_licenzo_default_support_unit';

    const META_VARIATION_CYCLE = '_licenzo_cycle';
    const META_VARIATION_MAX_ACTIVATIONS = '_licenzo_max_activations';
    const META_VARIATION_TRIAL_DAYS = '_licenzo_trial_days';
    const META_VARIATION_SUPPORT_FOR = '_licenzo_support_for';
    const META_VARIATION_SUPPORT_UNIT = '_licenzo_support_unit';

    const META_LAST_SYNC_STATUS = '_licenzo_last_sync_status';
    const META_LAST_SYNC_MESSAGE = '_licenzo_last_sync_message';
    const META_LAST_SYNC_AT = '_licenzo_last_sync_at';

    /**
     * Register Woo admin hooks.
     */
    public function register(): void
    {
        if (!is_admin() || !function_exists('wc_get_product')) {
            return;
        }

        add_filter('woocommerce_product_data_tabs', [$this, 'addProductDataTab']);
        add_action('woocommerce_product_data_panels', [$this, 'renderProductDataPanel']);
        add_action('woocommerce_process_product_meta', [$this, 'saveProductMeta'], 100, 2);
        add_action('woocommerce_product_after_variable_attributes', [$this, 'renderVariationFields'], 100, 3);
        add_action('woocommerce_save_product_variation', [$this, 'saveVariationFields'], 100, 2);
        add_action('woocommerce_ajax_save_product_variations', [$this, 'syncAfterVariationAjaxSave'], 100, 1);
        add_action('wp_ajax_' . static::AJAX_ACTION_LOAD_VARIATION_MAPPINGS, [$this, 'ajaxLoadVariationMappings']);
        add_action('admin_footer', [$this, 'renderAdminFooterScript']);
    }

    /**
     * Add Licenzo tab in Woo product data tabs.
     *
     * @param array $tabs
     * @return array
     */
    public function addProductDataTab($tabs): array
    {
        $tabs = is_array($tabs) ? $tabs : [];

        $tabs['licenzo'] = [
            'label'    => __('Licenzo', 'licenzo'),
            'target'   => 'licenzo_product_data',
            'class'    => ['show_if_simple', 'show_if_variable'],
            'priority' => 85,
        ];

        return $tabs;
    }

    /**
     * Render Licenzo product data panel in Woo admin product editor.
     *
     * @return void
     */
    public function renderProductDataPanel(): void
    {
        global $post;

        $postId = isset($post->ID) ? (int) $post->ID : 0;
        $linkedProduct = $postId > 0 ? $this->findLinkedLicenzoProduct($postId) : null;

        echo '<div id="licenzo_product_data" class="panel woocommerce_options_panel hidden">';

        wp_nonce_field(static::NONCE_ACTION, static::NONCE_NAME);

        echo '<div class="options_group">';
        echo '<p class="form-field"><strong>' . esc_html__('Licenzo Product Mapping', 'licenzo') . '</strong><br />';
        echo '<span class="description">' . esc_html__('Create or update the linked Licenzo product when this WooCommerce product is saved. Woo product name, description, and prices are reused automatically; use the fields below only for Licenzo-specific settings.', 'licenzo') . '</span></p>';

        woocommerce_wp_checkbox([
            'id'          => static::META_ENABLE_SYNC,
            'label'       => __('Sync to Licenzo', 'licenzo'),
            'description' => __('Enable one-step create/update of the matching Licenzo product when saving this WooCommerce product.', 'licenzo'),
            'desc_tip'    => true,
        ]);

        $this->renderLinkedProductInfo($postId, $linkedProduct);
        $this->renderLastSyncStatus($postId);
        echo '</div>';

        echo '<div class="options_group show_if_simple show_if_variable">';
        woocommerce_wp_select([
            'id'          => static::META_LICENZO_PRODUCT_STATUS,
            'label'       => __('Licenzo Status', 'licenzo'),
            'description' => __('Status to assign to the Licenzo product record.', 'licenzo'),
            'desc_tip'    => true,
            'value'       => $this->getPostMetaString($postId, static::META_LICENZO_PRODUCT_STATUS, Product::STATUS_ACTIVE),
            'options'     => [
                Product::STATUS_ACTIVE => __('Active', 'licenzo'),
                Product::STATUS_INACTIVE => __('Inactive', 'licenzo'),
                Product::STATUS_DRAFT => __('Draft', 'licenzo'),
            ],
        ]);

        woocommerce_wp_text_input([
            'id'          => static::META_LICENZO_PRODUCT_LINK,
            'label'       => __('Licenzo Product Link', 'licenzo'),
            'description' => __('Optional override for the Licenzo product link. Leave empty to use the Woo product permalink.', 'licenzo'),
            'desc_tip'    => true,
            'placeholder' => 'https://example.com/product/my-product',
            'wrapper_class' => 'show_if_simple show_if_variable',
        ]);

        echo '<p class="form-field"><strong>' . esc_html__('Licenzo Variation Defaults', 'licenzo') . '</strong><br />';
        echo '<span class="description">' . esc_html__('Used for simple products and as fallback values for variable products when a variation override is empty.', 'licenzo') . '</span></p>';

        woocommerce_wp_select([
            'id'            => static::META_DEFAULT_CYCLE,
            'label'         => __('Default License Cycle', 'licenzo'),
            'description'   => __('Licenzo pricing cycle for the mapped option/variation.', 'licenzo'),
            'desc_tip'      => true,
            'value'         => $this->getPostMetaString($postId, static::META_DEFAULT_CYCLE, ProductVariation::PRICE_CYCLE_MONTHLY),
            'wrapper_class' => 'show_if_simple show_if_variable',
            'options'       => $this->getPriceCycleOptions(false),
        ]);

        woocommerce_wp_text_input([
            'id'                => static::META_DEFAULT_MAX_ACTIVATIONS,
            'label'             => __('Default Max Activations', 'licenzo'),
            'type'              => 'number',
            'description'       => __('Maximum license activations allowed for this product/variation.', 'licenzo'),
            'desc_tip'          => true,
            'custom_attributes' => [
                'min'  => '1',
                'step' => '1',
            ],
            'value'             => $this->getPostMetaString($postId, static::META_DEFAULT_MAX_ACTIVATIONS, '1'),
            'wrapper_class'     => 'show_if_simple show_if_variable',
        ]);

        woocommerce_wp_text_input([
            'id'                => static::META_DEFAULT_TRIAL_DAYS,
            'label'             => __('Default Trial Days', 'licenzo'),
            'type'              => 'number',
            'description'       => __('Trial days for Licenzo variation records when applicable.', 'licenzo'),
            'desc_tip'          => true,
            'custom_attributes' => [
                'min'  => '0',
                'step' => '1',
            ],
            'value'             => $this->getPostMetaString($postId, static::META_DEFAULT_TRIAL_DAYS, '0'),
            'wrapper_class'     => 'show_if_simple show_if_variable',
        ]);

        woocommerce_wp_text_input([
            'id'                => static::META_DEFAULT_SUPPORT_FOR,
            'label'             => __('Default Support Duration', 'licenzo'),
            'type'              => 'number',
            'description'       => __('Support duration quantity for Licenzo records (0 disables support window).', 'licenzo'),
            'desc_tip'          => true,
            'custom_attributes' => [
                'min'  => '0',
                'step' => '1',
            ],
            'value'             => $this->getPostMetaString($postId, static::META_DEFAULT_SUPPORT_FOR, '0'),
            'wrapper_class'     => 'show_if_simple show_if_variable',
        ]);

        woocommerce_wp_select([
            'id'            => static::META_DEFAULT_SUPPORT_UNIT,
            'label'         => __('Default Support Unit', 'licenzo'),
            'description'   => __('Unit used with Default Support Duration.', 'licenzo'),
            'desc_tip'      => true,
            'value'         => $this->getPostMetaString($postId, static::META_DEFAULT_SUPPORT_UNIT, ProductVariation::SUPPORT_UNIT_DAY),
            'wrapper_class' => 'show_if_simple show_if_variable',
            'options'       => [
                ProductVariation::SUPPORT_UNIT_DAY => __('Day', 'licenzo'),
                ProductVariation::SUPPORT_UNIT_MONTH => __('Month', 'licenzo'),
                ProductVariation::SUPPORT_UNIT_YEAR => __('Year', 'licenzo'),
            ],
        ]);

        echo '</div>';

        echo '<div class="options_group show_if_variable">';
        echo '<p class="form-field"><strong>' . esc_html__('Variation Licenzo Mapping', 'licenzo') . '</strong><br />';
        echo '<span class="description">' . esc_html__('Load all WooCommerce variations into this tab and set Licenzo-specific cycles/limits per variation. This supports different cycles for different variations.', 'licenzo') . '</span></p>';
        echo '<p class="form-field">';
        echo '<button type="button" class="button" id="licenzo-load-variation-maps-button">' . esc_html__('Load Variation Mappings', 'licenzo') . '</button> ';
        echo '<span class="description">' . esc_html__('Refresh after adding/removing WooCommerce variations.', 'licenzo') . '</span>';
        echo '</p>';
        echo '<div class="form-field" style="padding:0 9px 9px 9px; margin:0;">';
        echo '<div id="licenzo-variation-mappings-root" data-loaded="0" data-product-id="' . esc_attr((string) $postId) . '" data-ajax-url="' . esc_url(admin_url('admin-ajax.php')) . '" data-ajax-action="' . esc_attr(static::AJAX_ACTION_LOAD_VARIATION_MAPPINGS) . '" data-ajax-nonce="' . esc_attr(wp_create_nonce(static::AJAX_NONCE_LOAD_VARIATION_MAPPINGS)) . '">';
        echo '<p class="description">' . esc_html__('Open the Licenzo tab and click "Load Variation Mappings" to edit per-variation Licenzo cycles and limits in one place.', 'licenzo') . '</p>';
        echo '</div>';
        echo '</div>';
        echo '</div>';

        echo '</div>';
    }

    /**
     * Render Licenzo override fields inside each Woo variation panel.
     *
     * @param int $loop
     * @param array $variationData
     * @param \WP_Post|\WC_Product_Variation $variation
     * @return void
     */
    public function renderVariationFields($loop, $variationData, $variation): void
    {
        $variationId = 0;

        if (is_object($variation) && isset($variation->ID)) {
            $variationId = (int) $variation->ID;
        } elseif (is_object($variation) && method_exists($variation, 'get_id')) {
            $variationId = (int) $variation->get_id();
        }

        if ($variationId <= 0) {
            return;
        }

        echo '<div class="form-row form-row-full">';
        echo '<p><strong>' . esc_html__('Licenzo Overrides', 'licenzo') . '</strong><br /><span class="description">' . esc_html__('Leave blank to use the Licenzo defaults from the parent WooCommerce product.', 'licenzo') . '</span></p>';
        echo '</div>';

        woocommerce_wp_select([
            'id'      => 'licenzo_variation_cycle_' . $loop,
            'name'    => 'licenzo_variation_cycle[' . $loop . ']',
            'label'   => __('Licenzo Cycle', 'licenzo'),
            'value'   => get_post_meta($variationId, static::META_VARIATION_CYCLE, true),
            'options' => $this->getPriceCycleOptions(true),
        ]);

        woocommerce_wp_text_input([
            'id'                => 'licenzo_variation_max_activations_' . $loop,
            'name'              => 'licenzo_variation_max_activations[' . $loop . ']',
            'label'             => __('Licenzo Max Activations', 'licenzo'),
            'type'              => 'number',
            'value'             => get_post_meta($variationId, static::META_VARIATION_MAX_ACTIVATIONS, true),
            'placeholder'       => __('Use default', 'licenzo'),
            'custom_attributes' => [
                'min'  => '1',
                'step' => '1',
            ],
        ]);

        woocommerce_wp_text_input([
            'id'                => 'licenzo_variation_trial_days_' . $loop,
            'name'              => 'licenzo_variation_trial_days[' . $loop . ']',
            'label'             => __('Licenzo Trial Days', 'licenzo'),
            'type'              => 'number',
            'value'             => get_post_meta($variationId, static::META_VARIATION_TRIAL_DAYS, true),
            'placeholder'       => __('Use default', 'licenzo'),
            'custom_attributes' => [
                'min'  => '0',
                'step' => '1',
            ],
        ]);

        woocommerce_wp_text_input([
            'id'                => 'licenzo_variation_support_for_' . $loop,
            'name'              => 'licenzo_variation_support_for[' . $loop . ']',
            'label'             => __('Licenzo Support Duration', 'licenzo'),
            'type'              => 'number',
            'value'             => get_post_meta($variationId, static::META_VARIATION_SUPPORT_FOR, true),
            'placeholder'       => __('Use default', 'licenzo'),
            'custom_attributes' => [
                'min'  => '0',
                'step' => '1',
            ],
        ]);

        woocommerce_wp_select([
            'id'      => 'licenzo_variation_support_unit_' . $loop,
            'name'    => 'licenzo_variation_support_unit[' . $loop . ']',
            'label'   => __('Licenzo Support Unit', 'licenzo'),
            'value'   => get_post_meta($variationId, static::META_VARIATION_SUPPORT_UNIT, true),
            'options' => [
                '' => __('Use default', 'licenzo'),
                ProductVariation::SUPPORT_UNIT_DAY => __('Day', 'licenzo'),
                ProductVariation::SUPPORT_UNIT_MONTH => __('Month', 'licenzo'),
                ProductVariation::SUPPORT_UNIT_YEAR => __('Year', 'licenzo'),
            ],
        ]);
    }

    /**
     * Save product-level Licenzo settings and sync Woo product to Licenzo if enabled.
     *
     * @param int $postId
     * @param \WP_Post $post
     * @return void
     */
    public function saveProductMeta($postId, $post = null): void
    {
        $postId = (int) $postId;
        if ($postId <= 0 || !$this->hasValidProductSaveNonce()) {
            return;
        }

        $this->saveProductMetaFields($postId);
        $this->saveTabVariationMappingFields($postId);

        if ($this->isSyncEnabled($postId)) {
            $this->syncWooProductToLicenzo($postId, 'product_save');
        }
    }

    /**
     * Save variation-level Licenzo override fields.
     *
     * @param int $variationId
     * @param int $index
     * @return void
     */
    public function saveVariationFields($variationId, $index): void
    {
        $variationId = (int) $variationId;
        $index = (int) $index;

        if ($variationId <= 0 || !$this->hasValidVariationSaveNonce()) {
            return;
        }

        $cycle = $this->getPostedIndexedValue('licenzo_variation_cycle', $index);
        $maxActivations = $this->getPostedIndexedValue('licenzo_variation_max_activations', $index);
        $trialDays = $this->getPostedIndexedValue('licenzo_variation_trial_days', $index);
        $supportFor = $this->getPostedIndexedValue('licenzo_variation_support_for', $index);
        $supportUnit = $this->getPostedIndexedValue('licenzo_variation_support_unit', $index);

        $this->updateMetaMaybeDelete($variationId, static::META_VARIATION_CYCLE, $this->normalizePriceCycle($cycle, ''), true);
        $this->updateMetaMaybeDelete($variationId, static::META_VARIATION_MAX_ACTIVATIONS, $this->normalizeOptionalPositiveInt($maxActivations), true);
        $this->updateMetaMaybeDelete($variationId, static::META_VARIATION_TRIAL_DAYS, $this->normalizeOptionalNonNegativeInt($trialDays), true);
        $this->updateMetaMaybeDelete($variationId, static::META_VARIATION_SUPPORT_FOR, $this->normalizeOptionalNonNegativeInt($supportFor), true);
        $this->updateMetaMaybeDelete($variationId, static::META_VARIATION_SUPPORT_UNIT, $this->normalizeSupportUnit($supportUnit, ''), true);
    }

    /**
     * Re-sync after Woo variation AJAX save completes.
     *
     * @param int $productId
     * @return void
     */
    public function syncAfterVariationAjaxSave($productId): void
    {
        $productId = (int) $productId;
        if ($productId <= 0 || !$this->hasValidVariationSaveNonce()) {
            return;
        }

        if ($this->isSyncEnabled($productId)) {
            $this->syncWooProductToLicenzo($productId, 'variation_ajax_save');
        }
    }

    /**
     * AJAX: Load variable product Licenzo mapping rows for all Woo variations.
     *
     * @return void
     */
    public function ajaxLoadVariationMappings(): void
    {
        if (!current_user_can('edit_products')) {
            wp_send_json_error(['message' => __('You are not allowed to edit products.', 'licenzo')], 403);
        }

        check_ajax_referer(static::AJAX_NONCE_LOAD_VARIATION_MAPPINGS, 'security');

        $productId = isset($_POST['product_id']) ? absint(wp_unslash($_POST['product_id'])) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
        if ($productId <= 0) {
            wp_send_json_error(['message' => __('Invalid product ID.', 'licenzo')], 400);
        }

        $wooProduct = function_exists('wc_get_product') ? wc_get_product($productId) : null;
        if (!$wooProduct || !$wooProduct->is_type('variable')) {
            wp_send_json_error(['message' => __('This WooCommerce product is not a variable product.', 'licenzo')], 400);
        }

        $html = $this->renderVariableMappingRowsHtml($wooProduct);

        wp_send_json_success([
            'html' => $html,
            'count' => count((array) $wooProduct->get_children()),
        ]);
    }

    /**
     * Render inline admin JS for Licenzo tab variation mapping AJAX loading.
     *
     * @return void
     */
    public function renderAdminFooterScript(): void
    {
        if (!$this->isWooProductEditorScreen()) {
            return;
        }

        ?>
        <script type="text/javascript">
            jQuery(function ($) {
                var rootSelector = '#licenzo-variation-mappings-root';
                var buttonSelector = '#licenzo-load-variation-maps-button';
                var loading = false;

                function isVariableProduct() {
                    return $('#product-type').val() === 'variable';
                }

                function isLicenzoTabActive() {
                    return $('.product_data_tabs .licenzo_tab').hasClass('active');
                }

                function getRoot() {
                    return $(rootSelector);
                }

                function renderMessage(type, message) {
                    var cssClass = type === 'error'
                        ? 'notice notice-error inline'
                        : (type === 'success' ? 'notice notice-success inline' : 'notice notice-info inline');

                    return '<div class="' + cssClass + '" style="margin:0 0 8px 0;"><p>' + message + '</p></div>';
                }

                function loadVariationMappings(forceReload) {
                    var $root = getRoot();
                    if (!$root.length || !isVariableProduct()) {
                        return;
                    }

                    if (loading) {
                        return;
                    }

                    if (!forceReload && String($root.attr('data-loaded')) === '1') {
                        return;
                    }

                    var productId = Number($root.data('product-id') || $('#post_ID').val() || 0);
                    if (!productId) {
                        $root.html(renderMessage('error', 'Save this product first so WooCommerce variations can be mapped.'));
                        return;
                    }

                    loading = true;
                    $root.attr('data-loaded', '0').html(renderMessage('info', 'Loading Licenzo variation mappings...'));

                    $.post(String($root.data('ajax-url') || ''), {
                        action: String($root.data('ajax-action') || ''),
                        security: String($root.data('ajax-nonce') || ''),
                        product_id: productId
                    }).done(function (response) {
                        if (!response || response.success !== true) {
                            var message = response && response.data && response.data.message
                                ? response.data.message
                                : 'Failed to load variation mappings.';
                            $root.html(renderMessage('error', message));
                            return;
                        }

                        var html = response.data && response.data.html ? response.data.html : '';
                        if (!html) {
                            $root.html(renderMessage('info', 'No variations available yet.'));
                            return;
                        }

                        $root.html(html).attr('data-loaded', '1');
                    }).fail(function (xhr) {
                        var message = 'Failed to load variation mappings.';
                        if (xhr && xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message) {
                            message = xhr.responseJSON.data.message;
                        }
                        $root.html(renderMessage('error', message));
                    }).always(function () {
                        loading = false;
                    });
                }

                $(document.body).on('click', '.product_data_tabs .licenzo_tab a', function () {
                    window.setTimeout(function () {
                        loadVariationMappings(false);
                    }, 0);
                });

                $(document.body).on('click', buttonSelector, function (event) {
                    event.preventDefault();
                    loadVariationMappings(true);
                });

                $('#woocommerce-product-data').on('woocommerce_variations_saved', function () {
                    var $root = getRoot();
                    if (!$root.length) {
                        return;
                    }
                    $root.attr('data-loaded', '0');
                    if (isLicenzoTabActive()) {
                        loadVariationMappings(true);
                    }
                });

                $(document.body).on('woocommerce-product-type-change', function (_event, productType) {
                    var $root = getRoot();
                    if (!$root.length) {
                        return;
                    }

                    if (String(productType || '') !== 'variable') {
                        $root.attr('data-loaded', '0').html('<p class="description">Variation mapping is available for variable WooCommerce products.</p>');
                        return;
                    }

                    if (isLicenzoTabActive()) {
                        loadVariationMappings(true);
                    }
                });
            });
        </script>
        <?php
    }

    /**
     * Save product-level Licenzo meta from current request.
     *
     * @param int $postId
     * @return void
     */
    private function saveTabVariationMappingFields(int $postId): void
    {
        if (!$this->hasPostedTabVariationMappings() || !function_exists('wc_get_product')) {
            return;
        }

        $wooProduct = wc_get_product($postId);
        if (!$wooProduct || !$wooProduct->is_type('variable')) {
            return;
        }

        $cycles = $this->getPostedAssocArray('licenzo_tab_variation_cycle');
        $maxActivations = $this->getPostedAssocArray('licenzo_tab_variation_max_activations');
        $trialDays = $this->getPostedAssocArray('licenzo_tab_variation_trial_days');
        $supportFor = $this->getPostedAssocArray('licenzo_tab_variation_support_for');
        $supportUnit = $this->getPostedAssocArray('licenzo_tab_variation_support_unit');

        foreach ((array) $wooProduct->get_children() as $childId) {
            $variationId = absint($childId);
            if ($variationId <= 0) {
                continue;
            }

            $hasRowInput = array_key_exists((string) $variationId, $cycles)
                || array_key_exists((string) $variationId, $maxActivations)
                || array_key_exists((string) $variationId, $trialDays)
                || array_key_exists((string) $variationId, $supportFor)
                || array_key_exists((string) $variationId, $supportUnit);

            if (!$hasRowInput) {
                continue;
            }

            $this->updateMetaMaybeDelete(
                $variationId,
                static::META_VARIATION_CYCLE,
                $this->normalizePriceCycle($this->getPostedAssocValue($cycles, $variationId), ''),
                true
            );
            $this->updateMetaMaybeDelete(
                $variationId,
                static::META_VARIATION_MAX_ACTIVATIONS,
                $this->normalizeOptionalPositiveInt($this->getPostedAssocValue($maxActivations, $variationId)),
                true
            );
            $this->updateMetaMaybeDelete(
                $variationId,
                static::META_VARIATION_TRIAL_DAYS,
                $this->normalizeOptionalNonNegativeInt($this->getPostedAssocValue($trialDays, $variationId)),
                true
            );
            $this->updateMetaMaybeDelete(
                $variationId,
                static::META_VARIATION_SUPPORT_FOR,
                $this->normalizeOptionalNonNegativeInt($this->getPostedAssocValue($supportFor, $variationId)),
                true
            );
            $this->updateMetaMaybeDelete(
                $variationId,
                static::META_VARIATION_SUPPORT_UNIT,
                $this->normalizeSupportUnit($this->getPostedAssocValue($supportUnit, $variationId), ''),
                true
            );
        }
    }

    /**
     * Check whether tab-based variation mapping arrays are present in current request.
     *
     * @return bool
     */
    private function hasPostedTabVariationMappings(): bool
    {
        return isset($_POST['licenzo_tab_variation_cycle']) // phpcs:ignore WordPress.Security.NonceVerification.Missing
            || isset($_POST['licenzo_tab_variation_max_activations']) // phpcs:ignore WordPress.Security.NonceVerification.Missing
            || isset($_POST['licenzo_tab_variation_trial_days']) // phpcs:ignore WordPress.Security.NonceVerification.Missing
            || isset($_POST['licenzo_tab_variation_support_for']) // phpcs:ignore WordPress.Security.NonceVerification.Missing
            || isset($_POST['licenzo_tab_variation_support_unit']); // phpcs:ignore WordPress.Security.NonceVerification.Missing
    }

    /**
     * Normalize a posted associative array payload (variation-id keyed).
     *
     * @param string $key
     * @return array<string,string>
     */
    private function getPostedAssocArray(string $key): array
    {
        if (!isset($_POST[$key]) || !is_array($_POST[$key])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
            return [];
        }

        $raw = array_map('sanitize_text_field', (array) wp_unslash($_POST[$key])); // phpcs:ignore WordPress.Security.NonceVerification.Missing
        $normalized = [];

        foreach ($raw as $index => $value) {
            $normalized[(string) $index] = (string) $value;
        }

        return $normalized;
    }

    /**
     * Read variation-id keyed posted value from normalized array.
     *
     * @param array<string,string> $values
     * @param int $variationId
     * @return string
     */
    private function getPostedAssocValue(array $values, int $variationId): string
    {
        $key = (string) $variationId;
        return array_key_exists($key, $values) ? (string) $values[$key] : '';
    }

    /**
     * Render HTML table for variable-product Licenzo mappings in the Licenzo tab.
     *
     * @param \WC_Product $wooProduct
     * @return string
     */
    private function renderVariableMappingRowsHtml($wooProduct): string
    {
        $childIds = array_map('absint', (array) $wooProduct->get_children());
        if (empty($childIds)) {
            return '<div class="notice notice-info inline" style="margin:0;"><p>' . esc_html__('No WooCommerce variations found yet. Add/save WooCommerce variations, then reload this section.', 'licenzo') . '</p></div>';
        }

        $rows = '';
        $rendered = 0;

        foreach ($childIds as $childId) {
            if ($childId <= 0) {
                continue;
            }

            $wooVariation = wc_get_product($childId);
            if (!$wooVariation) {
                continue;
            }

            $rows .= $this->renderVariableMappingRowHtml($wooVariation);
            $rendered++;
        }

        if ($rendered === 0) {
            return '<div class="notice notice-info inline" style="margin:0;"><p>' . esc_html__('No valid WooCommerce variation records are available to map.', 'licenzo') . '</p></div>';
        }

        $html  = '<div class="notice notice-info inline" style="margin:0 0 8px 0;"><p>';
        $html .= esc_html__('Each row maps one WooCommerce variation to Licenzo-specific settings. Leave fields blank to use the defaults above. Save with WooCommerce Update/Publish to persist these Licenzo tab changes.', 'licenzo');
        $html .= '</p></div>';
        $html .= '<table class="widefat striped" style="margin:0;">';
        $html .= '<thead><tr>';
        $html .= '<th style="width:26%;">' . esc_html__('Variation', 'licenzo') . '</th>';
        $html .= '<th style="width:17%;">' . esc_html__('Cycle', 'licenzo') . '</th>';
        $html .= '<th style="width:14%;">' . esc_html__('Max Activations', 'licenzo') . '</th>';
        $html .= '<th style="width:12%;">' . esc_html__('Trial Days', 'licenzo') . '</th>';
        $html .= '<th style="width:12%;">' . esc_html__('Support', 'licenzo') . '</th>';
        $html .= '<th style="width:19%;">' . esc_html__('Support Unit', 'licenzo') . '</th>';
        $html .= '</tr></thead><tbody>';
        $html .= $rows;
        $html .= '</tbody></table>';

        return $html;
    }

    /**
     * Render one variable mapping row (Licenzo tab).
     *
     * @param \WC_Product_Variation $wooVariation
     * @return string
     */
    private function renderVariableMappingRowHtml($wooVariation): string
    {
        $variationId = (int) $wooVariation->get_id();
        $name = sanitize_text_field((string) $wooVariation->get_name());
        $priceSummary = $this->getVariationPriceSummaryHtml($wooVariation);

        $cycleValue = (string) get_post_meta($variationId, static::META_VARIATION_CYCLE, true);
        $maxActivationValue = (string) get_post_meta($variationId, static::META_VARIATION_MAX_ACTIVATIONS, true);
        $trialDaysValue = (string) get_post_meta($variationId, static::META_VARIATION_TRIAL_DAYS, true);
        $supportForValue = (string) get_post_meta($variationId, static::META_VARIATION_SUPPORT_FOR, true);
        $supportUnitValue = (string) get_post_meta($variationId, static::META_VARIATION_SUPPORT_UNIT, true);

        $row  = '<tr>';
        $row .= '<td>';
        $row .= '<div style="font-weight:600;">' . esc_html($name) . '</div>';
        $row .= '<div class="description">#' . esc_html((string) $variationId) . '</div>';
        if ($priceSummary !== '') {
            $row .= '<div class="description">' . wp_kses_post($priceSummary) . '</div>';
        }
        $row .= '</td>';

        $row .= '<td><select name="licenzo_tab_variation_cycle[' . esc_attr((string) $variationId) . ']" style="width:100%;">';
        $row .= $this->buildSelectOptionsHtml($this->getPriceCycleOptions(true), $cycleValue);
        $row .= '</select></td>';

        $row .= '<td><input type="number" min="1" step="1" name="licenzo_tab_variation_max_activations[' . esc_attr((string) $variationId) . ']" value="' . esc_attr($maxActivationValue) . '" placeholder="' . esc_attr__('Default', 'licenzo') . '" style="width:100%;" /></td>';
        $row .= '<td><input type="number" min="0" step="1" name="licenzo_tab_variation_trial_days[' . esc_attr((string) $variationId) . ']" value="' . esc_attr($trialDaysValue) . '" placeholder="' . esc_attr__('Default', 'licenzo') . '" style="width:100%;" /></td>';
        $row .= '<td><input type="number" min="0" step="1" name="licenzo_tab_variation_support_for[' . esc_attr((string) $variationId) . ']" value="' . esc_attr($supportForValue) . '" placeholder="' . esc_attr__('Default', 'licenzo') . '" style="width:100%;" /></td>';

        $row .= '<td><select name="licenzo_tab_variation_support_unit[' . esc_attr((string) $variationId) . ']" style="width:100%;">';
        $row .= $this->buildSelectOptionsHtml([
            '' => __('Use default', 'licenzo'),
            ProductVariation::SUPPORT_UNIT_DAY => __('Day', 'licenzo'),
            ProductVariation::SUPPORT_UNIT_MONTH => __('Month', 'licenzo'),
            ProductVariation::SUPPORT_UNIT_YEAR => __('Year', 'licenzo'),
        ], $supportUnitValue);
        $row .= '</select></td>';

        $row .= '</tr>';

        return $row;
    }

    /**
     * Build HTML option list for a select field.
     *
     * @param array<string,string> $options
     * @param string $selectedValue
     * @return string
     */
    private function buildSelectOptionsHtml(array $options, string $selectedValue): string
    {
        $html = '';
        foreach ($options as $value => $label) {
            $html .= '<option value="' . esc_attr((string) $value) . '"' . selected((string) $selectedValue, (string) $value, false) . '>' . esc_html((string) $label) . '</option>';
        }

        return $html;
    }

    /**
     * Human-friendly price summary for a Woo variation.
     *
     * @param \WC_Product_Variation $wooVariation
     * @return string
     */
    private function getVariationPriceSummaryHtml($wooVariation): string
    {
        if (!function_exists('wc_price')) {
            return '';
        }

        $regular = $wooVariation->get_regular_price();
        $sale = $wooVariation->get_sale_price();

        if ($regular === '' && $sale === '') {
            return '';
        }

        $parts = [];
        if ($regular !== '') {
            $parts[] = sprintf(
                /* translators: %s: regular price */
                esc_html__('Regular: %s', 'licenzo'),
                wp_kses_post(wc_price((float) wc_format_decimal($regular)))
            );
        }
        if ($sale !== '') {
            $parts[] = sprintf(
                /* translators: %s: sale price */
                esc_html__('Sale: %s', 'licenzo'),
                wp_kses_post(wc_price((float) wc_format_decimal($sale)))
            );
        }

        return implode(' &middot; ', $parts);
    }

    /**
     * Check if current admin screen is the Woo product editor.
     *
     * @return bool
     */
    private function isWooProductEditorScreen(): bool
    {
        if (!is_admin() || !function_exists('get_current_screen')) {
            return false;
        }

        $screen = get_current_screen();
        if (!$screen) {
            return false;
        }

        return isset($screen->post_type) && $screen->post_type === 'product';
    }

    private function saveProductMetaFields(int $postId): void
    {
        $enableSync = isset($_POST[static::META_ENABLE_SYNC]) ? 'yes' : 'no'; // phpcs:ignore WordPress.Security.NonceVerification.Missing

        update_post_meta($postId, static::META_ENABLE_SYNC, $enableSync);
        update_post_meta(
            $postId,
            static::META_LICENZO_PRODUCT_STATUS,
            $this->normalizeProductStatus(
                isset($_POST[static::META_LICENZO_PRODUCT_STATUS]) ? sanitize_text_field(wp_unslash($_POST[static::META_LICENZO_PRODUCT_STATUS])) : Product::STATUS_ACTIVE // phpcs:ignore WordPress.Security.NonceVerification.Missing
            )
        );
        update_post_meta(
            $postId,
            static::META_LICENZO_PRODUCT_LINK,
            esc_url_raw(isset($_POST[static::META_LICENZO_PRODUCT_LINK]) ? wp_unslash($_POST[static::META_LICENZO_PRODUCT_LINK]) : '') // phpcs:ignore WordPress.Security.NonceVerification.Missing
        );
        update_post_meta(
            $postId,
            static::META_DEFAULT_CYCLE,
            $this->normalizePriceCycle(
                isset($_POST[static::META_DEFAULT_CYCLE]) ? sanitize_text_field(wp_unslash($_POST[static::META_DEFAULT_CYCLE])) : ProductVariation::PRICE_CYCLE_MONTHLY // phpcs:ignore WordPress.Security.NonceVerification.Missing
            )
        );
        update_post_meta(
            $postId,
            static::META_DEFAULT_MAX_ACTIVATIONS,
            (string) $this->normalizePositiveInt(
                isset($_POST[static::META_DEFAULT_MAX_ACTIVATIONS]) ? sanitize_text_field(wp_unslash($_POST[static::META_DEFAULT_MAX_ACTIVATIONS])) : 1, // phpcs:ignore WordPress.Security.NonceVerification.Missing
                1
            )
        );
        update_post_meta(
            $postId,
            static::META_DEFAULT_TRIAL_DAYS,
            (string) $this->normalizeNonNegativeInt(
                isset($_POST[static::META_DEFAULT_TRIAL_DAYS]) ? sanitize_text_field(wp_unslash($_POST[static::META_DEFAULT_TRIAL_DAYS])) : 0, // phpcs:ignore WordPress.Security.NonceVerification.Missing
                0
            )
        );
        update_post_meta(
            $postId,
            static::META_DEFAULT_SUPPORT_FOR,
            (string) $this->normalizeNonNegativeInt(
                isset($_POST[static::META_DEFAULT_SUPPORT_FOR]) ? sanitize_text_field(wp_unslash($_POST[static::META_DEFAULT_SUPPORT_FOR])) : 0, // phpcs:ignore WordPress.Security.NonceVerification.Missing
                0
            )
        );
        update_post_meta(
            $postId,
            static::META_DEFAULT_SUPPORT_UNIT,
            $this->normalizeSupportUnit(
                isset($_POST[static::META_DEFAULT_SUPPORT_UNIT]) ? sanitize_text_field(wp_unslash($_POST[static::META_DEFAULT_SUPPORT_UNIT])) : ProductVariation::SUPPORT_UNIT_DAY // phpcs:ignore WordPress.Security.NonceVerification.Missing
            )
        );
    }

    /**
     * Sync a Woo product to a linked Licenzo product + variations.
     *
     * @param int $wooProductId
     * @param string $context
     * @return array
     */
    private function syncWooProductToLicenzo(int $wooProductId, string $context = 'manual'): array
    {
        if (!function_exists('wc_get_product')) {
            return $this->failSync($wooProductId, 'WooCommerce is not available.');
        }

        $wooProduct = wc_get_product($wooProductId);
        if (!$wooProduct) {
            return $this->failSync($wooProductId, 'WooCommerce product could not be loaded.');
        }

        $isVariable = $wooProduct->is_type('variable');
        $isSimple = $wooProduct->is_type('simple');

        if (!$isVariable && !$isSimple) {
            return $this->failSync($wooProductId, 'Only simple and variable WooCommerce products can be mapped to Licenzo right now.');
        }

        $licenzoProduct = $this->upsertLicenzoProduct($wooProduct);
        if (!$licenzoProduct) {
            return $this->failSync($wooProductId, 'Failed to create or update the Licenzo product record.');
        }

        $defaults = $this->getLicenzoDefaultsForProduct($wooProductId);
        $syncedVariations = [];

        if ($isSimple) {
            $variation = $this->upsertSimpleLicenzoVariation($licenzoProduct, $wooProduct, $defaults);
            if (!$variation) {
                return $this->failSync($wooProductId, 'Failed to create or update the Licenzo variation for this WooCommerce product.');
            }

            $syncedVariations[] = (int) $variation->id;
            ProductVariation::where('product_id', $licenzoProduct->id)
                ->where('id', '!=', (int) $variation->id)
                ->delete();
        } else {
            $childIds = array_map('absint', (array) $wooProduct->get_children());
            if (empty($childIds)) {
                return $this->failSync($wooProductId, 'Variable product has no variations yet. Add WooCommerce variations first, then save again.');
            }

            foreach ($childIds as $childId) {
                if ($childId <= 0) {
                    continue;
                }

                $wooVariation = wc_get_product($childId);
                if (!$wooVariation) {
                    continue;
                }

                $variation = $this->upsertVariableLicenzoVariation($licenzoProduct, $wooVariation, $defaults);
                if ($variation) {
                    $syncedVariations[] = (int) $variation->id;
                }
            }

            if (empty($syncedVariations)) {
                return $this->failSync($wooProductId, 'No valid WooCommerce variations were available to sync into Licenzo.');
            }

            ProductVariation::where('product_id', $licenzoProduct->id)
                ->whereNotIn('id', $syncedVariations)
                ->delete();
        }

        update_post_meta($wooProductId, static::META_LICENZO_PRODUCT_ID, (int) $licenzoProduct->id);

        $message = sprintf(
            'Licenzo sync completed (%s): product #%d with %d variation(s).',
            $context,
            (int) $licenzoProduct->id,
            count($syncedVariations)
        );

        $this->storeSyncStatus($wooProductId, 'success', $message);

        return [
            'success' => true,
            'licenzo_product_id' => (int) $licenzoProduct->id,
            'variations_synced' => count($syncedVariations),
            'context' => $context,
        ];
    }

    /**
     * Create or update the Licenzo product mapped to a Woo product.
     *
     * @param \WC_Product $wooProduct
     * @return Product|null
     */
    private function upsertLicenzoProduct($wooProduct): ?Product
    {
        $wooProductId = (int) $wooProduct->get_id();
        $mapped = $this->findLinkedLicenzoProduct($wooProductId);

        $rawName = sanitize_text_field((string) $wooProduct->get_name());
        if ($rawName === '') {
            $rawName = 'Woo Product #' . $wooProductId;
        }

        $name = $this->resolveLicenzoProductName($rawName, $wooProductId, $mapped ? (int) $mapped->id : null);
        $slug = $this->generateUniqueSlug($name, $wooProductId, $mapped ? (int) $mapped->id : null);
        $status = $this->normalizeProductStatus(get_post_meta($wooProductId, static::META_LICENZO_PRODUCT_STATUS, true));
        $customProductLink = trim((string) get_post_meta($wooProductId, static::META_LICENZO_PRODUCT_LINK, true));

        $description = (string) $wooProduct->get_description();
        if ($description === '') {
            $description = (string) $wooProduct->get_short_description();
        }

        $payload = [
            'name'                  => $name,
            'slug'                  => $slug,
            'description'           => wp_kses_post($description),
            'status'                => $status,
            'provider_id'  => (string) $wooProductId,
            'provider'              => Product::PROVIDER_WOOCOMMERCE,
            'type'                  => $wooProduct->is_type('variable') ? Product::TYPE_VARIATION : Product::TYPE_SINGLE,
            'provider_product_name' => sanitize_text_field((string) $wooProduct->get_name()),
            'product_link'          => $customProductLink !== '' ? $customProductLink : esc_url_raw(get_permalink($wooProductId)),
        ];

        if ($mapped) {
            $mapped->update($payload);
            return $mapped->fresh();
        }

        return Product::create(array_merge($payload, [
            'uuid' => wp_generate_uuid4(),
        ]));
    }

    /**
     * Upsert Licenzo variation for a simple Woo product.
     *
     * @param Product $licenzoProduct
     * @param \WC_Product $wooProduct
     * @param array $defaults
     * @return ProductVariation|null
     */
    private function upsertSimpleLicenzoVariation(Product $licenzoProduct, $wooProduct, array $defaults): ?ProductVariation
    {
        $regularPrice = $this->resolveWooRegularPrice($wooProduct);
        $salePrice = $this->resolveWooSalePrice($wooProduct);

        $wooCurrency = function_exists('get_woocommerce_currency') ? get_woocommerce_currency() : 'USD';

        $payload = [
            'name'                  => sanitize_text_field((string) $wooProduct->get_name()),
            'provider_id' => null,
            'currency'              => $wooCurrency,
            'billing_cycle'           => $defaults['cycle'],
            'regular_price'         => $regularPrice,
            'sale_price'            => $salePrice,
            'max_activations'       => $defaults['max_activations'],
            'trial_days'            => $defaults['trial_days'],
            'support_for'           => $defaults['support_for'],
            'support_for_unit'      => $defaults['support_unit'],
        ];

        $existing = ProductVariation::where('product_id', $licenzoProduct->id)
            ->whereNull('provider_id')
            ->first();

        if ($existing) {
            $existing->update($payload);
            return $existing->fresh();
        }

        return ProductVariation::create(array_merge($payload, [
            'uuid'       => wp_generate_uuid4(),
            'product_id' => $licenzoProduct->id,
        ]));
    }

    /**
     * Upsert Licenzo variation for a Woo variable product child.
     *
     * @param Product $licenzoProduct
     * @param \WC_Product_Variation $wooVariation
     * @param array $defaults
     * @return ProductVariation|null
     */
    private function upsertVariableLicenzoVariation(Product $licenzoProduct, $wooVariation, array $defaults): ?ProductVariation
    {
        $variationId = (int) $wooVariation->get_id();
        if ($variationId <= 0) {
            return null;
        }

        $overrides = $this->getVariationOverrideValues($variationId);
        $variationConfig = [
            'cycle'           => $overrides['cycle'] !== '' ? $overrides['cycle'] : $defaults['cycle'],
            'max_activations' => $overrides['max_activations'] !== null ? $overrides['max_activations'] : $defaults['max_activations'],
            'trial_days'      => $overrides['trial_days'] !== null ? $overrides['trial_days'] : $defaults['trial_days'],
            'support_for'     => $overrides['support_for'] !== null ? $overrides['support_for'] : $defaults['support_for'],
            'support_unit'    => $overrides['support_unit'] !== '' ? $overrides['support_unit'] : $defaults['support_unit'],
        ];

        $wooCurrency = function_exists('get_woocommerce_currency') ? get_woocommerce_currency() : 'USD';

        $payload = [
            'name'                  => sanitize_text_field((string) $wooVariation->get_name()),
            'provider_id'  => (string) $variationId,
            'currency'              => $wooCurrency,
            'billing_cycle'           => $variationConfig['cycle'],
            'regular_price'         => $this->resolveWooRegularPrice($wooVariation),
            'sale_price'            => $this->resolveWooSalePrice($wooVariation),
            'max_activations'       => $variationConfig['max_activations'],
            'trial_days'            => $variationConfig['trial_days'],
            'support_for'           => $variationConfig['support_for'],
            'support_for_unit'      => $variationConfig['support_unit'],
        ];

        $existing = ProductVariation::where('product_id', $licenzoProduct->id)
            ->where('provider_id', (string) $variationId)
            ->first();

        if ($existing) {
            $existing->update($payload);
            return $existing->fresh();
        }

        return ProductVariation::create(array_merge($payload, [
            'uuid'       => wp_generate_uuid4(),
            'product_id' => $licenzoProduct->id,
        ]));
    }

    /**
     * Read Licenzo defaults from Woo product post meta.
     *
     * @param int $wooProductId
     * @return array
     */
    private function getLicenzoDefaultsForProduct(int $wooProductId): array
    {
        return [
            'cycle'           => $this->normalizePriceCycle(get_post_meta($wooProductId, static::META_DEFAULT_CYCLE, true), ProductVariation::PRICE_CYCLE_MONTHLY),
            'max_activations' => $this->normalizePositiveInt(get_post_meta($wooProductId, static::META_DEFAULT_MAX_ACTIVATIONS, true), 1),
            'trial_days'      => $this->normalizeNonNegativeInt(get_post_meta($wooProductId, static::META_DEFAULT_TRIAL_DAYS, true), 0),
            'support_for'     => $this->normalizeNonNegativeInt(get_post_meta($wooProductId, static::META_DEFAULT_SUPPORT_FOR, true), 0),
            'support_unit'    => $this->normalizeSupportUnit(get_post_meta($wooProductId, static::META_DEFAULT_SUPPORT_UNIT, true), ProductVariation::SUPPORT_UNIT_DAY),
        ];
    }

    /**
     * Read variation-specific Licenzo override meta values.
     *
     * @param int $variationId
     * @return array
     */
    private function getVariationOverrideValues(int $variationId): array
    {
        return [
            'cycle'           => $this->normalizePriceCycle(get_post_meta($variationId, static::META_VARIATION_CYCLE, true), ''),
            'max_activations' => $this->normalizeOptionalPositiveInt(get_post_meta($variationId, static::META_VARIATION_MAX_ACTIVATIONS, true)),
            'trial_days'      => $this->normalizeOptionalNonNegativeInt(get_post_meta($variationId, static::META_VARIATION_TRIAL_DAYS, true)),
            'support_for'     => $this->normalizeOptionalNonNegativeInt(get_post_meta($variationId, static::META_VARIATION_SUPPORT_FOR, true)),
            'support_unit'    => $this->normalizeSupportUnit(get_post_meta($variationId, static::META_VARIATION_SUPPORT_UNIT, true), ''),
        ];
    }

    /**
     * Find mapped Licenzo product from Woo product ID (meta-first, provider mapping fallback).
     *
     * @param int $wooProductId
     * @return Product|null
     */
    private function findLinkedLicenzoProduct(int $wooProductId): ?Product
    {
        $metaLinkedId = absint(get_post_meta($wooProductId, static::META_LICENZO_PRODUCT_ID, true));
        if ($metaLinkedId > 0) {
            $product = Product::with('variations')->find($metaLinkedId);
            if ($product) {
                return $product;
            }
        }

        $product = Product::whereIn('provider', ProviderAliasService::wooAliases())
            ->where('provider_id', (string) $wooProductId)
            ->with('variations')
            ->first();

        if ($product) {
            update_post_meta($wooProductId, static::META_LICENZO_PRODUCT_ID, (int) $product->id);
        }

        return $product;
    }

    /**
     * Render linked product info line.
     *
     * @param int $wooProductId
     * @param Product|null $linkedProduct
     * @return void
     */
    private function renderLinkedProductInfo(int $wooProductId, ?Product $linkedProduct): void
    {
        if ($wooProductId <= 0) {
            return;
        }

        echo '<p class="form-field">';
        echo '<label>' . esc_html__('Linked Licenzo Product', 'licenzo') . '</label>';

        if ($linkedProduct) {
            $url = $this->getLicenzoAdminProductEditUrl((int) $linkedProduct->id);
            echo '<span style="display:block;padding-top:7px;">';
            echo sprintf(
                /* translators: %1$d: Licenzo product ID, %2$s: product name */
                esc_html__('#%1$d %2$s', 'licenzo'),
                (int) $linkedProduct->id,
                esc_html((string) $linkedProduct->name)
            );
            if ($url !== '') {
                echo ' &middot; <a href="' . esc_url($url) . '" target="_blank" rel="noopener noreferrer">' . esc_html__('Open in Licenzo', 'licenzo') . '</a>';
            }
            echo '</span>';
        } else {
            echo '<span style="display:block;padding-top:7px;" class="description">' . esc_html__('No Licenzo product linked yet. Enable Sync to Licenzo and save this WooCommerce product to create one.', 'licenzo') . '</span>';
        }

        echo '</p>';
    }

    /**
     * Render last sync status line in panel.
     *
     * @param int $wooProductId
     * @return void
     */
    private function renderLastSyncStatus(int $wooProductId): void
    {
        if ($wooProductId <= 0) {
            return;
        }

        $status = (string) get_post_meta($wooProductId, static::META_LAST_SYNC_STATUS, true);
        $message = (string) get_post_meta($wooProductId, static::META_LAST_SYNC_MESSAGE, true);
        $at = (string) get_post_meta($wooProductId, static::META_LAST_SYNC_AT, true);

        if ($status === '' && $message === '') {
            return;
        }

        $label = $status === 'success' ? __('Last Sync', 'licenzo') : __('Last Sync Error', 'licenzo');
        $displayMessage = $message !== '' ? $message : ($status === 'success' ? __('Synced successfully.', 'licenzo') : __('Sync failed.', 'licenzo'));

        if ($at !== '') {
            /* translators: %s: UTC timestamp of last sync */
            $displayMessage .= ' ' . sprintf(__('(%s UTC)', 'licenzo'), esc_html($at));
        }

        echo '<p class="form-field">';
        echo '<label>' . esc_html($label) . '</label>';
        echo '<span style="display:block;padding-top:7px;" class="description">' . esc_html($displayMessage) . '</span>';
        echo '</p>';
    }

    /**
     * Store sync status metadata and add Woo admin error when failed.
     *
     * @param int $wooProductId
     * @param string $status
     * @param string $message
     * @return void
     */
    private function storeSyncStatus(int $wooProductId, string $status, string $message): void
    {
        update_post_meta($wooProductId, static::META_LAST_SYNC_STATUS, sanitize_key($status));
        update_post_meta($wooProductId, static::META_LAST_SYNC_MESSAGE, sanitize_text_field($message));
        update_post_meta($wooProductId, static::META_LAST_SYNC_AT, gmdate('Y-m-d H:i:s'));
    }

    /**
     * Fail helper that stores status and adds Woo save error notice.
     *
     * @param int $wooProductId
     * @param string $message
     * @return array
     */
    private function failSync(int $wooProductId, string $message): array
    {
        if ($wooProductId > 0) {
            $this->storeSyncStatus($wooProductId, 'error', $message);
        }

        if (class_exists('WC_Admin_Meta_Boxes') && method_exists('WC_Admin_Meta_Boxes', 'add_error')) {
            /* translators: %s: sync error message */
            \WC_Admin_Meta_Boxes::add_error(sprintf(__('Licenzo sync: %s', 'licenzo'), $message));
        }

        return [
            'success' => false,
            'message' => $message,
        ];
    }

    /**
     * Determine if Licenzo sync is enabled for the Woo product.
     *
     * @param int $postId
     * @return bool
     */
    private function isSyncEnabled(int $postId): bool
    {
        return $this->normalizeYesNo(get_post_meta($postId, static::META_ENABLE_SYNC, true)) === 'yes';
    }

    /**
     * Get posted array[index] value as string.
     *
     * @param string $key
     * @param int $index
     * @return string
     */
    private function getPostedIndexedValue(string $key, int $index): string
    {
        if (!isset($_POST[$key]) || !is_array($_POST[$key])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
            return '';
        }

        return isset($_POST[$key][$index]) ? sanitize_text_field(wp_unslash($_POST[$key][$index])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
    }

    /**
     * Update meta or delete when empty and allowed.
     *
     * @param int $postId
     * @param string $metaKey
     * @param mixed $value
     * @param bool $deleteWhenEmpty
     * @return void
     */
    private function updateMetaMaybeDelete(int $postId, string $metaKey, $value, bool $deleteWhenEmpty = false): void
    {
        $isEmpty = $value === '' || $value === null;

        if ($deleteWhenEmpty && $isEmpty) {
            delete_post_meta($postId, $metaKey);
            return;
        }

        update_post_meta($postId, $metaKey, $value);
    }

    /**
     * Resolve an admin URL to edit a Licenzo product in the SPA.
     *
     * @param int $licenzoProductId
     * @return string
     */
    private function getLicenzoAdminProductEditUrl(int $licenzoProductId): string
    {
        if ($licenzoProductId <= 0) {
            return '';
        }

        return admin_url('admin.php?page=licenzo-app') . '#/products/' . $licenzoProductId . '/edit';
    }

    /**
     * Build cycle select options.
     *
     * @param bool $allowBlankDefault
     * @return array
     */
    private function getPriceCycleOptions(bool $allowBlankDefault = false): array
    {
        $options = [];
        if ($allowBlankDefault) {
            $options[''] = __('Use default', 'licenzo');
        }

        $options[ProductVariation::PRICE_CYCLE_ONETIME] = __('One-time', 'licenzo');
        $options[ProductVariation::PRICE_CYCLE_WEEKLY] = __('Weekly', 'licenzo');
        $options[ProductVariation::PRICE_CYCLE_MONTHLY] = __('Monthly', 'licenzo');
        $options[ProductVariation::PRICE_CYCLE_YEARLY] = __('Yearly', 'licenzo');
        $options[ProductVariation::PRICE_CYCLE_LIFETIME] = __('Lifetime', 'licenzo');

        return $options;
    }

    /**
     * Get post meta as string with fallback.
     *
     * @param int $postId
     * @param string $metaKey
     * @param string $default
     * @return string
     */
    private function getPostMetaString(int $postId, string $metaKey, string $default = ''): string
    {
        if ($postId <= 0) {
            return $default;
        }

        $value = get_post_meta($postId, $metaKey, true);
        return $value === '' ? $default : (string) $value;
    }

    /**
     * Validate Licenzo nonce embedded in Woo product form.
     *
     * @return bool
     */
    private function hasValidLicenzoNonce(): bool
    {
        if (empty($_POST[static::NONCE_NAME])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
            return false;
        }

        return (bool) wp_verify_nonce(
            sanitize_text_field(wp_unslash($_POST[static::NONCE_NAME])), // phpcs:ignore WordPress.Security.NonceVerification.Missing
            static::NONCE_ACTION
        );
    }

    /**
     * Validate nonce for standard Woo product save request.
     *
     * Accepts the Licenzo panel nonce and falls back to Woo's product editor nonce.
     *
     * @return bool
     */
    private function hasValidProductSaveNonce(): bool
    {
        if ($this->hasValidLicenzoNonce()) {
            return true;
        }

        if (empty($_POST['woocommerce_meta_nonce'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
            return false;
        }

        return (bool) wp_verify_nonce(
            sanitize_text_field(wp_unslash($_POST['woocommerce_meta_nonce'])), // phpcs:ignore WordPress.Security.NonceVerification.Missing
            'woocommerce_save_data'
        );
    }

    /**
     * Validate nonce for Woo variation AJAX save requests.
     *
     * Woo variation saves use the AJAX `security` nonce (`save-variations`) and may not include
     * custom panel fields/nonces from other product data tabs in every request payload.
     *
     * @return bool
     */
    private function hasValidVariationSaveNonce(): bool
    {
        if ($this->hasValidLicenzoNonce()) {
            return true;
        }

        if (!empty($_POST['security']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['security'])), 'save-variations')) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
            return true;
        }

        return $this->hasValidProductSaveNonce();
    }

    /**
     * Normalize yes/no values.
     *
     * @param mixed $value
     * @return string
     */
    private function normalizeYesNo($value): string
    {
        return in_array(strtolower((string) $value), ['1', 'true', 'yes', 'on'], true) ? 'yes' : 'no';
    }

    /**
     * Normalize product status.
     *
     * @param mixed $value
     * @param string|null $default
     * @return string
     */
    private function normalizeProductStatus($value, ?string $default = null): string
    {
        $default = $default ?: Product::STATUS_ACTIVE;
        $value = sanitize_key((string) $value);
        $allowed = [
            Product::STATUS_ACTIVE,
            Product::STATUS_INACTIVE,
            Product::STATUS_DRAFT,
        ];

        return in_array($value, $allowed, true) ? $value : $default;
    }

    /**
     * Normalize price cycle.
     *
     * @param mixed $value
     * @param string $default
     * @return string
     */
    private function normalizePriceCycle($value, string $default = ProductVariation::PRICE_CYCLE_MONTHLY): string
    {
        $value = sanitize_key((string) $value);
        $allowed = [
            ProductVariation::PRICE_CYCLE_ONETIME,
            ProductVariation::PRICE_CYCLE_WEEKLY,
            ProductVariation::PRICE_CYCLE_MONTHLY,
            ProductVariation::PRICE_CYCLE_YEARLY,
            ProductVariation::PRICE_CYCLE_LIFETIME,
        ];

        if ($value === '' && $default === '') {
            return '';
        }

        return in_array($value, $allowed, true) ? $value : $default;
    }

    /**
     * Normalize support unit.
     *
     * @param mixed $value
     * @param string $default
     * @return string
     */
    private function normalizeSupportUnit($value, string $default = ProductVariation::SUPPORT_UNIT_DAY): string
    {
        $value = sanitize_key((string) $value);
        $allowed = [
            ProductVariation::SUPPORT_UNIT_DAY,
            ProductVariation::SUPPORT_UNIT_MONTH,
            ProductVariation::SUPPORT_UNIT_YEAR,
        ];

        if ($value === '' && $default === '') {
            return '';
        }

        return in_array($value, $allowed, true) ? $value : $default;
    }

    /**
     * Normalize positive integer (min 1).
     *
     * @param mixed $value
     * @param int $default
     * @return int
     */
    private function normalizePositiveInt($value, int $default = 1): int
    {
        $number = absint($value);
        return $number > 0 ? $number : $default;
    }

    /**
     * Normalize non-negative integer (min 0).
     *
     * @param mixed $value
     * @param int $default
     * @return int
     */
    private function normalizeNonNegativeInt($value, int $default = 0): int
    {
        if ($value === '' || $value === null) {
            return $default;
        }

        return max(0, (int) $value);
    }

    /**
     * Normalize optional positive integer (blank => null).
     *
     * @param mixed $value
     * @return int|null
     */
    private function normalizeOptionalPositiveInt($value): ?int
    {
        if ($value === '' || $value === null) {
            return null;
        }

        $number = absint($value);
        return $number > 0 ? $number : null;
    }

    /**
     * Normalize optional non-negative integer (blank => null).
     *
     * @param mixed $value
     * @return int|null
     */
    private function normalizeOptionalNonNegativeInt($value): ?int
    {
        if ($value === '' || $value === null) {
            return null;
        }

        return max(0, (int) $value);
    }

    /**
     * Resolve unique Licenzo product name when a conflicting product already exists.
     *
     * @param string $baseName
     * @param int $wooProductId
     * @param int|null $ignoreProductId
     * @return string
     */
    private function resolveLicenzoProductName(string $baseName, int $wooProductId, ?int $ignoreProductId = null): string
    {
        $baseName = trim($baseName);
        if ($baseName === '') {
            $baseName = 'Woo Product #' . $wooProductId;
        }

        $query = Product::where('name', $baseName);
        if ($ignoreProductId) {
            $query->where('id', '!=', $ignoreProductId);
        }

        if (!$query->exists()) {
            return $baseName;
        }

        $candidate = sprintf('%s (Woo %d)', $baseName, $wooProductId);
        $query = Product::where('name', $candidate);
        if ($ignoreProductId) {
            $query->where('id', '!=', $ignoreProductId);
        }

        if (!$query->exists()) {
            return $candidate;
        }

        $suffix = 2;
        while (true) {
            $numbered = sprintf('%s (Woo %d-%d)', $baseName, $wooProductId, $suffix);
            $query = Product::where('name', $numbered);
            if ($ignoreProductId) {
                $query->where('id', '!=', $ignoreProductId);
            }

            if (!$query->exists()) {
                return $numbered;
            }

            $suffix++;
        }
    }

    /**
     * Generate a unique slug for Licenzo product records.
     *
     * @param string $name
     * @param int $wooProductId
     * @param int|null $ignoreProductId
     * @return string
     */
    private function generateUniqueSlug(string $name, int $wooProductId, ?int $ignoreProductId = null): string
    {
        $base = sanitize_title($name);
        if ($base === '') {
            $base = 'woo-product-' . $wooProductId;
        }

        $candidate = $base;
        $suffix = 1;

        while (true) {
            $query = Product::where('slug', $candidate);
            if ($ignoreProductId) {
                $query->where('id', '!=', $ignoreProductId);
            }

            if (!$query->exists()) {
                return $candidate;
            }

            $suffix++;
            $candidate = $base . '-' . $wooProductId . '-' . $suffix;
        }
    }

    /**
     * Resolve Woo regular price as float.
     *
     * @param \WC_Product $wooProduct
     * @return float
     */
    private function resolveWooRegularPrice($wooProduct): float
    {
        $submitted = $this->resolveSubmittedWooPrice($wooProduct, 'regular');
        if ($submitted['found'] && $submitted['value'] !== null) {
            return $submitted['value'];
        }

        $metaRegular = $this->resolveWooMetaPrice($wooProduct, '_regular_price');
        if ($metaRegular !== null) {
            return $metaRegular;
        }

        $regular = $this->normalizeWooPriceValue($wooProduct->get_regular_price());
        if ($regular !== null) {
            return $regular;
        }

        $metaCurrent = $this->resolveWooMetaPrice($wooProduct, '_price');
        if ($metaCurrent !== null) {
            return $metaCurrent;
        }

        $current = $this->normalizeWooPriceValue($wooProduct->get_price());
        if ($current !== null) {
            return $current;
        }

        return 0.0;
    }

    /**
     * Resolve Woo sale price as nullable float.
     *
     * @param \WC_Product $wooProduct
     * @return float|null
     */
    private function resolveWooSalePrice($wooProduct): ?float
    {
        $submitted = $this->resolveSubmittedWooPrice($wooProduct, 'sale');
        if ($submitted['found']) {
            return $submitted['value'];
        }

        $metaSale = $this->resolveWooMetaPrice($wooProduct, '_sale_price');
        if ($metaSale !== null) {
            return $metaSale;
        }

        return $this->normalizeWooPriceValue($wooProduct->get_sale_price());
    }

    /**
     * Resolve Woo price submitted in current admin request.
     *
     * @param \WC_Product $wooProduct
     * @param string $type
     * @return array{found: bool, value: float|null}
     */
    private function resolveSubmittedWooPrice($wooProduct, string $type): array
    {
        $wooProductId = is_object($wooProduct) && method_exists($wooProduct, 'get_id')
            ? (int) $wooProduct->get_id()
            : 0;

        if ($wooProductId <= 0) {
            return ['found' => false, 'value' => null];
        }

        $rawPrice = $this->getSubmittedWooPriceRawValue($wooProductId, $type);
        if ($rawPrice === null) {
            return ['found' => false, 'value' => null];
        }

        return [
            'found' => true,
            'value' => $this->normalizeWooPriceValue($rawPrice),
        ];
    }

    /**
     * Resolve Woo product/variation submitted price raw value.
     *
     * @param int $wooProductId
     * @param string $type
     * @return string|null
     */
    private function getSubmittedWooPriceRawValue(int $wooProductId, string $type): ?string
    {
        $variationIds = [];
        if (isset($_POST['variable_post_id']) && is_array($_POST['variable_post_id'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
            $variationIds = array_map('absint', (array) wp_unslash($_POST['variable_post_id'])); // phpcs:ignore WordPress.Security.NonceVerification.Missing
        }

        $variationPriceField = $type === 'sale' ? 'variable_sale_price' : 'variable_regular_price';
        if (!empty($variationIds) && in_array($wooProductId, $variationIds, true)) {
            if (!isset($_POST[$variationPriceField]) || !is_array($_POST[$variationPriceField])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
                return '';
            }

            $variationPrices = array_map('sanitize_text_field', (array) wp_unslash($_POST[$variationPriceField])); // phpcs:ignore WordPress.Security.NonceVerification.Missing

            foreach ($variationIds as $index => $variationId) {
                if ((int) $variationId !== $wooProductId) {
                    continue;
                }

                if (!array_key_exists($index, $variationPrices)) {
                    return '';
                }

                $value = $variationPrices[$index];
                return is_scalar($value) ? (string) $value : '';
            }
        }

        $simpleField = $type === 'sale' ? '_sale_price' : '_regular_price';
        if (isset($_POST[$simpleField])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
            return sanitize_text_field(wp_unslash($_POST[$simpleField])); // phpcs:ignore WordPress.Security.NonceVerification.Missing
        }

        return null;
    }

    /**
     * Resolve Woo product/variation price from post meta.
     *
     * @param \WC_Product $wooProduct
     * @param string $metaKey
     * @return float|null
     */
    private function resolveWooMetaPrice($wooProduct, string $metaKey): ?float
    {
        $wooProductId = is_object($wooProduct) && method_exists($wooProduct, 'get_id')
            ? (int) $wooProduct->get_id()
            : 0;

        if ($wooProductId <= 0) {
            return null;
        }

        return $this->normalizeWooPriceValue(get_post_meta($wooProductId, $metaKey, true));
    }

    /**
     * Normalize Woo price values to nullable float.
     *
     * @param mixed $value
     * @return float|null
     */
    private function normalizeWooPriceValue($value): ?float
    {
        if ($value === '' || $value === null) {
            return null;
        }

        return (float) wc_format_decimal($value);
    }
}
