<?php
/**
 * Copyright © MageMe. All rights reserved.
 * See LICENSE for license terms, or https://mageme.com/license.
 */
/**
 * Hyva theme override of MageMe_EUWithdrawal::withdraw/form.phtml.
 *
 * Pure Tailwind + Alpine.js port of the 4-step withdrawal SPA. Owns Steps 2/3/4
 * (Step 1a/1b are separate pages with their own templates). Single x-data
 * component holds the entire state machine — items, reasons, seal answers,
 * panel routing, refund computation, finalize submission, copy-to-clipboard —
 * replacing the vanilla IIFE in `web/js/withdrawal-app.js` (loaded only for
 * Luma; removed via `default.xml` <remove> on Hyva) plus the qty-stepper.js
 * and withdrawal-summary.js helpers.
 *
 * Reads block data via $block->getStates() etc. (same Block class as Free —
 * MageMe\EUWithdrawal\Block\Withdraw\Form), serialises items into a JSON
 * bootstrap, and lets Alpine handle all client-side reactivity.
 *
 * @var \MageMe\EUWithdrawal\Block\Withdraw\Form $block
 * @var \Magento\Framework\Escaper                $escaper
 * @var \Hyva\Theme\Model\ViewModelRegistry        $viewModels
 */

/** @var \Hyva\Theme\ViewModel\HyvaCsp $hyvaCsp */

$formKey         = method_exists($block, 'getFormKey') ? $block->getFormKey() : '';
$finalizeUrl     = $block->getUrl('withdraw-contract/api/finalize');
$cancelUrl       = method_exists($block, 'getCancelActionUrl') ? $block->getCancelActionUrl() : '';
$orderHistoryUrl = $block->getUrl('sales/order/history');
$indexUrl        = $block->getUrl('withdraw-contract');
$homeUrl         = $block->getUrl('');
$contactUrl      = $block->getUrl('contact');
$returnPolicyUrl = method_exists($block, 'getReturnPolicyUrl') ? $block->getReturnPolicyUrl() : '';
$shippingNotice  = method_exists($block, 'getReturnShippingNotice') ? $block->getReturnShippingNotice() : '';
$blockingReason  = method_exists($block, 'getBlockingReason') ? $block->getBlockingReason() : null;

$orderMeta       = $block->getChildBlock('order_meta');
$orderIncrement  = $orderMeta instanceof \MageMe\EUWithdrawal\Block\Withdraw\OrderMeta ? $orderMeta->getOrderIncrementId() : '';
$orderForMeta    = $orderMeta instanceof \MageMe\EUWithdrawal\Block\Withdraw\OrderMeta ? $orderMeta->getOrder() : null;

$itemSelectorChild   = $block->getChildBlock('item_selector');
$selectionModeVm     = $itemSelectorChild ? $itemSelectorChild->getData('selection_mode_view_model') : null;
$isFullOrder         = $selectionModeVm !== null && $selectionModeVm->isFullOrderMode();

// Art. 11a(3) confirmation label: counsel-signed per-locale whitelist string,
// rendered verbatim (never through __()).
$confirmLabelVm      = $block->getData('confirm_button_label_view_model');
$confirmLabel        = $confirmLabelVm !== null ? $confirmLabelVm->getLabel() : 'Confirm withdrawal';
$pendingByItem       = method_exists($block, 'getPendingRequestsByOrderItemId') ? $block->getPendingRequestsByOrderItemId() : [];
$reasonsEnabled    = $itemSelectorChild instanceof \MageMe\EUWithdrawal\Block\Withdraw\ItemSelector
    && $itemSelectorChild->isReasonsEnabled();
$reasonOptions     = $itemSelectorChild instanceof \MageMe\EUWithdrawal\Block\Withdraw\ItemSelector
    ? $itemSelectorChild->getReasonOptions()
    : [];
$otherReasonAllowed = $itemSelectorChild instanceof \MageMe\EUWithdrawal\Block\Withdraw\ItemSelector
    && $itemSelectorChild->isOtherReasonEnabled();

// Discounted carriage, less anything a native credit memo already refunded:
// shipping_amount stays at the carrier's quote either way. The fallback keeps
// this template working against an older base module.
$shippingAmounts = $orderForMeta !== null && method_exists($block, 'getShippingRefundable')
    ? $block->getShippingRefundable($orderForMeta)
    : [
        'net' => $orderForMeta !== null ? (float) $orderForMeta->getShippingAmount() : 0.0,
        'tax' => $orderForMeta !== null ? (float) $orderForMeta->getShippingTaxAmount() : 0.0,
    ];
$shippingPaid = $shippingAmounts['net'];
$shippingTax  = $shippingAmounts['tax'];
$currency     = $orderForMeta !== null ? (string) $orderForMeta->getOrderCurrencyCode() : 'EUR';
// Order-level adjustment preview (payment discount, gift card, custom total):
// base + gap let Alpine distribute the same order-level adjustment the server
// applies. Empty for standard orders.
$orderLevel = $orderForMeta !== null && method_exists($block, 'getOrderLevelGap')
    ? $block->getOrderLevelGap($orderForMeta)
    : ['base' => 0.0, 'gap' => 0.0];

// Rows are grouped: a bundle's returnable components sit under a header naming
// the bundle they came from, and a bundle returned as one unit lists its
// contents as inert rows underneath itself. Standalone products follow under an
// "Individual items" sub-header. The Alpine state (qtyMap, sealMap, the POST
// body) stays keyed by the selectable unit only — the inert rows carry no id.
$items         = [];
$eligibleItems = [];
if ($itemSelectorChild instanceof \MageMe\EUWithdrawal\Block\Withdraw\ItemSelector) {
    $groups                  = $itemSelectorChild->getReturnGroups();
    $seenBundle              = false;
    $standaloneHeaderEmitted = false;

    foreach ($groups as $group) {
    $isBundleGroup = $group->type === \MageMe\EUWithdrawal\Model\Item\ReturnGroup::TYPE_BUNDLE;

    $standaloneHeader = false;
    if (!$isBundleGroup && $seenBundle && !$standaloneHeaderEmitted) {
        $standaloneHeaderEmitted = true;
        $standaloneHeader        = true;
    }
    if ($isBundleGroup) {
        $seenBundle = true;
    }

    $groupHeader      = $group->headerLabel;
    $bundled          = $groupHeader !== null;
    $groupCountLabel  = $bundled ? (string) __('%1 items', count($group->rows)) : '';
    $groupThumb       = '';
    if ($bundled && property_exists($group, 'headerOrderItemId') && $group->headerOrderItemId !== null) {
        $headerOi   = $itemSelectorChild->getOrderItem((int) $group->headerOrderItemId);
        $groupThumb = $headerOi !== null ? (string) $itemSelectorChild->getThumbnailUrl($headerOi) : '';
    }
    $lastSelectableIx = null;
    $contents         = [];

    foreach ($group->rows as $groupRow) {
        if (!$groupRow->selectable) {
            $childId  = (int) $groupRow->orderItemId;
            $childObj = $itemSelectorChild->getOrderItem($childId);
            $contents[] = [
                'id'    => $childId,
                'thumb' => $childObj !== null ? (string) $itemSelectorChild->getThumbnailUrl($childObj) : '',
                'name'  => (string) $groupRow->label,
                'opt'   => (string) $itemSelectorChild->getOptionSummary($childId),
                // Gross, like every other figure on this screen. The fallback
                // keeps an older base module working, on its own basis.
                'price' => $groupRow->priced
                    ? (method_exists($itemSelectorChild, 'formatItemDisplayPriceGross')
                        ? (string) $itemSelectorChild->formatItemDisplayPriceGross($childId)
                        : (string) $itemSelectorChild->formatItemDisplayPrice($childId))
                    : (string) __('Included'),
                'sealed' => $groupRow->sealed !== null ? [
                    'childSealId'  => $childId,
                    'kind'         => $groupRow->sealed->kind->questionKind(),
                    'parentLineId' => (int) $groupRow->sealed->lineOrderItemId,
                ] : null,
            ];
            continue;
        }

        $state         = $groupRow->state;
        $orderItemId   = (int) $state->orderItemId;
        $orderItemObj  = $itemSelectorChild->getOrderItem($orderItemId);
        $thumbnailUrl  = $orderItemObj !== null ? (string) $itemSelectorChild->getThumbnailUrl($orderItemObj) : '';
        $badge         = $itemSelectorChild->getItemStatusBadge($state);
        $returnedQty   = (int) $itemSelectorChild->getReturnedQty($state);
        $purchasedQty  = (int) $state->purchasedQty;
        if ($state->isEligible() && method_exists($itemSelectorChild, 'getLineSeal')) {
            $lineSeal      = $itemSelectorChild->getLineSeal($orderItemId);
            $sealKind      = $lineSeal['kind'] ?? null;
            $sealSubjectId = $lineSeal['subjectItemId'] ?? $orderItemId;
        } else {
            $sealKind      = $state->isEligible() ? $itemSelectorChild->getSealKind($orderItemId) : null;
            $sealSubjectId = $orderItemId;
        }
        $unitPrice     = (float) $state->unitDisplayPrice;
        // Tax: emit the row-level taxAmount attributed to the returnable unit
        // (children folded in) + qtyOrdered, NOT a pre-divided unitTax, so JS
        // can replicate RefundCalculator::calculate()'s single-round formula
        // exactly. See Free 0.12.2 CHANGELOG for rationale.
        $rawTaxAmount = (float) $state->rowTaxAmount;
        $orderedQty   = $orderItemObj !== null ? (float) $orderItemObj->getQtyOrdered() : 0.0;
        if ($orderedQty <= 0) {
            $orderedQty = $purchasedQty > 0 ? (float) $purchasedQty : 1.0;
        }

        $pendingRows = $pendingByItem[$orderItemId] ?? [];

        $strips = [];
        if ($badge !== null && $badge['tone'] === 'pending' && $pendingRows !== []) {
            foreach ($pendingRows as $info) {
                $strips[] = [
                    'tone'        => 'pending',
                    'requestId'   => (int) $info['requestId'],
                    'incrementId' => (string) ($info['incrementId'] ?? ''),
                    'status'      => (string) $info['status'],
                    'qty'         => (int) $info['qty'],
                    'cancellable' => (bool) $info['cancellable'],
                ];
            }
        } elseif ($badge !== null) {
            $tone  = $badge['tone'];
            $label = $tone === 'done'
                ? (string) __('Already returned.')
                : ($tone === 'pending'
                    ? (string) __('Return request awaiting review.')
                    : (string) $itemSelectorChild->getExclusionLabel((string) $state->exclusionReason));
            $strips[] = [
                'tone'        => $tone,
                'label'       => $label,
                'cancellable' => false,
            ];
        }

        $options = [];
        foreach ($itemSelectorChild->getOrderItemOptions($orderItemId) as $opt) {
            $options[] = ['label' => (string) $opt['label'], 'value' => (string) $opt['value']];
        }

        // Row-level net, for the same reason as taxAmount: multiplying the
        // already-rounded unitPrice drifts from the server's proration. An older
        // base module has no such property; omit the key rather than emit a zero,
        // which the client would read as a free row instead of falling back.
        $rowNetAmount = property_exists($state, 'rowNetAmount') ? $state->rowNetAmount : null;

        $lastSelectableIx = count($items);
        $items[] = [
            'id'               => $orderItemId,
            'name'             => (string) $state->name,
            'sku'              => method_exists($itemSelectorChild, 'getDisplaySku')
                ? (string) $itemSelectorChild->getDisplaySku($orderItemId, (string) $state->sku)
                : (string) $state->sku,
            'thumb'            => $thumbnailUrl,
            'unitPrice'        => $unitPrice,
            'taxAmount'        => round($rawTaxAmount, 4, PHP_ROUND_HALF_EVEN),
            'qtyOrdered'       => $orderedQty,
            'purchased'        => $purchasedQty,
            'returned'         => $returnedQty,
            'remaining'        => (int) $state->remainingQty,
            'eligible'         => (bool) $state->isEligible(),
            'sealKind'         => $sealKind,
            'sealSubjectId'    => $sealSubjectId,
            'options'          => $options,
            'strips'           => $strips,
            'standaloneHeader' => $standaloneHeader,
            'groupHeader'      => $groupHeader,
            'groupThumb'       => $groupThumb,
            'groupCountLabel'  => $groupCountLabel,
            'bundled'          => $bundled,
            'contents'         => [],
        ];
        if ($rowNetAmount !== null) {
            $items[$lastSelectableIx]['netAmount'] = round((float) $rowNetAmount, 4, PHP_ROUND_HALF_EVEN);
        }
        // Header and sub-header belong to the first row they precede only.
        $standaloneHeader = false;
        $groupHeader      = null;
        $groupThumb       = '';
        $groupCountLabel  = '';

        if ($state->isEligible() && (int) $state->remainingQty > 0) {
            $eligibleItems[$orderItemId] = [
                'qty'        => (int) $state->remainingQty,
                'taxAmount'  => round($rawTaxAmount, 4, PHP_ROUND_HALF_EVEN),
                'qtyOrdered' => $orderedQty,
            ];
        }
    }

    if ($contents !== [] && $lastSelectableIx !== null) {
        $items[$lastSelectableIx]['contents'] = $contents;
    }
    }
}

