import { Component, computed, inject, signal } from '@angular/core';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { ApiService, Addon, HallDetail, HallAvailability, Package, PackageItem, PackageContent, ItemMedia, ItemOption } from '../../core/api.service';
import { LangService } from '../../core/lang.service';
import { SelectionService, SelectedAddon, SelectedItem, ContentSwapChoice } from '../../core/selection.service';
import { WizardStateService } from '../../core/wizard-state.service';

declare const pannellum: any;

@Component({
  selector: 'app-hall-detail',
  standalone: true,
  imports: [RouterLink],
  template: `
    @if (loading()) {
      <div class="container center load muted">{{ lang.t('loading') }}</div>
    } @else if (hall()) {
      @if (hall(); as h) {
      <section class="detail fade-in">
        <!-- Hero -->
        <div class="hero" [style.background-image]="h.cover_url ? 'url(' + h.cover_url + ')' : ''">
          <div class="hero-shade"></div>
          <div class="container hero-in">
            <a routerLink="/results" class="back">‹ {{ lang.t('back_to_results') }}</a>
            <h1>{{ name(h) }}</h1>
            <p class="venue">{{ venueName(h) }}</p>
            <div class="facts">
              <span>{{ lang.t('capacity') }} {{ h.capacity_min }}–{{ h.capacity_max }} {{ lang.t('guests_unit') }}</span>
              <span class="dot">•</span>
              <span>{{ lang.t('from') }} {{ lang.money(h.min_budget) }}</span>
            </div>
          </div>
        </div>

        <div class="container body">
          <div class="main">
            @if (hallDesc(h)) {
              <h2>{{ lang.t('about') }}</h2>
              <p class="muted desc">{{ hallDesc(h) }}</p>
            }

            @if (featureKeys(h).length) {
              <h3 class="sub">{{ lang.t('features') }}</h3>
              <div class="chips">
                @for (f of featureKeys(h); track f) { <span class="chip chip--static">{{ featLabel(f) }}</span> }
              </div>
            }

            @if (allImages(h).length) {
              <h3 class="sub">{{ lang.t('gallery') }}</h3>
              @if (eventTypes(h).length) {
                <div class="chips g-filter">
                  <span class="chip" [class.chip--on]="galleryFilter() === ''" (click)="galleryFilter.set('')">{{ lang.t('all_occasions') }}</span>
                  @for (et of eventTypes(h); track et) {
                    <span class="chip" [class.chip--on]="galleryFilter() === et" (click)="galleryFilter.set(et)">{{ etLabel(et) }}</span>
                  }
                </div>
              }
              <div class="gallery">
                @for (g of images(h); track g.url) {
                  <div class="g-item" [style.background-image]="'url(' + g.url + ')'"></div>
                }
              </div>
            }
            @for (v of videos(h); track v.url) {
              @if (ytEmbed(v.url); as emb) {
                <iframe class="g-video g-yt" [src]="emb" title="video" frameborder="0"
                        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
                        allowfullscreen></iframe>
              } @else {
                <video class="g-video" controls [src]="v.url"></video>
              }
            }

            @if (panos().length) {
              <h3 class="sub">{{ lang.t('virtual_tour') }}</h3>
              @for (p of panos(); track p.id) {
                <div class="pano" [id]="p.id"></div>
              }
            }
          </div>

          <!-- Booking sidebar -->
          <aside class="side">
            <div class="card pick">
              <h3>{{ lang.t('choose_package') }}</h3>
              @if (h.packages.length) {
                @for (p of h.packages; track p.id) {
                  <label class="pkg" [class.pkg--on]="selectedPackageId() === p.id">
                    <input type="radio" name="pkg" [checked]="selectedPackageId() === p.id" (change)="pickPackage(p)" />
                    <span class="pkg-main">
                      <span class="pkg-name">{{ pkgName(p) }}</span>
                      <span class="pkg-price">{{ lang.money(p.price) }}@if (p.price_per_person) {<small> / {{ lang.t('per_person') }}</small>}</span>
                    </span>
                  </label>
                }
              } @else {
                <p class="muted">{{ lang.t('no_packages') }}</p>
              }

              @if (selectedPackage(); as sp) {
                @if (pkgDesc(sp)) { <p class="muted pkg-desc">{{ pkgDesc(sp) }}</p> }
                @if (sp.items?.length) {
                  <h4 class="incl-h">{{ lang.t('whats_included') }} <small class="incl-tip">{{ lang.t('customize_hint') }}</small></h4>
                  <ul class="incl-list">
                    @for (it of sp.items!; track it.id) {
                      <li [class.opt]="it.is_addon" [class.off]="!itemOn(it.id)">
                        <div class="incl-row">
                          <label class="incl-toggle">
                            <input type="checkbox" [checked]="itemOn(it.id)" (change)="toggleItem(it, $event)" />
                            <span class="incl-name">{{ itemName(it) }}</span>
                          </label>
                          <span class="incl-meta">
                            @if (variantDelta(it)) {
                              <span class="incl-cost">{{ variantDelta(it) > 0 ? '+' : '' }}{{ lang.money(variantDelta(it)) }}</span>
                            }
                            @if (itemOn(it.id) && it.is_addon && it.qty_mode === 'per_guest') {
                              <span class="incl-qty">× {{ guestCount() }} ({{ lang.t('per_person') }})</span>
                            } @else if (itemOn(it.id) && it.is_addon) {
                              <span class="qstep mini">
                                <button type="button" (click)="itemQty(it, -1)">−</button>
                                <span class="qn">{{ itemCount(it.id) }}</span>
                                <button type="button" (click)="itemQty(it, 1)">+</button>
                              </span>
                            } @else if (itemQtyLabel(it)) {
                              <span class="incl-qty">{{ itemQtyLabel(it) }}</span>
                            }
                            @if (it.is_addon && it.price) { <span class="incl-cost">+{{ lang.money((it.price || 0) * effAddonQty(it)) }}</span> }
                            @else if (it.is_addon) { <span class="incl-opt">{{ lang.t('optional') }}</span> }
                            @if (hasDetails(it)) {
                              <button type="button" class="info-ico" [class.on]="openContents().has(it.id)"
                                      (click)="toggleContents(it.id)" [title]="lang.t('view_details')">i</button>
                            }
                          </span>
                        </div>
                        @if (it.options?.length && itemOn(it.id)) {
                          <div class="incl-variant">
                            <span class="incl-variant-lbl">{{ lang.t('swap_option') }}</span>
                            <select class="variant-sel" (change)="setVariant(it, $event)" (click)="$event.stopPropagation()">
                              <option value="0">{{ itemName(it) }}</option>
                              @for (o of it.options!; track $index) {
                                <option [value]="$index + 1">{{ optLabel(o) }} ({{ o.price_delta > 0 ? '+' : '' }}{{ lang.money(o.price_delta) }})</option>
                              }
                            </select>
                          </div>
                        }
                        @if (hasDetails(it) && openContents().has(it.id)) {
                          <div class="incl-contents fade-in">
                            @if (itemDesc(it)) { <p class="incl-desc">{{ itemDesc(it) }}</p> }

                            @if (it.media?.length) {
                              <div class="item-media">
                                @for (m of it.media!; track m.url) {
                                  @if (m.type === 'video') {
                                    <video class="im-thumb im-video" [src]="m.url" (click)="openLightbox(m)" muted></video>
                                  } @else {
                                    <button type="button" class="im-thumb" [style.background-image]="'url(' + m.url + ')'"
                                            (click)="openLightbox(m)">
                                      @if (m.type === '360') { <span class="im-badge">360°</span> }
                                    </button>
                                  }
                                }
                              </div>
                            }

                            @if (it.contents?.length) {
                              <p class="incl-contents-h">{{ lang.t('view_contents') }}</p>
                              <ul>
                                @for (c of it.contents!; track $index) {
                                  <li>
                                    <div class="c-main">
                                      <span>{{ loc(c.name, c.name_ar) }}</span>
                                      @if (c.swap_options?.length) {
                                        <select class="swap-sel" [value]="swapIdx(it, $index)" (change)="setContentSwap(it, $index, c, $event)" (click)="$event.stopPropagation()">
                                          <option [value]="0">{{ lang.t('keep_as_is') }}</option>
                                          @for (so of c.swap_options!; track $index) {
                                            <option [value]="$index + 1">{{ lang.t('swap_to_prefix') }} {{ loc(so.name, so.name_ar) }}@if (so.price_delta) { ({{ so.price_delta > 0 ? '+' : '' }}{{ lang.money(so.price_delta) }}{{ c.quantity > 1 ? ' ' + lang.t('each') : '' }}) }</option>
                                          }
                                        </select>
                                      }
                                      @if (swapIdx(it, $index) > 0 && c.quantity > 1) {
                                        <span class="swap-qty">
                                          <span class="swap-qty-lbl">{{ lang.t('replace_qty') }}</span>
                                          <button type="button" (click)="swapQtyStep(it, $index, c, -1)">−</button>
                                          <span class="qn">{{ swapQty(it, $index, c) }}</span>
                                          <button type="button" (click)="swapQtyStep(it, $index, c, 1)">+</button>
                                          <span class="muted">/ {{ c.quantity }}</span>
                                        </span>
                                      }
                                    </div>
                                    <span class="muted c-qty">{{ contentQty(c) }}@if (swapDelta(it, $index, c)) {<span class="c-delta"> · {{ swapDelta(it, $index, c) > 0 ? '+' : '' }}{{ lang.money(swapDelta(it, $index, c)) }}</span>}</span>
                                  </li>
                                }
                              </ul>
                            }
                          </div>
                        }
                      </li>
                    }
                  </ul>
                }
              }

              @if (h.addons.length) {
                <h3 class="addon-h">{{ lang.t('optional_addons') }}</h3>
                @for (a of h.addons; track a.id) {
                  <div class="addon" [class.addon--on]="qty(a.id) > 0">
                    <label class="addon-toggle">
                      <input type="checkbox" [checked]="qty(a.id) > 0" (change)="toggleAddon(a, $event)" />
                      <span class="addon-name">{{ addonName(a) }}</span>
                    </label>
                    @if (qty(a.id) > 0) {
                      <span class="qstep">
                        <button type="button" (click)="decAddon(a)">−</button>
                        <span class="qn">{{ qty(a.id) }}</span>
                        <button type="button" (click)="incAddon(a)">+</button>
                      </span>
                    }
                    <span class="addon-price">{{ lang.money(a.price * (qty(a.id) || 1)) }}</span>
                  </div>
                }
              }

              @if (avail()) {
                <div class="cal">
                  <div class="cal-head">
                    <button type="button" class="cal-nav" (click)="shiftMonth(-1)">‹</button>
                    <span class="cal-title">{{ lang.t('availability') }} · {{ monthLabel() }}</span>
                    <button type="button" class="cal-nav" (click)="shiftMonth(1)">›</button>
                  </div>
                  <div class="cal-grid">
                    @for (w of weekdays(); track w) { <span class="cal-dow">{{ w }}</span> }
                    @for (c of calCells(); track $index) {
                      @if (c) {
                        <span class="cal-day" [class.taken]="c.taken" [class.past]="c.past" [class.chosen]="c.chosen" [class.free]="!c.taken && !c.past"
                              [title]="c.taken ? lang.t('cal_booked') : (c.past ? '' : lang.t('cal_available'))">{{ c.day }}</span>
                      } @else { <span class="cal-day empty"></span> }
                    }
                  </div>
                  <div class="cal-legend">
                    <span><i class="lg free"></i> {{ lang.t('cal_available') }}</span>
                    <span><i class="lg taken"></i> {{ lang.t('cal_booked') }}</span>
                    @if (chosenDates().length) { <span><i class="lg chosen"></i> {{ lang.t('cal_your_date') }}</span> }
                  </div>
                </div>
              }

              <div class="est">
                <span>{{ lang.t('estimated_total') }}</span>
                <strong>{{ lang.money(estimate()) }}</strong>
              </div>
              <button class="btn btn--gold btn--lg full" (click)="add(h)">
                {{ selection.has(h.id) ? lang.t('update_selection') : lang.t('add_to_selection') }}
              </button>
            </div>
          </aside>
        </div>
      </section>
      }
    } @else {
      <div class="container center load muted">—</div>
    }

    <!-- Media lightbox -->
    @if (lightbox(); as m) {
      <div class="lb" (click)="closeLightbox()">
        <button type="button" class="lb-close" (click)="closeLightbox()">×</button>
        <div class="lb-stage" (click)="$event.stopPropagation()">
          @if (m.type === 'video') {
            <video class="lb-media" [src]="m.url" controls autoplay></video>
          } @else if (m.type === '360') {
            <div class="lb-360" id="bms-lb-360"></div>
          } @else {
            <img class="lb-media" [src]="m.url" [alt]="m.title || ''" />
          }
          @if (m.title) { <p class="lb-title">{{ m.title }}</p> }
        </div>
      </div>
    }
  `,
  styles: [`
    .load { padding: 90px 0; font-size: 1.1rem; }
    .hero { min-height: 360px; background-size: cover; background-position: center;
      background-color: #d9ccb3; position: relative; display: flex; align-items: flex-end; }
    .hero-shade { position: absolute; inset: 0; background: linear-gradient(180deg, rgba(35,32,32,.15), rgba(35,32,32,.72)); }
    .hero-in { position: relative; color: #fff; padding: 40px 24px 36px; }
    .back { color: #fff; opacity: .9; font-weight: 600; }
    [dir='rtl'] .back { }
    .hero-in h1 { color: #fff; margin: 16px 0 6px; }
    .venue { opacity: .9; margin: 0 0 12px; }
    .facts { display: flex; gap: 10px; align-items: center; font-weight: 500; }
    .dot { opacity: .6; }
    .body { display: grid; grid-template-columns: 1fr 380px; gap: 40px; padding-top: 44px; padding-bottom: 70px; align-items: start; }
    h2 { margin-bottom: 14px; }
    .sub { margin: 30px 0 14px; }
    .desc { font-size: 1.06rem; }
    .chip--static { cursor: default; }
    .g-filter { margin-bottom: 14px; }
    .g-filter .chip { cursor: pointer; }
    .gallery { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
    .g-item { padding-top: 70%; background-size: cover; background-position: center; border-radius: 10px; background-color: var(--cream-2); }
    .g-video { width: 100%; border-radius: 10px; margin-top: 14px; }
    .g-yt { aspect-ratio: 16 / 9; height: auto; border: 0; }
    .pano { width: 100%; height: 420px; border-radius: 12px; margin-top: 14px; overflow: hidden; background: #14110e; }
    .side { position: sticky; top: 92px; }
    .cal { margin: 16px 0; padding: 12px; border: 1px solid var(--line-soft, #e7dec9); border-radius: 12px; background: var(--cream, #faf6ef); }
    .cal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
    .cal-title { font-size: .85rem; font-weight: 700; color: var(--ink); }
    .cal-nav { border: 1px solid var(--line-soft); background: #fff; border-radius: 6px; width: 26px; height: 26px; cursor: pointer; font-size: 1rem; line-height: 1; }
    .cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; }
    .cal-dow { text-align: center; font-size: .66rem; color: var(--ink-soft); padding: 2px 0; }
    .cal-day { text-align: center; padding: 6px 0; font-size: .78rem; border-radius: 6px; background: #fff; color: var(--ink); }
    .cal-day.empty { background: transparent; }
    .cal-day.free { background: #e7f5ec; color: #1d7a46; }
    .cal-day.taken { background: #f1ece3; color: #b0a48e; text-decoration: line-through; }
    .cal-day.past { background: transparent; color: #cbc3b4; }
    .cal-day.chosen { outline: 2px solid var(--gold, #c5a44f); font-weight: 700; }
    .cal-legend { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 8px; font-size: .72rem; color: var(--ink-soft); }
    .cal-legend .lg { display: inline-block; width: 10px; height: 10px; border-radius: 3px; margin-inline-end: 4px; vertical-align: middle; }
    .cal-legend .lg.free { background: #e7f5ec; border: 1px solid #1d7a46; }
    .cal-legend .lg.taken { background: #f1ece3; border: 1px solid #b0a48e; }
    .cal-legend .lg.chosen { background: #fff; outline: 2px solid var(--gold, #c5a44f); }
    .pick { padding: 24px; }
    .pick h3 { margin-bottom: 16px; }
    .pkg { display: flex; align-items: center; gap: 12px; padding: 14px; border: 1px solid var(--line); border-radius: 10px; margin-bottom: 10px; cursor: pointer; transition: all .2s; }
    .pkg--on { border-color: var(--gold); background: #fffdf8; }
    .pkg-main { display: flex; justify-content: space-between; width: 100%; }
    .pkg-name { font-weight: 600; }
    .pkg-price { color: var(--gold-deep); font-weight: 600; }
    .pkg-desc { font-size: .92rem; margin: 4px 0 14px; }
    .incl-h { font-size: 1rem; margin: 22px 0 10px; }
    .incl-tip { font-weight: 400; color: var(--ink-soft); font-size: .78rem; }
    .incl-list { list-style: none; margin: 0; padding: 0; }
    .incl-list li { padding: 9px 0; border-bottom: 1px dashed var(--line-soft); font-size: .94rem; }
    .incl-row { display: flex; justify-content: space-between; align-items: center; gap: 10px; }
    .incl-toggle { display: inline-flex; align-items: center; gap: 9px; cursor: pointer; flex: 1; }
    .incl-list li.off .incl-name { text-decoration: line-through; opacity: .5; }
    .incl-qty { font-size: .84rem; color: var(--ink-soft); white-space: nowrap; }
    .info-ico { width: 19px; height: 19px; flex: 0 0 19px; border-radius: 50%; border: 1px solid var(--gold-soft);
      background: #fff; color: var(--gold-deep); font-style: italic; font-weight: 700; font-size: .76rem;
      line-height: 1; cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
      font-family: Georgia, 'Times New Roman', serif; transition: all .2s; padding: 0; }
    .info-ico:hover, .info-ico.on { background: var(--gold); color: #fff; border-color: var(--gold); }
    .incl-contents { margin: 6px 0 4px; padding: 10px 14px; background: var(--cream, #faf6ef);
      border-radius: 8px; border: 1px solid var(--line-soft); }
    .incl-contents-h { margin: 0 0 6px; font-size: .76rem; font-weight: 700; text-transform: uppercase;
      letter-spacing: .5px; color: var(--gold-deep); }
    .incl-contents ul { list-style: none; margin: 0; padding: 0; }
    .incl-contents li { display: flex; justify-content: space-between; gap: 12px; padding: 4px 0;
      font-size: .88rem; color: var(--ink); border-bottom: 1px dashed var(--line-soft); }
    .incl-contents li:last-child { border-bottom: 0; }
    .incl-contents .c-main { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
    .incl-contents .c-qty { white-space: nowrap; }
    .swap-sel { font-size: .78rem; padding: 2px 6px; border: 1px solid var(--gold-soft, #d9c38a);
      border-radius: 6px; background: #fffdf6; color: var(--ink); max-width: 220px; }
    .swap-qty { display: inline-flex; align-items: center; gap: 5px; margin-inline-start: 8px; font-size: .76rem; color: var(--ink-soft); }
    .swap-qty-lbl { white-space: nowrap; }
    .swap-qty button { width: 20px; height: 20px; border: 1px solid var(--gold-soft, #d9c38a); background: #fffdf6;
      border-radius: 5px; cursor: pointer; font-size: .85rem; line-height: 1; color: var(--ink); padding: 0; }
    .swap-qty button:hover { border-color: var(--gold); color: var(--gold-deep); }
    .swap-qty .qn { min-width: 14px; text-align: center; font-weight: 600; color: var(--ink); }
    .c-delta { color: var(--gold-deep); font-weight: 600; }
    [dir='rtl'] .incl-contents { text-align: right; }
    .incl-variant { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 4px 0 2px;
      padding-inline-start: 28px; }
    .incl-variant-lbl { font-size: .76rem; color: var(--ink-soft); white-space: nowrap; }
    .variant-sel { flex: 1; min-width: 160px; max-width: 320px; font-size: .82rem; padding: 5px 8px;
      border: 1px solid var(--gold-soft, #d9c38a); border-radius: 6px; background: #fffdf6; color: var(--ink); }
    .incl-desc { margin: 0 0 8px; font-size: .9rem; color: var(--ink); line-height: 1.5; }
    .item-media { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }
    .im-thumb { width: 78px; height: 60px; border-radius: 8px; border: 1px solid var(--line-soft); padding: 0;
      background-size: cover; background-position: center; cursor: pointer; position: relative; overflow: hidden; }
    .im-thumb:hover { box-shadow: 0 2px 8px rgba(0,0,0,.18); }
    .im-video { object-fit: cover; }
    .im-badge { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
      background: rgba(35,32,32,.42); color: #fff; font-size: .72rem; font-weight: 700; letter-spacing: .5px; }
    .lb { position: fixed; inset: 0; z-index: 2000; background: rgba(20,18,18,.92); display: flex;
      align-items: center; justify-content: center; padding: 24px; animation: lbin .2s ease; }
    @keyframes lbin { from { opacity: 0; } to { opacity: 1; } }
    .lb-close { position: absolute; top: 18px; right: 24px; background: none; border: 0; color: #fff;
      font-size: 2.4rem; line-height: 1; cursor: pointer; }
    .lb-stage { max-width: 1000px; width: 100%; text-align: center; }
    .lb-media { max-width: 100%; max-height: 80vh; border-radius: 10px; }
    .lb-360 { width: 100%; height: 72vh; border-radius: 10px; overflow: hidden; }
    .lb-title { color: #fff; margin-top: 12px; font-size: 1rem; }
    .incl-meta { display: inline-flex; gap: 8px; align-items: center; white-space: nowrap; }
    .incl-cost { color: var(--gold-deep); font-weight: 600; font-size: .86rem; }
    .incl-opt { font-size: .68rem; text-transform: uppercase; letter-spacing: .5px; color: var(--gold-deep);
      border: 1px solid var(--gold-soft); border-radius: 999px; padding: 1px 8px; }
    .qstep.mini button { width: 22px; height: 22px; font-size: .9rem; }
    .addon-h { font-size: 1rem; margin: 22px 0 12px; }
    .addon { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border: 1px solid var(--line); border-radius: 8px; margin-bottom: 8px; }
    .addon--on { border-color: var(--gold); }
    .addon-toggle { display: flex; align-items: center; gap: 10px; flex: 1; cursor: pointer; }
    .addon-name { flex: 1; }
    .addon-price { color: var(--ink-soft); font-size: .9rem; white-space: nowrap; }
    .qstep { display: inline-flex; align-items: center; gap: 8px; }
    .qstep button { width: 26px; height: 26px; border: 1px solid var(--line); background: var(--cream);
      border-radius: 7px; cursor: pointer; font-size: 1rem; line-height: 1; color: var(--ink); }
    .qstep button:hover { border-color: var(--gold); color: var(--gold-deep); }
    .qstep .qn { min-width: 18px; text-align: center; font-weight: 600; }
    .est { display: flex; justify-content: space-between; align-items: baseline; margin: 20px 0 14px; padding-top: 16px; border-top: 1px dashed var(--line); }
    .est strong { font-size: 1.3rem; color: var(--ink); }
    .full { width: 100%; }
    @media (max-width: 900px) { .body { grid-template-columns: 1fr; } .side { position: static; } }
  `],
})
export class HallDetailComponent {
  lang = inject(LangService);
  selection = inject(SelectionService);
  private api = inject(ApiService);
  private route = inject(ActivatedRoute);
  private router = inject(Router);
  private state = inject(WizardStateService);
  private sanitizer = inject(DomSanitizer);
  private ytCache = new Map<string, SafeResourceUrl>();

