<?php
/**
 * Bundle Product Selector Service
 * 
 * Handles product filtering, scoring, and selection for bundle building.
 * Supports specific products, categories, preferred products, and exclusions.
 *
 * @package BoostBot
 * @since 1.2.0
 */

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

class BoostBot_Bundle_Product_Selector {

    /**
     * Product settings
     *
     * @var array
     */
    private $settings;

    /**
     * Constructor
     *
     * @param array $settings Widget product settings
     */
    public function __construct( array $settings = array() ) {
        $this->settings = wp_parse_args( $settings, $this->get_default_settings() );
    }

    /**
     * Get default product settings
     *
     * @return array
     */
    private function get_default_settings(): array {
        return array(
            // Mode: all | categories | specific
            'mode'                     => 'all',

            // Categories filter
            'allowed_categories'       => array(),
            'excluded_categories'      => array(),

            // Specific products
            'specific_product_ids'     => array(),

            // Preferred products (higher score)
            'preferred_product_ids'    => array(),

            // Excluded products
            'excluded_product_ids'     => array(),

            // Filters
            'include_on_sale'          => true,
            'include_variable'         => true,
            'include_featured_only'    => false,
            'min_stock'                => 1,
            'min_rating'               => 0,

            // Limits
            'min_products_in_bundle'   => 2,
            'max_products_in_bundle'   => 5,
            'max_single_product_percent' => 50,
        );
    }

    /**
     * Get candidate products based on settings and criteria
     *
     * @param array  $criteria Customer criteria (budget, category, preferences)
     * @param string $language Target language slug for Polylang translation (e.g. 'he', 'en').
     * @return array Scored products array
     */
    public function get_candidates( array $criteria, string $language = '' ): array {
        // Build WooCommerce query args
        $args = array(
            'status'       => 'publish',
            'limit'        => 200,
            'stock_status' => 'instock',
            'orderby'      => 'popularity',
            'order'        => 'DESC',
        );

        // Mode-based filtering
        switch ( $this->settings['mode'] ) {
            case 'specific':
                if ( ! empty( $this->settings['specific_product_ids'] ) ) {
                    $args['include'] = array_map( 'intval', $this->settings['specific_product_ids'] );
                }
                break;

            case 'categories':
                if ( ! empty( $this->settings['allowed_categories'] ) ) {
                    $cats = $this->settings['allowed_categories'];
                    if ( is_numeric( $cats[0] ) ) {
                        // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- WooCommerce product category filtering
                        $args['tax_query'] = array( array(
                            'taxonomy' => 'product_cat',
                            'field'    => 'term_id',
                            'terms'    => array_map( 'absint', $cats ),
                        ) );
                    } else {
                        $args['category'] = $cats;
                    }
                }
                break;
        }

        // Customer category preference
        if ( ! empty( $criteria['category'] ) ) {
            $args['category'] = array( sanitize_text_field( $criteria['category'] ) );
        }

        // Get products from WooCommerce
        $products = wc_get_products( $args );

        if ( empty( $products ) ) {
            return array();
        }

        // Resolve products to the target language via Polylang translations.
        if ( $language && function_exists( 'pll_get_post_language' ) && function_exists( 'pll_get_post' ) ) {
            $resolved = array();
            $seen_ids = array();
            foreach ( $products as $product ) {
                $pid  = $product->get_id();
                $lang = pll_get_post_language( $pid );

                if ( $lang && $lang !== $language ) {
                    $translated_id = pll_get_post( $pid, $language );
                    if ( $translated_id && $translated_id !== $pid ) {
                        $translated = wc_get_product( $translated_id );
                        if ( $translated ) {
                            $product = $translated;
                            $pid     = $translated_id;
                        }
                    }
                }

                if ( in_array( $pid, $seen_ids, true ) ) {
                    continue;
                }
                $seen_ids[] = $pid;
                $resolved[] = $product;
            }
            $products = $resolved;
        }

        // Apply additional filters
        $products = $this->apply_filters( $products );

        // Apply exclusions
        $products = $this->apply_exclusions( $products );

        // Score products based on criteria
        $scored = $this->score_products( $products, $criteria );

        return $scored;
    }