$boot = [
    'formKey'          => $formKey,
    'finalizeUrl'      => $finalizeUrl,
    'cancelUrl'        => $cancelUrl,
    'orderHistoryUrl'  => $orderHistoryUrl,
    'indexUrl'         => $indexUrl,
    'homeUrl'          => $homeUrl,
    'contactUrl'       => $contactUrl,
    'returnPolicyUrl'  => $returnPolicyUrl,
    'shippingNotice'   => $shippingNotice,
    'orderIncrementId' => $orderIncrement,
    'shippingPaid'     => round($shippingPaid, 2),
    'shippingTax'      => round($shippingTax, 2),
    'currency'         => $currency,
    'eligibleItems'    => (object) $eligibleItems,
    'orderItemsBase'   => round($orderLevel['base'], 4),
    'orderLevelGap'    => round($orderLevel['gap'], 4),
    // `inclTax` reports the store's sales-price display setting. The summary no
    // longer branches on it — every figure is gross — but the key stays for
    // themes that read the payload.
    'inclTax'          => method_exists($block, 'isInclTaxDisplay') && $block->isInclTaxDisplay(),
    'hideTax'          => method_exists($block, 'isVatLineHidden')
        ? $block->isVatLineHidden()
        : (method_exists($block, 'isTaxLineHidden') && $block->isTaxLineHidden()),
    'items'            => $items,
    'reasonsEnabled'   => $reasonsEnabled,
    'otherReasonAllowed' => $otherReasonAllowed,
    'reasonOptions'    => $reasonOptions,
    'fullOrder'        => $isFullOrder,
    'i18n'             => [
        'orderPrefix'        => (string) __('Order #'),
        'qtyPrefix'          => (string) __('Qty: '),
        'submissionFailed'   => (string) __('Submission failed. Please try again.'),
        'networkError'       => (string) __('Network error. Please try again.'),
        'invalidServerReply' => (string) __('Invalid server response.'),
        'copied'             => (string) __('Copied!'),
    ],
];

$noEligibleItems = method_exists($block, 'hasAnyEligibleItem') && !$block->hasAnyEligibleItem();
$mismatched      = method_exists($block, 'isRequestedOrderMismatched') && $block->isRequestedOrderMismatched();
?>
<?= /* @noEscape */ $block->getLayout()->createBlock(\MageMe\Core\Block\Init::class)
        ->getInlineCss('Hyva_MageMeEUWithdrawal::css/qty-stepper.css') ?>
<section class="mm-eu-w-spa max-w-6xl mx-auto px-4 py-6 max-sm:px-0 max-sm:py-4"
         x-data='withdrawalApp(<?= /* @noEscape */ json_encode($boot, JSON_THROW_ON_ERROR | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>)'
         x-cloak>
    <?= $block->getBlockHtml('formkey') ?>

    <?php if ($mismatched): ?>
        <div role="alert" class="mb-6 rounded-lg border border-red-300 bg-red-50 px-4 py-3 text-sm text-red-800">
            <?= $escaper->escapeHtml(__('That order does not belong to your account. Please select one of your own orders.')) ?>
        </div>
    <?php endif; ?>

    <!-- Panel 2: select items (gated on order-level eligibility) -->
<?php if ($blockingReason !== null): ?>
    <div x-show="step === '2'" class="max-w-2xl">
        <div class="rounded-xl border border-gray-200 bg-white p-7 max-sm:border-0 max-sm:rounded-none max-sm:shadow-none max-sm:p-0 max-sm:bg-transparent">
            <h2 class="text-[22px] font-semibold text-gray-900 leading-tight max-sm:text-[19px]"><?= $escaper->escapeHtml(__('Withdrawal not available for this order')) ?></h2>
            <p class="mt-3 rounded-lg bg-primary/10 px-4 py-3 text-[14px] text-gray-700"><?= $escaper->escapeHtml(__('This order is not eligible for withdrawal.')) ?></p>
            <div class="mt-6">
                <a href="<?= $escaper->escapeUrl($indexUrl) ?>" class="text-[15px] text-primary hover:underline">&larr; <?= $escaper->escapeHtml(__('Back to order search')) ?></a>
            </div>
        </div>
    </div>