  hall = signal<HallDetail | null>(null);
  loading = signal(true);
  selectedPackageId = signal<number | null>(null);
  qtys = signal<Record<number, number>>({});
  itemCfg = signal<Record<number, { on: boolean; qty: number }>>({});
  itemVariant = signal<Record<number, number>>({});
  /** itemId → { contentIndex → chosen swap index (0 = original) } */
  contentSwap = signal<Record<number, Record<number, number>>>({});
  /** itemId → { contentIndex → how many of the row's units to replace } */
  contentSwapQty = signal<Record<number, Record<number, number>>>({});
  openContents = signal<Set<number>>(new Set());
  lightbox = signal<ItemMedia | null>(null);
  galleryFilter = signal<string>('');
  panos = signal<{ url: string; id: string }[]>([]);
  // Availability calendar (reflects the guest's chosen date / flexible month)
  avail = signal<HallAvailability | null>(null);
  calMonth = signal<string>(''); // 'YYYY-MM'
  chosenDates = signal<string[]>([]);
  calCells = signal<({ day: number; date: string; taken: boolean; past: boolean; chosen: boolean } | null)[]>([]);

  private etLabels: Record<string, { ar: string; en: string }> = {
    Wedding: { ar: 'زفاف', en: 'Wedding' }, Engagement: { ar: 'خطوبة', en: 'Engagement' },
    Corporate: { ar: 'مؤتمر / شركة', en: 'Corporate' }, Birthday: { ar: 'عيد ميلاد', en: 'Birthday' },
    Other: { ar: 'أخرى', en: 'Other' },
  };