    /**
     * Get a single replacement product for an existing bundle item.
     *
     * Returns the best-scoring product that is not already part of the current
     * bundle and still fits inside the remaining shopper budget.
     *
     * @param int         $swap_out_id       Product ID being replaced.
     * @param array       $exclude_ids       Product IDs already present in the bundle.
     * @param float       $remaining_budget  Remaining pre-discount budget for the replacement item.
     * @param string|null $category          Optional category constraint.
     * @param array       $preferences       Optional preference keywords.
     * @param string      $language          Target language slug for Polylang translation.
     * @return array|null Replacement product payload or null when no candidate fits.
     */
    public function get_replacement_candidate(
        int $swap_out_id,
        array $exclude_ids,
        float $remaining_budget,
        ?string $category,
        array $preferences = array(),
        string $language = ''
    ): ?array {
        if ( $remaining_budget <= 0 ) {
            return null;
        }

        $criteria = array(
            'budget'      => $remaining_budget,
            'category'    => $category ? sanitize_text_field( $category ) : null,
            'preferences' => array_values(
                array_filter(
                    array_map( 'sanitize_text_field', $preferences )
                )
            ),
        );

        $excluded_ids = array_values(
            array_unique(
                array_filter(
                    array_map( 'intval', array_merge( $exclude_ids, array( $swap_out_id ) ) )
                )
            )
        );

        $scored_products = $this->get_candidates( $criteria, $language );
        $max_single_percent = floatval( $this->settings['max_single_product_percent'] ?? 0 );

        foreach ( $scored_products as $item ) {
            $product = $item['product'] ?? null;
            if ( ! $product || ! is_object( $product ) || ! method_exists( $product, 'get_id' ) ) {
                continue;
            }

            $product_id = (int) $product->get_id();
            if ( in_array( $product_id, $excluded_ids, true ) ) {
                continue;
            }

            $price = (float) $product->get_price();
            if ( $price <= 0 || $price > $remaining_budget ) {
                continue;
            }

            if ( $max_single_percent > 0 && $price > ( $remaining_budget * $max_single_percent / 100 ) ) {
                continue;
            }

            return $this->format_product_for_bundle( $product, (float) ( $item['score'] ?? 0 ) );
        }

        return null;
    }

    /**
     * Apply setting-based filters
     *
     * @param array $products WC_Product array
     * @return array Filtered products
     */
    private function apply_filters( array $products ): array {
        return array_filter( $products, function( $product ) {
            // Variable products filter
            if ( $product->is_type( 'variable' ) && ! $this->settings['include_variable'] ) {
                return false;
            }

            // Featured only filter
            if ( $this->settings['include_featured_only'] && ! $product->is_featured() ) {
                return false;
            }

            // On sale filter (exclude non-sale if setting is false)
            if ( ! $this->settings['include_on_sale'] && $product->is_on_sale() ) {
                return false;
            }

            // Min stock filter
            $min_stock = intval( $this->settings['min_stock'] );
            if ( $min_stock > 0 && $product->get_stock_quantity() !== null ) {
                if ( $product->get_stock_quantity() < $min_stock ) {
                    return false;
                }
            }

            // Min rating filter
            $min_rating = floatval( $this->settings['min_rating'] );
            if ( $min_rating > 0 && $product->get_average_rating() < $min_rating ) {
                return false;
            }

            // Excluded categories
            if ( ! empty( $this->settings['excluded_categories'] ) ) {
                $product_cats = wp_get_post_terms( $product->get_id(), 'product_cat', array( 'fields' => 'slugs' ) );
                $excluded = array_map( 'sanitize_title', $this->settings['excluded_categories'] );
                
                if ( array_intersect( $product_cats, $excluded ) ) {
                    return false;
                }
            }

            return true;
        } );
    }

    /**
     * Remove excluded products
     *
     * @param array $products WC_Product array
     * @return array Filtered products
     */
    private function apply_exclusions( array $products ): array {
        $excluded = array_map( 'intval', $this->settings['excluded_product_ids'] );

        if ( empty( $excluded ) ) {
            return $products;
        }

        return array_filter( $products, function( $product ) use ( $excluded ) {
            return ! in_array( $product->get_id(), $excluded, true );
        } );
    }