<?php else: ?>
    <div x-show="step === '2'" class="grid gap-6 lg:grid-cols-[1fr_320px]">
        <div class="space-y-6">
            <div class="rounded-xl border border-gray-200 bg-white p-7 max-sm:border-0 max-sm:rounded-none max-sm:shadow-none max-sm:p-0 max-sm:bg-transparent">
                <h2 class="text-[22px] font-semibold text-gray-900 leading-tight max-sm:text-[19px]"><?= $escaper->escapeHtml(__('Select items to return')) ?></h2>
                <p class="mt-2 text-[14px] text-gray-600">
                    <?= $escaper->escapeHtml($isFullOrder
                        ? __('Review the items included in your return.')
                        : __('Choose all items or only some of them. Set the return quantity for each item you want to withdraw.')) ?>
                </p>

                <?php if ($orderMeta instanceof \MageMe\EUWithdrawal\Block\Withdraw\OrderMeta): ?>
                    <div class="mt-5 grid grid-cols-1 gap-4 rounded-lg bg-gray-50 px-5 py-4 sm:grid-cols-3 text-[15px] text-gray-700">
                        <div class="flex items-start gap-2.5">
                            <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="mt-0.5 shrink-0 text-gray-500"><path d="M3 9l9-4 9 4v10l-9 4-9-4z"/><polyline points="3 9 12 13 21 9"/><line x1="12" y1="13" x2="12" y2="22"/></svg>
                            <div class="min-w-0">
                                <div class="text-[15px] font-semibold text-gray-900 leading-tight"><?= $escaper->escapeHtml(__('Order #%1', $orderMeta->getOrderIncrementId())) ?></div>
                                <div class="mt-0.5 text-[13px] text-gray-500"><?= $escaper->escapeHtml(__('Placed on %1', $orderMeta->getPlacedAt())) ?></div>
                            </div>
                        </div>
                        <div class="flex items-start gap-2.5">
                            <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="mt-0.5 shrink-0 text-gray-500"><path d="M3 6h12v9H3z"/><path d="M15 10h4l2 3v2h-6"/><circle cx="7" cy="17" r="2"/><circle cx="17" cy="17" r="2"/></svg>
                            <div class="min-w-0">
                                <?php if ($orderMeta->isAwaitingDelivery()): ?>
                                    <div class="text-[15px] font-semibold text-gray-900 leading-tight"><?= $escaper->escapeHtml(__('Awaiting delivery')) ?></div>
                                <?php else: ?>
                                    <div class="text-[15px] font-semibold text-gray-900 leading-tight sm:whitespace-nowrap"><?= $escaper->escapeHtml(__('Delivered on %1', $orderMeta->getDeliveredAt())) ?></div>
                                <?php endif; ?>
                                <?php if ($orderForMeta !== null): ?>
                                    <div class="mt-0.5 text-[13px] text-gray-500"><?= $escaper->escapeHtml(__('Delivery: %1', $orderForMeta->getShippingDescription() ?: '—')) ?></div>
                                <?php endif; ?>
                            </div>
                        </div>
                        <div class="flex items-start gap-2.5 sm:justify-end">
                            <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="mt-0.5 shrink-0 text-gray-500"><rect x="3" y="5" width="18" height="14" rx="2"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
                            <div class="min-w-0">
                                <div class="text-[13px] text-gray-500"><?= $escaper->escapeHtml(__('Order total')) ?></div>
                                <div class="text-[18px] font-bold text-gray-900 leading-tight">
                                    <?php if ($orderForMeta !== null): ?>
                                        <?= /* @noEscape */ $orderForMeta->formatPrice($orderForMeta->getGrandTotal()) ?>
                                    <?php else: ?>—<?php endif; ?>
                                </div>
                            </div>
                        </div>
                    </div>
                <?php endif; ?>

                <div class="mt-6 overflow-x-auto max-sm:overflow-visible">
                    <table class="w-full max-sm:block">
                        <thead class="max-sm:hidden">
                            <tr class="border-b border-gray-200 text-left text-[14px] text-gray-500">
                                <th class="py-3 pr-2 w-[90px]"></th>
                                <th class="py-3 pr-2 font-normal"><?= $escaper->escapeHtml(__('Item')) ?></th>
                                <th class="py-3 px-2 w-[80px] text-right font-normal"><?= $escaper->escapeHtml(__('Price')) ?></th>
                                <th class="py-3 px-2 w-[80px] text-center font-normal"><?= $escaper->escapeHtml(__('Purchased')) ?></th>
                                <th class="py-3 px-2 w-[80px] text-center font-normal"><?= $escaper->escapeHtml(__('Returned')) ?></th>
                                <th class="py-3 px-2 w-[124px] text-center font-normal"><?= $escaper->escapeHtml(__('Return now')) ?></th>
                                <th class="py-3 pl-2 w-[88px] text-right font-normal"><?= $escaper->escapeHtml(__('Total')) ?></th>
                            </tr>
                        </thead>
                        <tbody class="max-sm:block">
                            <template x-for="(row, idx) in items" :key="row.id">
                                <template x-if="true">
                                    <tbody class="contents">
                                        <template x-if="row.standaloneHeader">
                                            <tr class="max-sm:block">
                                                <td colspan="7" class="border-t border-gray-100 pt-4 text-[12px] font-semibold uppercase tracking-wide text-gray-500 max-sm:block max-sm:pt-4">
                                                    <?= $escaper->escapeHtml(__('Other items')) ?>
                                                </td>
                                            </tr>
                                        </template>
                                        <template x-if="row.groupHeader">
                                            <tr class="max-sm:block">
                                                <td colspan="7" class="border-b border-gray-100 pt-5 pb-3 max-sm:block">
                                                    <div class="flex items-center gap-3">
                                                        <template x-if="row.groupThumb">
                                                            <img :src="row.groupThumb" :alt="row.groupHeader" class="h-14 w-14 shrink-0 rounded-lg border border-gray-200 bg-gray-50 object-cover">
                                                        </template>
                                                        <span class="text-[13px] font-semibold uppercase tracking-wide text-gray-900" x-text="row.groupHeader"></span>
                                                        <span class="text-[12px] text-gray-500" x-text="row.groupCountLabel"></span>
                                                    </div>
                                                </td>
                                            </tr>
                                        </template>
                                        <tr :class="(!row.eligible ? 'opacity-60 ' : '') + ((!row.bundled && idx !== items.length - 1 && !(<?= $reasonsEnabled ? 'true' : 'false' ?> && row.eligible && qtyOf(row.id) > 0)) ? 'border-b border-gray-100' : '')" class="max-sm:grid max-sm:grid-cols-[64px_1fr_auto] max-sm:items-start max-sm:gap-x-3 max-sm:gap-y-2.5 max-sm:rounded-lg max-sm:border max-sm:border-gray-200 max-sm:bg-white max-sm:p-3.5 max-sm:mb-3">
                                            <td class="py-4 pr-2 align-middle max-sm:col-start-1 max-sm:row-start-1 max-sm:p-0">
                                                <template x-if="row.thumb">
                                                    <img :src="row.thumb" :alt="row.name" class="h-14 w-14 rounded-lg border border-gray-200 bg-gray-50 object-cover max-sm:h-16 max-sm:w-16">
                                                </template>
                                                <template x-if="!row.thumb">
                                                    <div class="flex h-14 w-14 items-center justify-center rounded-lg border border-gray-200 bg-gray-50 text-gray-400 max-sm:h-16 max-sm:w-16">
                                                        <svg viewBox="0 0 24 24" width="40" height="40" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-4 9 4v10l-9 4-9-4z"/><polyline points="3 9 12 13 21 9"/><line x1="12" y1="13" x2="12" y2="22"/></svg>
                                                    </div>
                                                </template>
                                            </td>
                                            <td class="py-4 pr-2 align-middle max-sm:col-start-2 max-sm:row-start-1 max-sm:p-0">
                                                <div class="text-[15px] font-semibold text-gray-900 leading-tight" x-text="row.name"></div>
                                                <div class="mt-1 text-[12px] text-gray-500"><?= $escaper->escapeHtml(__('SKU:')) ?> <span x-text="row.sku"></span></div>
                                                <template x-for="opt in row.options" :key="opt.label + opt.value">
                                                    <div class="mt-0.5 text-[12px] text-gray-600">
                                                        <span class="font-medium" x-text="opt.label + ':'"></span>
                                                        <span x-text="opt.value"></span>
                                                    </div>
                                                </template>
                                            </td>
                                            <td class="py-4 px-2 text-right align-middle text-[16px] text-gray-700 whitespace-nowrap max-sm:hidden" x-text="formatPrice(unitDisplay(row))"></td>
                                            <td class="py-4 px-2 text-center align-middle text-[16px] text-gray-700 max-sm:col-start-3 max-sm:row-start-1 max-sm:p-0 max-sm:text-right max-sm:text-[14px] max-sm:font-semibold max-sm:before:content-['Qty:_'] max-sm:before:text-gray-500 max-sm:before:font-normal" x-text="row.purchased"></td>
                                            <td class="py-4 px-2 text-center align-middle text-[16px] text-gray-700 max-sm:hidden" x-text="row.returned"></td>
                                            <td class="py-4 px-2 text-center align-middle max-sm:col-start-1 max-sm:col-end-3 max-sm:row-start-2 max-sm:p-0 max-sm:text-left">
                                                <div x-show="!fullOrder"
                                                     class="mm-euw-qty inline-flex w-28 items-stretch overflow-hidden rounded-lg border border-gray-200 max-sm:w-32 max-sm:h-9"
                                                     :class="!canEditQty(row) ? 'opacity-50' : ''">
                                                    <button type="button"
                                                            class="mm-euw-qty__btn flex w-8 shrink-0 items-center justify-center bg-gray-50 text-[18px] text-gray-900 hover:bg-gray-100 disabled:cursor-not-allowed disabled:hover:bg-gray-50 max-sm:w-10 max-sm:h-9"
                                                            :disabled="!canEditQty(row) || (qtyOf(row.id) <= 0)"
                                                            @click="dec(row)"
                                                            aria-label="<?= $escaper->escapeHtmlAttr(__('Decrease quantity')) ?>">−</button>
                                                    <input type="number"
                                                           class="mm-euw-qty__input min-w-0 flex-1 border-0 bg-transparent text-center text-[16px] focus:outline-none focus:ring-0 [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none max-sm:h-9"
                                                           :value="qtyOf(row.id)"
                                                           min="0"
                                                           :max="row.remaining"
                                                           :disabled="!canEditQty(row)"
                                                           @input="setQty(row, parseInt($event.target.value, 10) || 0)">
                                                    <button type="button"
                                                            class="mm-euw-qty__btn flex w-8 shrink-0 items-center justify-center bg-gray-50 text-[18px] text-gray-900 hover:bg-gray-100 disabled:cursor-not-allowed disabled:hover:bg-gray-50 max-sm:w-10 max-sm:h-9"
                                                            :disabled="!canEditQty(row) || (qtyOf(row.id) >= row.remaining)"
                                                            @click="inc(row)"
                                                            aria-label="<?= $escaper->escapeHtmlAttr(__('Increase quantity')) ?>">+</button>
                                                </div>
                                                <span x-show="fullOrder && row.eligible" class="text-[16px] text-gray-700" x-text="qtyOf(row.id)"></span>
                                            </td>
                                            <td class="py-4 pl-2 text-right align-middle text-[16px] font-medium text-gray-900 whitespace-nowrap max-sm:col-start-3 max-sm:row-start-2 max-sm:p-0" x-text="formatPrice(lineDisplay(row, qtyOf(row.id)))"></td>
                                        </tr>

                                        <!-- Per-item seal question (Art. 16(e)/(i)) -->
                                        <!-- Stays visible when sealOf === true so the customer can revert "Yes broken"
                                             back to "No intact" after a misclick — matches Luma's withdrawal-summary.js
                                             behaviour (sealRow stays visible if brokenChecked === true even at qty 0). -->
                                        <template x-if="row.eligible && row.sealKind && (qtyOf(row.id) > 0 || sealOf(row.id) === true)">
                                            <tr class="max-sm:block max-sm:mb-3"><td colspan="7" class="bg-transparent pt-1 pb-4 max-sm:block max-sm:p-0">
                                                <div class="mx-2 rounded-md border border-amber-200 bg-[#fffaf0] px-4 py-3.5">
                                                    <div class="mb-3 flex items-center gap-2.5">
                                                        <span aria-hidden="true" class="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-amber-500 text-[14px] font-bold leading-none text-white">?</span>
                                                        <span class="text-[14px] font-semibold leading-snug text-gray-900"
                                                              x-text="row.sealKind === 'hygiene' ? '<?= $escaper->escapeJs(__('Is the hygiene seal still intact?')) ?>' : '<?= $escaper->escapeJs(__('Is the original software / media seal still intact?')) ?>'"></span>
                                                    </div>
                                                    <label class="mb-2 flex cursor-pointer items-center gap-3 rounded border border-gray-200 border-l-[3px] border-l-emerald-500 bg-white px-3.5 py-3 transition-colors has-[:checked]:border-emerald-500 has-[:checked]:bg-emerald-50">
                                                        <span aria-hidden="true" class="shrink-0 text-emerald-600"><svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="9 12 11 14 15 10"/></svg></span>
                                                        <span class="flex-1">
                                                            <span class="block text-[14px] font-semibold leading-snug text-gray-900"><?= $escaper->escapeHtml(__('Yes — it\'s still sealed')) ?></span>
                                                            <span class="mt-0.5 block text-[12px] leading-snug text-gray-500"><?= $escaper->escapeHtml(__('Refund possible — return the unopened package.')) ?></span>
                                                        </span>
                                                        <input type="radio" :name="'seal_' + row.id" :checked="sealOf(row.id) === false" @change="setSeal(row, false)" class="ml-auto h-[18px] w-[18px] shrink-0 accent-emerald-600">
                                                    </label>
                                                    <?= $block->getChildHtml('seal_photo_control') ?>
                                                    <label class="flex cursor-pointer items-center gap-3 rounded border border-gray-200 border-l-[3px] border-l-red-600 bg-white px-3.5 py-3 transition-colors has-[:checked]:border-red-600 has-[:checked]:bg-red-50">
                                                        <span aria-hidden="true" class="shrink-0 text-red-600"><svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2 1 22h22z"/><line x1="12" y1="9" x2="12" y2="14"/><circle cx="12" cy="17" r="0.7"/></svg></span>
                                                        <span class="flex-1">
                                                            <span class="block text-[14px] font-semibold leading-snug text-red-800"><?= $escaper->escapeHtml(__('No — I\'ve opened it')) ?></span>
                                                            <span class="mt-0.5 block text-[12px] leading-snug text-red-700"
                                                                  x-text="row.sealKind === 'hygiene' ? '<?= $escaper->escapeJs(__('This item cannot be returned (Art. 16(e) Directive 2011/83/EU).')) ?>' : '<?= $escaper->escapeJs(__('This item cannot be returned (Art. 16(i) Directive 2011/83/EU).')) ?>'"></span>
                                                        </span>
                                                        <input type="radio" :name="'seal_' + row.id" :checked="sealOf(row.id) === true" @change="setSeal(row, true)" class="ml-auto h-[18px] w-[18px] shrink-0 accent-red-600">
                                                    </label>
                                                    <template x-if="sealOf(row.id) === true">
                                                        <p class="mt-2.5 flex items-start gap-1.5 text-[12px] italic leading-snug text-amber-800">
                                                            <span aria-hidden="true" class="shrink-0 text-amber-500">⚠</span>
                                                            <span><?= $escaper->escapeHtml(__('Once you confirm the seal is broken, this item is excluded from the withdrawal request and the quantity will be reset to zero.')) ?></span>
                                                        </p>
                                                    </template>
                                                </div>
                                            </td></tr>
                                        </template>

                                        <!-- Per-item reason -->
                                        <?php if ($reasonsEnabled): ?>
                                            <template x-if="row.eligible && qtyOf(row.id) > 0">
                                                <tr class="max-sm:block max-sm:mb-3"><td colspan="7" :class="(!row.bundled && idx !== items.length - 1) ? 'border-b border-gray-100 pb-4' : 'pb-3'" class="max-sm:block max-sm:p-0">
                                                    <div class="mx-2 rounded-md bg-gray-50 px-4 py-3">
                                                        <label :for="'reason-' + row.id" class="block text-sm font-medium text-gray-700"><?= $escaper->escapeHtml(__('Reason for return (optional)')) ?></label>
                                                        <select :id="'reason-' + row.id"
                                                                class="mt-1 w-full rounded-md border-gray-300 text-sm focus:border-primary focus:ring-primary"
                                                                @change="setReason(row, $event.target.value, $event.target.options[$event.target.selectedIndex].text)">
                                                            <?php foreach ($reasonOptions as $opt): ?>
                                                                <option value="<?= $escaper->escapeHtmlAttr($opt['value']) ?>"
                                                                        :selected="reasonOf(row.id).code === '<?= $escaper->escapeJs($opt['value']) ?>'">
                                                                    <?= $escaper->escapeHtml($opt['label']) ?>
                                                                </option>
                                                            <?php endforeach; ?>
                                                        </select>
                                                        <?php if ($otherReasonAllowed): ?>
                                                            <textarea x-show="reasonOf(row.id).code === 'other'"
                                                                      class="mt-2 w-full rounded-md border-gray-300 text-sm focus:border-primary focus:ring-primary"
                                                                      rows="2"
                                                                      maxlength="500"
                                                                      :value="reasonOf(row.id).text"
                                                                      @input="setReasonText(row, $event.target.value)"
                                                                      placeholder="<?= $escaper->escapeHtmlAttr(__('Tell us more (optional, max 500 chars)')) ?>"></textarea>
                                                        <?php endif; ?>
                                                    </div>
                                                </td></tr>
                                            </template>
                                        <?php endif; ?>

                                        <!-- Status strips (Already returned, pending request, ineligible) -->
                                        <template x-for="strip in row.strips" :key="strip.tone + (strip.requestId || strip.label)">
                                            <tr class="max-sm:block max-sm:mb-3"><td colspan="7" class="pb-2 max-sm:block max-sm:p-0">
                                                <div class="flex items-center gap-2 rounded-md px-3 py-2 text-xs"
                                                     :class="strip.tone === 'done' ? 'bg-emerald-50 text-emerald-800' : (strip.tone === 'pending' ? 'bg-amber-50 text-amber-900' : 'bg-gray-100 text-gray-700')">
                                                    <span class="shrink-0" x-html="stripIcon(strip.tone)"></span>
                                                    <span class="grow" x-text="stripText(strip)"></span>
                                                    <template x-if="strip.tone === 'pending' && strip.cancellable">
                                                        <button type="button"
                                                                class="ml-auto text-red-700 underline hover:text-red-800 disabled:opacity-50"
                                                                @click="cancelRequest(strip.requestId)">
                                                            <?= $escaper->escapeHtml(__('Cancel request')) ?>
                                                        </button>
                                                    </template>
                                                </div>
                                            </td></tr>
                                        </template>

                                        <!-- Inert bundle contents: shown for information, never selectable -->
                                        <template x-if="row.contents.length">
                                            <tr class="max-sm:block">
                                                <td colspan="7" class="pt-3 pb-3.5 max-sm:block">
                                                    <div class="flex items-baseline justify-between">
                                                        <span class="text-[11px] uppercase tracking-wide text-gray-500"><?= $escaper->escapeHtml(__('Included in this bundle')) ?></span>
                                                        <span class="text-[11px] uppercase tracking-wide text-gray-500"><?= $escaper->escapeHtml(__('Included value')) ?></span>
                                                    </div>
                                                </td>
                                            </tr>
                                        </template>
                                        <template x-for="child in row.contents" :key="child.id">
                                            <tr class="max-sm:block">
                                                <td colspan="7" class="py-2 max-sm:block max-sm:px-3.5">
                                                    <div class="flex items-center gap-2">
                                                        <template x-if="child.thumb">
                                                            <img :src="child.thumb" :alt="child.name" class="h-10 w-10 shrink-0 rounded border border-gray-200 bg-gray-50 object-cover">
                                                        </template>
                                                        <span class="text-[14px] text-gray-900" x-text="child.name"></span>
                                                        <template x-if="child.sealed">
                                                            <span class="inline-flex items-center gap-1 rounded-full border border-amber-300 bg-amber-50 px-2 py-0.5 text-[11px] font-medium text-amber-700 whitespace-nowrap">
                                                                <svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3l7 3v5c0 4.4-3 8-7 10-4-2-7-5.6-7-10V6z"/></svg>
                                                                <span x-text="child.sealed.kind === 'hygiene' ? '<?= $escaper->escapeJs(__('Hygiene seal')) ?>' : '<?= $escaper->escapeJs(__('Media seal')) ?>'"></span>
                                                            </span>
                                                        </template>
                                                        <span class="text-[12px] text-gray-500" x-show="child.opt" x-text="child.opt"></span>
                                                        <span class="ml-auto text-[13px] text-gray-700 whitespace-nowrap" x-text="child.price"></span>
                                                    </div>
                                                </td>
                                            </tr>
                                        </template>
                                    </tbody>
                                </template>
                            </template>
                        </tbody>
                    </table>
                </div>
                <?php if ($isFullOrder): ?>
                <p class="mt-5 flex items-start gap-2.5 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3 text-sm leading-snug text-gray-600">
                    <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="mt-0.5 shrink-0 text-primary"><circle cx="12" cy="12" r="9"/><line x1="12" y1="11" x2="12" y2="16"/><circle cx="12" cy="8" r="0.6" fill="currentColor" stroke="none"/></svg>
                    <span><?= $escaper->escapeHtml(__('This request covers all returnable items of the order.')) ?>
                    <a class="text-primary hover:underline" href="<?= $escaper->escapeUrl($contactUrl) ?>"><?= $escaper->escapeHtml(__('Contact us for a partial return.')) ?></a></span>
                </p>
                <?php endif; ?>
            </div>

            <div class="flex items-start gap-3 rounded-lg border border-primary/30 bg-primary/10 px-4 py-3.5 text-[14px] text-primary-darker leading-snug">
                <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="mt-0.5 shrink-0 text-primary"><circle cx="12" cy="12" r="9"/><path d="M12 11v5"/><circle cx="12" cy="8" r="1" fill="currentColor" stroke="none"/></svg>
                <span><?= $escaper->escapeHtml(__('You have %1 days from the day you received your order to withdraw from the contract without giving a reason.', $block->getWithdrawalPeriodDays())) ?></span>
            </div>
        </div>

        <!-- Sidebar: return summary -->
        <aside class="space-y-4">
            <div class="rounded-xl border border-gray-200 bg-white p-5 max-sm:border-0 max-sm:rounded-none max-sm:shadow-none max-sm:p-0 max-sm:bg-transparent">
                <h3 class="text-[20px] font-semibold text-gray-900"><?= $escaper->escapeHtml(__('Return summary')) ?></h3>
                <template x-if="!hasSelection() && anySealBroken()">
                    <p class="mt-3 text-sm text-gray-600"><?= $escaper->escapeHtml(__('A seal was opened, so there is nothing to return and no refund is due.')) ?></p>
                </template>
                <template x-if="!hasSelection() && !anySealBroken() && !<?= $noEligibleItems ? 'true' : 'false' ?>">
                    <p class="mt-3 text-sm text-gray-600"><?= $escaper->escapeHtml(__('No items selected yet. Use the + button next to an item to add it to your return.')) ?></p>
                </template>
                <template x-if="hasSelection()">
                    <dl class="mt-3 space-y-2 text-[15px]">
                        <div class="flex justify-between"><dt class="text-gray-600"><?= $escaper->escapeHtml(__('Subtotal')) ?></dt><dd class="text-gray-900" x-text="formatPrice(subtotal())"></dd></div>
                        <div class="flex justify-between"><dt class="text-gray-600"><?= $escaper->escapeHtml(__('Shipping & Handling')) ?></dt><dd class="text-gray-900" x-text="formatPrice(shippingRefund())"></dd></div>
                        <div class="flex justify-between -mt-1.5" x-show="!hideTax()"><dt class="text-xs text-gray-500 pl-3.5"><?= $escaper->escapeHtml(__('Of which VAT')) ?></dt><dd class="text-xs text-gray-500" x-text="formatPrice(tax())"></dd></div>
                        <div class="flex justify-between" x-show="Math.abs(orderAdjustment()) >= 0.005"><dt class="text-gray-600"><?= $escaper->escapeHtml(__('Order Adjustment')) ?></dt><dd class="text-gray-900" x-text="formatPrice(orderAdjustment())"></dd></div>
                        <div class="mt-3 flex justify-between border-t border-gray-200 pt-3 text-[17px] font-bold text-gray-900"><dt><?= $escaper->escapeHtml(__('Total refund')) ?></dt><dd x-text="formatPrice(grandTotal())"></dd></div>
                    </dl>
                </template>
                <p x-show="hasSelection()" class="mt-4 flex items-center gap-2.5 rounded-lg border border-emerald-200 bg-emerald-50 px-4 py-3 text-[14px] leading-snug text-emerald-800">
                    <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="shrink-0"><polyline points="5 13 9 17 19 7"/></svg>
                    <span><?= $escaper->escapeHtml(__('The refund will be issued using your original payment method within 5-7 business days after we receive your return.')) ?></span>
                </p>
            </div>
        </aside>

        <!-- Action bar -->
        <div class="lg:col-span-2 flex flex-wrap items-center gap-4 pt-2 max-sm:flex-col max-sm:items-stretch max-sm:gap-3">
            <a href="<?= $escaper->escapeUrl($indexUrl) ?>" class="text-[15px] text-gray-500 hover:text-gray-700 font-medium max-sm:order-last max-sm:text-center max-sm:mt-2">← <?= $escaper->escapeHtml(__('Back to order search')) ?></a>
            <div class="ml-auto flex flex-wrap items-center gap-3 max-sm:ml-0 max-sm:flex-col max-sm:items-stretch max-sm:w-full">
                <a href="<?= $escaper->escapeUrl($homeUrl) ?>" class="rounded-lg border border-gray-300 bg-white px-5 py-2.5 text-[15px] font-semibold text-gray-700 hover:bg-gray-50 max-sm:w-full max-sm:py-3 max-sm:text-[16px] max-sm:text-center"><?= $escaper->escapeHtml(__('Cancel')) ?></a>
                <button type="button"
                        class="rounded-lg bg-primary px-5 py-2.5 text-[15px] font-semibold text-white hover:bg-primary-darker disabled:cursor-not-allowed disabled:bg-gray-300 max-sm:w-full max-sm:py-3 max-sm:text-[16px]"
                        :disabled="!canContinue()"
                        @click="goToReview()">
                    <?= $escaper->escapeHtml(__('Continue to review')) ?>
                </button>
            </div>
        </div>
    </div>