  private featLabels: Record<string, { ar: string; en: string }> = {
    parking: { ar: 'موقف سيارات', en: 'Parking' }, valet: { ar: 'خدمة صف السيارات', en: 'Valet' },
    stage: { ar: 'مسرح', en: 'Stage' }, bridal_room: { ar: 'غرفة العروس', en: 'Bridal room' },
    led_screens: { ar: 'شاشات LED', en: 'LED screens' }, sound_system: { ar: 'نظام صوت', en: 'Sound system' },
    garden: { ar: 'حديقة', en: 'Garden' }, river_view: { ar: 'إطلالة على النيل', en: 'River view' },
    catering_kitchen: { ar: 'مطبخ تموين', en: 'Catering kitchen' },
  };

  estimate = computed(() => {
    const h = this.hall();
    if (!h) return 0;
    const pkg = h.packages.find((p) => p.id === this.selectedPackageId());
    const base = pkg?.price ?? h.base_price ?? h.min_budget ?? 0;
    const addons = h.addons.reduce((s, a) => s + (this.qty(a.id) > 0 ? a.price * this.qty(a.id) : 0), 0);
    const cfg = this.itemCfg();
    const extras = (pkg?.items ?? []).reduce((s, it) =>
      s + (it.is_addon && cfg[it.id]?.on ? (it.price ?? 0) * this.effAddonQty(it) : 0), 0);
    // Variant swaps + buffet-content substitutions adjust the total by their
    // price difference (inclusions always apply; add-ons only when selected).
    const variants = (pkg?.items ?? []).reduce((s, it) => {
      const applies = it.is_addon ? !!cfg[it.id]?.on : true;
      if (!applies) { return s; }
      const swaps = (it.contents ?? []).reduce((cs, c, ci) => cs + this.swapDelta(it, ci, c), 0);
      return s + this.variantDelta(it) + swaps;
    }, 0);
    return base + addons + extras + variants;
  });

