<?php

class OvisMinMax {

    /**
     * Merge two indexed arrays
     *
     * @param array $current - Existing array
     * @param array $incoming - New array to merge
     * @return array
     */
    private function mergeIndexedArray($current, $incoming) {
        if(!is_array($current) || !is_array($incoming)) return array();

        $merged = array_merge($current, $incoming);
        $merged = array_map('trim', $merged);
        $merged = array_unique($merged);
        
        // Re-index from 0
        return array_values($merged);
    }


    /**
     * Merge OVIS MinMax `totals` object
     *
     * @param object $target
     * @param object $source
     * @return void
     */
    private function mergeTotals(&$target, $source) {
        if(!is_object($source)) return;

        foreach($source as $key => $value) {
            if(!isset($target->$key)) {
                $target->$key = 0;
            }

            if(is_numeric($value)) {
                $target->$key += $value;
            }
        }
    }


    /**
     * Merge OVIS MinMax `specs` object
     *
     * @param object $target
     * @param object $source
     * @return void
     */
    private function mergeSpecs(&$target, $source) {
        if(!is_object($source)) return;

        foreach ($source as $key => $val) {

            // Special handling for "models" - array of objects with brand + models array/object
            if ($key === 'models') {
                $current = isset($target->$key) && is_array($target->$key) ? $target->$key : [];

                // Index existing models by brand
                $indexedCurrent = [];
                foreach ($current as $item) {
                    if (isset($item->brand)) {
                        $indexedCurrent[$item->brand] = $item;
                    }
                }

                foreach ($val as $item) {
                    if (!isset($item->brand)) continue;
                    $brand = $item->brand;

                    // Normalize models to array
                    $modelsToMerge = [];
                    if (isset($item->models)) {
                        if (is_array($item->models)) {
                            $modelsToMerge = $item->models;
                        } elseif (is_object($item->models)) {
                            $modelsToMerge = array_values((array)$item->models);
                        }
                    }

                    if (isset($indexedCurrent[$brand])) {
                        // Merge models arrays
                        $existingModels = $indexedCurrent[$brand]->models ?? [];
                        if (is_object($existingModels)) {
                            $existingModels = array_values((array)$existingModels);
                        }
                        $mergedModels = array_unique(array_merge($existingModels, $modelsToMerge));
                        // Assign merged models array (keep as array)
                        $indexedCurrent[$brand]->models = array_values($mergedModels);
                    } else {
                        // Add new brand-model group
                        $newItem = clone $item;
                        $newItem->models = array_values($modelsToMerge);
                        $indexedCurrent[$brand] = $newItem;
                    }
                }

                // Store merged models as an array
                $target->$key = array_values($indexedCurrent);
                continue;
            }

            // If value is object or array, handle min/max or list merges
            if (is_object($val) || is_array($val)) {

                $valArr = (array) $val;
                $hasMinMax = isset($valArr['min']) || isset($valArr['max']);

                if ($hasMinMax) {
                    // This is a min/max pair - merge the values
                    if (!isset($target->$key)) {
                        $target->$key = new stdClass();
                    }
                    
                    if (isset($valArr['min'])) {
                        $existingMin = isset($target->$key->min) ? floatval($target->$key->min) : INF;
                        $newMin = floatval($valArr['min']);
                        $target->$key->min = strval(min($existingMin, $newMin));
                    }
                    
                    if (isset($valArr['max'])) {
                        $existingMax = isset($target->$key->max) ? floatval($target->$key->max) : -INF;
                        $newMax = floatval($valArr['max']);
                        $target->$key->max = strval(max($existingMax, $newMax));
                    }
                } else {
                    // Indexed array - e.g., brands
                    $existing = [];
                    if(isset($target->$key)) {
                        $existing = is_array($target->$key)
                            ? $target->$key
                            : array_values((array)$target->$key);
                    }

                    $incomingArray = array_values($valArr);
                    $merged = $this->mergeIndexedArray($existing, $incomingArray);

                    if($key === 'brands') {
                        sort($merged, SORT_STRING | SORT_FLAG_CASE);
                    }

                    $target->$key = $merged;
                }
            } else {
                // Primitive value, just assign directly
                $target->$key = $val;
            }
        }
    }