<?php endif; ?>

    <!-- Panel 3: review & confirm -->
    <div x-show="step === '3'" x-cloak class="grid gap-6 lg:grid-cols-[1fr_320px]">
        <div class="space-y-6">
            <div class="rounded-xl border border-gray-200 bg-white p-6 max-sm:border-0 max-sm:rounded-none max-sm:shadow-none max-sm:p-0 max-sm:bg-transparent">
                <h2 class="text-2xl font-semibold text-gray-900 max-sm:text-[19px]"><?= $escaper->escapeHtml(__('Review and confirm')) ?></h2>
                <?php if ($orderMeta instanceof \MageMe\EUWithdrawal\Block\Withdraw\OrderMeta): ?>
                    <div class="mt-4 flex items-start gap-2.5 rounded-lg bg-gray-50 px-[18px] py-4">
                        <span aria-hidden="true" class="mt-0.5 shrink-0 text-gray-500">
                            <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-4 9 4v10l-9 4-9-4z"/><polyline points="3 9 12 13 21 9"/><line x1="12" y1="13" x2="12" y2="22"/></svg>
                        </span>
                        <div class="min-w-0">
                            <div class="text-[15px] font-semibold leading-snug text-gray-900"><?= $escaper->escapeHtml(__('Order #%1', $orderMeta->getOrderIncrementId())) ?></div>
                            <div class="mt-0.5 text-[13px] leading-snug text-gray-500">
                                <?= $escaper->escapeHtml(__('Placed on %1', $orderMeta->getPlacedAt())) ?>
                                <span aria-hidden="true" class="mx-1.5 font-bold text-gray-400">&bull;</span>
                                <?php if ($orderMeta->isAwaitingDelivery()): ?>
                                    <?= $escaper->escapeHtml(__('Awaiting delivery')) ?>
                                <?php else: ?>
                                    <?= $escaper->escapeHtml(__('Delivered on %1', $orderMeta->getDeliveredAt())) ?>
                                <?php endif; ?>
                            </div>
                        </div>
                    </div>
                <?php endif; ?>
                <table class="mt-4 w-full text-sm max-sm:block">
                    <thead class="max-sm:hidden">
                        <tr class="border-b border-gray-200 text-left text-xs uppercase tracking-wide text-gray-500">
                            <th class="py-2 pr-2 w-16"></th>
                            <th class="py-2 pr-2"><?= $escaper->escapeHtml(__('Item')) ?></th>
                            <th class="py-2 px-2 text-center"><?= $escaper->escapeHtml(__('Qty')) ?></th>
                            <th class="py-2 px-2"><?= $escaper->escapeHtml(__('Reason')) ?></th>
                            <th class="py-2 pl-2 text-right"><?= $escaper->escapeHtml(__('Total')) ?></th>
                        </tr>
                    </thead>
                    <tbody class="max-sm:block">
                        <template x-for="row in selectedRows()" :key="row.id">
                            <tr class="border-b border-gray-100 max-sm:grid max-sm:grid-cols-[56px_1fr_auto] max-sm:items-center max-sm:gap-x-3 max-sm:gap-y-1.5 max-sm:rounded-lg max-sm:border max-sm:border-gray-200 max-sm:bg-white max-sm:p-3 max-sm:mb-3">
                                <td class="py-3 pr-2 max-sm:col-start-1 max-sm:row-start-1 max-sm:p-0">
                                    <template x-if="row.thumb"><img :src="row.thumb" :alt="row.name" class="h-12 w-12 rounded object-cover max-sm:h-14 max-sm:w-14"></template>
                                    <template x-if="!row.thumb"><div class="flex h-12 w-12 items-center justify-center rounded bg-gray-100 text-gray-400 max-sm:h-14 max-sm:w-14"><svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-4 9 4v10l-9 4-9-4z"/><polyline points="3 9 12 13 21 9"/><line x1="12" y1="13" x2="12" y2="22"/></svg></div></template>
                                </td>
                                <td class="py-3 pr-2 text-gray-900 max-sm:col-start-2 max-sm:row-start-1 max-sm:p-0 max-sm:font-semibold max-sm:leading-snug">
                                    <div x-text="row.name"></div>
                                    <template x-if="photoSummaryMap[row.id] && (photoSummaryMap[row.id].photos.length || photoSummaryMap[row.id].skipped)">
                                        <div class="mt-1 flex flex-wrap items-center gap-1.5">
                                            <template x-for="ph in photoSummaryMap[row.id].photos" :key="ph.url">
                                                <img :src="ph.url" :alt="ph.name" class="h-9 w-9 rounded border border-gray-200 object-cover">
                                            </template>
                                            <span x-show="photoSummaryMap[row.id].skipped && !photoSummaryMap[row.id].photos.length"
                                                  class="text-[11px] text-gray-500"><?= $escaper->escapeHtml(__('Seal photos skipped')) ?></span>
                                        </div>
                                    </template>
                                </td>
                                <td class="py-3 px-2 text-center text-gray-700 max-sm:col-start-3 max-sm:row-start-1 max-sm:p-0 max-sm:text-right max-sm:before:content-['Qty:_'] max-sm:before:text-gray-500" x-text="qtyOf(row.id)"></td>
                                <td class="py-3 px-2 text-gray-700 max-sm:col-span-3 max-sm:row-start-2 max-sm:p-0 max-sm:pt-1.5 max-sm:mt-1.5 max-sm:border-t max-sm:border-gray-200 max-sm:text-[13px] max-sm:before:content-['Reason:_'] max-sm:before:font-medium max-sm:before:text-gray-900" x-text="reasonDisplayFor(row.id) || '—'"></td>
                                <td class="py-3 pl-2 text-right text-gray-900 max-sm:col-span-3 max-sm:row-start-3 max-sm:p-0 max-sm:pt-1 max-sm:font-semibold max-sm:text-[15px]" x-text="formatPrice(lineDisplay(row, qtyOf(row.id)))"></td>
                            </tr>
                        </template>
                    </tbody>
                </table>

                <?php if ($shippingNotice !== ''): ?>
                    <div class="mt-4 flex items-start gap-3 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3">
                        <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="mt-0.5 shrink-0 text-gray-500"><path d="M3 6h12v9H3z"/><path d="M15 10h4l2 3v2h-6"/><circle cx="7" cy="17" r="2"/><circle cx="17" cy="17" r="2"/></svg>
                        <div class="grow">
                            <div class="font-medium text-gray-900"><?= $escaper->escapeHtml(__('Return shipping')) ?></div>
                            <p class="mt-0.5 text-sm text-gray-600"><?= $escaper->escapeHtml($shippingNotice) ?></p>
                        </div>
                        <?php if ($returnPolicyUrl !== ''): ?>
                            <a href="<?= $escaper->escapeUrl($returnPolicyUrl) ?>" target="_blank" rel="noopener" class="text-sm text-primary-darker hover:underline">
                                <?= $escaper->escapeHtml(__('View return policy')) ?> →
                            </a>
                        <?php endif; ?>
                    </div>
                <?php endif; ?>

                <div class="mt-4 flex items-start gap-3 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3">
                    <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="mt-0.5 shrink-0 text-gray-500"><rect x="3" y="5" width="18" height="14" rx="2"/><line x1="3" y1="10" x2="21" y2="10"/><line x1="7" y1="15" x2="11" y2="15"/></svg>
                    <div>
                        <div class="font-medium text-gray-900"><?= $escaper->escapeHtml(__('Refund method')) ?></div>
                        <p class="mt-0.5 text-sm text-gray-600"><?= $escaper->escapeHtml(__('Refund will be issued to your original payment method.')) ?></p>
                    </div>
                </div>
            </div>
        </div>

        <aside>
            <div class="rounded-xl border border-gray-200 bg-white p-5 max-sm:border-0 max-sm:rounded-none max-sm:shadow-none max-sm:p-0 max-sm:bg-transparent">
                <h3 class="text-[20px] font-semibold text-gray-900"><?= $escaper->escapeHtml(__('Return summary')) ?></h3>
                <dl class="mt-3 space-y-2 text-[15px]">
                    <div class="flex justify-between"><dt class="text-gray-600"><?= $escaper->escapeHtml(__('Subtotal')) ?></dt><dd class="text-gray-900" x-text="formatPrice(subtotal())"></dd></div>
                    <div class="flex justify-between"><dt class="text-gray-600"><?= $escaper->escapeHtml(__('Shipping & Handling')) ?></dt><dd class="text-gray-900" x-text="formatPrice(shippingRefund())"></dd></div>
                    <div class="flex justify-between -mt-1.5" x-show="!hideTax()"><dt class="text-xs text-gray-500 pl-3.5"><?= $escaper->escapeHtml(__('Of which VAT')) ?></dt><dd class="text-xs text-gray-500" x-text="formatPrice(tax())"></dd></div>
                    <div class="flex justify-between" x-show="Math.abs(orderAdjustment()) >= 0.005"><dt class="text-gray-600"><?= $escaper->escapeHtml(__('Order Adjustment')) ?></dt><dd class="text-gray-900" x-text="formatPrice(orderAdjustment())"></dd></div>
                    <div class="mt-3 flex justify-between border-t border-gray-200 pt-3 text-[17px] font-bold text-gray-900"><dt><?= $escaper->escapeHtml(__('Total refund')) ?></dt><dd x-text="formatPrice(grandTotal())"></dd></div>
                </dl>
                <p class="mt-4 flex items-center gap-2.5 rounded-lg border border-emerald-200 bg-emerald-50 px-4 py-3 text-[14px] leading-snug text-emerald-800">
                    <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="shrink-0"><polyline points="5 13 9 17 19 7"/></svg>
                    <span><?= $escaper->escapeHtml(__('The refund will be issued using your original payment method within 5-7 business days after we receive your return.')) ?></span>
                </p>
            </div>
        </aside>

        <div class="lg:col-span-2 flex flex-wrap items-center gap-4 pt-2 max-sm:flex-col max-sm:items-stretch max-sm:gap-3">
            <a href="#" @click.prevent="step = '2'; syncStepperStore();" class="text-[15px] text-gray-500 hover:text-gray-700 font-medium max-sm:order-last max-sm:text-center max-sm:mt-2">← <?= $escaper->escapeHtml(__('Back to items')) ?></a>
            <div class="ml-auto flex flex-wrap items-center gap-3 max-sm:ml-0 max-sm:flex-col max-sm:items-stretch max-sm:w-full">
                <a href="<?= $escaper->escapeUrl($homeUrl) ?>" class="rounded-lg border border-gray-300 bg-white px-5 py-2.5 text-[15px] font-semibold text-gray-700 hover:bg-gray-50 max-sm:w-full max-sm:py-3 max-sm:text-[16px] max-sm:text-center"><?= $escaper->escapeHtml(__('Cancel')) ?></a>
                <div class="flex flex-col items-end max-sm:w-full max-sm:items-stretch">
                    <button type="button"
                            class="rounded-lg bg-primary px-5 py-2.5 text-[15px] font-semibold text-white hover:bg-primary-darker disabled:cursor-not-allowed disabled:bg-gray-300 max-sm:w-full max-sm:py-3 max-sm:text-[16px]"
                            :disabled="submitting"
                            @click="submit()">
                        <span x-text="submitting ? '<?= $escaper->escapeJs(__('Submitting…')) ?>' : '<?= $escaper->escapeJs($confirmLabel) ?>'"></span>
                    </button>
                    <small class="mt-1 text-xs text-red-700 max-sm:text-center" x-show="errorMessage" x-text="errorMessage"></small>
                </div>
            </div>
        </div>
    </div>

    <!-- Panel 4: submitted -->
    <div x-show="step === '4'" x-cloak class="space-y-6">
        <div class="grid gap-0 rounded-xl border border-gray-200 bg-white max-sm:border-0 max-sm:rounded-none max-sm:shadow-none max-sm:bg-transparent md:grid-cols-[3fr_2fr] md:divide-x md:divide-gray-200">
            <div class="flex flex-col items-center justify-center p-10 text-center max-sm:p-6">
                <div class="flex h-14 w-14 items-center justify-center rounded-full border-2 border-emerald-200 bg-emerald-50 text-emerald-600">
                    <svg viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="5 13 9 17 19 7"/></svg>
                </div>
                <h1 class="mt-5 text-[22px] font-bold text-gray-900 leading-tight max-sm:text-[19px]"><?= $escaper->escapeHtml(__('Your return request has been submitted!')) ?></h1>
                <p class="mt-3 max-w-[360px] text-[14px] leading-relaxed text-gray-500">
                    <?= $escaper->escapeHtml(__("We've emailed you a notification with your return details and a prepaid return label (if applicable). You have 14 days from the delivery date to send your items back.")) ?>
                </p>
                <div class="mt-6 text-[13px] text-gray-500"><?= $escaper->escapeHtml(__('Return request number')) ?></div>
                <div class="relative mt-2 mb-4 w-full max-w-[320px] rounded-lg border border-gray-200 bg-gray-50 px-10 py-2.5">
                    <code class="block text-center font-mono text-[15px] font-semibold leading-5 text-gray-900" x-text="submitted.rrNumber || '#0'"></code>
                    <span x-show="copyFlash" x-cloak class="pointer-events-none absolute right-11 top-1/2 -translate-y-1/2 text-[12px] text-emerald-600"><?= $escaper->escapeHtml(__('Copied!')) ?></span>
                    <button type="button"
                            class="absolute right-2.5 top-1/2 -translate-y-1/2 inline-flex h-6 w-6 items-center justify-center rounded text-gray-500 hover:text-primary transition-colors"
                            @click="copyRr()"
                            aria-label="<?= $escaper->escapeHtmlAttr(__('Copy request number')) ?>">
                        <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="8" y="8" width="12" height="12" rx="2"/><path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2"/></svg>
                    </button>
                </div>
            </div>
            <div class="flex flex-col justify-center p-10 max-sm:p-6">
                <div class="flex items-start gap-3.5 pb-5">
                    <div class="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary">
                        <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="5" width="18" height="14" rx="2"/><polyline points="3 7 12 13 21 7"/></svg>
                    </div>
                    <div class="min-w-0">
                        <div class="text-[14px] font-semibold text-gray-900"><?= $escaper->escapeHtml(__('Confirmation email sent')) ?></div>
                        <div class="mt-1 text-[13px] leading-relaxed text-gray-500">
                            <?= $escaper->escapeHtml(__("We've sent the details to")) ?>
                            <div><strong class="text-gray-900 font-semibold" x-text="submitted.email">—</strong></div>
                        </div>
                    </div>
                </div>
                <div class="flex items-start gap-3.5 border-t border-gray-200 pt-5">
                    <div class="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary">
                        <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="5" width="16" height="16" rx="2"/><line x1="16" y1="3" x2="16" y2="7"/><line x1="8" y1="3" x2="8" y2="7"/><line x1="4" y1="11" x2="20" y2="11"/></svg>
                    </div>
                    <div class="min-w-0">
                        <div class="text-[14px] font-semibold text-gray-900"><?= $escaper->escapeHtml(__('What happens next?')) ?></div>
                        <ol class="mt-1.5 list-inside list-decimal space-y-0.5 text-[13px] leading-relaxed text-gray-500">
                            <li><?= $escaper->escapeHtml(__('Pack your items securely')) ?></li>
                            <li><?= $escaper->escapeHtml(__('Send them back using the provided instructions')) ?></li>
                            <li><?= $escaper->escapeHtml(__("We'll inspect your return")) ?></li>
                            <li><?= $escaper->escapeHtml(__('Your refund will be issued to your original payment method')) ?></li>
                        </ol>
                    </div>
                </div>
            </div>
        </div>
        <div class="flex items-center justify-center gap-3 py-5 text-[14px] text-gray-500 max-sm:flex-col max-sm:gap-2">
            <span><?= $escaper->escapeHtml(__('Need more help?')) ?></span>
            <a href="<?= $escaper->escapeUrl($orderHistoryUrl) ?>" class="font-medium text-primary hover:underline"><?= $escaper->escapeHtml(__('View my returns')) ?></a>
            <span aria-hidden="true" class="text-gray-300 max-sm:hidden">|</span>
            <a href="<?= $escaper->escapeUrl($contactUrl) ?>" class="font-medium text-primary hover:underline"><?= $escaper->escapeHtml(__('Contact support')) ?></a>
        </div>
    </div>