  constructor() {
    const id = Number(this.route.snapshot.paramMap.get('id'));
    this.api.hall(id).subscribe({
      next: (r) => {
        this.hall.set(r.data.hall);
        // Default the gallery to the guest's chosen occasion, when imagery exists for it.
        const wantEt = this.state.answers().event_type;
        if (wantEt && this.eventTypes(r.data.hall).includes(wantEt)) {
          this.galleryFilter.set(wantEt);
        }
        const ps = r.data.hall.gallery
          .filter((g) => g.type === '360')
          .map((g, i) => ({ url: g.url, id: `bms-pano-${id}-${i}` }));
        this.panos.set(ps);
        if (ps.length) { setTimeout(() => this.initPanos(), 80); }
        const existing = this.selection.items().find((s) => s.hallId === id);
        if (existing) {
          this.selectedPackageId.set(existing.packageId);
          const q: Record<number, number> = {};
          existing.addons.forEach((a) => (q[a.addon_id] = a.qty));
          this.qtys.set(q);
        } else if (r.data.hall.packages.length) {
          this.selectedPackageId.set(r.data.hall.packages[0].id);
        }
        this.initItems(this.selectedPackage(), existing);
        this.loading.set(false);
        this.initCalendar(id);
      },
      error: () => this.loading.set(false),
    });
  }