    /**
     * Merge OVIS MinMax object
     * 
     * @param object $oMinMax
     * @return object|null
     */
    public function merge($oMinMax) {

        if(empty($oMinMax)) return null;
        if(!(is_object($oMinMax))) return null;
        if(!isset($oMinMax->data) || empty($oMinMax->data)) return null;

        // Create a new object to store the merged results
        $mergedResult = new stdClass();
        $mergedResult->data = new stdClass();

        // All categories with condition `new` merged
        $globalNew = new stdClass();
        $globalNew->totals = new stdClass();
        $globalNew->specs = new stdClass();

        // All categories with condition `occasion` merged
        $globalOccasion = new stdClass();
        $globalOccasion->totals = new stdClass();
        $globalOccasion->specs = new stdClass();

        // All categories with condition `rental` merged
        $globalRental = new stdClass();
        $globalRental->totals = new stdClass();
        $globalRental->specs = new stdClass();

        // All categories with all conditions merged
        $globalAll = new stdClass();
        $globalAll->totals = new stdClass();
        $globalAll->specs = new stdClass();

        // Iterate each category like camper, caravan, trailer
        foreach($oMinMax->data as $categoryKey => $categoryData) {

            if(!is_object($categoryData)) continue;

            $mergedAll = new stdClass();
            $mergedAll->totals = new stdClass();
            $mergedAll->specs = new stdClass();

            $mergedCategory = new stdClass();

            foreach (['new', 'occasion', 'rental'] as $conditionKey) {

                if(!isset($categoryData->$conditionKey)) continue;

                $conditionData = $categoryData->$conditionKey;

                // Prepare fresh objects for merged condition
                $mergedCondition = (object)[
                    'totals' => new stdClass(),
                    'specs' => new stdClass(),
                ];
                
                // Handle both styles (with/without "totals" and "specs")
                $totalsSource = isset($conditionData->totals) && is_object($conditionData->totals)
                    ? $conditionData->totals
                    : new stdClass();

                $specsSource = isset($conditionData->specs) && is_object($conditionData->specs)
                    ? $conditionData->specs
                    : $conditionData; // fallback: use whole conditionData if no "specs"

                $this->mergeTotals($mergedCondition->totals, $totalsSource);
                $this->mergeSpecs($mergedCondition->specs, $specsSource);

                // Assign merged condition data for this category
                $mergedCategory->$conditionKey = $mergedCondition;

                // Also merge into the all-condition for this category
                $this->mergeTotals($mergedAll->totals, $mergedCondition->totals);
                $this->mergeSpecs($mergedAll->specs, $mergedCondition->specs);

                // Accumulate global conditions
                switch ($conditionKey) {
                    case 'new':
                        $this->mergeTotals($globalNew->totals, $mergedCondition->totals);
                        $this->mergeSpecs($globalNew->specs, $mergedCondition->specs);
                        break;
                    case 'occasion':
                        $this->mergeTotals($globalOccasion->totals, $mergedCondition->totals);
                        $this->mergeSpecs($globalOccasion->specs, $mergedCondition->specs);
                        break;
                    case 'rental':
                        $this->mergeTotals($globalRental->totals, $mergedCondition->totals);
                        $this->mergeSpecs($globalRental->specs, $mergedCondition->specs);
                        break;
                }
            }

            // Assign the merged all-condition for the category
            $mergedCategory->all = $mergedAll;

            // Store the fully merged category into result
            $mergedResult->data->$categoryKey = $mergedCategory;

            // Merge category all-condition into global all
            $this->mergeTotals($globalAll->totals, $mergedAll->totals);
            $this->mergeSpecs($globalAll->specs, $mergedAll->specs);
        }

        $mergedResult->errors = '';
        $mergedResult->result = true;
        $mergedResult->merged = true;

        $mergedResult->data->all = new stdClass();
        $mergedResult->data->all->new = $globalNew;
        $mergedResult->data->all->occasion = $globalOccasion;
        $mergedResult->data->all->rental = $globalRental;
        $mergedResult->data->all->all = $globalAll;

        return $mergedResult;
    }

}