</section>

<script>
(function () {
    'use strict';
    function withdrawalApp(boot) {
        return {
            step: '2',
            boot: boot,
            items: boot.items || [],
            fullOrder: boot.fullOrder || false,
            qtyMap: {},        // { itemId: qty }
            reasonMap: {},     // { itemId: { code, codeLabel, text } }
            sealMap: {},       // { itemId: boolean | null }
            preSealQty: {},    // { itemId: int }  qty stashed when "Yes broken" picked, restored on revert to "No intact"
            photoGateMap: {},  // { itemId: bool } Pro seal-photo gate; key present only while the control is active
            photoSummaryMap: {}, // { itemId: { photos: [{url,name}], skipped } } echoed onto the review step
            submitted: { rrNumber: '', email: '' },
            errorMessage: '',
            submitting: false,
            copyFlash: false,

            init() {
                this.items.forEach(row => {
                    // Full-order mode: seed every eligible row with its full remaining qty.
                    // Per-item mode: start at 0 (customer selects manually).
                    this.qtyMap[row.id] = (this.fullOrder && row.eligible) ? (row.remaining || 0) : 0;
                    this.reasonMap[row.id] = { code: '', codeLabel: '', text: '' };
                    this.sealMap[row.id] = null;
                    (row.contents || []).forEach(c => { if (c.sealed) this.sealMap[c.sealed.childSealId] = null; });
                });
                if (!this.boot.formKey) {
                    const hidden = this.$root.querySelector('input[name="form_key"]');
                    if (hidden && hidden.value) this.boot.formKey = hidden.value;
                }
                // Pro seal-photo gate: the nested euwSealPhoto component dispatches
                // mm-eu-w-photo-gate (bubbling) on every photo/skip change. Record
                // per-item satisfaction so canContinue() can require a photo or an
                // explicit skip. No event = control inactive (mode=disabled / Pro
                // absent) = no requirement.
                this.$root.addEventListener('mm-eu-w-photo-gate', (e) => {
                    if (e.detail && typeof e.detail.itemId !== 'undefined') {
                        this.photoGateMap = Object.assign({}, this.photoGateMap, {
                            [e.detail.itemId]: !!e.detail.satisfied,
                        });
                        this.photoSummaryMap = Object.assign({}, this.photoSummaryMap, {
                            [e.detail.itemId]: {
                                photos: e.detail.photos || [],
                                skipped: !!e.detail.skipped,
                            },
                        });
                    }
                });
                this.syncStepperStore();
            },

            // Mirror panel state to the shared Alpine store so the
            // progress_stepper.phtml component (rendered in a sibling block,
            // outside this x-data scope) can react to step changes.
            syncStepperStore() {
                if (window.Alpine && Alpine.store('withdrawal')) {
                    Alpine.store('withdrawal').step = parseInt(this.step, 10) || 2;
                }
            },

            qtyOf(id) { return this.qtyMap[id] || 0; },
            reasonOf(id) { return this.reasonMap[id] || { code: '', codeLabel: '', text: '' }; },
            sealOf(id) { return this.sealMap[id]; },

            canEditQty(row) {
                if (!row.eligible) return false;
                if (row.sealKind && this.sealMap[row.id] === true) return false;
                if (this.hasBrokenSealedChild(row)) return false;
                return true;
            },

            inc(row) {
                if (!this.canEditQty(row)) return;
                const cur = this.qtyOf(row.id);
                if (cur < row.remaining) this.qtyMap[row.id] = cur + 1;
            },

            dec(row) {
                if (!this.canEditQty(row)) return;
                const cur = this.qtyOf(row.id);
                if (cur > 0) this.qtyMap[row.id] = cur - 1;
            },

            setQty(row, qty) {
                if (!this.canEditQty(row)) { this.qtyMap[row.id] = 0; return; }
                qty = Math.max(0, Math.min(row.remaining, qty));
                this.qtyMap[row.id] = qty;
            },

            setSeal(row, opened) {
                if (this.sealMap[row.id] === opened) return;
                this.sealMap[row.id] = opened;
                if (opened === true) {
                    // Art. 16(e)/(i): broken seal makes item ineligible. Stash current qty so a misclick
                    // (Yes → then No) can restore the previous qty instead of leaving the item at 0.
                    const cur = this.qtyOf(row.id);
                    if (cur > 0) this.preSealQty[row.id] = cur;
                    this.qtyMap[row.id] = 0;
                } else if (opened === false) {
                    if (this.fullOrder) {
                        // Full-order mode: always restore to full remaining qty on seal revert.
                        this.qtyMap[row.id] = row.remaining || 0;
                        delete this.preSealQty[row.id];
                    } else {
                        const stashed = this.preSealQty[row.id];
                        if (stashed && this.qtyOf(row.id) === 0) {
                            const cap = typeof row.remaining === 'number' && row.remaining > 0 ? row.remaining : stashed;
                            this.qtyMap[row.id] = Math.min(stashed, cap);
                            delete this.preSealQty[row.id];
                        }
                    }
                }
            },

            // A sealed component inside a bundle keys its answer by the child's own
            // order-item id and gates the whole bundle line (the returnable unit).
            childSealOf(child) { return child.sealed ? this.sealMap[child.sealed.childSealId] : undefined; },

            hasBrokenSealedChild(row) {
                return (row.contents || []).some(c => c.sealed && this.sealMap[c.sealed.childSealId] === true);
            },

            setChildSeal(row, child, opened) {
                if (!child.sealed) return;
                const id = child.sealed.childSealId;
                if (this.sealMap[id] === opened) return;
                this.sealMap[id] = opened;
                if (opened === true) {
                    // Art. 16(e)/(i): a broken component seal excludes the whole bundle.
                    // Stash the parent qty so a misclick (No → Yes) can restore it.
                    const cur = this.qtyOf(row.id);
                    if (cur > 0) this.preSealQty[row.id] = cur;
                    this.qtyMap[row.id] = 0;
                } else if (opened === false && !this.hasBrokenSealedChild(row)) {
                    if (this.fullOrder) {
                        this.qtyMap[row.id] = row.remaining || 0;
                        delete this.preSealQty[row.id];
                    } else {
                        const stashed = this.preSealQty[row.id];
                        if (stashed && this.qtyOf(row.id) === 0) {
                            const cap = typeof row.remaining === 'number' && row.remaining > 0 ? row.remaining : stashed;
                            this.qtyMap[row.id] = Math.min(stashed, cap);
                            delete this.preSealQty[row.id];
                        }
                    }
                }
            },

            setReason(row, code, label) {
                const r = this.reasonMap[row.id] || { code: '', codeLabel: '', text: '' };
                this.reasonMap[row.id] = { code: code || '', codeLabel: label || '', text: r.text || '' };
            },

            setReasonText(row, text) {
                const r = this.reasonMap[row.id] || { code: '', codeLabel: '', text: '' };
                this.reasonMap[row.id] = { code: r.code, codeLabel: r.codeLabel, text: text || '' };
            },

            reasonDisplayFor(id) {
                const r = this.reasonOf(id);
                if (!r) return '';
                if (r.code === 'other') return (r.text || '').trim() || r.codeLabel || '';
                return r.codeLabel || '';
            },

            hasSelection() {
                return this.items.some(r => this.qtyOf(r.id) > 0);
            },

            // A selectable line whose seal the customer declared open. Used to
            // explain an empty selection ("seal opened, no refund") rather than
            // showing the "use the + button" hint.
            anySealBroken() {
                return this.items.some(r => r.sealKind && this.sealMap[r.id] === true);
            },

            canContinue() {
                if (!this.hasSelection()) return false;
                // Block if any qty>0 item with sealKind hasn't been answered
                for (const r of this.items) {
                    if (this.qtyOf(r.id) > 0 && r.sealKind && this.sealMap[r.id] === null) return false;
                }
                return true;
            },

            selectedRows() {
                return this.items.filter(r => this.qtyOf(r.id) > 0);
            },

            isFullReturn() {
                const eligibleIds = Object.keys(this.boot.eligibleItems || {});
                if (eligibleIds.length === 0) return false;
                const selectedCount = this.selectedRows().length;
                if (selectedCount !== eligibleIds.length) return false;
                for (const id of eligibleIds) {
                    const need = Number((this.boot.eligibleItems[id] || {}).qty || 0);
                    if (this.qtyOf(Number(id)) < need) return false;
                }
                return true;
            },

            hideTax() { return !!this.boot.hideTax; },

            // Gross per-unit price — the amount actually refunded, matching the
            // confirmation page and the receipt.
            unitDisplay(row) {
                const net = Number(row.unitPrice || 0);
                const ordered = Number(row.qtyOrdered) || 1;
                return this.roundHalfEven(net + this.roundHalfEven(Number(row.taxAmount || 0) / ordered, 4), 4);
            },

            // Prorate the row, exactly as RefundCalculator does. Multiplying the
            // already-rounded unitPrice drifts by up to qty * 0.00005. The
            // fallback serves pages cached before netAmount was emitted.
            lineNetFor(row, qty) {
                if (typeof row.netAmount !== 'undefined' && typeof row.qtyOrdered !== 'undefined') {
                    const ordered = Number(row.qtyOrdered) || 1;
                    return this.roundHalfEven(qty * Number(row.netAmount || 0) / ordered, 4);
                }
                return qty * Number(row.unitPrice || 0);
            },

            // Net and VAT round separately, the way RefundCalculator does, so the
            // line matches the stored refund_amount to the cent.
            lineDisplay(row, qty) {
                return this.roundHalfEven(this.lineNetFor(row, qty) + this.lineTaxFor(row, qty), 4);
            },

            itemsNet() {
                return this.selectedRows().reduce((sum, r) => sum + this.lineNetFor(r, this.qtyOf(r.id)), 0);
            },

            subtotal() {
                return this.roundHalfEven(this.itemsNet() + this.itemTax(), 4);
            },

            // Banker's rounding (PHP_ROUND_HALF_EVEN). Required for exact
            // parity with server-side RefundCalculator::calculate() — without
            // this, the displayed total drifts 0.01-0.05c from the value
            // RefundCalculator writes to mm_eu_withdrawal_request.
            roundHalfEven(value, dp) {
                if (!isFinite(value)) return 0;
                const factor = Math.pow(10, dp);
                const scaled = value * factor;
                const floor = Math.floor(scaled);
                const diff = scaled - floor;
                if (Math.abs(diff - 0.5) < 1e-9) {
                    return ((floor % 2 === 0) ? floor : floor + 1) / factor;
                }
                return Math.round(scaled) / factor;
            },

            lineTaxFor(row, qty) {
                if (typeof row.taxAmount !== 'undefined' && typeof row.qtyOrdered !== 'undefined') {
                    const ordered = Number(row.qtyOrdered) || 1;
                    return this.roundHalfEven(qty * Number(row.taxAmount || 0) / ordered, 4);
                }
                // Legacy schema fallback (pre-0.1.1 cached pages):
                return qty * Number(row.unitTax || 0);
            },

            itemTax() {
                let t = 0;
                for (const r of this.selectedRows()) {
                    t += this.lineTaxFor(r, this.qtyOf(r.id));
                }
                return this.roundHalfEven(t, 4);
            },

            shipNet() { return this.isFullReturn() ? this.roundHalfEven(Number(this.boot.shippingPaid || 0), 4) : 0; },
            shipTax() { return this.isFullReturn() ? this.roundHalfEven(Number(this.boot.shippingTax || 0), 4) : 0; },

            // Displayed VAT line: full refund VAT (item + shipping). Subtotal and
            // Shipping are gross, so this breaks the tax out rather than adding it.
            tax() { return this.roundHalfEven(this.itemTax() + this.shipTax(), 4); },

            shippingRefund() {
                return this.roundHalfEven(this.shipNet() + this.shipTax(), 4);
            },

            // itemsNet() here, not subtotal(): the order-level gap was computed
            // against a net item base, so the ratio must use the same basis.
            orderAdjustment() {
                const base = Number(this.boot.orderItemsBase || 0);
                const gap = Number(this.boot.orderLevelGap || 0);
                if (Math.abs(gap) <= 0.005 || base <= 0) return 0;
                return this.roundHalfEven(gap * (this.itemsNet() / base), 4);
            },

            grandTotal() {
                return this.roundHalfEven(
                    this.subtotal() + this.shippingRefund() + this.orderAdjustment(),
                    4,
                );
            },

            formatPrice(amount) {
                const cur = this.boot.currency || 'EUR';
                try {
                    return new Intl.NumberFormat(document.documentElement.lang || 'en-US', { style: 'currency', currency: cur }).format(amount);
                } catch {
                    return cur + ' ' + Number(amount).toFixed(2);
                }
            },

            stripIcon(tone) {
                if (tone === 'done') return '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="5 13 9 17 19 7"/></svg>';
                if (tone === 'pending') return '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><polyline points="12 7 12 12 15 14"/></svg>';
                return '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><line x1="12" y1="8" x2="12" y2="12"/></svg>';
            },

            stripText(strip) {
                if (strip.tone === 'done') return strip.label || (this.boot.i18n && this.boot.i18n.alreadyReturned) || 'Already returned.';
                if (strip.tone === 'pending') {
                    if (strip.requestId) {
                        const id = strip.incrementId || ('#' + strip.requestId);
                        const status = strip.status === 'pending' ? 'awaiting admin review' : 'awaiting confirmation';
                        return 'Request ' + id + ' ' + status + ' (' + strip.qty + ' item' + (strip.qty === 1 ? '' : 's') + ').';
                    }
                    return strip.label || 'Return request awaiting review.';
                }
                return strip.label || '';
            },

            goToReview() {
                if (!this.canContinue()) return;
                // Pro seal-photo gate: Continue is not disabled. If an intact
                // selected item still has neither a photo nor an explicit skip,
                // tell the control(s) to highlight, scroll to the first, and stay.
                const blocked = this.items.find(r =>
                    this.qtyOf(r.id) > 0 && r.sealKind && this.sealMap[r.id] === false
                    && this.photoGateMap[r.id] === false);
                if (blocked) {
                    this.$dispatch('mm-eu-w-photo-enforce');
                    const el = this.$root.querySelector('[data-role="seal-extra"][data-item-id="' + blocked.id + '"]');
                    if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' });
                    return;
                }
                this.errorMessage = '';
                this.step = '3';
                this.syncStepperStore();
                window.scrollTo(0, 0);
            },

            async submit() {
                if (this.submitting) return;
                this.submitting = true;
                this.errorMessage = '';

                const itemReasons = {};
                for (const r of this.selectedRows()) {
                    const reason = this.reasonOf(r.id);
                    if (!reason || (reason.code === '' && (reason.text || '').trim() === '')) continue;
                    itemReasons[String(r.id)] = {
                        code: reason.code || '',
                        text: reason.code === 'other' ? (reason.text || '').trim() : '',
                    };
                }

                // Per-item seal-broken declaration — sent so the server enforces
                // the Art. 16(e)/(i) exclusion itself, not just via client qty-zeroing.
                const itemSeal = Object.fromEntries([
                    ...this.items.filter(r => this.sealMap[r.id] === true).map(r => [String(r.sealSubjectId ?? r.id), 1]),
                    ...this.items.flatMap(r => (r.contents || [])
                        .filter(c => c.sealed && this.sealMap[c.sealed.childSealId] === true)
                        .map(c => [String(c.sealed.childSealId), 1])),
                ]);
                const body = {
                    orderId: this.boot.orderIncrementId || '',
                    items: Object.fromEntries(this.selectedRows().map(r => [String(r.id), this.qtyOf(r.id)])),
                    itemReasons: itemReasons,
                    itemSeal: itemSeal,
                    formKey: this.boot.formKey || '',
                };

                // Propagate `?t=` (Pro magic-link) AND `?order_id=` (Free session-
                // verified fallback) from the page URL so the server-side
                // CustomerIdentityFactory can resolve the guest's bound order.
                // Without `?order_id=` the JSON-POST URL has no query string at
                // all in the base module, and identity resolution fails with the misleading
                // "We could not find a matching order for that email" error.
                let url = this.boot.finalizeUrl;
                const pageParams = new URLSearchParams(window.location.search);
                const t = pageParams.get('t');
                const oid = pageParams.get('order_id');
                const extra = [];
                if (t) extra.push('t=' + encodeURIComponent(t));
                if (oid) extra.push('order_id=' + encodeURIComponent(oid));
                if (extra.length) url += (url.indexOf('?') === -1 ? '?' : '&') + extra.join('&');

                const beforeSubmit = new CustomEvent('mm-eu-w:before-submit', { cancelable: true, detail: { body } });
                if (!document.dispatchEvent(beforeSubmit)) {
                    if (beforeSubmit.detail.cancelMessage) {
                        this.errorMessage = beforeSubmit.detail.cancelMessage;
                    }
                    this.submitting = false;
                    return;
                }
                try {
                    const resp = await fetch(url, {
                        method: 'POST',
                        credentials: 'same-origin',
                        headers: {
                            'Content-Type': 'application/json',
                            'Accept': 'application/json',
                            'X-Requested-With': 'XMLHttpRequest',
                            'X-Magento-Form-Key': this.boot.formKey || '',
                        },
                        body: JSON.stringify(body),
                    });
                    const data = await resp.json().catch(() => ({ ok: false, error: this.boot.i18n.invalidServerReply }));
                    if (resp.ok && data.ok) {
                        this.submitted = {
                            rrNumber: data.incrementId || ('#' + (data.requestId || 0)),
                            email: data.customerEmail || '',
                        };
                        this.step = '4';
                        this.syncStepperStore();
                        window.scrollTo(0, 0);
                    } else {
                        this.errorMessage = data.error || this.boot.i18n.submissionFailed;
                    }
                } catch (e) {
                    this.errorMessage = this.boot.i18n.networkError;
                } finally {
                    this.submitting = false;
                }
            },

            async cancelRequest(requestId) {
                let url = this.boot.cancelUrl;
                if (!url || !requestId) return;
                // Same dual-param propagation as submitFinal — The base module needs
                // `?order_id=` so CustomerIdentityFactory can pass the
                // ownership check (Cancel::canSeeRequest).
                const pageParams = new URLSearchParams(window.location.search);
                const t = pageParams.get('t');
                const oid = pageParams.get('order_id');
                const extra = [];
                if (t) extra.push('t=' + encodeURIComponent(t));
                if (oid) extra.push('order_id=' + encodeURIComponent(oid));
                if (extra.length) url += (url.indexOf('?') === -1 ? '?' : '&') + extra.join('&');
                const body = new FormData();
                body.append('form_key', this.boot.formKey || '');
                body.append('request_id', String(requestId));
                try {
                    await fetch(url, { method: 'POST', body, credentials: 'same-origin', headers: { 'X-Requested-With': 'XMLHttpRequest' }, redirect: 'follow' });
                } catch {}
                window.location.reload();
            },

            async copyRr() {
                const v = this.submitted.rrNumber || '';
                if (!v) return;
                try {
                    if (navigator.clipboard) {
                        await navigator.clipboard.writeText(v);
                    } else {
                        const ta = document.createElement('textarea');
                        ta.value = v; ta.setAttribute('readonly', ''); ta.style.position = 'fixed'; ta.style.opacity = '0';
                        document.body.appendChild(ta); ta.select();
                        document.execCommand('copy');
                        document.body.removeChild(ta);
                    }
                    this.copyFlash = true;
                    setTimeout(() => { this.copyFlash = false; }, 2000);
                } catch {}
            },
        };
    }
    window.addEventListener('alpine:init', function () {
        // Shared store so the standalone progress_stepper.phtml component
        // (rendered in a sibling block, outside this SPA's x-data scope) can
        // bind its circle/title/connector classes to the live step number.
        Alpine.store('withdrawal', { step: 2 });
        Alpine.data('withdrawalApp', withdrawalApp);
    }, { once: true });
})();
</script>
<?php isset($hyvaCsp) && $hyvaCsp->registerInlineScript() ?>
