<?php
defined('ABSPATH') || exit;

/**
 * Queries WooCommerce for products eligible for the merchant feed.
 *
 * INCLUDED types:  simple, variation, composite, bundle
 * EXCLUDED:        variable (parent) products — their variations are synced instead
 *
 * Per-product exclusion: _jra_merchant_exclude meta = '1' on the product or its parent
 * Per-category exclusion: WC categories listed in the jra_merchant_excluded_categories option
 *
 * All queries use WP_Query / get_posts (not wc_get_products) so we can
 * efficiently retrieve only IDs and apply date filters.
 */
class JRA_Merchant_Product_Query {

    /** @var int[] Category IDs to exclude from the feed */
    private array $excludedCategoryIds;

    public function __construct() {
        $this->excludedCategoryIds = array_map('intval', (array) get_option('jra_merchant_excluded_categories', []));
    }

    // -------------------------------------------------------------------------
    // Public API
    // -------------------------------------------------------------------------

    /**
     * All product IDs eligible for the feed (full sync).
     *
     * @return int[]
     */
    public function getAllProductIds(): array {
        return $this->buildQuery(null);
    }

    /**
     * A small sample of eligible product IDs for preview / dry-run.
     * Returns at most $limit IDs, taken from the head of the full set.
     *
     * @return int[]
     */
    public function getPreviewIds(int $limit = 10): array {
        return array_slice($this->buildQuery(null), 0, $limit);
    }

    /**
     * Product IDs modified after the given UTC timestamp (delta sync).
     *
     * @return int[]
     */
    public function getModifiedSince(DateTime $since): array {
        return $this->buildQuery($since);
    }

    /**
     * IDs of trashed products (for deletion sync).
     * Uses direct meta access for SKU since wc_get_product() returns false for trash.
     *
     * @return array<int, string>  [ product_id => offer_id ]
     */
    public function getTrashedOfferIds(?DateTime $since = null): array {
        $args = [
            'post_type'      => ['product', 'product_variation'],
            'post_status'    => 'trash',
            'posts_per_page' => -1,
            'fields'         => 'ids',
        ];

        if ($since) {
            $args['date_query'] = [[
                'column'    => 'post_modified_gmt',
                'after'     => $since->format('Y-m-d H:i:s'),
                'inclusive' => false,
            ]];
        }

        $ids    = get_posts($args);
        $offers = [];
        foreach ($ids as $id) {
            $sku          = get_post_meta((int)$id, '_sku', true);
            $offers[(int)$id] = 'SKU-' . ($sku !== '' ? $sku : $id);
        }

        return $offers;
    }

    // -------------------------------------------------------------------------
    // Private
    // -------------------------------------------------------------------------

    /** @return int[] */
    private function buildQuery(?DateTime $modifiedAfter): array {
        $ids = array_merge(
            $this->querySimpleProducts($modifiedAfter),
            $this->queryVariations($modifiedAfter)
        );

        $ids = array_unique(array_map('intval', $ids));

        return array_values(array_filter($ids, fn(int $id) => !$this->isExcluded($id)));
    }

    /** @return int[] Simple, composite, and bundle products */
    private function querySimpleProducts(?DateTime $modifiedAfter): array {
        $args = [
            'post_type'      => 'product',
            'post_status'    => 'publish',
            'posts_per_page' => -1,
            'fields'         => 'ids',
            'tax_query'      => [[ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
                'taxonomy' => 'product_type',
                'field'    => 'slug',
                'terms'    => ['simple', 'composite', 'bundle'],
            ]],
        ];

        if ($modifiedAfter) {
            $args['date_query'] = [[
                'column'    => 'post_modified_gmt',
                'after'     => $modifiedAfter->format('Y-m-d H:i:s'),
                'inclusive' => false,
            ]];
        }

        return (array) get_posts($args);
    }

    /** @return int[] Published product variations */
    private function queryVariations(?DateTime $modifiedAfter): array {
        $args = [
            'post_type'      => 'product_variation',
            'post_status'    => 'publish',
            'posts_per_page' => -1,
            'fields'         => 'ids',
        ];

        if ($modifiedAfter) {
            $args['date_query'] = [[
                'column'    => 'post_modified_gmt',
                'after'     => $modifiedAfter->format('Y-m-d H:i:s'),
                'inclusive' => false,
            ]];
        }

        return (array) get_posts($args);
    }

    private function isExcluded(int $id): bool {
        // Direct flag on this product
        if (get_post_meta($id, '_jra_merchant_exclude', true) === '1') {
            return true;
        }

        $post = get_post($id);
        if (!$post) {
            return true;
        }

        // For variations: also check parent's flag
        $categorySourceId = $id;
        if ($post->post_type === 'product_variation') {
            $parentId = (int)$post->post_parent;
            if ($parentId && get_post_meta($parentId, '_jra_merchant_exclude', true) === '1') {
                return true;
            }
            $categorySourceId = $parentId ?: $id;
        }

        // Category exclusion (Pro)
        if ( jpffgmc_fs()->is__premium_only() ) {
            if (!empty($this->excludedCategoryIds)) {
                $catIds = wp_get_post_terms($categorySourceId, 'product_cat', ['fields' => 'ids']);
                if (is_array($catIds) && array_intersect($catIds, $this->excludedCategoryIds)) {
                    return true;
                }
            }
        }

        return false;
    }
}