  /** Show availability for the guest's chosen date (its month) or flexible month. */
  private initCalendar(hallId: number): void {
    const dates = (this.state.answers().candidate_dates as string[] | undefined) || [];
    this.chosenDates.set(dates);
    const month = dates.length ? dates[0].slice(0, 7) : new Date().toISOString().slice(0, 7);
    this.loadMonth(hallId, month);
  }

  loadMonth(hallId: number, month: string): void {
    this.calMonth.set(month);
    this.api.hallAvailability(hallId, month).subscribe({
      next: (r) => { this.avail.set(r.data); this.buildCells(r.data); },
      error: () => {},
    });
  }

  private buildCells(a: HallAvailability): void {
    const cells: ({ day: number; date: string; taken: boolean; past: boolean; chosen: boolean } | null)[] = [];
    for (let i = 0; i < a.first_dow; i++) { cells.push(null); }
    const chosen = new Set(this.chosenDates());
    for (let d = 1; d <= a.days; d++) {
      const date = `${a.month}-${String(d).padStart(2, '0')}`;
      cells.push({ day: d, date, taken: !!a.taken[date], past: date < a.today, chosen: chosen.has(date) });
    }
    this.calCells.set(cells);
  }

  shiftMonth(delta: number): void {
    const h = this.hall();
    if (!h) { return; }
    const [y, m] = this.calMonth().split('-').map(Number);
    const dt = new Date(y, m - 1 + delta, 1);
    this.loadMonth(h.id, `${dt.getFullYear()}-${String(dt.getMonth() + 1).padStart(2, '0')}`);
  }

