<?php
/** Paid profile-aware ranking for One-click Store Types. */
class BeRocket_AAPF_Paid_One_Click_Store_Type_Ranking {
    /**
     * A short-lived override used only while preparing the client-side preview
     * sets. It never changes the stored selection and therefore cannot affect
     * another request or the eventual setup identity.
     */
    protected static $preview_type_override = null;

    public function __construct() {
        add_filter('brapf_one_click_attribute_semantic_priority', array($this, 'add_profile_semantics'), 20, 3);
        add_filter('brapf_one_click_recommendation_ranking_config', array($this, 'extend_profile_limit'));
        add_filter('brapf_one_click_recommendation_candidates', array($this, 'prioritize_candidates'), 20, 5);
        add_filter('brapf_one_click_recommendations', array($this, 'annotate_recommendations'), 20, 6);
    }

    /**
     * Preset semantics supplement, rather than replace, the core semantics.
     * They are only evaluated while a paid profile is active.
     */
    public function add_profile_semantics($semantic, $taxonomy, $label) {
        $profile = $this->get_active_profile();
        if (empty($profile['semantics'])) {
            return $semantic;
        }
        $name = strtolower(str_replace(array('pa_', '_', '-'), array('', ' ', ' '), (string)$taxonomy) . ' ' . (string)$label);
        foreach ($profile['semantics'] as $kind => $definition) {
            foreach ($definition['aliases'] as $alias) {
                if (strpos($name, $alias) !== false) {
                    return array('priority' => $definition['priority'], 'kind' => $kind);
                }
            }
        }
        return $semantic;
    }

    /** Paid profiles may use Price plus six eligible additional filters. */
    public function extend_profile_limit($config) {
        if (!empty($this->get_active_profile())) {
            $config['max_additional_filters'] = 6;
        }
        return $config;
    }

    /**
     * Candidates reach this method only after core eligibility checks. We do
     * not add candidates or change their definitions, only their score/reason.
     */
    public function prioritize_candidates($candidates, $snapshot, $attribute_analysis, $capability, $config) {
        $profile = $this->get_active_profile($capability);
        if (empty($profile)) {
            return $candidates;
        }
        foreach ((array)$candidates as $index => $candidate) {
            $role = $this->get_candidate_role($candidate);
            if (!$role || !isset($profile['priorities'][$role])) {
                continue;
            }
            $candidates[$index]['score'] = (float)$candidate['score'] + (float)$profile['priorities'][$role];
            $candidates[$index]['reasons'] = array_values(array_unique(array_merge(
                isset($candidate['reasons']) && is_array($candidate['reasons']) ? $candidate['reasons'] : array(),
                array('store_type:' . $profile['id'], 'preset_priority:' . $role)
            )));
        }
        return $candidates;
    }

    /** Store the profile provenance in recommendation details for UI/debugging. */
    public function annotate_recommendations($recommendations, $candidates, $snapshot, $attribute_analysis, $capability, $config) {
        $profile = $this->get_active_profile($capability);
        if (empty($profile)) {
            return $recommendations;
        }
        foreach ((array)$recommendations as $index => $recommendation) {
            $recommendations[$index]['details'] = isset($recommendation['details']) && is_array($recommendation['details'])
                ? $recommendation['details']
                : array();
            $recommendations[$index]['details']['store_type'] = $profile['id'];
        }
        return $recommendations;
    }

    /** Run a query-free preview calculation for a specific allowed profile. */
    public static function with_preview_type($type, $callback) {
        $previous_type = self::$preview_type_override;
        self::$preview_type_override = BeRocket_AAPF_Paid_One_Click_Store_Types::normalize_type($type);
        try {
            return call_user_func($callback);
        } finally {
            self::$preview_type_override = $previous_type;
        }
    }

    protected function get_candidate_role($candidate) {
        $family = isset($candidate['family']) ? $candidate['family'] : '';
        $family_roles = array(
            'price' => 'price',
            'categories' => 'categories',
            'brand' => 'brand',
            'rating' => 'rating',
        );
        if (isset($family_roles[$family])) {
            return $family_roles[$family];
        }
        $semantic = isset($candidate['details']['attribute_metrics']['semantic']['kind'])
            ? $candidate['details']['attribute_metrics']['semantic']['kind']
            : '';
        return is_string($semantic) ? sanitize_key($semantic) : '';
    }