    /**
     * Score products for ranking
     *
     * @param array $products WC_Product array
     * @param array $criteria Customer criteria
     * @return array Scored products
     */
    private function score_products( array $products, array $criteria ): array {
        $preferred = array_map( 'intval', $this->settings['preferred_product_ids'] );
        $scored = array();

        foreach ( $products as $product ) {
            $score = 0;

            // Preferred products get massive boost (+100)
            if ( in_array( $product->get_id(), $preferred, true ) ) {
                $score += 100;
            }

            // Sales count score (max 20 points)
            $sales = intval( $product->get_total_sales() );
            $score += min( $sales / 10, 20 );

            // Rating score (max 25 points)
            $rating = floatval( $product->get_average_rating() );
            $score += $rating * 5;

            // Review count score (max 15 points)
            $reviews = intval( $product->get_review_count() );
            $score += min( $reviews, 15 );

            // On sale bonus (+10)
            if ( $product->is_on_sale() ) {
                $score += 10;
            }

            // Featured bonus (+15)
            if ( $product->is_featured() ) {
                $score += 15;
            }

            // Preference matching (from customer input)
            if ( ! empty( $criteria['preferences'] ) ) {
                foreach ( (array) $criteria['preferences'] as $pref ) {
                    if ( $this->matches_preference( $product, $pref ) ) {
                        $score += 25;
                    }
                }
            }

            // Occasion matching (if provided)
            if ( ! empty( $criteria['occasion'] ) ) {
                if ( $this->matches_occasion( $product, $criteria['occasion'] ) ) {
                    $score += 20;
                }
            }

            $scored[] = array(
                'product' => $product,
                'score'   => $score,
            );
        }

        // Sort by score descending
        usort( $scored, function( $a, $b ) {
            return $b['score'] - $a['score'];
        } );

        return $scored;
    }