  monthLabel(): string {
    const [y, m] = this.calMonth().split('-').map(Number);
    if (!y) { return ''; }
    const months = this.lang.lang() === 'ar'
      ? ['يناير','فبراير','مارس','أبريل','مايو','يونيو','يوليو','أغسطس','سبتمبر','أكتوبر','نوفمبر','ديسمبر']
      : ['January','February','March','April','May','June','July','August','September','October','November','December'];
    return `${months[m - 1]} ${y}`;
  }

  weekdays(): string[] {
    return this.lang.lang() === 'ar'
      ? ['أحد','إثن','ثلا','أرب','خمي','جمع','سبت']
      : ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
  }

  selectedPackage = computed<Package | null>(() =>
    this.hall()?.packages.find((p) => p.id === this.selectedPackageId()) ?? null);

  loc(en?: string, ar?: string): string { return (this.lang.lang() === 'ar' && ar) ? ar : (en ?? ''); }

  /** A YouTube URL → a sanitized embed URL (cached); null for non-YouTube. */
  ytEmbed(url: string): SafeResourceUrl | null {
    if (!url) { return null; }
    if (this.ytCache.has(url)) { return this.ytCache.get(url)!; }
    const m = url.match(/(?:youtube\.com\/(?:watch\?(?:.*&)?v=|embed\/|shorts\/|live\/)|youtu\.be\/)([A-Za-z0-9_-]{11})/);
    if (!m) { return null; }
    const safe = this.sanitizer.bypassSecurityTrustResourceUrl(`https://www.youtube.com/embed/${m[1]}?rel=0`);
    this.ytCache.set(url, safe);
    return safe;
  }

  /** "2 × سفنديش" / "2 × chafing dishes" (unit optional). */
  contentQty(c: PackageContent): string {
    const unit = this.loc(c.unit, c.unit_ar);
    return unit ? `${c.quantity} × ${unit}` : `× ${c.quantity}`;
  }

  // ---- buffet-content substitution (replace partial/full qty of a line) ----

  /** Chosen substitute for a content row (0 = keep original). */
  swapIdx(it: PackageItem, idx: number): number {
    return this.contentSwap()[it.id]?.[idx] ?? 0;
  }

  /** How many of the row's units are being replaced (defaults to the full qty). */
  swapQty(it: PackageItem, idx: number, c: PackageContent): number {
    const q = this.contentSwapQty()[it.id]?.[idx];
    return q && q > 0 ? Math.min(q, c.quantity) : c.quantity;
  }

