(function () {
  const config = window.PinetaBookingFormConfig;
  if (!config) return;

  const roomsData = [
    {id:'6',floor:3},{id:'7',floor:3},{id:'8',floor:3},{id:'9',floor:3},{id:'10',floor:3},
    {id:'5',floor:3,gridCol:1,gridRow:2},{id:'11',floor:3,gridCol:5,gridRow:2},
    {id:'3А',floor:2},{id:'2А',floor:2},{id:'1',floor:2},{id:'2',floor:2},{id:'3',floor:2},
    {id:'1А',floor:2,gridCol:1,gridRow:2},{id:'4',floor:2,gridCol:5,gridRow:2}
  ];
  const roomNamesMap = config.roomNames || {};
  const roomIdMap = config.roomIds || {};
  const roomPhotosMap = config.roomPhotos || {};
  const isAdminMode = config.mode === 'admin';
  const isDiscountMode = config.mode === 'guest' && config.variant === 'discount';
  const createdBookingData = config.createdBookingData || null;

  const arrivalInput = document.getElementById('arrivalDate');
  const nightsInput = document.getElementById('nightsInput');
  const roomSelect = document.getElementById('roomSelect');
  const breakfastInput = document.getElementById('breakfastInput');
  const childrenSelect = document.getElementById('childrenSelect');
  const adultsSelect = document.getElementById('guestsCount');
  const sourceInput = document.getElementById('sourceInput');
  const adminDiscountInput = document.getElementById('adminDiscountInput');
  const adminDiscountCodeInput = document.getElementById('adminDiscountCodeInput');
  const adminDiscountCodePreview = document.getElementById('adminDiscountCodePreview');
  const adminDiscountCodeBox = document.getElementById('adminDiscountCodeBox');
  const discountInput = document.getElementById('discountInput');
  const departureInput = document.getElementById('departureDateHidden');
  const fullAmountInput = document.getElementById('fullAmountInput');
  const prepaymentInput = document.getElementById('prepaymentAmountInput');
  const summaryTotal = document.getElementById('summaryTotal');
  const summaryNights = document.getElementById('summaryNights');
  const totalMain = document.getElementById('totalMain');
  const prepaymentLine = document.getElementById('prepaymentLine');
  const remainingLine = document.getElementById('remainingLine');
  const quoteWarning = document.getElementById('quoteWarning');
  const restrictionStatus = document.getElementById('restrictionStatus');
  const minNightsValue = document.getElementById('minNightsValue');
  const loadingIndicator = document.getElementById('formLoadingIndicator');
  const galleryModal = document.getElementById('roomGalleryModal');
  const galleryGrid = document.getElementById('roomGalleryGrid');
  const galleryTitle = document.getElementById('roomGalleryTitle');
  const galleryClose = document.getElementById('roomGalleryClose');
  const form = document.getElementById(isAdminMode ? 'adminBookingForm' : (isDiscountMode ? 'guestBookingDiscountForm' : 'guestBookingForm'));
  const submitBtn = document.getElementById('submitBtn');
  const submitLoadingIndicator = document.getElementById('submitLoadingIndicator');
  const submitSuccessBox = document.getElementById('submitSuccessBox');
  const submitErrorBox = document.getElementById('submitErrorBox');
  const offerMessage = document.getElementById('offerMessage');
  const paymentDetailsBlock = document.getElementById('paymentDetailsBlock');
  const paymentDetailsMessage = document.getElementById('paymentDetailsMessage');
  const copyOfferBtn = document.getElementById('copyOfferBtn');
  const copyPaymentBtn = document.getElementById('copyPaymentBtn');
  const receiptUploadForm = document.getElementById('receiptUploadForm');
  const receiptReservationIdEl = document.getElementById('receiptReservationId');
  const receiptReservationIdInput = document.getElementById('receiptReservationIdInput');
  const receiptFileInput = document.getElementById('receiptFileInput');
  const receiptUploadBtn = document.getElementById('receiptUploadBtn');
  const receiptUploadStatus = document.getElementById('receiptUploadStatus');
  const receiptUploadLoading = document.getElementById('receiptUploadLoading');
  const receiptUploadLoadingText = document.getElementById('receiptUploadLoadingText');
  const guestNameInput = document.getElementById('guestNameInput');
  const bookingLinkInput = document.getElementById('bookingLinkInput');
  const bookingLinkWrap = document.getElementById('bookingLinkWrap');
  const statusInput = document.getElementById('statusInput');
  const guestLangInput = document.getElementById('guestLang');
  const adminMsgLangButtons = Array.from(document.querySelectorAll('.admin-msg-lang-btn'));

  const guestRoomTexts = {
    it:{doubleGarden:'camera doppia con vista giardino',doubleSea:'camera doppia con vista mare',tripleSea:'camera tripla con vista mare',smallDoubleGarden:'piccola camera doppia con vista giardino',photo:'Foto',galleryTitle:'Foto della camera {code}',photoAlt:'Camera {code}',photoCaption:'Foto {num}'},
    en:{doubleGarden:'double room with garden view',doubleSea:'double room with sea view',tripleSea:'triple room with sea view',smallDoubleGarden:'small double room with garden view',photo:'Photo',galleryTitle:'Photos of room {code}',photoAlt:'Room {code}',photoCaption:'Photo {num}'},
    ru:{doubleGarden:'двухместный номер с видом на сад',doubleSea:'двухместный номер с видом на море',tripleSea:'трехместный номер с видом на море',smallDoubleGarden:'небольшой двухместный номер с видом на сад',photo:'Фото',galleryTitle:'Фотографии номера {code}',photoAlt:'Номер {code}',photoCaption:'Фото {num}'},
    uk:{doubleGarden:'двомісний номер з видом на сад',doubleSea:'двомісний номер з видом на море',tripleSea:'тримісний номер з видом на море',smallDoubleGarden:'невеликий двомісний номер з видом на сад',photo:'Фото',galleryTitle:'Фотографії номера {code}',photoAlt:'Номер {code}',photoCaption:'Фото {num}'},
    fr:{doubleGarden:'chambre double avec vue sur le jardin',doubleSea:'chambre double avec vue sur la mer',tripleSea:'chambre triple avec vue sur la mer',smallDoubleGarden:'petite chambre double avec vue sur le jardin',photo:'Photo',galleryTitle:'Photos de la chambre {code}',photoAlt:'Chambre {code}',photoCaption:'Photo {num}'},
    de:{doubleGarden:'Doppelzimmer mit Gartenblick',doubleSea:'Doppelzimmer mit Meerblick',tripleSea:'Dreibettzimmer mit Meerblick',smallDoubleGarden:'kleines Doppelzimmer mit Gartenblick',photo:'Foto',galleryTitle:'Fotos von Zimmer {code}',photoAlt:'Zimmer {code}',photoCaption:'Foto {num}'}
  };
  const guestRoomTypes = {
    '1А':'doubleGarden','2А':'doubleSea','3А':'tripleSea','1':'tripleSea','2':'doubleSea','3':'tripleSea','4':'smallDoubleGarden','5':'doubleGarden','6':'doubleSea','7':'doubleSea','8':'doubleSea','9':'doubleSea','10':'tripleSea','11':'smallDoubleGarden'
  };
  const guestUiTexts = {
    it:{paymentAlert:'Attenzione! I dati per il pagamento anticipato si trovano in basso nella pagina, nel secondo messaggio per l’ospite.', receiptLoading:'Carichiamo la ricevuta…', uploading:'Caricamento…', uploadReceipt:'Carica ricevuta', reservationNotFound:'Numero di prenotazione non trovato per il caricamento della ricevuta.', chooseFileFirst:'Selezionate prima un file JPG, PNG, WEBP o PDF.', uploadFailed:'Non è stato possibile caricare la ricevuta.', receiptUploaded:'Ricevuta caricata.', selectBeforeCalc:'Prima scegliete data, camera e attendete il calcolo.', chooseRoomPlaceholder:'Scegli la camera', noFileChosen:'Nessun file scelto', minNightsStatus:'Minimo notti per il periodo selezionato: {min}.', calcFailed:'Non è stato possibile calcolare il prezzo. Controllate le tariffe dopo l’importazione nel database.', quoteReady:'Tariffa: {rate}. Il prezzo è stato calcolato automaticamente.', quoteDefault:'Scegliete data, camera e numero di notti per vedere il totale.'},
    en:{paymentAlert:'Attention! The prepayment details are shown at the bottom of the page in the second guest message.', receiptLoading:'Uploading the receipt…', uploading:'Uploading…', uploadReceipt:'Upload receipt', reservationNotFound:'Booking number for the receipt upload was not found.', chooseFileFirst:'First choose a JPG, PNG, WEBP or PDF file.', uploadFailed:'Could not upload the receipt.', receiptUploaded:'Receipt uploaded.', selectBeforeCalc:'First choose the date, room and wait for the calculation.', chooseRoomPlaceholder:'Choose a room', noFileChosen:'No file chosen', minNightsStatus:'Minimum number of nights for the selected period: {min}.', calcFailed:'Could not calculate the price. Check the rates after importing into the database.', quoteReady:'Rate: {rate}. The price was calculated automatically.', quoteDefault:'Choose date, room and number of nights to see the total.'},
    ru:{paymentAlert:'Внимание! Реквизиты для предоплаты находятся внизу страницы во втором гостевом сообщении.', receiptLoading:'Загружаем квитанцию…', uploading:'Загружаем…', uploadReceipt:'Загрузить квитанцию', reservationNotFound:'Не найден номер бронирования для загрузки квитанции.', chooseFileFirst:'Сначала выберите файл JPG, PNG, WEBP или PDF.', uploadFailed:'Не удалось загрузить квитанцию.', receiptUploaded:'Квитанция загружена.', selectBeforeCalc:'Сначала выберите дату, номер и дождитесь расчёта.', chooseRoomPlaceholder:'Выберите номер', noFileChosen:'Файл не выбран', minNightsStatus:'Минимальное количество ночей для выбранного периода: {min}.', calcFailed:'Не удалось рассчитать стоимость. Проверьте тарифы после импорта в базу.', quoteReady:'Тариф: {rate}. Стоимость рассчитана автоматически.', quoteDefault:'Выберите дату, номер и количество ночей, чтобы увидеть итог.'},
    uk:{paymentAlert:'Увага! Реквізити для передоплати знаходяться внизу сторінки у другому гостьовому повідомленні.', receiptLoading:'Завантажуємо квитанцію…', uploading:'Завантажуємо…', uploadReceipt:'Завантажити квитанцію', reservationNotFound:'Не знайдено номер бронювання для завантаження квитанції.', chooseFileFirst:'Спочатку оберіть файл JPG, PNG, WEBP або PDF.', uploadFailed:'Не вдалося завантажити квитанцію.', receiptUploaded:'Квитанцію завантажено.', selectBeforeCalc:'Спочатку оберіть дату, номер і дочекайтеся розрахунку.', chooseRoomPlaceholder:'Оберіть номер', noFileChosen:'Файл не вибрано', minNightsStatus:'Мінімальна кількість ночей для вибраного періоду: {min}.', calcFailed:'Не вдалося розрахувати вартість. Перевірте тарифи після імпорту в базу.', quoteReady:'Тариф: {rate}. Вартість розрахована автоматично.', quoteDefault:'Оберіть дату, номер і кількість ночей, щоб побачити підсумок.'},
    fr:{paymentAlert:'Attention ! Les coordonnées pour le prépaiement se trouvent en bas de la page, dans le deuxième message client.', receiptLoading:'Téléchargement du reçu…', uploading:'Téléchargement…', uploadReceipt:'Télécharger le reçu', reservationNotFound:'Numéro de réservation introuvable pour télécharger le reçu.', chooseFileFirst:'Choisissez d’abord un fichier JPG, PNG, WEBP ou PDF.', uploadFailed:'Impossible de télécharger le reçu.', receiptUploaded:'Reçu téléchargé.', selectBeforeCalc:'Choisissez d’abord la date, la chambre et attendez le calcul.', chooseRoomPlaceholder:'Choisissez une chambre', noFileChosen:'Aucun fichier choisi', minNightsStatus:'Nombre minimum de nuits pour la période choisie : {min}.', calcFailed:'Impossible de calculer le prix. Vérifiez les tarifs après l’importation dans la base.', quoteReady:'Tarif : {rate}. Le prix a été calculé automatiquement.', quoteDefault:'Choisissez la date, la chambre et le nombre de nuits pour voir le total.'},
    de:{paymentAlert:'Achtung! Die Angaben für die Anzahlung finden Sie unten auf der Seite in der zweiten Gästemitteilung.', receiptLoading:'Beleg wird hochgeladen…', uploading:'Hochladen…', uploadReceipt:'Beleg hochladen', reservationNotFound:'Für den Beleg-Upload wurde keine Buchungsnummer gefunden.', chooseFileFirst:'Bitte wählen Sie zuerst eine JPG-, PNG-, WEBP- oder PDF-Datei aus.', uploadFailed:'Der Beleg konnte nicht hochgeladen werden.', receiptUploaded:'Beleg hochgeladen.', selectBeforeCalc:'Wählen Sie zuerst Datum und Zimmer aus und warten Sie auf die Berechnung.', chooseRoomPlaceholder:'Zimmer auswählen', noFileChosen:'Keine Datei ausgewählt', minNightsStatus:'Mindestanzahl der Nächte für den gewählten Zeitraum: {min}.', calcFailed:'Der Preis konnte nicht berechnet werden. Prüfen Sie die Tarife nach dem Import in die Datenbank.', quoteReady:'Tarif: {rate}. Der Preis wurde automatisch berechnet.', quoteDefault:'Wählen Sie Datum, Zimmer und Anzahl der Nächte, um die Summe zu sehen.'}
  };
  function getUiLang() {
    const raw = String((guestLangInput && guestLangInput.value) || localStorage.getItem('hotel-pineta-lang') || document.documentElement.lang || 'ru').toLowerCase().split('-')[0];
    return guestUiTexts[raw] ? raw : 'ru';
  }
  function uiText(key) {
    const lang = getUiLang();
    return (guestUiTexts[lang] && guestUiTexts[lang][key]) || (guestUiTexts.ru && guestUiTexts.ru[key]) || '';
  }
  function uiTextFormat(key, vars = {}) {
    let text = uiText(key);
    Object.keys(vars || {}).forEach((name) => {
      text = text.split('{' + name + '}').join(String(vars[name] ?? ''));
    });
    return text;
  }
  function roomText(key, vars = {}) {
    const lang = getUiLang();
    const dict = (guestRoomTexts[lang] || guestRoomTexts.ru || {});
    let text = dict[key] || (guestRoomTexts.ru && guestRoomTexts.ru[key]) || '';
    Object.keys(vars || {}).forEach((name) => {
      text = text.split('{' + name + '}').join(String(vars[name] ?? ''));
    });
    return text;
  }
  function translatedRoomDescription(code) {
    const normalized = normalizeCode(code);
    const type = guestRoomTypes[normalized];
    if (!type) {
      const fallback = roomNamesMap[normalized] || `№ ${normalized}`;
      return fallback.includes('—') ? fallback.split('—').slice(1).join('—').trim() : fallback.replace(/^№?\s*[^\s]+/,'').trim();
    }
    return roomText(type);
  }
  function displayRoomCode(code) {
    return String(code || '').replace(/А/g, 'A').replace(/а/g, 'A');
  }
  function translatedRoomLabel(code) {
    const normalized = normalizeCode(code);
    if (isAdminMode) return roomNamesMap[normalized] || `№ ${displayRoomCode(normalized)}`;
    const description = translatedRoomDescription(normalized);
    return description ? `№ ${displayRoomCode(normalized)} — ${description}` : (roomNamesMap[normalized] || `№ ${displayRoomCode(normalized)}`);
  }
  function translatedRoomLabelForOffer(code) {
    const normalized = normalizeCode(code);
    const description = translatedRoomDescription(normalized);
    return description ? `№ ${displayRoomCode(normalized)} — ${description}` : (roomNamesMap[normalized] || `№ ${displayRoomCode(normalized)}`);
  }
  function syncReceiptFileName() {
    const label = document.getElementById('receiptFileName');
    if (!label) return;
    label.textContent = receiptFileInput && receiptFileInput.files && receiptFileInput.files.length ? receiptFileInput.files[0].name : uiText('noFileChosen');
  }

  const priceMap = {
    with_breakfast: document.getElementById('priceWB'),
    without_breakfast: document.getElementById('priceNB'),
    with_breakfast_3plus: document.getElementById('priceWB3'),
    without_breakfast_3plus: document.getElementById('priceNB3'),
    with_breakfast_discount: document.getElementById('priceWBD'),
    without_breakfast_discount: document.getElementById('priceNBD'),
    with_breakfast_discount_3plus: document.getElementById('priceWBD3'),
    without_breakfast_discount_3plus: document.getElementById('priceNBD3')
  };
  const lineMap = {
    with_breakfast: document.getElementById('priceLineWB'),
    without_breakfast: document.getElementById('priceLineNB'),
    with_breakfast_3plus: document.getElementById('priceLineWB3'),
    without_breakfast_3plus: document.getElementById('priceLineNB3'),
    with_breakfast_discount: document.getElementById('priceLineWBD'),
    without_breakfast_discount: document.getElementById('priceLineNBD'),
    with_breakfast_discount_3plus: document.getElementById('priceLineWBD3'),
    without_breakfast_discount_3plus: document.getElementById('priceLineNBD3')
  };

  const adminGuestLangMap = {it:'it-IT',en:'en-GB',ru:'ru-RU',uk:'uk-UA',fr:'fr-FR',de:'de-DE'};
  const adminGuestTexts = {
    it:{hello:'Buongiorno!',helloName:'{name}, buongiorno!',offerRoom:'Le proponiamo: {room}.',offerDate:'Data di arrivo: {date}',offerNights:'Numero di notti: {nights}',offerGuests:'Numero di ospiti: {guests}',offerMeal:'Trattamento: {meal}',offerTotal:'Totale: {total}',offerPrepayment:'Acconto: {prepayment}',bookingInstant:'Fonte della prenotazione: Booking. La prenotazione viene confermata subito, la ricevuta non è necessaria.',offerTail:'In caso di cancellazione l’acconto non è rimborsabile. Dopo il pagamento dell’acconto riceverete il messaggio di conferma della prenotazione.',breakfastYes:'con colazione',breakfastNo:'senza colazione',payToday:'Per confermare la prenotazione dovete effettuare l’acconto di {amount} oggi.',payTodayTomorrow:'Per confermare la prenotazione dovete effettuare l’acconto di {amount} oggi o domani.',payThreeDays:'Per confermare la prenotazione dovete effettuare l’acconto di {amount} entro tre giorni.',paymentHeader:'Dati per il pagamento della prenotazione preliminare',paymentAmount:'Importo dell’acconto: {amount}',paymentRecipient:'Beneficiario: PINETA SRL',paymentBank:'Banca: BANCO BPM'},
    en:{hello:'Hello!',helloName:'Hello, {name}!',offerRoom:'We offer you: {room}.',offerDate:'Arrival date: {date}',offerNights:'Number of nights: {nights}',offerGuests:'Number of guests: {guests}',offerMeal:'Meal plan: {meal}',offerTotal:'Total: {total}',offerPrepayment:'Prepayment: {prepayment}',bookingInstant:'Booking source: Booking. The booking is confirmed immediately, and no receipt is required.',offerTail:'In case of cancellation, the prepayment is non-refundable. After the prepayment is paid, you will receive a booking confirmation message.',breakfastYes:'with breakfast',breakfastNo:'without breakfast',payToday:'To confirm the booking, you need to pay the prepayment of {amount} today.',payTodayTomorrow:'To confirm the booking, you need to pay the prepayment of {amount} today or tomorrow.',payThreeDays:'To confirm the booking, you need to pay the prepayment of {amount} within three days.',paymentHeader:'Payment details for the provisional booking',paymentAmount:'Prepayment amount: {amount}',paymentRecipient:'Recipient: PINETA SRL',paymentBank:'Bank: BANCO BPM'},
    ru:{hello:'Здравствуйте!',helloName:'{name}, здравствуйте!',offerRoom:'Предлагаем Вам: {room}.',offerDate:'Дата заезда: {date}',offerNights:'Количество ночей: {nights}',offerGuests:'Количество гостей: {guests}',offerMeal:'Питание: {meal}',offerTotal:'Итого: {total}',offerPrepayment:'Предоплата: {prepayment}',bookingInstant:'Источник бронирования: Booking. Бронь подтверждается сразу, квитанция не требуется.',offerTail:'В случае отмены бронирования предоплата не возвращается. После оплаты предоплаты Вы получите сообщение о подтверждении бронирования.',breakfastYes:'с завтраком',breakfastNo:'без завтрака',payToday:'Для бронирования Вы должны сделать предоплату {amount} сегодня.',payTodayTomorrow:'Для бронирования Вы должны сделать предоплату {amount} сегодня или завтра.',payThreeDays:'Для бронирования Вы должны сделать предоплату {amount} в течение трёх дней.',paymentHeader:'Реквизиты для оплаты предварительного бронирования',paymentAmount:'Сумма предоплаты: {amount}',paymentRecipient:'Получатель: PINETA SRL',paymentBank:'Банк: BANCO BPM'},
    uk:{hello:'Вітаємо!',helloName:'{name}, вітаємо!',offerRoom:'Пропонуємо Вам: {room}.',offerDate:'Дата заїзду: {date}',offerNights:'Кількість ночей: {nights}',offerGuests:'Кількість гостей: {guests}',offerMeal:'Харчування: {meal}',offerTotal:'Загалом: {total}',offerPrepayment:'Передоплата: {prepayment}',bookingInstant:'Джерело бронювання: Booking. Бронювання підтверджується одразу, квитанція не потрібна.',offerTail:'У разі скасування бронювання передоплата не повертається. Після оплати передоплати Ви отримаєте повідомлення про підтвердження бронювання.',breakfastYes:'зі сніданком',breakfastNo:'без сніданку',payToday:'Щоб підтвердити бронювання, потрібно внести передоплату {amount} сьогодні.',payTodayTomorrow:'Щоб підтвердити бронювання, потрібно внести передоплату {amount} сьогодні або завтра.',payThreeDays:'Щоб підтвердити бронювання, потрібно внести передоплату {amount} протягом трьох днів.',paymentHeader:'Реквізити для оплати попереднього бронювання',paymentAmount:'Сума передоплати: {amount}',paymentRecipient:'Отримувач: PINETA SRL',paymentBank:'Банк: BANCO BPM'},
    fr:{hello:'Bonjour !',helloName:'{name}, bonjour !',offerRoom:'Nous vous proposons : {room}.',offerDate:'Date d’arrivée : {date}',offerNights:'Nombre de nuits : {nights}',offerGuests:'Nombre de clients : {guests}',offerMeal:'Formule : {meal}',offerTotal:'Total : {total}',offerPrepayment:'Acompte : {prepayment}',bookingInstant:'Source de réservation : Booking. La réservation est confirmée immédiatement et aucun reçu n’est nécessaire.',offerTail:'En cas d’annulation, l’acompte n’est pas remboursable. Après le paiement de l’acompte, vous recevrez le message de confirmation de la réservation.',breakfastYes:'avec petit-déjeuner',breakfastNo:'sans petit-déjeuner',payToday:'Pour confirmer la réservation, vous devez verser l’acompte de {amount} aujourd’hui.',payTodayTomorrow:'Pour confirmer la réservation, vous devez verser l’acompte de {amount} aujourd’hui ou demain.',payThreeDays:'Pour confirmer la réservation, vous devez verser l’acompte de {amount} dans un délai de trois jours.',paymentHeader:'Coordonnées de paiement pour la réservation provisoire',paymentAmount:'Montant de l’acompte : {amount}',paymentRecipient:'Bénéficiaire : PINETA SRL',paymentBank:'Banque : BANCO BPM'},
    de:{hello:'Guten Tag!',helloName:'{name}, guten Tag!',offerRoom:'Wir bieten Ihnen an: {room}.',offerDate:'Anreisedatum: {date}',offerNights:'Anzahl der Nächte: {nights}',offerGuests:'Anzahl der Gäste: {guests}',offerMeal:'Verpflegung: {meal}',offerTotal:'Gesamt: {total}',offerPrepayment:'Anzahlung: {prepayment}',bookingInstant:'Buchungsquelle: Booking. Die Buchung wird sofort bestätigt, ein Beleg ist nicht erforderlich.',offerTail:'Im Falle einer Stornierung wird die Anzahlung nicht zurückerstattet. Nach Zahlung der Anzahlung erhalten Sie eine Bestätigung Ihrer Buchung.',breakfastYes:'mit Frühstück',breakfastNo:'ohne Frühstück',payToday:'Um die Buchung zu bestätigen, müssen Sie die Anzahlung von {amount} heute leisten.',payTodayTomorrow:'Um die Buchung zu bestätigen, müssen Sie die Anzahlung von {amount} heute oder morgen leisten.',payThreeDays:'Um die Buchung zu bestätigen, müssen Sie die Anzahlung von {amount} innerhalb von drei Tagen leisten.',paymentHeader:'Zahlungsdaten für die vorläufige Buchung',paymentAmount:'Anzahlungsbetrag: {amount}',paymentRecipient:'Empfänger: PINETA SRL',paymentBank:'Bank: BANCO BPM'}
  };

  const discountCodeTexts = {
    it:'Codice sconto fino al 25%: {code}',
    en:'Discount code up to 25%: {code}',
    ru:'Код скидки До 25%: {code}',
    uk:'Код знижки До 25%: {code}',
    fr:'Code de remise jusqu’à 25 % : {code}',
    de:'Rabattcode bis zu 25 %: {code}'
  };

  function discountCodeText(code) {
    let text = discountCodeTexts[getAdminGuestLang()] || discountCodeTexts.ru;
    return text.replace('{code}', String(code || ''));
  }

  function adminDiscountActive() {
    return isAdminMode && !!adminDiscountInput?.checked && !isBookingSource();
  }

  function generateAdminDiscountCode() {
    const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
    let tail = '';
    for (let i = 0; i < 5; i += 1) tail += chars[Math.floor(Math.random() * chars.length)];
    return `PINETA25-${tail}`;
  }

  function activeDiscountCode() {
    if (isDiscountMode) return String(discountInput?.value || '').trim();
    if (adminDiscountActive()) {
      if (adminDiscountCodeInput && !adminDiscountCodeInput.value) {
        adminDiscountCodeInput.value = generateAdminDiscountCode();
      }
      return String(adminDiscountCodeInput?.value || '').trim();
    }
    return '';
  }

  function syncAdminDiscountCodeBox() {
    if (!isAdminMode) return;
    const active = adminDiscountActive();
    if (!active && adminDiscountCodeInput) adminDiscountCodeInput.value = '';
    const code = active ? activeDiscountCode() : '';
    if (adminDiscountCodePreview) {
      if ('value' in adminDiscountCodePreview) {
        adminDiscountCodePreview.value = code;
      } else {
        adminDiscountCodePreview.textContent = code || '—';
      }
    }
    if (adminDiscountCodeBox) adminDiscountCodeBox.hidden = !active;
  }


  let selectedCode = config.selectedCode || '';
  let statusMap = {};
  let currentQuote = null;
  let currentPricing = null;
  let requestCounter = 0;

  function normalizeCode(code) {
    return String(code || '').trim().replace(/A/g, 'А').replace(/a/g, 'а');
  }

  function formatMoney(value) {
    const n = Number(value || 0);
    return isFinite(n) ? `${n.toFixed(2)} €` : '—';
  }

  function getNights() {
    return Math.max(1, parseInt(nightsInput?.value || '1', 10) || 1);
  }

  function todayIso() {
    const dt = new Date();
    const y = dt.getFullYear();
    const m = String(dt.getMonth() + 1).padStart(2, '0');
    const d = String(dt.getDate()).padStart(2, '0');
    return `${y}-${m}-${d}`;
  }

  function getArrivalMinDate() {
    const extendedDateRangeInput = document.getElementById('extendedDateRangeInput');
    if (isAdminMode && extendedDateRangeInput && extendedDateRangeInput.checked) {
      return '2026-04-01';
    }
    return todayIso();
  }

  function enforceArrivalMinDate() {
    if (!arrivalInput) return;
    const minDate = getArrivalMinDate();
    arrivalInput.min = minDate;
    if (!arrivalInput.value || arrivalInput.value < minDate) {
      arrivalInput.value = minDate;
    }
  }

  function updateDepartureDate() {
    if (!departureInput || !arrivalInput?.value) return;
    const dt = new Date(`${arrivalInput.value}T00:00:00`);
    dt.setDate(dt.getDate() + getNights());
    const y = dt.getFullYear();
    const m = String(dt.getMonth() + 1).padStart(2, '0');
    const d = String(dt.getDate()).padStart(2, '0');
    departureInput.value = `${y}-${m}-${d}`;
  }

  function activeRateKey() {
    const longStay = getNights() > 3;
    const useDiscountRates = isDiscountMode || adminDiscountActive();
    if (useDiscountRates) {
      return breakfastInput?.checked ? (longStay ? 'with_breakfast_discount_3plus' : 'with_breakfast_discount') : (longStay ? 'without_breakfast_discount_3plus' : 'without_breakfast_discount');
    }
    return breakfastInput?.checked ? (longStay ? 'with_breakfast_3plus' : 'with_breakfast') : (longStay ? 'without_breakfast_3plus' : 'without_breakfast');
  }

  function isBookingSource() {
    return isAdminMode && sourceInput && sourceInput.value === 'booking';
  }

  function setLoading(isLoading) {
    if (!loadingIndicator) return;
    loadingIndicator.hidden = !isLoading;
  }

  function setSubmitLoading(isLoading, text) {
    if (submitLoadingIndicator) {
      const loadingText = submitLoadingIndicator.querySelector('.loading-text');
      if (loadingText && text) loadingText.textContent = text;
      submitLoadingIndicator.hidden = !isLoading;
    }
  }

  function updateHighlight() {
    Object.values(lineMap).forEach(line => line && line.classList.remove('active'));
    const key = activeRateKey();
    if (lineMap[key]) lineMap[key].classList.add('active');
  }

  function getSelectedRoomId() {
    return roomIdMap[normalizeCode(selectedCode)] || roomSelect?.value || '';
  }

  function getSelectedRoomLabel() {
    return selectedCode ? translatedRoomLabel(selectedCode) : (roomSelect?.selectedOptions?.[0]?.textContent ?? '—');
  }

  function roomStatus(code) {
    const raw = statusMap[normalizeCode(code)] || statusMap[code] || 'free';
    const status = String(raw).toLowerCase();
    if (['held', 'hold', 'awaiting_prepayment', 'payment_uploaded', 'payment_rejected'].includes(status)) return 'held';
    if (['confirmed', 'occupied', 'occupied_unknown', 'blocked', 'closed'].includes(status)) return 'occupied';
    return 'free';
  }

  function canChooseStatus(status) {
    return status === 'free';
  }

  function notifyBookingUiUpdated() {
    document.dispatchEvent(new CustomEvent('pineta:booking-form-updated'));
  }

  function syncRoomSelect() {
    if (!roomSelect) return;
    const currentSelectedId = getSelectedRoomId();
    roomSelect.innerHTML = '<option value="">' + uiText('chooseRoomPlaceholder') + '</option>';
    roomsData.forEach(room => {
      const code = normalizeCode(room.id);
      const status = roomStatus(code);
      if (!canChooseStatus(status)) return;
      const option = document.createElement('option');
      option.value = roomIdMap[code] || '';
      option.textContent = translatedRoomLabel(code);
      option.dataset.code = code;
      if (currentSelectedId && String(option.value) === String(currentSelectedId)) option.selected = true;
      roomSelect.appendChild(option);
    });
    if (selectedCode && !canChooseStatus(roomStatus(selectedCode))) {
      selectedCode = '';
      roomSelect.value = '';
    }
    notifyBookingUiUpdated();
  }

  function renderRooms() {
    const floor3 = document.getElementById('gridFloor3');
    const floor2 = document.getElementById('gridFloor2');
    if (!floor3 || !floor2) return;
    floor3.innerHTML = '';
    floor2.innerHTML = '';
    roomsData.forEach(room => {
      const code = normalizeCode(room.id);
      const status = roomStatus(code);
      const slot = document.createElement('div');
      slot.className = 'room-slot';
      if (room.gridCol) slot.style.gridColumn = String(room.gridCol);
      if (room.gridRow) slot.style.gridRow = String(room.gridRow);
      const card = document.createElement('div');
      card.className = `room-card ${status === 'held' ? 'held' : ''} ${status === 'occupied' ? 'occupied' : ''} ${!canChooseStatus(status) ? 'unavailable' : ''} ${selectedCode === code ? 'selected' : ''}`;
      const desc = isAdminMode
        ? ((roomNamesMap[code] || `№ ${code}`).includes('—') ? (roomNamesMap[code] || `№ ${code}`).split('—').slice(1).join('—').trim() : (roomNamesMap[code] || `№ ${code}`).replace(/^№?\s*[^\s]+/,'').trim())
        : translatedRoomDescription(code);
      card.innerHTML = `<span class="room-name">№ ${code}</span><span class="room-desc">${desc}</span>`;
      if (canChooseStatus(status)) {
        card.addEventListener('click', function () {
          selectedCode = code;
          syncRoomSelect();
          renderRooms();
          refreshAll();
        });
      }
      const photoBtn = document.createElement('button');
      photoBtn.type = 'button';
      photoBtn.className = 'room-photo-btn';
      photoBtn.textContent = isAdminMode ? 'Фото' : roomText('photo');
      photoBtn.addEventListener('click', function (event) {
        event.preventDefault();
        event.stopPropagation();
        openGallery(code);
      });
      slot.appendChild(card);
      slot.appendChild(photoBtn);
      (room.floor === 3 ? floor3 : floor2).appendChild(slot);
    });
  }

  function openGallery(code) {
    if (!galleryModal || !galleryGrid || !galleryTitle) return;
    galleryTitle.textContent = isAdminMode ? `Фотографии номера ${code}` : roomText('galleryTitle', {code});
    galleryGrid.innerHTML = '';
    const photos = roomPhotosMap[normalizeCode(code)] || [];
    photos.forEach((src, idx) => {
      const card = document.createElement('div');
      card.className = 'room-gallery-card';
      const altText = isAdminMode ? `Номер ${code}` : roomText('photoAlt', {code});
      const captionText = isAdminMode ? `Фото ${idx + 1}` : roomText('photoCaption', {num: idx + 1});
      card.innerHTML = `<img src="${src}" alt="${altText}"><div class="room-gallery-caption">${captionText}</div>`;
      galleryGrid.appendChild(card);
    });
    galleryModal.hidden = false;
    document.body.style.overflow = 'hidden';
  }

  function closeGallery() {
    if (!galleryModal) return;
    galleryModal.hidden = true;
    document.body.style.overflow = '';
  }

  function getAdminGuestLang() {
    const stored = (() => { try { return localStorage.getItem('hotel-pineta-lang') || ''; } catch (_e) { return ''; } })();
    const raw = String((guestLangInput && guestLangInput.value) || stored || document.documentElement.lang || 'ru').trim().toLowerCase().split('-')[0];
    return adminGuestTexts[raw] ? raw : 'ru';
  }

  function syncAdminMsgLangButtons() {
    const current = getAdminGuestLang();
    adminMsgLangButtons.forEach((btn) => {
      const active = btn.dataset.lang === current;
      btn.classList.toggle('active', active);
      btn.setAttribute('aria-pressed', active ? 'true' : 'false');
    });
  }

  function adminGuestText(key, vars = {}) {
    const lang = getAdminGuestLang();
    const dict = adminGuestTexts[lang] || adminGuestTexts.ru;
    let text = dict[key] || adminGuestTexts.ru[key] || '';
    Object.keys(vars || {}).forEach((name) => {
      text = text.split('{' + name + '}').join(String(vars[name] ?? ''));
    });
    return text;
  }


  function extractOfferRoomCode(value) {
    let text = normalizeCode(String(value || '').replace(/\s+/g, ' ').trim());
    const match = text.match(/(?:№\s*)?([0-9]{1,2}\s*[Аа]?)/u);
    return match ? normalizeCode(match[1].replace(/\s+/g, '').toUpperCase()) : '';
  }
  function buildOfferRoomLine(roomText, codeCandidate) {
    const code = extractOfferRoomCode(codeCandidate || roomText);
    const label = code ? translatedRoomLabelForOffer(code) : String(roomText || '—');
    return adminGuestText('offerRoom', {room: label || String(roomText || '—')});
  }

  function formatDateForGuest(value) {
    if (!value) return '';
    const d = new Date(value + 'T00:00:00');
    if (Number.isNaN(d.getTime())) return value;
    return d.toLocaleDateString(adminGuestLangMap[getAdminGuestLang()] || 'ru-RU');
  }

  function getPrepaymentTimingMessage(arrivalDate, prepayment, deadlineDays) {
    if (deadlineDays !== null && deadlineDays !== undefined && deadlineDays !== '') {
      const days = Math.max(1, parseInt(deadlineDays, 10) || 1);
      const redDays = `🔴 ${days}`;
      const lang = getAdminGuestLang();
      if (lang === 'uk') return `Щоб підтвердити бронювання, потрібно внести передоплату ${prepayment} протягом ${redDays} дн.`;
      if (lang === 'it') return `Per confermare la prenotazione dovete effettuare l’acconto di ${prepayment} entro ${redDays} giorni.`;
      if (lang === 'en') return `To confirm the booking, you need to pay the prepayment of ${prepayment} within ${redDays} day(s).`;
      if (lang === 'fr') return `Pour confirmer la réservation, vous devez verser l’acompte de ${prepayment} dans un délai de ${redDays} jour(s).`;
      if (lang === 'de') return `Um die Buchung zu bestätigen, müssen Sie die Anzahlung von ${prepayment} innerhalb von ${redDays} Tag(en) leisten.`;
      return `Для бронирования Вы должны сделать предоплату ${prepayment} в течение ${redDays} дн.`;
    }
    const today = new Date();
    today.setHours(0,0,0,0);
    const m = String(arrivalDate || '').match(/^(\d{4})-(\d{2})-(\d{2})$/);
    if (!m) return adminGuestText('payThreeDays', {amount: prepayment});
    const arrival = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
    arrival.setHours(0,0,0,0);
    const diff = Math.round((arrival.getTime() - today.getTime()) / 86400000);
    if (diff <= 1) return adminGuestText('payToday', {amount: prepayment});
    if (diff === 2) return adminGuestText('payTodayTomorrow', {amount: prepayment});
    return adminGuestText('payThreeDays', {amount: prepayment});
  }

  function buildOfferMessage(details) {
    if (!offerMessage) return '';
    const guestName = (details?.guest_name || '').trim();
    const greeting = guestName ? adminGuestText('helloName', {name: guestName}) : adminGuestText('hello');
    const roomText = details?.room_code || details?.room_label || '—';
    const roomLine = buildOfferRoomLine(roomText, details?.room_code || details?.room_label || selectedCode);
    const date = formatDateForGuest(details?.arrival_date || '');
    const nights = details?.nights || '—';
    const guests = details?.guests_count || '—';
    const breakfast = details?.breakfast ? adminGuestText('breakfastYes') : adminGuestText('breakfastNo');
    const total = formatMoney(details?.full_amount);
    const prepayment = formatMoney(details?.prepayment_amount);
    const discountCode = String(details?.discount_code || '').trim();
    const discountLine = discountCode ? `\n${discountCodeText(discountCode)}` : '';

    if (isBookingSource()) {
      return `${greeting}\n\n${roomLine}\n${adminGuestText('offerDate',{date:date||'—'})}\n${adminGuestText('offerNights',{nights:nights||'—'})}\n${adminGuestText('offerGuests',{guests:guests||'—'})}\n${adminGuestText('offerMeal',{meal:breakfast})}\n${adminGuestText('offerTotal',{total})}\n${adminGuestText('offerPrepayment',{prepayment})}${discountLine}\n\n${adminGuestText('bookingInstant')}`;
    }

    return `${greeting}\n\n${roomLine}\n${adminGuestText('offerDate',{date:date||'—'})}\n${adminGuestText('offerNights',{nights:nights||'—'})}\n${adminGuestText('offerGuests',{guests:guests||'—'})}\n${adminGuestText('offerMeal',{meal:breakfast})}\n${adminGuestText('offerTotal',{total})}\n${adminGuestText('offerPrepayment',{prepayment})}${discountLine}\n\n${getPrepaymentTimingMessage(details?.arrival_date || '', prepayment, details?.payment_deadline_days)} ${adminGuestText('offerTail')}`;
  }

  function buildPaymentMessage(details) {
    const prepayment = formatMoney(details?.prepayment_amount);
    return `${adminGuestText('paymentHeader')}

${adminGuestText('paymentAmount', {amount: prepayment})}
${adminGuestText('paymentRecipient')}
${adminGuestText('paymentBank')}
Codice Iban: IT25T0503449251000000007146
Codice SWIFT: BAPPIT21B7C`;
  }

  function composeOfferMessage() {
    if (!offerMessage) return;
    if (config.hasSuccessFlash && createdBookingData && !currentQuote) {
      offerMessage.value = createdBookingData.offer_message || offerMessage.value || '';
      return;
    }
    const details = {
      guest_name: guestNameInput?.value || createdBookingData?.guest_name || '',
      room_label: getSelectedRoomLabel() || createdBookingData?.room_label || '',
      room_code: selectedCode ? `№ ${selectedCode}` : (createdBookingData?.room_code ? `№ ${createdBookingData.room_code}` : '—'),
      arrival_date: arrivalInput?.value || createdBookingData?.arrival_date || '',
      nights: nightsInput?.value || createdBookingData?.nights || '',
      guests_count: adultsSelect?.value || createdBookingData?.guests_count || '',
      breakfast: breakfastInput ? !!breakfastInput.checked : !!createdBookingData?.breakfast,
      full_amount: currentQuote ? (currentQuote.full_amount ?? currentQuote.total_amount) : (createdBookingData?.full_amount ?? null),
      prepayment_amount: currentQuote ? (currentQuote.prepayment_amount ?? 0) : (createdBookingData?.prepayment_amount ?? null),
      discount_code: activeDiscountCode() || createdBookingData?.discount_code || '',
      payment_deadline_days: currentQuote ? currentQuote.payment_deadline_days : (createdBookingData?.payment_deadline_days ?? null),
    };
    offerMessage.value = buildOfferMessage(details);
  }

  function copyText(btn, text, fallbackEl, idleLabel, successLabel) {
    if (!btn) return;
    navigator.clipboard.writeText(text || '').then(function () {
      btn.textContent = successLabel;
      setTimeout(function () { btn.textContent = idleLabel; }, 1400);
    }).catch(function () {
      if (fallbackEl) fallbackEl.select();
    });
  }

  function hidePaymentDetailsBlock() {
    if (paymentDetailsBlock) paymentDetailsBlock.hidden = true;
    if (paymentDetailsMessage) paymentDetailsMessage.value = '';
  }

  function showPaymentDetailsBlock(details) {
    if (!paymentDetailsBlock || !paymentDetailsMessage) return;
    if (isBookingSource()) {
      hidePaymentDetailsBlock();
      return;
    }
    paymentDetailsBlock.hidden = false;
    paymentDetailsMessage.value = buildPaymentMessage(details);
  }

  function setReceiptUploadStatus(message, type) {
    if (!receiptUploadStatus) return;
    receiptUploadStatus.textContent = message || '';
    receiptUploadStatus.className = 'receipt-upload-status' + (type ? (' ' + type) : '');
  }

  function setReceiptUploadLoading(isLoading, message) {
    if (receiptUploadLoading) receiptUploadLoading.hidden = !isLoading;
    if (receiptUploadLoadingText) receiptUploadLoadingText.textContent = message || uiText('receiptLoading');
    if (receiptUploadBtn) receiptUploadBtn.textContent = isLoading ? uiText('uploading') : uiText('uploadReceipt');
    if (receiptFileInput) receiptFileInput.disabled = !!isLoading;
    syncReceiptUploadReadyState();
  }

  function syncReceiptUploadReadyState() {
    if (!receiptUploadBtn) return;
    const hasReservation = !!(receiptReservationIdInput && String(receiptReservationIdInput.value || '').trim());
    const hasFile = !!(receiptFileInput && receiptFileInput.files && receiptFileInput.files.length > 0);
    const loading = !!receiptUploadLoading && receiptUploadLoading.hidden === false;
    receiptUploadBtn.disabled = !hasReservation || !hasFile || loading;
  }

  function showReceiptUploadBlock(details) {
    const reservationId = String(details?.reservation_id || details?.booking_number || '').trim();
    if (document.getElementById('receiptUploadBlock')) {
      document.getElementById('receiptUploadBlock').hidden = reservationId === '';
    }
    if (receiptReservationIdEl) receiptReservationIdEl.textContent = reservationId || '—';
    if (receiptReservationIdInput) receiptReservationIdInput.value = reservationId;
      const receiptTokenInput = document.getElementById('receiptTokenInput');
      if (receiptTokenInput && details && details.secure_token) receiptTokenInput.value = details.secure_token;
    if (receiptFileInput) receiptFileInput.value = '';
    syncReceiptFileName();
    setReceiptUploadStatus('', '');
    syncReceiptUploadReadyState();
  }

  async function submitReceiptUpload(event) {
    if (event) {
      event.preventDefault();
      event.stopPropagation();
    }
    if (!receiptUploadForm || !receiptReservationIdInput || !config.receiptUploadUrl) return false;

    const reservationId = String(receiptReservationIdInput.value || '').trim();
    const file = receiptFileInput && receiptFileInput.files ? receiptFileInput.files[0] : null;
    if (!reservationId) {
      setReceiptUploadStatus(uiText('reservationNotFound'), 'error');
      return false;
    }
    if (!file) {
      setReceiptUploadStatus(uiText('chooseFileFirst'), 'error');
      return false;
    }

    const formData = new FormData(receiptUploadForm);
    const receiptTokenInput = document.getElementById('receiptTokenInput');
    if (receiptTokenInput && receiptTokenInput.value && !formData.has('token')) formData.append('token', receiptTokenInput.value);
    formData.set('reservation_id', reservationId);
    setReceiptUploadStatus('', '');
    setReceiptUploadLoading(true, uiText('receiptLoading'));

    try {
      const response = await fetch(config.receiptUploadUrl, {
        method: 'POST',
        body: formData,
        headers: {
          'Accept': 'application/json',
          'X-Requested-With': 'XMLHttpRequest'
        }
      });
      const data = await response.json();
      if (!response.ok || !data.ok) {
        throw new Error(data.message || uiText('uploadFailed'));
      }
      if (receiptFileInput) receiptFileInput.value = '';
      syncReceiptFileName();
      setReceiptUploadStatus(data.message || uiText('receiptUploaded'), 'ok');
    } catch (error) {
      setReceiptUploadStatus(error && error.message ? error.message : uiText('uploadFailed'), 'error');
    } finally {
      setReceiptUploadLoading(false, uiText('receiptLoading'));
    }
    return false;
  }

  function syncSourceMode() {
    if (!isAdminMode || !sourceInput || !submitBtn) return;
    if (bookingLinkWrap) bookingLinkWrap.style.display = isBookingSource() ? '' : 'none';
    if (isBookingSource()) {
      submitBtn.textContent = 'Создать и подтвердить бронирование';
      if (bookingLinkInput) bookingLinkInput.required = true;
      if (statusInput) statusInput.value = 'confirmed';
      if (adminDiscountInput) {
        adminDiscountInput.checked = false;
        adminDiscountInput.disabled = true;
      }
    } else {
      submitBtn.textContent = 'Создать предварительное бронирование';
      if (bookingLinkInput) bookingLinkInput.required = false;
      if (adminDiscountInput) adminDiscountInput.disabled = false;
    }
    syncAdminDiscountCodeBox();
    composeOfferMessage();
  }

  function setStatusMessage(type, message) {
    if (!restrictionStatus) return;
    restrictionStatus.textContent = message || '';
    restrictionStatus.className = 'status-line' + (type ? (' ' + type) : '');
  }

  function setSubmitMessage(type, message) {
    if (submitSuccessBox) submitSuccessBox.style.display = 'none';
    if (submitErrorBox) submitErrorBox.style.display = 'none';
    if (!message) return;
    const box = type === 'error' ? submitErrorBox : submitSuccessBox;
    if (!box) return;
    box.textContent = message;
    box.style.display = 'block';
  }

  async function fetchJsonWithTimeout(url, options = {}, timeoutMs = 6000) {
    const controller = new AbortController();
    const timer = window.setTimeout(function () { controller.abort(); }, timeoutMs);
    try {
      const response = await fetch(url, Object.assign({}, options, {signal: controller.signal}));
      const data = await response.json();
      return {response, data};
    } finally {
      window.clearTimeout(timer);
    }
  }

  async function fetchAvailability() {
    if (!arrivalInput?.value) return;
    const current = ++requestCounter;
    try {
      const url = new URL(config.availabilityUrl, window.location.origin);
      url.searchParams.set('arrival_date', arrivalInput.value);
      url.searchParams.set('nights', String(getNights()));
      const selectedId = getSelectedRoomId();
      if (selectedId) url.searchParams.set('room_id', selectedId);
      url.searchParams.set('breakfast', breakfastInput?.checked ? '1' : '0');
      url.searchParams.set('is_discount', (isDiscountMode || adminDiscountActive()) ? '1' : '0');
      url.searchParams.set('source', isAdminMode ? (sourceInput?.value || 'admin') : 'site');
      if (isAdminMode) {
        const extendedDateRangeInput = document.getElementById('extendedDateRangeInput');
        url.searchParams.set('extended_date_range', extendedDateRangeInput?.checked ? '1' : '0');
      }
      const {response, data} = await fetchJsonWithTimeout(url.toString(), {headers: {'Accept': 'application/json'}}, 15000);
      if (current !== requestCounter) return;
      statusMap = {};
      Object.entries(data.status_map || {}).forEach(([roomKey, status]) => {
        const normalizedKey = normalizeCode(String(roomKey));
        const code = Object.prototype.hasOwnProperty.call(roomIdMap, normalizedKey)
          ? normalizedKey
          : (Object.keys(roomIdMap).find(key => String(roomIdMap[key]) === String(roomKey)) || roomKey);
        statusMap[normalizeCode(code)] = status;
      });
      currentPricing = data.pricing || null;
      if (selectedCode && !canChooseStatus(roomStatus(selectedCode))) {
        selectedCode = '';
      }
      syncRoomSelect();
      renderRooms();
      fillPricing();
    } catch (e) {
      currentPricing = null;
      setStatusMessage('error', 'Не удалось загрузить занятость комнат. Проверьте импорт данных в базу.');
      fillPricing();
    }
  }

  async function fetchQuote() {
    const roomId = getSelectedRoomId();
    if (!roomId || !arrivalInput?.value || !departureInput?.value) {
      currentQuote = null;
      renderSummary();
      composeOfferMessage();
      return;
    }
    const url = new URL(config.quoteUrl, window.location.origin);
    url.searchParams.set('room_id', String(roomId));
    url.searchParams.set('arrival_date', arrivalInput.value);
    url.searchParams.set('departure_date', departureInput.value);
    url.searchParams.set('breakfast', breakfastInput?.checked ? '1' : '0');
    if (config.mode === 'guest') {
      url.searchParams.set('variant', config.variant || 'regular');
      url.searchParams.set('source', 'site');
      if (isDiscountMode && discountInput?.value) {
        url.searchParams.set('discount_code', discountInput.value);
      }
    } else {
      url.searchParams.set('source', sourceInput?.value || 'admin');
      url.searchParams.set('is_discount', adminDiscountActive() ? '1' : '0');
      const extendedDateRangeInput = document.getElementById('extendedDateRangeInput');
      url.searchParams.set('extended_date_range', extendedDateRangeInput?.checked ? '1' : '0');
    }
    try {
      const {response, data} = await fetchJsonWithTimeout(url.toString(), {headers: {'Accept': 'application/json'}}, 15000);
      if (data.ok) {
        currentQuote = data.quote || null;
        if (data.pricing) {
          currentPricing = data.pricing;
          fillPricing();
        }
        if (currentQuote) {
          currentQuote.prepayment_amount = currentQuote.prepayment_amount ?? 0;
          currentQuote.remaining_amount = currentQuote.remaining_amount ?? currentQuote.full_amount;
        }
        setStatusMessage('ok', isAdminMode ? `Минимум дней на выбранную дату: ${data.quote.min_nights}.` : uiTextFormat('minNightsStatus', {min: data.quote.min_nights}));
      } else {
        currentQuote = null;
        setStatusMessage('error', data.message || 'Не удалось рассчитать стоимость.');
      }
    } catch (e) {
      currentQuote = null;
      setStatusMessage('error', isAdminMode ? 'Не удалось рассчитать стоимость. Проверьте тарифы после импорта в базу.' : uiText('calcFailed'));
    }
    renderSummary();
    composeOfferMessage();
  }

  function fillPricing() {
    Object.values(priceMap).forEach(node => node && (node.textContent = '—'));
    if (minNightsValue) minNightsValue.textContent = '—';
    if (!currentPricing) {
      updateHighlight();
      return;
    }
    Object.entries(priceMap).forEach(([key, node]) => {
      if (node && currentPricing[key] != null) node.textContent = formatMoney(currentPricing[key]);
    });
    if (minNightsValue) minNightsValue.textContent = currentPricing.min_nights || 1;
    updateHighlight();
  }

  function renderSummary() {
    const nights = getNights();
    const total = currentQuote ? formatMoney(currentQuote.full_amount) : '—';
    if (summaryNights) summaryNights.textContent = String(nights);
    if (summaryTotal) summaryTotal.textContent = total;
    if (totalMain) totalMain.textContent = total;
    if (prepaymentLine) prepaymentLine.textContent = currentQuote ? formatMoney(currentQuote.prepayment_amount || 0) : '—';
    if (remainingLine) remainingLine.textContent = currentQuote ? formatMoney(currentQuote.remaining_amount || currentQuote.full_amount) : '—';
    if (fullAmountInput && currentQuote) fullAmountInput.value = Number(currentQuote.full_amount).toFixed(2);
    if (prepaymentInput && currentQuote && prepaymentInput.dataset.autofill !== 'manual') prepaymentInput.value = Number(currentQuote.prepayment_amount || 0).toFixed(2);
    if (quoteWarning) {
      quoteWarning.textContent = currentQuote ? (isAdminMode ? `Тариф: ${currentQuote.rate_name}. Стоимость рассчитана автоматически.` : uiTextFormat('quoteReady', {rate: currentQuote.rate_name})) : (isAdminMode ? 'Выберите дату, номер и количество ночей, чтобы увидеть итог.' : uiText('quoteDefault'));
    }
    composeOfferMessage();
  }

  function resetComputedState() {
    currentPricing = null;
    currentQuote = null;
    fillPricing();
    renderSummary();
    composeOfferMessage();
  }

  async function refreshAll() {
    enforceArrivalMinDate();
    updateDepartureDate();
    if (roomSelect && selectedCode) {
      const selectedId = roomIdMap[normalizeCode(selectedCode)] || '';
      roomSelect.value = selectedId;
    }
    setLoading(true);
    try {
      await fetchAvailability();
      await fetchQuote();
    } finally {
      setLoading(false);
    }
  }

  function markDraftStatusRed() {
    const draftField = sourceInput && sourceInput.value === 'draft' ? sourceInput : (statusInput && statusInput.value === 'draft' ? statusInput : null);
    if (!draftField) return false;
    draftField.classList.add('draft-blocked');
    draftField.style.color = '#b91c1c';
    draftField.style.borderColor = '#b91c1c';
    draftField.style.backgroundColor = '#fff5f5';
    draftField.focus();
    return true;
  }

  function clearDraftStatusRed() {
    [sourceInput, statusInput].forEach(function (field) {
      if (!field || field.value === 'draft') return;
      field.classList.remove('draft-blocked');
      field.style.color = '';
      field.style.borderColor = '';
      field.style.backgroundColor = '';
    });
  }

  async function submitAdminForm(event) {
    event.preventDefault();
    if (!form || !submitBtn) return;
    if (markDraftStatusRed()) {
      setSubmitMessage('', '');
      setSubmitLoading(false);
      return;
    }
    if (!currentQuote) {
      setSubmitMessage('error', 'Сначала выберите дату, номер и дождитесь расчёта.');
      return;
    }
    if (isBookingSource() && bookingLinkInput && !(bookingLinkInput.value || '').trim()) {
      setSubmitMessage('error', 'Добавьте ссылку на страницу Booking.');
      return;
    }

    syncAdminDiscountCodeBox();
    setSubmitMessage('', '');
    submitBtn.disabled = true;
    setSubmitLoading(true, 'Сохраняем бронирование…');
    const originalText = submitBtn.textContent;
    submitBtn.textContent = 'Сохраняем...';

    try {
      const formData = new FormData(form);
      const response = await fetch(form.action, {
        method: 'POST',
        body: formData,
        headers: {
          'Accept': 'application/json',
          'X-Requested-With': 'XMLHttpRequest'
        }
      });
      const data = await response.json();
      if (!response.ok || !data.ok) {
        throw new Error(data.message || 'Не удалось создать бронирование.');
      }
      if (data.created_url) {
        window.location.href = data.created_url;
        return;
      }

      const booking = data.booking || {};
      currentQuote = {
        full_amount: booking.full_amount,
        prepayment_amount: booking.prepayment_amount,
        remaining_amount: booking.remaining_amount,
        rate_name: booking.rate_name,
        min_nights: booking.min_nights_applied || 1,
      };
      renderSummary();
      if (offerMessage) offerMessage.value = data.offer_message || offerMessage.value;
      if (booking.status === 'confirmed' || isBookingSource()) {
        hidePaymentDetailsBlock();
        showReceiptUploadBlock({ reservation_id: '' });
      } else {
        showPaymentDetailsBlock(booking);
        showReceiptUploadBlock(booking);
      }
      setSubmitMessage('success', data.message || 'Бронирование сохранено.');
      setStatusMessage('ok', booking.status === 'confirmed' ? 'Бронирование Booking подтверждено сразу.' : 'Предварительное бронирование создано. Номер временно удержан.');
      if (quoteWarning) {
        quoteWarning.textContent = booking.status === 'confirmed'
          ? 'Предоплата считается внесённой на 100%, квитанция не требуется.'
          : 'После создания предварительного бронирования отправьте гостю сообщение и реквизиты для оплаты.';
      }
      if (form) {
        form.reset();
        selectedCode = '';
        currentPricing = null;
        currentQuote = null;
        if (arrivalInput) arrivalInput.value = getArrivalMinDate();
        if (nightsInput) nightsInput.value = '1';
        if (guestLangInput) guestLangInput.value = 'ru';
        syncRoomSelect();
        renderRooms();
        syncAdminMsgLangButtons();
        syncSourceMode();
        refreshAll();
      }
    } catch (error) {
      setSubmitMessage('error', error && error.message ? error.message : 'Ошибка сервера.');
    } finally {
      submitBtn.disabled = false;
      submitBtn.textContent = originalText;
      setSubmitLoading(false);
      syncSourceMode();
    }
  }

  roomSelect?.addEventListener('change', function () {
    const option = this.options[this.selectedIndex];
    selectedCode = option?.dataset.code || '';
    renderRooms();
    refreshAll();
  });
  arrivalInput?.addEventListener('change', function () { enforceArrivalMinDate(); hidePaymentDetailsBlock(); refreshAll(); });
  nightsInput?.addEventListener('input', function () { hidePaymentDetailsBlock(); refreshAll(); });
  breakfastInput?.addEventListener('change', function () { hidePaymentDetailsBlock(); refreshAll(); });
  adultsSelect?.addEventListener('change', function () { renderSummary(); composeOfferMessage(); });
  childrenSelect?.addEventListener('change', renderSummary);
  discountInput?.addEventListener('input', refreshAll);
  prepaymentInput?.addEventListener('input', function () { this.dataset.autofill = 'manual'; });
  document.getElementById('extendedDateRangeInput')?.addEventListener('change', function () { enforceArrivalMinDate(); hidePaymentDetailsBlock(); refreshAll(); });
  sourceInput?.addEventListener('change', function () { clearDraftStatusRed(); hidePaymentDetailsBlock(); syncSourceMode(); refreshAll(); });
  guestNameInput?.addEventListener('input', composeOfferMessage);
  adminDiscountInput?.addEventListener('change', function () { hidePaymentDetailsBlock(); syncAdminDiscountCodeBox(); refreshAll(); });
  bookingLinkInput?.addEventListener('input', composeOfferMessage);
  guestLangInput?.addEventListener('change', function () { syncAdminMsgLangButtons(); composeOfferMessage(); });
  document.addEventListener('pineta:guest-language-changed', function () { syncAdminMsgLangButtons(); composeOfferMessage(); });
  adminMsgLangButtons.forEach((btn) => btn.addEventListener('click', function () {
    if (!guestLangInput) return;
    guestLangInput.value = btn.dataset.lang || 'ru';
    syncAdminMsgLangButtons();
    composeOfferMessage();
  }));
  galleryClose?.addEventListener('click', closeGallery);
  galleryModal?.addEventListener('click', function (e) { if (e.target === galleryModal) closeGallery(); });
  document.addEventListener('keydown', function (e) { if (e.key === 'Escape') closeGallery(); });
  if (copyOfferBtn && offerMessage) copyOfferBtn.addEventListener('click', function () { copyText(copyOfferBtn, offerMessage.value || '', offerMessage, 'Скопировать сообщение', 'Скопировано'); });
  if (copyPaymentBtn && paymentDetailsMessage) copyPaymentBtn.addEventListener('click', function () { copyText(copyPaymentBtn, paymentDetailsMessage.value || '', paymentDetailsMessage, 'Скопировать реквизиты', 'Скопировано'); });
  if (receiptUploadForm) receiptUploadForm.addEventListener('submit', submitReceiptUpload);
  if (receiptUploadBtn) receiptUploadBtn.addEventListener('click', submitReceiptUpload);
  if (receiptFileInput) receiptFileInput.addEventListener('change', function () { setReceiptUploadStatus('', ''); syncReceiptFileName(); syncReceiptUploadReadyState(); });
  statusInput?.addEventListener('change', clearDraftStatusRed);
  if (form && isAdminMode) form.addEventListener('submit', submitAdminForm);
  if (form && !isAdminMode) form.addEventListener('submit', function (event) {
    if (!currentQuote) {
      event.preventDefault();
      setStatusMessage('error', uiText('selectBeforeCalc'));
      return false;
    }
    if (submitBtn) submitBtn.disabled = true;
    setSubmitLoading(true, uiText('uploading'));
    return true;
  });

  enforceArrivalMinDate();
  updateDepartureDate();
  syncRoomSelect();
  renderRooms();
  syncAdminMsgLangButtons();
  syncSourceMode();
  if (createdBookingData) {
    if (offerMessage && createdBookingData.offer_message) offerMessage.value = createdBookingData.offer_message;
    if (paymentDetailsMessage && createdBookingData.payment_message) paymentDetailsMessage.value = createdBookingData.payment_message;
    showPaymentDetailsBlock(createdBookingData);
    showReceiptUploadBlock(createdBookingData);
    if (!isAdminMode && config.hasSuccessFlash) {
      window.setTimeout(function () {
        alert(uiText('paymentAlert'));
      }, 100);
    }
  }
  refreshAll();
})();