    /**
     * Check if product matches a preference keyword
     *
     * @param WC_Product $product
     * @param string $pref Preference keyword
     * @return bool
     */
    private function matches_preference( $product, string $pref ): bool {
        $pref = mb_strtolower( trim( $pref ) );

        if ( empty( $pref ) ) {
            return false;
        }

        // Check name
        if ( mb_strpos( mb_strtolower( $product->get_name() ), $pref ) !== false ) {
            return true;
        }

        // Check short description
        if ( mb_strpos( mb_strtolower( $product->get_short_description() ), $pref ) !== false ) {
            return true;
        }

        // Check description
        if ( mb_strpos( mb_strtolower( $product->get_description() ), $pref ) !== false ) {
            return true;
        }

        // Check categories
        $cats = wp_get_post_terms( $product->get_id(), 'product_cat', array( 'fields' => 'names' ) );
        foreach ( $cats as $cat ) {
            if ( mb_strpos( mb_strtolower( $cat ), $pref ) !== false ) {
                return true;
            }
        }

        // Check tags
        $tags = wp_get_post_terms( $product->get_id(), 'product_tag', array( 'fields' => 'names' ) );
        foreach ( $tags as $tag ) {
            if ( mb_strpos( mb_strtolower( $tag ), $pref ) !== false ) {
                return true;
            }
        }

        // Check attributes
        foreach ( $product->get_attributes() as $attr ) {
            $values = array();
            if ( is_object( $attr ) && method_exists( $attr, 'get_options' ) ) {
                $values = $attr->get_options();
            } elseif ( is_array( $attr ) ) {
                $values = $attr;
            }
            
            foreach ( $values as $val ) {
                if ( mb_strpos( mb_strtolower( (string) $val ), $pref ) !== false ) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Check if product matches occasion
     *
     * @param WC_Product $product
     * @param string $occasion Occasion type
     * @return bool
     */
    private function matches_occasion( $product, string $occasion ): bool {
        $occasion_keywords = array(
            'birthday'  => array( 'יום הולדת', 'מתנה', 'חגיגה', 'birthday', 'gift' ),
            'wedding'   => array( 'חתונה', 'כלה', 'חתן', 'wedding', 'bridal' ),
            'holiday'   => array( 'חג', 'פסח', 'ראש השנה', 'חנוכה', 'holiday' ),
            'self'      => array(), // No specific keywords needed
        );

        $keywords = $occasion_keywords[ $occasion ] ?? array();

        if ( empty( $keywords ) ) {
            return false;
        }

        foreach ( $keywords as $kw ) {
            if ( $this->matches_preference( $product, $kw ) ) {
                return true;
            }
        }

        return false;
    }

    /**
     * Build optimal bundle from scored products
     *
     * @param array $scored Scored products
     * @param float $budget Customer budget
     * @return array Bundle data
     */
    public function build_bundle( array $scored, float $budget ): array {
        $min_products = intval( $this->settings['min_products_in_bundle'] );
        $max_products = intval( $this->settings['max_products_in_bundle'] );
        $max_single_percent = floatval( $this->settings['max_single_product_percent'] );

        $bundle = array();
        $total = 0;

        foreach ( $scored as $item ) {
            $product = $item['product'];
            $price = floatval( $product->get_price() );

            // Skip if no price
            if ( $price <= 0 ) {
                continue;
            }

            // Check if single product would exceed percentage limit
            if ( $max_single_percent > 0 && $price > ( $budget * $max_single_percent / 100 ) ) {
                continue;
            }

            // Check if adding this product exceeds budget
            if ( count( $bundle ) < $max_products && ( $total + $price ) <= $budget ) {
                $bundle[] = $this->format_product_for_bundle( $product, (float) $item['score'] );

                $total += $price;
            }

            // Check if we have enough products and budget is well-used (70%+)
            if ( count( $bundle ) >= $min_products && $total >= $budget * 0.7 ) {
                break;
            }
        }

        // Calculate totals
        $original_total = array_sum( array_column( $bundle, 'original_price' ) );

        return array(
            'products'       => $bundle,
            'total_price'    => round( $total, 2 ),
            'original_price' => round( $original_total, 2 ),
            'product_count'  => count( $bundle ),
        );
    }

    /**
     * Get available categories for discovery step
     *
     * @return array Category names
     */
    public function get_available_categories( string $language = '' ): array {
        $args = array(
            'taxonomy'   => 'product_cat',
            'hide_empty' => true,
            'parent'     => 0, // Top-level only
        );

        if ( $language ) {
            $args['lang'] = $language;
        }

        // Filter by allowed categories if specified
        if ( ! empty( $this->settings['allowed_categories'] ) ) {
            $args['slug'] = $this->settings['allowed_categories'];
        }

        // Exclude categories
        if ( ! empty( $this->settings['excluded_categories'] ) ) {
            $args['exclude'] = array();
            foreach ( $this->settings['excluded_categories'] as $cat_slug ) {
                $term = get_term_by( 'slug', $cat_slug, 'product_cat' );
                if ( $term ) {
                    $args['exclude'][] = $term->term_id;
                }
            }
        }

        $terms = get_terms( $args );

        if ( is_wp_error( $terms ) ) {
            return array();
        }

        if ( $language && function_exists( 'pll_get_term_language' ) && function_exists( 'pll_get_term' ) ) {
            $resolved = array();
            $seen_ids = array();

            foreach ( $terms as $term ) {
                $term_id   = (int) $term->term_id;
                $term_lang = pll_get_term_language( $term_id );

                if ( $term_lang && $term_lang !== $language ) {
                    $translated_id = pll_get_term( $term_id, $language );
                    if ( ! $translated_id || $translated_id === $term_id ) {
                        continue;
                    }

                    $translated_term = get_term( $translated_id, 'product_cat' );
                    if ( $translated_term && ! is_wp_error( $translated_term ) ) {
                        $term    = $translated_term;
                        $term_id = (int) $translated_term->term_id;
                    } else {
                        continue;
                    }
                }

                if ( in_array( $term_id, $seen_ids, true ) ) {
                    continue;
                }

                $seen_ids[] = $term_id;
                $resolved[] = $term;
            }

            $terms = $resolved;
        }

        return array_map( function( $term ) {
            return array(
                'slug'  => $term->slug,
                'name'  => $term->name,
                'count' => $term->count,
            );
        }, $terms );
    }

    /**
     * Normalize a WooCommerce product into the bundle payload shape.
     *
     * @param WC_Product $product WooCommerce product.
     * @param float      $score   Product ranking score.
     * @return array
     */
    private function format_product_for_bundle( $product, float $score = 0.0 ): array {
        $price         = (float) $product->get_price();
        $regular_price = (float) $product->get_regular_price();
        $original      = $regular_price > 0 ? $regular_price : $price;

        return array(
            'id'             => (int) $product->get_id(),
            'name'           => $product->get_name(),
            'price'          => $price,
            'regular_price'  => $regular_price,
            'original_price' => $original,
            'image'          => wp_get_attachment_url( $product->get_image_id() ) ?: '',
            'permalink'      => urldecode( $product->get_permalink() ),
            'rating'         => (float) $product->get_average_rating(),
            'on_sale'        => (bool) $product->is_on_sale(),
            'score'          => round( $score, 2 ),
        );
    }
}