    protected function get_active_profile($capability = null) {
        $type = self::$preview_type_override !== null && BeRocket_AAPF_Paid_One_Click_Store_Types::is_available($capability)
            ? self::$preview_type_override
            : BeRocket_AAPF_Paid_One_Click_Store_Types::get_active_type($capability);
        $profiles = $this->get_profiles();
        return isset($profiles[$type]) ? $profiles[$type] : array();
    }

    /**
     * Priority bands express a stable profile order after Price (which stays
     * pinned in core). A band is wider than the core score range, so a high
     * generic Rating score cannot displace an eligible profile attribute.
     * Brand candidates receive the same role boost, preserving the core
     * preference for BeRocket Brands over other providers.
     */
    protected function get_profiles() {
        $profiles = array(
            'fashion' => array(
                'id' => 'fashion',
                'priorities' => array('categories' => 700, 'brand' => 600, 'size' => 500, 'color' => 400, 'gender' => 300, 'material' => 200, 'price' => 1),
                'semantics' => array(
                    'size' => array('priority' => .90, 'aliases' => array('size', 'fit')),
                    'color' => array('priority' => .95, 'aliases' => array('colour', 'color')),
                    'gender' => array('priority' => .70, 'aliases' => array('gender', 'sex')),
                    'material' => array('priority' => .80, 'aliases' => array('material', 'fabric')),
                ),
            ),
            'electronics' => array(
                'id' => 'electronics',
                'priorities' => array('categories' => 700, 'brand' => 600, 'memory' => 500, 'screen' => 400, 'processor' => 300, 'rating' => 200, 'price' => 1),
                'semantics' => array(
                    'memory' => array('priority' => .90, 'aliases' => array('memory', ' ram', 'storage', 'capacity')),
                    'screen' => array('priority' => .85, 'aliases' => array('screen', 'display', 'diagonal')),
                    'processor' => array('priority' => .85, 'aliases' => array('processor', ' cpu', 'chipset')),
                ),
            ),
            'furniture' => array(
                'id' => 'furniture',
                'priorities' => array('categories' => 700, 'brand' => 600, 'material' => 500, 'color' => 400, 'room' => 300, 'style' => 200, 'price' => 1),
                'semantics' => array(
                    'material' => array('priority' => .80, 'aliases' => array('material', 'fabric', 'wood')),
                    'color' => array('priority' => .95, 'aliases' => array('colour', 'color')),
                    'room' => array('priority' => .75, 'aliases' => array('room', 'placement')),
                    'style' => array('priority' => .70, 'aliases' => array('style', 'collection')),
                ),
            ),
            'auto_parts' => array(
                'id' => 'auto_parts',
                'priorities' => array('categories' => 700, 'brand' => 600, 'vehicle' => 500, 'part_position' => 400, 'price' => 1),
                'semantics' => array(
                    'vehicle' => array('priority' => .80, 'aliases' => array('vehicle', 'car make', 'compatib', 'model', 'make')),
                    'part_position' => array('priority' => .75, 'aliases' => array('position', 'location', 'side', 'axle')),
                ),
            ),
            'cosmetics' => array(
                'id' => 'cosmetics',
                'priorities' => array('categories' => 700, 'brand' => 600, 'shade' => 500, 'skin' => 400, 'concern' => 300, 'price' => 1),
                'semantics' => array(
                    'shade' => array('priority' => .90, 'aliases' => array('shade', 'colour', 'color', 'tone')),
                    'skin' => array('priority' => .85, 'aliases' => array('skin', 'complexion')),
                    'concern' => array('priority' => .80, 'aliases' => array('concern', 'benefit', 'condition')),
                ),
            ),
        );
        return apply_filters('brapf_one_click_paid_store_type_profiles', $profiles);
    }
}
new BeRocket_AAPF_Paid_One_Click_Store_Type_Ranking();
