<?php

namespace Modules\Bms\Services\Catalog;

use Illuminate\Support\Collection;

/**
 * Shapes package items for the funnel — the single source of truth shared by the
 * public client funnel (CatalogController) and the P1 staff console. Each item
 * inherits name/media/variants/contents from its linked ERP product
 * (ProductPresentation); the item's own non-empty columns override the product
 * (per-package customization). Buffet "contents" carry swap_options so a line can
 * be substituted (fully or partially) with a price delta.
 */
class PackagePresenter
{
    /** Present a collection/array of BmsPackageItem rows for one package. */
    public function present($items): array
    {
        $items = $items instanceof Collection ? $items : collect($items);
        $contentsMap = $this->comboContentsMap($items);

        return $items->map(fn ($it) => $this->item($it, $contentsMap))->values()->all();
    }

    /** Shape one package item, inheriting from the linked product. */
    public function item($it, array $contentsMap = []): array
    {
        $pres = app(ProductPresentation::class)->for((int) $it->business_id, $it->product_id, $it->variation_id);

        $nonEmpty = fn ($v) => is_array($v) ? ! empty($v) : ($v !== null && $v !== '');
        $inherit = fn ($own, $key) => $nonEmpty($own) ? $own : ($pres[$key] ?? ($key === 'media' || $key === 'variants' || $key === 'contents' ? [] : null));

        return [
            'id'             => $it->id,
            'name'           => $inherit($it->name, 'name'),
            'name_ar'        => $inherit($it->name_ar, 'name_ar'),
            'description'    => $inherit($it->description, 'description'),
            'description_ar' => $inherit($it->description_ar, 'description_ar'),
            'media'          => $inherit(is_array($it->media) ? array_values($it->media) : [], 'media'),
            'options'        => $inherit(is_array($it->options) ? array_values($it->options) : [], 'variants'),
            'category'       => $it->category,
            'quantity'       => (float) $it->quantity,
            'qty_mode'       => $it->qty_mode,
            'is_optional'    => (bool) $it->is_optional,
            'is_addon'       => (bool) $it->is_addon,
            'price'          => $it->addon_price !== null ? (float) $it->addon_price : null,
            'contents'       => $this->mergeContents(
                $pres['contents'] ?? [],
                is_array($it->contents) ? array_values($it->contents) : [],
                $contentsMap[$it->product_id] ?? []
            ),
        ];
    }

    /**
     * Resolve buffet contents (product-first): the combo product defines the
     * MEMBERSHIP (which dishes), while the item's curated `contents` act as an
     * overlay merged by index — supplying swap_options (substitution) and
     * localized unit labels that the combo doesn't carry. When the item isn't
     * product-linked, its curated contents are used wholesale.
     */
    protected function mergeContents(array $inherited, array $curated, array $fallback): array
    {
        $base = ! empty($inherited) ? $inherited : (! empty($curated) ? $curated : $fallback);
        if (empty($curated) || $base === $curated) {
            return array_values($base);
        }

        foreach ($base as $i => $row) {
            $over = $curated[$i] ?? null;
            if (! $over) {
                continue;
            }
            if (! empty($over['swap_options'])) {
                $row['swap_options'] = $over['swap_options'];
            }
            foreach (['unit', 'unit_ar', 'name_ar'] as $k) {
                if (empty($row[$k]) && ! empty($over[$k])) {
                    $row[$k] = $over[$k];
                }
            }
            $base[$i] = $row;
        }

        return array_values($base);
    }

    /**
     * Describe one buffet substitution for display/reporting (not pricing —
     * BookingService recomputes the authoritative line total). Resolves the
     * replaced qty and per-unit delta from the item's curated contents.
     *
     * @return array{from:string,to:string,qty:float,of:float,delta:float,label:string}
     */
    public function swapLine($item, array $cs): array
    {
        $ci = (int) ($cs['index'] ?? -1);
        $ti = (int) ($cs['to_index'] ?? 0);
        $rowQty = (float) (is_array($item->contents) ? ($item->contents[$ci]['quantity'] ?? 1) : 1);
        $qty = min(max((float) ($cs['qty'] ?? $rowQty), 0), $rowQty);
        $opt = is_array($item->contents) ? ($item->contents[$ci]['swap_options'][$ti - 1] ?? null) : null;
        $delta = $ti > 0 && $opt ? (float) ($opt['price_delta'] ?? 0) * $qty : 0.0;
        $label = ($cs['from'] ?? '') . ' → ' . ($cs['to'] ?? '')
            . ($rowQty > 1 ? ' (' . (int) $qty . '/' . (int) $rowQty . ')' : '');

        return ['from' => $cs['from'] ?? '', 'to' => $cs['to'] ?? '', 'qty' => $qty, 'of' => $rowQty, 'delta' => $delta, 'label' => $label];
    }

    /**
     * Resolve buffet/menu contents for combo-product package items:
     * combo product id => [{name, quantity}, ...].
     */
    public function comboContentsMap($items): array
    {
        $items = $items instanceof Collection ? $items : collect($items);
        $productIds = $items->pluck('product_id')->filter()->unique()->values();
        if ($productIds->isEmpty()) {
            return [];
        }

        $combos = \App\Product::whereIn('id', $productIds)->where('type', 'combo')
            ->with('variations:id,product_id,combo_variations')->get();
        if ($combos->isEmpty()) {
            return [];
        }

        $byProduct = [];
        $componentVarIds = [];
        foreach ($combos as $combo) {
            $cv = optional($combo->variations->first())->combo_variations ?? [];
            $byProduct[$combo->id] = $cv;
            foreach ($cv as $row) {
                $componentVarIds[] = (int) ($row['variation_id'] ?? 0);
            }
        }
        $componentVarIds = array_values(array_filter(array_unique($componentVarIds)));
        $vars = \App\Variation::whereIn('id', $componentVarIds)->with('product:id,name')->get()->keyBy('id');

        $map = [];
        foreach ($byProduct as $productId => $cv) {
            $list = [];
            foreach ($cv as $row) {
                $var = $vars->get((int) ($row['variation_id'] ?? 0));
                if (! $var) {
                    continue;
                }
                $list[] = [
                    'name'     => optional($var->product)->name ?? '—',
                    'quantity' => (float) ($row['quantity'] ?? 1),
                ];
            }
            if ($list) {
                $map[$productId] = $list;
            }
        }

        return $map;
    }
}