  /** Price impact of a row's substitution: per-unit delta × replaced qty. */
  swapDelta(it: PackageItem, idx: number, c: PackageContent): number {
    const sw = this.swapIdx(it, idx);
    if (sw <= 0 || !c.swap_options?.length) { return 0; }
    return (c.swap_options[sw - 1]?.price_delta ?? 0) * this.swapQty(it, idx, c);
  }

  setContentSwap(it: PackageItem, idx: number, c: PackageContent, ev: Event): void {
    const sw = Number((ev.target as HTMLSelectElement).value) || 0;
    this.contentSwap.update((m) => ({ ...m, [it.id]: { ...(m[it.id] ?? {}), [idx]: sw } }));
    // Default a fresh swap to replacing the whole line; clear qty when reverting.
    this.contentSwapQty.update((m) => ({
      ...m, [it.id]: { ...(m[it.id] ?? {}), [idx]: sw > 0 ? c.quantity : 0 },
    }));
  }

  swapQtyStep(it: PackageItem, idx: number, c: PackageContent, delta: number): void {
    const cur = this.swapQty(it, idx, c);
    const next = Math.min(c.quantity, Math.max(1, cur + delta));
    this.contentSwapQty.update((m) => ({ ...m, [it.id]: { ...(m[it.id] ?? {}), [idx]: next } }));
  }
  name(h: HallDetail): string { return this.loc(h.name, h.name_ar); }
  venueName(h: HallDetail): string { return this.loc(h.venue?.name, h.venue?.name_ar); }
  pkgName(p: Package): string { return this.loc(p.name, p.name_ar); }
  pkgDesc(p: Package): string { return this.loc(p.description, p.description_ar); }
  hallDesc(h: HallDetail): string { return this.loc(h.description, h.description_ar); }
  addonName(a: Addon): string { return this.loc(a.name, a.name_ar); }
  itemName(it: PackageItem): string { return this.loc(it.name, it.name_ar); }
  /** Quantity badge for an inclusion: "× 200" or "× 1 / guest" (per-guest). */
  itemQtyLabel(it: PackageItem): string {
    if (it.qty_mode === 'per_guest') { return `× ${it.quantity} / ${this.lang.t('per_person')}`; }
    return it.quantity > 1 ? `× ${it.quantity}` : '';
  }
  incAddon(a: Addon): void { this.qtys.update((q) => ({ ...q, [a.id]: (q[a.id] || 0) + 1 })); }
  decAddon(a: Addon): void { this.qtys.update((q) => ({ ...q, [a.id]: Math.max(0, (q[a.id] || 0) - 1) })); }

  // ---- package component customization ----
  pickPackage(p: Package): void { this.selectedPackageId.set(p.id); this.initItems(p); }

  /** Seed the editable config for a package: core items on, optional off
   *  (restoring any prior choices for this hall from the selection). */
  initItems(p: Package | null, existing?: { items?: SelectedItem[] }): void {
    const prior = existing ?? this.selection.items().find((s) => s.hallId === this.hall()?.id);
    const cfg: Record<number, { on: boolean; qty: number }> = {};
    const variants: Record<number, number> = {};
    const swaps: Record<number, Record<number, number>> = {};
    const swapQtys: Record<number, Record<number, number>> = {};
    for (const it of p?.items ?? []) {
      const ex = prior?.items?.find((e) => e.item_id === it.id);
      cfg[it.id] = ex ? { on: ex.included, qty: ex.qty } : { on: !it.is_addon, qty: it.quantity };
      if (ex?.variant_index) { variants[it.id] = ex.variant_index; } // restore a prior variant swap
      for (const cs of ex?.content_swaps ?? []) { // restore prior buffet substitutions
        swaps[it.id] = { ...(swaps[it.id] ?? {}), [cs.index]: cs.to_index ?? 0 };
        swapQtys[it.id] = { ...(swapQtys[it.id] ?? {}), [cs.index]: cs.qty ?? cs.of ?? 0 };
      }
    }
    this.itemCfg.set(cfg);
    this.itemVariant.set(variants);
    this.contentSwap.set(swaps);
    this.contentSwapQty.set(swapQtys);
  }

  /** Select a variant for an item (0 = base, 1..n = options[index-1]). */
  setVariant(it: PackageItem, ev: Event): void {
    const idx = Number((ev.target as HTMLSelectElement).value) || 0;
    this.itemVariant.update((v) => ({ ...v, [it.id]: idx }));
  }

  optLabel(o: ItemOption): string { return this.loc(o.label, o.label_ar); }

  /** Price difference of the item's currently selected variant (0 for base). */
  variantDelta(it: PackageItem): number {
    const idx = this.itemVariant()[it.id] ?? 0;
    if (idx <= 0 || !it.options?.length) { return 0; }
    return it.options[idx - 1]?.price_delta ?? 0;
  }

  /** Mount a Pannellum 360° viewer per panorama (retries until the lib loads). */
  private initPanos(tries = 0): void {
    if (typeof pannellum === 'undefined') {
      if (tries < 40) { setTimeout(() => this.initPanos(tries + 1), 120); }
      return;
    }
    for (const p of this.panos()) {
      const el = document.getElementById(p.id);
      if (!el || el.dataset['init']) { continue; }
      el.dataset['init'] = '1';
      pannellum.viewer(p.id, {
        type: 'equirectangular', panorama: p.url, autoLoad: true,
        autoRotate: -2, showZoomCtrl: true, showFullscreenCtrl: true, hfov: 110,
      });
    }
  }

  toggleContents(id: number): void {
    this.openContents.update((s) => {
      const next = new Set(s);
      next.has(id) ? next.delete(id) : next.add(id);
      return next;
    });
  }

  /** An item has an info panel when it carries a description, media or contents. */
  hasDetails(it: PackageItem): boolean {
    return !!(this.itemDesc(it) || it.media?.length || it.contents?.length);
  }
  itemDesc(it: PackageItem): string { return this.loc(it.description, it.description_ar); }

  /** Open a media item in the lightbox; mount a 360 viewer when needed. */
  openLightbox(m: ItemMedia): void {
    this.lightbox.set(m);
    if (m.type === '360') { this.mountLightbox360(m, 0); }
  }
  closeLightbox(): void { this.lightbox.set(null); }

  private mountLightbox360(m: ItemMedia, tries: number): void {
    if (typeof pannellum === 'undefined' || !document.getElementById('bms-lb-360')) {
      if (tries < 40) { setTimeout(() => this.mountLightbox360(m, tries + 1), 120); }
      return;
    }
    pannellum.viewer('bms-lb-360', {
      type: 'equirectangular', panorama: m.url, autoLoad: true,
      autoRotate: -2, showZoomCtrl: true, showFullscreenCtrl: true, hfov: 110,
    });
  }

  itemOn(id: number): boolean { return !!this.itemCfg()[id]?.on; }
  itemCount(id: number): number { return this.itemCfg()[id]?.qty ?? 0; }

  /** Guest count from the wizard (falls back to the package minimum, then 100). */
  guestCount(): number {
    const a = this.state.answers() as { attendees?: number | string };
    return Number(a?.attendees) || this.selectedPackage()?.min_pax || 100;
  }
  /** Effective quantity for an add-on: per-guest add-ons scale with the guest count. */
  effAddonQty(it: PackageItem): number {
    return it.qty_mode === 'per_guest' ? this.guestCount() : (this.itemCount(it.id) || 1);
  }
  toggleItem(it: PackageItem, ev: Event): void {
    const on = (ev.target as HTMLInputElement).checked;
    this.itemCfg.update((c) => ({ ...c, [it.id]: { on, qty: c[it.id]?.qty || it.quantity } }));
  }
  itemQty(it: PackageItem, delta: number): void {
    this.itemCfg.update((c) => ({ ...c, [it.id]: { on: c[it.id]?.on ?? true, qty: Math.max(1, (c[it.id]?.qty || it.quantity) + delta) } }));
  }
  allImages(h: HallDetail) { return h.gallery.filter((g) => g.type === 'image'); }
  images(h: HallDetail) {
    const f = this.galleryFilter();
    const imgs = this.allImages(h);
    return f ? imgs.filter((g) => g.event_type === f) : imgs;
  }
  /** Distinct occasions present among the hall's images. */
  eventTypes(h: HallDetail): string[] {
    return [...new Set(this.allImages(h).map((g) => g.event_type).filter((e): e is string => !!e))];
  }
  etLabel(k: string): string { const e = this.etLabels[k]; return e ? e[this.lang.lang()] : k; }
  videos(h: HallDetail) { return h.gallery.filter((g) => g.type === 'video'); }
  featureKeys(h: HallDetail): string[] { return Object.keys(h.features || {}).filter((k) => (h.features as any)[k]); }
  featLabel(k: string): string { const e = this.featLabels[k]; return e ? e[this.lang.lang()] : k; }

  qty(id: number): number { return this.qtys()[id] || 0; }

  toggleAddon(a: Addon, ev: Event): void {
    const on = (ev.target as HTMLInputElement).checked;
    this.qtys.update((q) => ({ ...q, [a.id]: on ? 1 : 0 }));
  }

  add(h: HallDetail): void {
    const pkg: Package | null = h.packages.find((p) => p.id === this.selectedPackageId()) ?? null;
    const addons: SelectedAddon[] = h.addons
      .filter((a) => this.qty(a.id) > 0)
      .map((a) => ({ addon_id: a.id, name: this.addonName(a), price: a.price, qty: this.qty(a.id) }));
    const items: SelectedItem[] = (pkg?.items ?? []).map((it) => {
      const vi = this.itemVariant()[it.id] ?? 0;
      const vlabel = vi > 0 && it.options?.length ? this.optLabel(it.options[vi - 1]) : '';
      // Capture any buffet content substitutions (e.g. replace 1 of 2 Mixed
      // Grills with Seafood Grill) — qty + per-unit delta drive the total.
      const swapMap = this.contentSwap()[it.id] ?? {};
      const content_swaps: ContentSwapChoice[] = Object.entries(swapMap)
        .filter(([, sw]) => Number(sw) > 0)
        .map(([ci, sw]) => {
          const idx = Number(ci);
          const c = it.contents?.[idx];
          const o = c?.swap_options?.[Number(sw) - 1];
          return {
            index: idx, to_index: Number(sw),
            from: c ? this.loc(c.name, c.name_ar) : '', to: o ? this.loc(o.name, o.name_ar) : '',
            qty: c ? this.swapQty(it, idx, c) : 1, of: c?.quantity ?? 1, delta: o?.price_delta ?? 0,
          };
        });
      return {
        item_id: it.id, name: this.itemName(it),
        qty: it.is_addon ? this.effAddonQty(it) : this.itemCount(it.id),
        included: this.itemOn(it.id), is_optional: it.is_optional, is_addon: !!it.is_addon, price: it.price ?? 0,
        variant_index: vi, variant_delta: this.variantDelta(it), variant_label: vlabel,
        content_swaps,
      };
    });
    this.selection.upsert(h, pkg, addons, items);
    this.router.navigate(['/selection']);
  }
}
