'use strict';

import intlTelInput from 'intl-tel-input';
import 'intl-tel-input/styles';

document.addEventListener('DOMContentLoaded', () => {
  const table = document.querySelector('.datatables-owner-serenatas');
  const serenataModalElement = document.getElementById('modalAddSerenata');
  const serenataViewModalElement = document.getElementById('modalSerenataView');
  const serenataViewFrame = document.getElementById('serenataViewFrame');
  const addSerenataForm = document.getElementById('addNewSerenataForm');
  const iaAssistOverlay = document.getElementById('iaAssistOverlay');
  const btnOpenIaAssist = document.getElementById('btnOpenIaAssist');
  const btnCloseIaAssistOverlay = document.getElementById('btnCloseIaAssistOverlay');
  const btnCancelIaAssistOverlay = document.getElementById('btnCancelIaAssistOverlay');
  const iaRawText = document.getElementById('ia_raw_text');
  const iaApplyButton = document.getElementById('btnApplyIaParse');
  const basePriceInput = document.getElementById('base_price');
  const giftAmountInput = document.getElementById('gift_amount');
  const giftIncludedAmountPreview = document.getElementById('gift_included_amount_preview');
  const giftAdditionalAmountPreview = document.getElementById('gift_additional_amount_preview');
  const artistAmountInput = document.getElementById('artist_amount');
  const otherCostAmountInput = document.getElementById('other_cost_amount');
  const baseCostPreview = document.getElementById('base_cost_preview');
  const giftItemsList = document.getElementById('giftItemsList');
  const giftItemsInput = document.getElementById('gift_items');
  const artistItemsList = document.getElementById('artistItemsList');
  const artistItemsInput = document.getElementById('artist_items');
  const otherCostItemsList = document.getElementById('otherCostItemsList');
  const otherCostItemsInput = document.getElementById('other_cost_items');
  const discountAmountInput = document.getElementById('discount_amount');
  const mariachiGroupInput = document.getElementById('mariachi_group_id');
  const packageSelect = document.getElementById('serenata_package_id');
  const serviceAddressInput = document.getElementById('service_address');
  const localityInput = document.getElementById('locality');
  const neighborhoodInput = document.getElementById('neighborhood');
  const serviceCountryInput = document.getElementById('service_country');
  const serviceDepartmentInput = document.getElementById('service_department');
  const serviceLocalityInput = document.getElementById('service_locality');
  const serviceSectorInput = document.getElementById('service_sector');
  const btnOpenServiceAddressMap = document.getElementById('btnOpenServiceAddressMap');
  const localityOptionsList = document.getElementById('locality_options');
  const neighborhoodOptionsList = document.getElementById('neighborhood_options');
  const btnEditLocality = document.getElementById('btnEditLocality');
  const btnEditNeighborhood = document.getElementById('btnEditNeighborhood');
  const btnEditServiceCountry = document.getElementById('btnEditServiceCountry');
  const btnEditServiceDepartment = document.getElementById('btnEditServiceDepartment');
  const btnEditServiceLocality = document.getElementById('btnEditServiceLocality');
  const btnEditServiceSector = document.getElementById('btnEditServiceSector');
  const totalPreview = document.getElementById('total_price_preview');
  const profitPreview = document.getElementById('profit_preview');
  const serviceStatusInput = document.getElementById('service_status');
  const cancellationReasonWrap = document.getElementById('cancellationReasonWrap');
  const cancellationReasonInput = document.getElementById('cancellation_reason');
  const commissionRateInput = document.getElementById('commission_rate');
  const commissionAmountPreview = document.getElementById('commission_amount_preview');
  const eventDateInput = document.getElementById('event_date');
  const eventTimeInput = document.getElementById('event_time');
  const eventTimeDisplayInput = document.getElementById('event_time_display');
  const eventTimeModeInput = document.getElementById('event_time_window_mode');
  const btnEventTimePicker = document.getElementById('btnEventTimePicker');
  const eventTimeWindowPreview = document.getElementById('event_time_window_preview');
  const eventTimeModeButtons = Array.from(document.querySelectorAll('#eventTimeModeButtons .event-window-mode-btn'));
  const modalEventTimeWindowElement = document.getElementById('modalEventTimeWindowPicker');
  const modalEventTimeModeButtons = Array.from(document.querySelectorAll('#modalEventTimeModeButtons .event-window-mode-btn'));
  const modalEventTimeWindowPreview = document.getElementById('modalEventTimeWindowPreview');
  const modalEventTimeReferenceLabel = document.getElementById('modalEventTimeReferenceLabel');
  const modalEventTimeMeridiem = document.getElementById('modalEventTimeMeridiem');
  const modalEventTimeCenterLabel = document.getElementById('modalEventTimeCenterLabel');
  const modalEventTimeHourButtons = Array.from(document.querySelectorAll('.js-event-clock-hour'));
  const btnApplyEventTimeWindow = document.getElementById('btnApplyEventTimeWindow');
  const internalNotesCollapse = document.getElementById('internalNotesCollapse');
  const internalNotesToggleIcon = document.getElementById('internalNotesToggleIcon');
  const googleMapsApiKey = window.__googleMapsApiKey || null;
  const googleMapsCountry = (window.__googleMapsCountry || 'co').toLowerCase();
  const serenataPackages = Array.isArray(window.__serenataPackages) ? window.__serenataPackages : [];
  const packageTemplateUrl = window.__packageTemplateUrl || null;
  const aiParseUrl = window.__aiParseUrl || null;
  const addressResolveUrl = window.__addressResolveUrl || null;
  const addressSuggestionsUrl = window.__addressSuggestionsUrl || null;
  const locationOptionsUrl = window.__locationOptionsUrl || null;
  const locationOptionsSeed = window.__locationOptionsSeed || {
    neighborhoods: [],
    localities: [],
    countries: [],
    departments: [],
    service_localities: [],
    service_sectors: []
  };
  let currentServiceAddressMapsUrl = '';
  let currentServiceAddressPlaceId = '';
  let syncEventTimeWindowField = () => {};
  const phoneFieldIds = ['contact_phone_1', 'contact_phone_2'];
  const phoneFieldInstances = new Map();
  const toTitleCase = value => {
    let normalized = String(value || '')
      .normalize('NFC')
      .replace(/\s+/g, ' ')
      .trim();

    if (!normalized) return '';

    normalized = normalized
      .toLocaleLowerCase('es-CO')
      .replace(/(^|[\s/-])([a-záéíóúñ])/g, (_, prefix, chr) => `${prefix}${chr.toLocaleUpperCase('es-CO')}`)
      .replace(/\bd\.?\s*c\.?\b/gi, 'D.C.')
      .replace(/\b([ivxlcdm]{2,})\b/gi, match => match.toUpperCase())
      .replace(/([a-záéíóúñ])([ÁÉÍÓÚ])\b/g, (_, first, lastAccentUpper) => `${first}${lastAccentUpper.toLocaleLowerCase('es-CO')}`);

    return normalized;
  };

  const normalizePhoneValue = value => {
    let normalized = String(value || '').trim();
    if (!normalized) return '';

    normalized = normalized
      .replace(/[^\d+]/g, '')
      .replace(/(?!^)\+/g, '');

    if (normalized.startsWith('00')) {
      normalized = `+${normalized.slice(2)}`;
    }

    const digits = normalized.replace(/\D/g, '');
    if (!digits) return '';

    if (normalized.startsWith('+')) {
      return `+${digits}`;
    }

    if (digits.length === 10 && digits.startsWith('3')) {
      return `+57${digits}`;
    }

    return digits;
  };

  const normalizeEventTimeValue = value => {
    const textValue = String(value || '').trim();
    if (!textValue) return '';

    const directMatch = textValue.match(/\b(\d{1,2}):(\d{2})\b/);
    const meridiemMatch = textValue.match(/\b(\d{1,2})(?::(\d{2}))?\s*(a\.?\s*m\.?|p\.?\s*m\.?|am|pm)\b/i);
    const hourOnlyMatch = textValue.match(/\bhora\s*[:.-]?\s*(\d{1,2})(?::(\d{2}))?\b/i);
    const match = meridiemMatch || hourOnlyMatch || directMatch;

    if (!match) return textValue;

    let hour = Number(match[1] || 0);
    const minute = String(match[2] || '00').padStart(2, '0');
    const meridiem = String(match[3] || '')
      .toLowerCase()
      .replace(/\./g, '')
      .replace(/\s+/g, '');

    if (meridiem === 'pm' && hour < 12) hour += 12;
    if (meridiem === 'am' && hour === 12) hour = 0;

    if (hour < 0 || hour > 23) {
      return textValue;
    }

    return `${String(hour).padStart(2, '0')}:${minute}`;
  };

  const getPhoneFieldElements = fieldId => ({
    hiddenInput: document.getElementById(fieldId),
    displayInput: document.getElementById(`${fieldId}_display`)
  });

  const getPhoneCountryData = () => {
    if (typeof intlTelInput.getCountryData === 'function') {
      return intlTelInput.getCountryData();
    }

    return [];
  };

  const resolvePhoneCountryParts = value => {
    const normalized = normalizePhoneValue(value);
    if (!normalized) {
      return {
        normalized: '',
        iso2: 'co',
        dialCode: '57',
        nationalNumber: ''
      };
    }

    const countries = getPhoneCountryData()
      .map(country => ({
        iso2: String(country.iso2 || '').toLowerCase(),
        dialCode: String(country.dialCode || '').replace(/\D/g, '')
      }))
      .filter(country => country.iso2 && country.dialCode)
      .sort((a, b) => b.dialCode.length - a.dialCode.length);

    const digits = normalized.replace(/\D/g, '');
    const matchedCountry = normalized.startsWith('+')
      ? countries.find(country => digits.startsWith(country.dialCode))
      : null;

    if (matchedCountry) {
      return {
        normalized,
        iso2: matchedCountry.iso2,
        dialCode: matchedCountry.dialCode,
        nationalNumber: digits.slice(matchedCountry.dialCode.length)
      };
    }

    if (digits.length === 10 && digits.startsWith('3')) {
      return {
        normalized: `+57${digits}`,
        iso2: 'co',
        dialCode: '57',
        nationalNumber: digits
      };
    }

    if (digits.length === 12 && digits.startsWith('57')) {
      return {
        normalized: `+${digits}`,
        iso2: 'co',
        dialCode: '57',
        nationalNumber: digits.slice(2)
      };
    }

    return {
      normalized,
      iso2: 'co',
      dialCode: '57',
      nationalNumber: normalized.startsWith('+') ? digits.slice(2) : digits
    };
  };

  const buildPhoneValueFromCountry = (dialCode, rawValue) => {
    const digits = String(rawValue || '').replace(/\D/g, '');
    const cleanDialCode = String(dialCode || '').replace(/\D/g, '');
    if (!digits) return '';
    return cleanDialCode ? `+${cleanDialCode}${digits}` : digits;
  };

  const syncPhoneFieldValue = fieldId => {
    const { hiddenInput, displayInput } = getPhoneFieldElements(fieldId);
    if (!hiddenInput || !displayInput) return '';

    const iti = phoneFieldInstances.get(fieldId);
    const rawDisplayValue = String(displayInput.value || '').trim();

    if (!rawDisplayValue) {
      hiddenInput.value = '';
      return '';
    }

    if (rawDisplayValue.startsWith('+') || rawDisplayValue.startsWith('00')) {
      const normalizedDirect = normalizePhoneValue(rawDisplayValue);
      hiddenInput.value = normalizedDirect;
      return normalizedDirect;
    }

    const selectedCountry = iti && typeof iti.getSelectedCountryData === 'function'
      ? iti.getSelectedCountryData()
      : null;
    const fallbackValue = buildPhoneValueFromCountry(selectedCountry?.dialCode || '', rawDisplayValue);
    const normalized = normalizePhoneValue(fallbackValue);

    hiddenInput.value = normalized;
    return normalized;
  };

  const setPhoneFieldValue = (fieldId, value) => {
    const parts = resolvePhoneCountryParts(value);
    const normalized = parts.normalized;
    const { hiddenInput, displayInput } = getPhoneFieldElements(fieldId);
    if (!hiddenInput || !displayInput) return;

    const iti = phoneFieldInstances.get(fieldId);
    hiddenInput.value = normalized;

    if (!normalized) {
      displayInput.value = '';
      return;
    }

    if (iti) {
      iti.setCountry(parts.iso2 || 'co');
    }

    displayInput.value = parts.nationalNumber || normalized.replace(/\D/g, '');
    hiddenInput.value = syncPhoneFieldValue(fieldId);
  };

  const initializePhoneField = fieldId => {
    const { hiddenInput, displayInput } = getPhoneFieldElements(fieldId);
    if (!hiddenInput || !displayInput) return;
    const phoneModalContainer = displayInput.closest('.modal');
    const initialValue = normalizePhoneValue(hiddenInput.value || displayInput.value);

    displayInput.value = '';

    const iti = intlTelInput(displayInput, {
      initialCountry: 'co',
      countryOrder: ['co', 'us', 'es'],
      separateDialCode: true,
      nationalMode: false,
      autoPlaceholder: 'polite',
      dropdownContainer: phoneModalContainer || document.body,
      loadUtils: () => import('intl-tel-input/utils')
    });

    phoneFieldInstances.set(fieldId, iti);

    if (initialValue) {
      setPhoneFieldValue(fieldId, initialValue);
    } else {
      iti.setCountry('co');
      hiddenInput.value = '';
      displayInput.value = '';
    }

    ['input', 'change', 'blur'].forEach(eventName => {
      displayInput.addEventListener(eventName, () => {
        syncPhoneFieldValue(fieldId);
      });
    });

    displayInput.addEventListener('countrychange', () => {
      syncPhoneFieldValue(fieldId);
    });
  };

  const syncAllPhoneFields = () => {
    phoneFieldIds.forEach(syncPhoneFieldValue);
  };

  const formatCopInteger = value =>
    Math.round(Number(value || 0)).toLocaleString('es-CO', {
      minimumFractionDigits: 0,
      maximumFractionDigits: 0
    });

  const parseCopInput = value => {
    const raw = String(value || '').trim();
    if (!raw) return 0;
    const digitsOnly = raw.replace(/[^\d]/g, '');
    if (!digitsOnly) return 0;
    return Number(digitsOnly) || 0;
  };

  const applyCopMask = input => {
    if (!input) return;
    const numericValue = parseCopInput(input.value);
    input.value = formatCopInteger(numericValue);
  };

  const resetPhoneFields = () => {
    phoneFieldIds.forEach(fieldId => {
      const { hiddenInput, displayInput } = getPhoneFieldElements(fieldId);
      const iti = phoneFieldInstances.get(fieldId);
      if (hiddenInput) hiddenInput.value = '';
      if (displayInput) displayInput.value = '';
      if (iti) iti.setCountry('co');
    });
  };

  const extractPhoneCandidates = value => {
    const matches = String(value || '').match(/(?:\+|00)?\d[\d\s().-]{7,}\d/g) || [];

    return [...new Set(matches
      .map(normalizePhoneValue)
      .map(item => {
        if (!item) return '';
        if (item.startsWith('+')) return item;
        return item.length >= 8 ? item : '';
      })
      .filter(Boolean))];
  };

  const notyf = new Notyf({
    duration: 3200,
    dismissible: true,
    ripple: true,
    position: { x: 'right', y: 'top' },
    types: [
      { type: 'info', className: 'notyf__info', icon: { className: 'icon-base ti tabler-info-circle' } },
      { type: 'warning', className: 'notyf__warning', icon: { className: 'icon-base ti tabler-alert-triangle' } },
      { type: 'success', className: 'notyf__success', icon: { className: 'icon-base ti tabler-check' } },
      { type: 'error', className: 'notyf__error', icon: { className: 'icon-base ti tabler-x' } }
    ]
  });

  if (!table) {
    return;
  }

  if (window.bootstrap?.Tooltip) {
    document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(element => {
      window.bootstrap.Tooltip.getOrCreateInstance(element);
    });
  }

  if (internalNotesCollapse && internalNotesToggleIcon) {
    internalNotesCollapse.addEventListener('show.bs.collapse', () => {
      internalNotesToggleIcon.classList.remove('tabler-plus');
      internalNotesToggleIcon.classList.add('tabler-minus');
    });
    internalNotesCollapse.addEventListener('hide.bs.collapse', () => {
      internalNotesToggleIcon.classList.remove('tabler-minus');
      internalNotesToggleIcon.classList.add('tabler-plus');
    });
  }

  if (serenataViewModalElement) {
    const hideSerenataViewModal = () => {
      const instance = bootstrap.Modal.getInstance(serenataViewModalElement);
      if (instance) {
        instance.hide();
      }
    };

    document.addEventListener('click', event => {
      const trigger = event.target.closest('.btn-open-serenata-view');
      if (!trigger) return;

      event.preventDefault();
      const viewUrl = trigger.getAttribute('data-view-url') || trigger.getAttribute('href');
      if (!viewUrl || !serenataViewFrame) return;

      serenataViewFrame.setAttribute('src', viewUrl);
      bootstrap.Modal.getOrCreateInstance(serenataViewModalElement).show();
    });

    serenataViewModalElement.addEventListener('hidden.bs.modal', () => {
      if (serenataViewFrame) {
        serenataViewFrame.setAttribute('src', 'about:blank');
      }
    });

    window.addEventListener('message', event => {
      if (event.origin !== window.location.origin) return;
      if (event.data?.type !== 'serenata-embed-close-request') return;
      if (event.data?.context && event.data.context !== 'serenata-detail') return;

      hideSerenataViewModal();
    });
  }

  const copyToClipboard = async text => {
    if (!text) return false;
    if (navigator.clipboard && window.isSecureContext) {
      await navigator.clipboard.writeText(text);
      return true;
    }
    const tmp = document.createElement('textarea');
    tmp.value = text;
    tmp.setAttribute('readonly', 'readonly');
    tmp.style.position = 'fixed';
    tmp.style.opacity = '0';
    document.body.appendChild(tmp);
    tmp.focus();
    tmp.select();
    const ok = document.execCommand('copy');
    document.body.removeChild(tmp);
    return ok;
  };

  const buildServiceAddressMapsUrl = () => {
    const serviceAddress = String(serviceAddressInput?.value || '').trim();
    const neighborhood = String(neighborhoodInput?.value || '').trim();
    const serviceSector = String(serviceSectorInput?.value || '').trim();
    const serviceLocality = String(serviceLocalityInput?.value || '').trim();
    const locality = String(localityInput?.value || '').trim();
    const serviceDepartment = String(serviceDepartmentInput?.value || '').trim();
    const serviceCountry = String(serviceCountryInput?.value || '').trim();

    const hasMeaningfulLocation =
      !!serviceAddress ||
      (!!locality && !!neighborhood) ||
      (!!locality && !!serviceSector) ||
      (!!locality && !!serviceLocality);

    if (!hasMeaningfulLocation) return '';

    const parts = [
      serviceAddress,
      neighborhood,
      serviceSector,
      serviceLocality,
      locality,
      serviceDepartment,
      serviceCountry
    ].filter(Boolean);

    return `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(parts.join(', '))}`;
  };

  const getServiceAddressResolvePayload = () => ({
    service_address: (serviceAddressInput?.value || '').trim(),
    address_additional: (document.getElementById('address_additional')?.value || '').trim(),
    service_country: (serviceCountryInput?.value || '').trim(),
    service_department: (serviceDepartmentInput?.value || '').trim(),
    neighborhood: (neighborhoodInput?.value || '').trim(),
    locality: (localityInput?.value || '').trim(),
    service_locality: (serviceLocalityInput?.value || '').trim(),
    service_sector: (serviceSectorInput?.value || '').trim()
  });

  const updateServiceAddressMapsState = url => {
    currentServiceAddressMapsUrl = String(url || currentServiceAddressMapsUrl || '').trim();
    if (!btnOpenServiceAddressMap) return;
    const fallbackUrl = buildServiceAddressMapsUrl();
    btnOpenServiceAddressMap.disabled = !currentServiceAddressMapsUrl && !fallbackUrl;
  };

  const resolveServiceAddressForMaps = async csrfToken => {
    if (!addressResolveUrl) return null;

    const payload = getServiceAddressResolvePayload();
    if (!payload.service_address && !payload.locality && !payload.neighborhood) {
      return null;
    }

    try {
      const response = await fetch(addressResolveUrl, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Accept: 'application/json',
          'X-Requested-With': 'XMLHttpRequest',
          'X-CSRF-TOKEN': csrfToken
        },
        body: JSON.stringify(payload)
      });
      const data = await response.json();
      if (!response.ok || !data?.success) return null;
      if (data?.fields) {
        if (data.fields.service_address) {
          setFieldValue('service_address', data.fields.service_address);
        }
        applyResolvedAddressFields(data.fields);
      }
      updateServiceAddressMapsState(data.google_maps_url || '');
      return data;
    } catch (error) {
      console.error('Service address maps resolve error', error);
      return null;
    }
  };

  document.addEventListener('click', async event => {
    const copyBtn = event.target.closest('.btn-copy-serenata');
    if (!copyBtn) return;
    event.preventDefault();

    const targetId = copyBtn.getAttribute('data-copy-target');
    const source = targetId ? document.getElementById(targetId) : null;
    const text = source ? source.value : '';
    if (!text) {
      notyf.open({ type: 'error', message: 'No hay datos para copiar.' });
      return;
    }
    try {
      await copyToClipboard(text);
      notyf.open({ type: 'success', message: 'Serenata copiada para WhatsApp.' });
    } catch (error) {
      notyf.open({ type: 'error', message: 'No se pudo copiar la serenata.' });
    }
  });

  const toastData = window.__toastData || {};
  if (toastData.success) {
    notyf.open({ type: 'success', message: toastData.success });
  }
  if (Array.isArray(toastData.errors) && toastData.errors.length) {
    toastData.errors.forEach(error => {
      notyf.open({ type: 'error', message: error, duration: 4500 });
    });
  }

  const dt = new DataTable(table, {
    order: [[0, 'desc']],
    pageLength: 10,
    layout: {
      topStart: {
        rowClass: 'row m-3 my-0 justify-content-between',
        features: [{ pageLength: { menu: [10, 25, 50, 100], text: '_MENU_' } }]
      },
      topEnd: {
        features: [
          { search: { placeholder: 'Buscar serenata', text: '_INPUT_' } },
          {
            buttons: [
              {
                extend: 'collection',
                className: 'btn btn-label-secondary dropdown-toggle me-2',
                text: '<span class="d-flex align-items-center gap-2"><i class="icon-base ti tabler-upload icon-xs"></i><span class="d-none d-sm-inline-block">Exportar</span></span>',
                buttons: ['copy', 'csv', 'excel', 'pdf', 'print']
              },
              {
                text: '<span class="d-flex align-items-center gap-2"><i class="icon-base ti tabler-plus icon-xs"></i><span class="d-none d-sm-inline-block">Nueva serenata</span></span>',
                className: 'btn btn-primary',
                action: function () {
                  if (serenataModalElement) {
                    bootstrap.Modal.getOrCreateInstance(serenataModalElement).show();
                  }
                }
              }
            ]
          }
        ]
      },
      bottomStart: {
        rowClass: 'row mx-3 justify-content-between',
        features: ['info']
      },
      bottomEnd: 'paging'
    },
    language: {
      search: '',
      searchPlaceholder: 'Buscar serenata',
      lengthMenu: '_MENU_'
    },
    initComplete: function () {
      const api = this.api();
      let selectedStart = null;
      let selectedEnd = null;
      let selectedDay = null;

      const createFilter = (columnIndex, containerClass, id, placeholder, extractor = null) => {
        const container = document.querySelector(containerClass);
        if (!container) return;

        const select = document.createElement('select');
        select.id = id;
        select.className = 'form-select';
        select.innerHTML = `<option value="">${placeholder}</option>`;
        container.appendChild(select);

        const values = new Set(
          api.column(columnIndex).data().toArray().map(item => {
            const helper = document.createElement('div');
            helper.innerHTML = item;
            const textValue = (helper.textContent || helper.innerText || '').replace(/\s+/g, ' ').trim();
            return extractor ? extractor(textValue) : textValue;
          })
        );

        [...values].filter(Boolean).sort().forEach(value => {
          const option = document.createElement('option');
          option.value = value;
          option.textContent = value;
          select.appendChild(option);
        });

        select.addEventListener('change', () => {
          const value = select.value ? `^${select.value}$` : '';
          api.column(columnIndex).search(value, true, false).draw();
        });
      };

      const toIsoFromTableDate = value => {
        if (!value) return null;
        const match = String(value || '').match(/(\d{1,2})\/(\d{1,2})\/(\d{4})/);
        if (!match) return null;
        const [, day, month, year] = match;
        if (!day || !month || !year) return null;
        const d = Number(day);
        const m = Number(month);
        const y = Number(year);
        if (!Number.isInteger(d) || !Number.isInteger(m) || !Number.isInteger(y)) return null;
        return `${String(y).padStart(4, '0')}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
      };

      const toIsoFromDateObj = value => {
        if (!(value instanceof Date) || Number.isNaN(value.getTime())) return null;
        const y = value.getFullYear();
        const m = value.getMonth() + 1;
        const d = value.getDate();
        return `${String(y).padStart(4, '0')}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
      };

      const parseIsoFromHumanDate = raw => {
        const text = String(raw || '').trim();
        const match = text.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
        if (!match) return null;
        const d = Number(match[1]);
        const m = Number(match[2]);
        const y = Number(match[3]);
        if (!Number.isInteger(d) || !Number.isInteger(m) || !Number.isInteger(y)) return null;
        if (d < 1 || d > 31 || m < 1 || m > 12) return null;
        return `${String(y).padStart(4, '0')}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
      };

      const dateContainer = document.querySelector('.serenata_date');
      if (dateContainer) {
        const serviceDateSet = new Set(
          api
            .column(1)
            .data()
            .toArray()
            .map(item => {
              const helper = document.createElement('div');
              helper.innerHTML = item;
              return (helper.textContent || helper.innerText || '').replace(/\s+/g, ' ').trim();
            })
            .map(toIsoFromTableDate)
            .filter(Boolean)
        );

        const wrap = document.createElement('div');
        wrap.className = 'd-flex gap-2';
        wrap.innerHTML = `
          <input type="text" id="SerenataDateRange" class="form-control" placeholder="Filtrar por fecha o rango">
          <button type="button" class="btn btn-label-secondary" id="SerenataDateRangeClear">Limpiar</button>
        `;
        dateContainer.appendChild(wrap);

        DataTable.ext.search.push((settings, data) => {
          if (settings.nTable !== table) return true;
          if (!selectedStart && !selectedEnd && !selectedDay) return true;

          const rowDateIso = toIsoFromTableDate(data[1] || '');
          if (!rowDateIso) return false;

          if (!selectedStart && !selectedEnd && selectedDay) {
            const rowDay = Number((rowDateIso.split('-')[2] || '0'));
            return rowDay === selectedDay;
          }

          if (selectedStart && selectedEnd) {
            return rowDateIso >= selectedStart && rowDateIso <= selectedEnd;
          }
          if (selectedStart) {
            return rowDateIso === selectedStart;
          }
          return true;
        });

        const dateInput = document.getElementById('SerenataDateRange');
        const clearButton = document.getElementById('SerenataDateRangeClear');

        const datePicker = flatpickr(dateInput, {
          mode: 'range',
          dateFormat: 'Y-m-d',
          altInput: true,
          altFormat: 'd/m/Y',
          allowInput: true,
          onChange(selectedDates) {
            if (!selectedDates.length) {
              selectedStart = null;
              selectedEnd = null;
              selectedDay = null;
            } else if (selectedDates.length === 1) {
              selectedStart = toIsoFromDateObj(selectedDates[0]);
              selectedEnd = null;
              selectedDay = null;
            } else {
              selectedStart = toIsoFromDateObj(selectedDates[0]);
              selectedEnd = toIsoFromDateObj(selectedDates[1]);
              selectedDay = null;
            }
            api.draw();
          },
          onDayCreate(_dObj, _dStr, _fp, dayElem) {
            const iso = toIsoFromDateObj(dayElem.dateObj);
            if (serviceDateSet.has(iso)) {
              dayElem.classList.add('serenata-date-highlight-day');
            }
          }
        });

        const onManualDateInput = rawValue => {
          const value = String(rawValue || '').trim();
          if (!value) {
            selectedStart = null;
            selectedEnd = null;
            selectedDay = null;
            api.draw();
            return;
          }

          if (/^\d{1,2}$/.test(value)) {
            const day = Number(value);
            if (day >= 1 && day <= 31) {
              selectedStart = null;
              selectedEnd = null;
              selectedDay = day;
              api.draw();
              return;
            }
          }

          const normalized = value
            .replace(/\s+a\s+/i, ' to ')
            .replace(/\s*-\s*/g, ' to ')
            .replace(/\s+/g, ' ');

          if (normalized.includes(' to ')) {
            const [fromRaw, toRaw] = normalized.split(' to ');
            const fromIso = parseIsoFromHumanDate(fromRaw);
            const toIso = parseIsoFromHumanDate(toRaw);
            if (fromIso && toIso) {
              selectedStart = fromIso <= toIso ? fromIso : toIso;
              selectedEnd = fromIso <= toIso ? toIso : fromIso;
              selectedDay = null;
              api.draw();
              return;
            }
          }

          const singleIso = parseIsoFromHumanDate(value);
          if (singleIso) {
            selectedStart = singleIso;
            selectedEnd = null;
            selectedDay = null;
            api.draw();
          }
        };

        dateInput?.addEventListener('input', event => onManualDateInput(event.target.value));
        datePicker.altInput?.addEventListener('input', event => onManualDateInput(event.target.value));

        clearButton?.addEventListener('click', () => {
          selectedStart = null;
          selectedEnd = null;
          selectedDay = null;
          datePicker.clear();
          api.draw();
        });
      }

      createFilter(2, '.serenata_group', 'SerenataGroup', 'Filtrar por grupo');
      createFilter(4, '.serenata_payment', 'SerenataPayment', 'Filtrar por pago');
      createFilter(5, '.serenata_status', 'SerenataStatus', 'Filtrar por estado');
      createFilter(6, '.serenata_method', 'SerenataMethod', 'Filtrar por método');
    }
  });

  setTimeout(() => {
    const mappings = [
      { selector: '.dt-buttons .btn', remove: 'btn-secondary' },
      { selector: '.dt-search .form-control', remove: 'form-control-sm' },
      { selector: '.dt-length .form-select', remove: 'form-select-sm', add: 'ms-0' },
      { selector: '.dt-length', add: 'mb-md-6 mb-0' },
      {
        selector: '.dt-layout-end',
        remove: 'justify-content-between',
        add: 'd-flex gap-md-4 justify-content-md-between justify-content-center gap-2 flex-wrap'
      },
      { selector: '.dt-buttons', add: 'd-flex gap-2 mb-md-0 mb-4' },
      { selector: '.dt-layout-table', remove: 'row mt-2' },
      { selector: '.dt-layout-full', remove: 'col-md col-12', add: 'table-responsive' }
    ];

    mappings.forEach(({ selector, remove, add }) => {
      document.querySelectorAll(selector).forEach(element => {
        if (remove) remove.split(' ').forEach(className => element.classList.remove(className));
        if (add) add.split(' ').forEach(className => element.classList.add(className));
      });
    });
  }, 100);

  const updateTotal = () => {
    if (!totalPreview || !basePriceInput || !giftAmountInput || !discountAmountInput) return;
    const base = parseCopInput(basePriceInput.value || '0');
    const gift = parseCopInput(giftAmountInput.value || '0');
    const artist = parseCopInput(artistAmountInput?.value || '0');
    const otherCost = parseCopInput(otherCostAmountInput?.value || '0');
    const discount = parseCopInput(discountAmountInput.value || '0');
    const baseCost = Math.max(0, gift + artist + otherCost);
    const totalCharged = Math.max(0, base - discount);
    const profit = totalCharged - baseCost;
    const commissionRate = Math.max(0, Math.min(100, parseFloat(commissionRateInput?.value || '0') || 0));
    const commissionAmount = commissionRate > 0
      ? Math.max(0, profit * (commissionRate / 100))
      : Math.max(0, profit);

    if (baseCostPreview) {
      baseCostPreview.value = formatCopInteger(baseCost);
    }
    totalPreview.value = formatCopInteger(totalCharged);
    if (profitPreview) {
      profitPreview.value = formatCopInteger(profit);
    }
    if (commissionAmountPreview) {
      commissionAmountPreview.value = formatCopInteger(commissionAmount);
    }
  };

  const syncGiftItems = () => {
    if (!giftItemsList || !giftItemsInput || !giftAmountInput) return;

    const selectedItems = [];
    let giftTotal = 0;
    let includedTotal = 0;
    let additionalTotal = 0;

    giftItemsList.querySelectorAll('.gift-item-row').forEach(row => {
      const check = row.querySelector('.gift-item-check');
      const qtyInput = row.querySelector('.gift-item-qty');
      if (!check || !qtyInput) return;

      const isSelected = !!check.checked;
      qtyInput.disabled = !isSelected;
      if (!isSelected) return;

      const quantity = Math.max(1, parseInt(qtyInput.value || '1', 10) || 1);
      qtyInput.value = quantity.toString();

      const code = row.dataset.giftCode || '';
      const name = row.dataset.giftName || '';
      const unitPrice = parseFloat(row.dataset.giftPrice || '0') || 0;
      const giftType = String(row.dataset.giftType || 'included');
      const total = unitPrice * quantity;

      selectedItems.push({
        code,
        name,
        unit_price: unitPrice,
        quantity,
        total
      });
      giftTotal += total;
      if (giftType === 'additional') {
        additionalTotal += total;
      } else {
        includedTotal += total;
      }
    });

    giftItemsInput.value = JSON.stringify(selectedItems);
    giftAmountInput.value = String(Math.round(giftTotal));
    if (giftIncludedAmountPreview) {
      giftIncludedAmountPreview.value = formatCopInteger(includedTotal);
    }
    if (giftAdditionalAmountPreview) {
      giftAdditionalAmountPreview.value = formatCopInteger(additionalTotal);
    }
    giftAmountInput.dispatchEvent(new Event('input', { bubbles: true }));
  };

  const initGiftPagination = () => {
    if (!giftItemsList) return;

    const renderGiftPage = (panel, requestedPage) => {
      if (!panel) return;
      const list = panel.querySelector('.gift-tab-list');
      const pagination = panel.querySelector('.gift-pagination');
      if (!list || !pagination) return;

      const rows = Array.from(list.querySelectorAll('.gift-item-col'));
      const visibleRows = rows.filter(row => !row.classList.contains('gift-hidden-by-group'));
      const overflowPageSize = Math.max(1, parseInt(panel.dataset.pageSize || '3', 10) || 3);
      const firstPageSize = Math.max(overflowPageSize, parseInt(panel.dataset.firstPageSize || '9', 10) || 9);
      const totalPages = visibleRows.length <= firstPageSize
        ? 1
        : 1 + Math.ceil((visibleRows.length - firstPageSize) / overflowPageSize);
      const page = Math.min(totalPages, Math.max(1, requestedPage));
      const [start, end] = page === 1
        ? [0, firstPageSize]
        : [
            firstPageSize + ((page - 2) * overflowPageSize),
            firstPageSize + ((page - 1) * overflowPageSize)
          ];

      rows.forEach(row => {
        if (row.classList.contains('gift-hidden-by-group')) {
          row.classList.add('d-none');
        }
      });

      visibleRows.forEach((row, index) => {
        row.classList.toggle('d-none', index < start || index >= end);
      });

      panel.dataset.currentPage = String(page);

      const info = pagination.querySelector('.gift-page-info');
      const prevBtn = pagination.querySelector('.gift-page-prev');
      const nextBtn = pagination.querySelector('.gift-page-next');

      if (info) info.textContent = `${page} / ${totalPages}`;
      if (prevBtn) prevBtn.disabled = page <= 1;
      if (nextBtn) nextBtn.disabled = page >= totalPages;
      pagination.classList.toggle('d-none', visibleRows.length <= firstPageSize);
    };

    giftItemsList.querySelectorAll('.gift-tab-panel').forEach(panel => {
      renderGiftPage(panel, 1);

      const prevBtn = panel.querySelector('.gift-page-prev');
      const nextBtn = panel.querySelector('.gift-page-next');

      if (prevBtn && prevBtn.dataset.paginationBound !== '1') {
        prevBtn.dataset.paginationBound = '1';
        prevBtn.addEventListener('click', () => {
          const currentPage = parseInt(panel.dataset.currentPage || '1', 10) || 1;
          renderGiftPage(panel, currentPage - 1);
        });
      }

      if (nextBtn && nextBtn.dataset.paginationBound !== '1') {
        nextBtn.dataset.paginationBound = '1';
        nextBtn.addEventListener('click', () => {
          const currentPage = parseInt(panel.dataset.currentPage || '1', 10) || 1;
          renderGiftPage(panel, currentPage + 1);
        });
      }
    });
  };

  const syncArtistItems = () => {
    if (!artistItemsList || !artistItemsInput || !artistAmountInput) return;

    const selectedItems = [];
    let artistTotal = 0;

    artistItemsList.querySelectorAll('.artist-item-row').forEach(row => {
      const check = row.querySelector('.artist-item-check');
      const priceInput = row.querySelector('.artist-item-price');
      if (!check || !priceInput) return;

      const isSelected = !!check.checked;
      priceInput.disabled = !isSelected;
      if (!isSelected) return;

      const code = row.dataset.artistCode || '';
      const name = row.dataset.artistName || '';
      const unitPrice = Math.max(0, parseCopInput(priceInput.value || '0'));
      priceInput.value = formatCopInteger(unitPrice);

      selectedItems.push({
        code,
        name,
        unit_price: unitPrice
      });
      artistTotal += unitPrice;
    });

    artistItemsInput.value = JSON.stringify(selectedItems);
    artistAmountInput.value = formatCopInteger(artistTotal);
    artistAmountInput.dispatchEvent(new Event('input', { bubbles: true }));
  };

  const syncOtherCostItems = () => {
    if (!otherCostItemsList || !otherCostItemsInput || !otherCostAmountInput) return;

    const selectedItems = [];
    let otherCostTotal = 0;

    otherCostItemsList.querySelectorAll('.other-cost-item-row').forEach(row => {
      const check = row.querySelector('.other-cost-item-check');
      const priceInput = row.querySelector('.other-cost-item-price');
      if (!check || !priceInput) return;

      const isSelected = !!check.checked;
      priceInput.disabled = !isSelected;
      if (!isSelected) return;

      const code = row.dataset.otherCostCode || '';
      const name = row.dataset.otherCostName || '';
      const unitPrice = Math.max(0, parseCopInput(priceInput.value || '0'));
      priceInput.value = formatCopInteger(unitPrice);

      selectedItems.push({
        code,
        name,
        unit_price: unitPrice
      });
      otherCostTotal += unitPrice;
    });

    otherCostItemsInput.value = JSON.stringify(selectedItems);
    otherCostAmountInput.value = formatCopInteger(otherCostTotal);
    otherCostAmountInput.dispatchEvent(new Event('input', { bubbles: true }));
  };

  const filterPackageOptionsByCity = () => {
    if (!packageSelect || !localityInput) return;
    const normalizeCity = value =>
      String(value || '')
        .toLowerCase()
        .normalize('NFD')
        .replace(/[\u0300-\u036f]/g, '')
        .replace(/[^a-z0-9\s]/g, ' ')
        .replace(/\s+/g, ' ')
        .trim();

    const city = normalizeCity(localityInput.value || '');
    const cityTokens = city.split(' ').filter(Boolean);
    const selectedGroupId = String(mariachiGroupInput?.value || '').trim();
    let firstVisibleOption = null;

    Array.from(packageSelect.options).forEach(option => {
      if (!option.value) return;
      const optionCityRaw = (option.dataset.city || '*').trim();
      const optionCity = normalizeCity(optionCityRaw);
      const optionTokens = optionCity.split(' ').filter(Boolean);
      const optionGroupId = String(option.dataset.mariachiGroupId || '').trim();

      let visible = !selectedGroupId || !optionGroupId || optionGroupId === selectedGroupId;
      if (!visible) {
        option.hidden = true;
        option.disabled = true;
        return;
      }

      if (!city || optionCityRaw === '*' || optionCity === '*') {
        visible = true;
      } else if (optionCity === city) {
        visible = true;
      } else if (optionCity && city && (optionCity.includes(city) || city.includes(optionCity))) {
        visible = true;
      } else if (optionTokens.length && cityTokens.length) {
        const shared = optionTokens.filter(token => cityTokens.includes(token));
        visible = shared.length > 0;
      }

      option.hidden = !visible;
      option.disabled = !visible;
      if (visible && !firstVisibleOption) {
        firstVisibleOption = option;
      }
    });

    const selectedOption = packageSelect.options[packageSelect.selectedIndex];
    const selectedVisible = selectedOption && !selectedOption.hidden && !selectedOption.disabled;
    if (!selectedVisible && firstVisibleOption) {
      packageSelect.value = firstVisibleOption.value;
      packageSelect.dispatchEvent(new Event('change', { bubbles: true }));
    } else if (!selectedVisible) {
      packageSelect.value = '';
    }
  };

  const applyCatalogScopeByGroup = () => {
    const selectedGroupId = String(mariachiGroupInput?.value || '').trim();
    const toggleRows = rows => {
      rows.forEach(row => {
        const rowGroupId = String(row.dataset.mariachiGroupId || '').trim();
        const visible = !selectedGroupId || !rowGroupId || rowGroupId === selectedGroupId;
        const column = row.parentElement;

        if (column) {
          if (column.classList.contains('gift-item-col')) {
            column.classList.toggle('gift-hidden-by-group', !visible);
          }
          column.classList.toggle('d-none', !visible);
        }

        if (!visible) {
          row.querySelectorAll('input[type="checkbox"]').forEach(input => {
            input.checked = false;
          });
          row.querySelectorAll('input[type="number"]').forEach(input => {
            input.disabled = true;
          });
        }
      });
    };

    if (giftItemsList) {
      toggleRows(Array.from(giftItemsList.querySelectorAll('.gift-item-row')));
      initGiftPagination();
    }
    if (artistItemsList) {
      toggleRows(Array.from(artistItemsList.querySelectorAll('.artist-item-row')));
    }
    if (otherCostItemsList) {
      toggleRows(Array.from(otherCostItemsList.querySelectorAll('.other-cost-item-row')));
    }

    filterPackageOptionsByCity();
    syncGiftItems();
    syncArtistItems();
    syncOtherCostItems();
    updateTotal();
  };

  const applyDateAwareCatalogPrices = ({ giftOptions = [], artistOptions = [], otherCostOptions = [] } = {}) => {
    const giftByCode = new Map((giftOptions || []).map(item => [String(item.code || ''), Number(item.price || 0)]));
    const artistByCode = new Map((artistOptions || []).map(item => [String(item.code || ''), Number(item.price || 0)]));
    const otherCostByCode = new Map((otherCostOptions || []).map(item => [String(item.code || ''), Number(item.price || 0)]));

    if (giftItemsList) {
      giftItemsList.querySelectorAll('.gift-item-row').forEach(row => {
        const code = String(row.dataset.giftCode || '');
        if (!giftByCode.has(code)) return;
        const price = giftByCode.get(code);
        row.dataset.giftPrice = String(price);
        const priceLabel = row.querySelector('.form-check-label .text-body-secondary');
        if (priceLabel) {
          priceLabel.textContent = `($${Math.round(price).toLocaleString('es-CO')})`;
        }
      });
    }

    if (artistItemsList) {
      artistItemsList.querySelectorAll('.artist-item-row').forEach(row => {
        const code = String(row.dataset.artistCode || '');
        if (!artistByCode.has(code)) return;
        const price = artistByCode.get(code);
        const priceInput = row.querySelector('.artist-item-price');
        if (priceInput) {
          priceInput.value = formatCopInteger(Math.max(0, Math.round(price)));
        }
      });
    }

    if (otherCostItemsList) {
      otherCostItemsList.querySelectorAll('.other-cost-item-row').forEach(row => {
        const code = String(row.dataset.otherCostCode || '');
        if (!otherCostByCode.has(code)) return;
        const price = otherCostByCode.get(code);
        const priceInput = row.querySelector('.other-cost-item-price');
        if (priceInput) {
          priceInput.value = formatCopInteger(Math.max(0, Math.round(price)));
        }
      });
    }
  };

  const applyPackageTemplate = selected => {
    if (!selected) return;
    const selectedGroupId = String(mariachiGroupInput?.value || '').trim();

    if (basePriceInput) {
      basePriceInput.value = formatCopInteger(selected.base_price || 0);
      basePriceInput.dispatchEvent(new Event('input', { bubbles: true }));
    }

    const giftCodes = new Set(Array.isArray(selected.gift_codes) ? selected.gift_codes.map(String) : []);
    const artistCodes = new Set(Array.isArray(selected.artist_codes) ? selected.artist_codes.map(String) : []);
    const otherCostCodes = new Set(Array.isArray(selected.other_cost_codes) ? selected.other_cost_codes.map(String) : []);

    if (giftItemsList) {
      giftItemsList.querySelectorAll('.gift-item-row').forEach(row => {
        const rowGroupId = String(row.dataset.mariachiGroupId || '').trim();
        const code = String(row.dataset.giftCode || '');
        const check = row.querySelector('.gift-item-check');
        const qtyInput = row.querySelector('.gift-item-qty');
        if (!check) return;
        if (selectedGroupId && rowGroupId && rowGroupId !== selectedGroupId) {
          check.checked = false;
          return;
        }
        const enabled = giftCodes.has(code);
        check.checked = enabled;
        if (qtyInput && enabled) qtyInput.value = '1';
      });
    }
    if (artistItemsList) {
      artistItemsList.querySelectorAll('.artist-item-row').forEach(row => {
        const rowGroupId = String(row.dataset.mariachiGroupId || '').trim();
        const code = String(row.dataset.artistCode || '');
        const check = row.querySelector('.artist-item-check');
        if (!check) return;
        if (selectedGroupId && rowGroupId && rowGroupId !== selectedGroupId) {
          check.checked = false;
          return;
        }
        check.checked = artistCodes.has(code);
      });
    }
    if (otherCostItemsList) {
      otherCostItemsList.querySelectorAll('.other-cost-item-row').forEach(row => {
        const rowGroupId = String(row.dataset.mariachiGroupId || '').trim();
        const code = String(row.dataset.otherCostCode || '');
        const check = row.querySelector('.other-cost-item-check');
        if (!check) return;
        if (selectedGroupId && rowGroupId && rowGroupId !== selectedGroupId) {
          check.checked = false;
          return;
        }
        check.checked = otherCostCodes.has(code);
      });
    }

    syncGiftItems();
    syncArtistItems();
    syncOtherCostItems();
    updateTotal();
  };

  let packageTemplateAbortController = null;
  let lastPackageTemplateRequestId = 0;

  const refreshPackageTemplateByDate = async () => {
    if (!packageSelect) return;
    const packageId = packageSelect.value;
    if (!packageId) return;

    const fallbackTemplate = serenataPackages.find(item => String(item.id) === String(packageId));
    if (!packageTemplateUrl) {
      applyPackageTemplate(fallbackTemplate);
      return;
    }

    try {
      if (packageTemplateAbortController) {
        packageTemplateAbortController.abort();
      }
      packageTemplateAbortController = new AbortController();
      const currentRequestId = ++lastPackageTemplateRequestId;

      const query = new URLSearchParams({ package_id: String(packageId) });
      const eventDate = (eventDateInput?.value || '').trim();
      if (eventDate) {
        query.set('event_date', eventDate);
      }

      const response = await fetch(`${packageTemplateUrl}?${query.toString()}`, {
        method: 'GET',
        headers: { Accept: 'application/json' },
        signal: packageTemplateAbortController.signal
      });

      if (currentRequestId !== lastPackageTemplateRequestId) {
        return;
      }

      if (!response.ok) {
        applyPackageTemplate(fallbackTemplate);
        return;
      }

      const payload = await response.json();
      applyDateAwareCatalogPrices({
        giftOptions: payload.gift_options || [],
        artistOptions: payload.artist_options || [],
        otherCostOptions: payload.other_cost_options || []
      });
      applyPackageTemplate(payload.template || fallbackTemplate);
    } catch (error) {
      if (error?.name === 'AbortError') return;
      applyPackageTemplate(fallbackTemplate);
    }
  };

  if (giftItemsList) {
    giftItemsList.querySelectorAll('.gift-item-check, .gift-item-qty').forEach(control => {
      const eventName = control.classList.contains('gift-item-qty') ? 'input' : 'change';
      control.addEventListener(eventName, syncGiftItems);
    });
  }
  if (artistItemsList) {
    artistItemsList.querySelectorAll('.artist-item-check, .artist-item-price').forEach(control => {
      const eventName = control.classList.contains('artist-item-price') ? 'input' : 'change';
      control.addEventListener(eventName, syncArtistItems);
    });
  }
  if (otherCostItemsList) {
    otherCostItemsList.querySelectorAll('.other-cost-item-check, .other-cost-item-price').forEach(control => {
      const eventName = control.classList.contains('other-cost-item-price') ? 'input' : 'change';
      control.addEventListener(eventName, syncOtherCostItems);
    });
  }

  [giftAmountInput, artistAmountInput, otherCostAmountInput].forEach(input => {
    if (input) input.addEventListener('input', updateTotal);
  });
  [basePriceInput, discountAmountInput].forEach(input => {
    if (!input) return;
    input.addEventListener('input', () => {
      applyCopMask(input);
      updateTotal();
    });
    input.addEventListener('blur', () => {
      applyCopMask(input);
      updateTotal();
    });
  });
  [commissionRateInput].forEach(input => {
    if (input) input.addEventListener('input', updateTotal);
  });
  initGiftPagination();
  applyCatalogScopeByGroup();
  filterPackageOptionsByCity();
  syncGiftItems();
  syncArtistItems();
  syncOtherCostItems();
  updateTotal();

  if (localityInput) {
    localityInput.addEventListener('change', filterPackageOptionsByCity);
    localityInput.addEventListener('input', filterPackageOptionsByCity);
  }
  if (mariachiGroupInput) {
    mariachiGroupInput.addEventListener('change', applyCatalogScopeByGroup);
  }
  if (packageSelect) {
    packageSelect.addEventListener('change', refreshPackageTemplateByDate);
  }
  if (eventDateInput) {
    eventDateInput.addEventListener('change', refreshPackageTemplateByDate);
    eventDateInput.addEventListener('input', refreshPackageTemplateByDate);
  }
  if (packageSelect?.value) {
    refreshPackageTemplateByDate();
  }

  let serenataValidation = null;

  if (addSerenataForm && window.FormValidation) {
    serenataValidation = FormValidation.formValidation(addSerenataForm, {
      fields: {
        mariachi_group_id: { validators: { notEmpty: { message: 'Selecciona el grupo musical.' } } },
        payer_name: { validators: { notEmpty: { message: 'Ingresa quién realiza el pago.' } } },
        payer_email: { validators: { emailAddress: { message: 'Ingresa un correo válido.' } } },
        service_address: { validators: { notEmpty: { message: 'Ingresa la dirección.' } } },
        service_country: { validators: { notEmpty: { message: 'Ingresa el país.' } } },
        service_department: { validators: { stringLength: { max: 120, message: 'Máximo 120 caracteres.' } } },
        neighborhood: { validators: { notEmpty: { message: 'Ingresa el barrio.' } } },
        locality: { validators: { notEmpty: { message: 'Selecciona la ciudad o municipio.' } } },
        service_locality: { validators: { stringLength: { max: 120, message: 'Máximo 120 caracteres.' } } },
        service_sector: { validators: { stringLength: { max: 120, message: 'Máximo 120 caracteres.' } } },
        contact_phone_1_display: { validators: { notEmpty: { message: 'Ingresa al menos un contacto.' } } },
        reason: { validators: { notEmpty: { message: 'Ingresa el motivo.' } } },
        from_name: { validators: { notEmpty: { message: 'Ingresa de parte de quién.' } } },
        to_name: { validators: { notEmpty: { message: 'Ingresa para quién.' } } },
        event_date: { validators: { notEmpty: { message: 'Selecciona la fecha.' } } },
        event_time: { validators: { notEmpty: { message: 'Selecciona la hora base.' } } },
        event_time_display: { validators: { notEmpty: { message: 'Selecciona la franja de llegada.' } } },
        base_price: { validators: { notEmpty: { message: 'Ingresa precio base.' } } },
        cancellation_reason: {
          validators: {
            callback: {
              message: 'Ingresa el motivo de cancelación.',
              callback: function (input) {
                const status = (serviceStatusInput?.value || '').trim();
                if (status !== 'Cancelada') return true;
                return (input.value || '').trim().length > 0;
              }
            }
          }
        },
        address_additional: { validators: { stringLength: { max: 255, message: 'Máximo 255 caracteres.' } } },
        internal_notes: { validators: { stringLength: { max: 1200, message: 'Máximo 1200 caracteres.' } } },
        client_notes: { validators: { stringLength: { max: 1200, message: 'Máximo 1200 caracteres.' } } }
      },
      plugins: {
        trigger: new FormValidation.plugins.Trigger(),
        bootstrap5: new FormValidation.plugins.Bootstrap5({
          eleValidClass: '',
          rowSelector: '.form-control-validation'
        }),
        submitButton: new FormValidation.plugins.SubmitButton(),
        autoFocus: new FormValidation.plugins.AutoFocus()
      }
    });

    serenataValidation.on('core.form.valid', () => {
      syncAllPhoneFields();
      if (lockSerenataSubmit()) {
        addSerenataForm.submit();
      }
    });

    serenataValidation.on('core.form.invalid', () => {
      unlockSerenataSubmit();
    });
  }

  if (addSerenataForm) {
    addSerenataForm.addEventListener('submit', () => {
      syncAllPhoneFields();
    });
  }

  const lockSerenataSubmit = () => {
    if (!addSerenataForm || addSerenataForm.dataset.submitting === '1') {
      return false;
    }

    addSerenataForm.dataset.submitting = '1';

    addSerenataForm.querySelectorAll('button[type="submit"], input[type="submit"]').forEach(button => {
      if (button.dataset.originalLabel === undefined) {
        button.dataset.originalLabel = button.innerHTML || button.value || '';
      }

      button.disabled = true;
      button.classList.add('disabled');

      if (button.tagName === 'BUTTON') {
        button.innerHTML = 'Guardando...';
      } else if (button.tagName === 'INPUT') {
        button.value = 'Guardando...';
      }
    });

    return true;
  };

  const unlockSerenataSubmit = () => {
    if (!addSerenataForm) {
      return;
    }

    delete addSerenataForm.dataset.submitting;

    addSerenataForm.querySelectorAll('button[type="submit"], input[type="submit"]').forEach(button => {
      button.disabled = false;
      button.classList.remove('disabled');

      if (button.dataset.originalLabel !== undefined) {
        if (button.tagName === 'BUTTON') {
          button.innerHTML = button.dataset.originalLabel;
        } else if (button.tagName === 'INPUT') {
          button.value = button.dataset.originalLabel;
        }
      }
    });
  };

  if (addSerenataForm) {
    addSerenataForm.addEventListener('submit', event => {
      syncAllPhoneFields();
      if (!lockSerenataSubmit()) {
        event.preventDefault();
      }
    });

    addSerenataForm.addEventListener('reset', () => {
      window.setTimeout(() => {
        resetPhoneFields();
        unlockSerenataSubmit();
      }, 0);
    });

    window.addEventListener('pageshow', () => {
      unlockSerenataSubmit();
    });
  }

  const updateServiceStatusFields = () => {
    const status = (serviceStatusInput?.value || '').trim();
    const isCancelled = status === 'Cancelada';

    if (cancellationReasonWrap) {
      cancellationReasonWrap.classList.toggle('d-none', !isCancelled);
    }
    if (cancellationReasonInput) {
      cancellationReasonInput.required = isCancelled;
      if (!isCancelled) cancellationReasonInput.value = '';
    }

  };

  if (serviceStatusInput) {
    serviceStatusInput.addEventListener('change', updateServiceStatusFields);
    updateServiceStatusFields();
  }

  if (window.__openAddSerenata && serenataModalElement) {
    bootstrap.Modal.getOrCreateInstance(serenataModalElement).show();
  }

  const openIaOverlay = () => {
    if (!iaAssistOverlay) return;
    iaAssistOverlay.classList.remove('d-none');
  };

  const closeIaOverlay = () => {
    if (!iaAssistOverlay) return;
    iaAssistOverlay.classList.add('d-none');
  };

  if (btnOpenIaAssist) {
    btnOpenIaAssist.addEventListener('click', openIaOverlay);
  }
  if (btnCloseIaAssistOverlay) {
    btnCloseIaAssistOverlay.addEventListener('click', closeIaOverlay);
  }
  if (btnCancelIaAssistOverlay) {
    btnCancelIaAssistOverlay.addEventListener('click', closeIaOverlay);
  }

  const setFieldValue = (id, value) => {
    if (!value) return;
    if (phoneFieldIds.includes(id)) {
      setPhoneFieldValue(id, value);
      return;
    }
    const input = document.getElementById(id);
    if (!input) return;
    const normalizedValue = id === 'event_time'
      ? normalizeEventTimeValue(value)
      : value.trim();
    if (input.tagName === 'SELECT') {
      const existingOption = Array.from(input.options || []).find(option => (option.value || '').trim() === normalizedValue);
      if (!existingOption) {
        const option = new Option(normalizedValue, normalizedValue, true, true);
        input.appendChild(option);
      }
      input.value = normalizedValue;
    } else {
      input.value = normalizedValue;
    }
    input.dispatchEvent(new Event('input', { bubbles: true }));
    input.dispatchEvent(new Event('change', { bubbles: true }));
  };

  phoneFieldIds.forEach(initializePhoneField);

  const setLocalityFromMaps = value => {
    if (!value) return;
    setFieldValue('locality', toTitleCase(value));
  };

  const setCountryFromMaps = value => {
    if (!value) return;
    setFieldValue('service_country', toTitleCase(value));
  };

  const setDepartmentFromMaps = value => {
    if (!value) return;
    setFieldValue('service_department', toTitleCase(value));
  };

  const setServiceLocalityFromMaps = value => {
    if (!value) return;
    setFieldValue('service_locality', toTitleCase(value));
  };

  const setServiceSectorFromMaps = value => {
    if (!value) return;
    setFieldValue('service_sector', toTitleCase(value));
  };

  const setNeighborhoodFromMaps = value => {
    if (!value) return;
    setFieldValue('neighborhood', toTitleCase(value));
  };

  const localityCatalog = new Set();
  const neighborhoodCatalog = new Set();

  const addLocationOption = (type, value) => {
    const normalized = toTitleCase(String(value || '').trim());
    if (!normalized) return;
    if (type === 'locality') {
      localityCatalog.add(normalized);
      return;
    }
    neighborhoodCatalog.add(normalized);
  };

  const renderLocationDatalists = () => {
    const renderSelect = (selectElement, sourceSet) => {
      if (!selectElement || selectElement.tagName !== 'SELECT') return;
      const selectedValue = String(selectElement.value || '').trim();
      const values = [...sourceSet].sort((a, b) => a.localeCompare(b, 'es', { sensitivity: 'base' }));
      const uniqueValues = [...new Set(values)];
      const hasBlank = Array.from(selectElement.options).some(option => option.value === '');
      selectElement.innerHTML = hasBlank ? '<option value=""></option>' : '';
      uniqueValues.forEach(value => {
        selectElement.appendChild(new Option(value, value, false, value === selectedValue));
      });
      if (selectedValue && !uniqueValues.includes(selectedValue)) {
        selectElement.appendChild(new Option(selectedValue, selectedValue, true, true));
      } else if (selectedValue) {
        selectElement.value = selectedValue;
      }
      if (window.jQuery && window.jQuery.fn && window.jQuery.fn.select2 && window.jQuery(selectElement).data('select2')) {
        window.jQuery(selectElement).trigger('change.select2');
      }
    };

    renderSelect(localityInput, localityCatalog);
    renderSelect(neighborhoodInput, neighborhoodCatalog);

    if (localityOptionsList) {
      localityOptionsList.innerHTML = '';
      [...localityCatalog]
        .sort((a, b) => a.localeCompare(b, 'es', { sensitivity: 'base' }))
        .forEach(value => {
          const option = document.createElement('option');
          option.value = value;
          localityOptionsList.appendChild(option);
        });
    }

    if (neighborhoodOptionsList) {
      neighborhoodOptionsList.innerHTML = '';
      [...neighborhoodCatalog]
        .sort((a, b) => a.localeCompare(b, 'es', { sensitivity: 'base' }))
        .forEach(value => {
          const option = document.createElement('option');
          option.value = value;
          neighborhoodOptionsList.appendChild(option);
        });
    }
  };

  const locationFieldMap = {
    locality: { input: localityInput, button: btnEditLocality },
    neighborhood: { input: neighborhoodInput, button: btnEditNeighborhood },
    service_country: { input: serviceCountryInput, button: btnEditServiceCountry },
    service_department: { input: serviceDepartmentInput, button: btnEditServiceDepartment },
    service_locality: { input: serviceLocalityInput, button: btnEditServiceLocality },
    service_sector: { input: serviceSectorInput, button: btnEditServiceSector }
  };

  const lockLocationField = (input, button, locked) => {
    if (!input || !button) return;
    input.dataset.locked = locked ? '1' : '0';
    input.classList.toggle('location-locked', locked);
    if (input.tagName !== 'SELECT') {
      input.readOnly = locked;
    }
    button.classList.toggle('btn-outline-secondary', locked);
    button.classList.toggle('btn-outline-primary', !locked);
    button.innerHTML = locked
      ? '<i class="icon-base ti tabler-lock"></i>'
      : '<i class="icon-base ti tabler-lock-open"></i>';
    button.title = locked ? 'Desbloquear edición' : 'Bloquear campo';
  };

  const lockLocationFields = () => {
    Object.values(locationFieldMap).forEach(({ input, button }) => {
      lockLocationField(input, button, true);
    });
  };

  const toggleLocationField = (type) => {
    const field = locationFieldMap[type];
    const input = field?.input;
    const button = field?.button;
    if (!input || !button) return;
    const isLocked = input.dataset.locked === '1';
    lockLocationField(input, button, !isLocked);
    if (!isLocked) return;
    input?.focus();
  };

  const normalizeLookup = value =>
    String(value || '')
      .toLowerCase()
      .normalize('NFD')
      .replace(/[\u0300-\u036f]/g, '')
      .replace(/[^a-z0-9\s]/g, ' ')
      .replace(/\s+/g, ' ')
      .trim();

  const getCatalogValues = type => {
    const source = type === 'locality' ? [...localityCatalog] : [...neighborhoodCatalog];
    return source.sort((a, b) => a.localeCompare(b, 'es', { sensitivity: 'base' }));
  };

  const setupLocationAutocomplete = (input, type) => {
    if (!input) return;
    if (input.tagName !== 'INPUT') return;
    const wrapper = input.closest('.input-group');
    if (!wrapper) return;
    wrapper.classList.add('position-relative');

    const suggestionBox = document.createElement('div');
    suggestionBox.className = 'gm-address-suggestions d-none';
    wrapper.appendChild(suggestionBox);

    let hideTimer = null;

    const renderSuggestions = queryRaw => {
      const query = normalizeLookup(queryRaw);
      const all = getCatalogValues(type);
      let matches = all;
      if (query) {
        const starts = all.filter(value => normalizeLookup(value).startsWith(query));
        const includes = all.filter(value => !normalizeLookup(value).startsWith(query) && normalizeLookup(value).includes(query));
        matches = [...starts, ...includes];
      }

      suggestionBox.innerHTML = '';
      if (!matches.length) {
        suggestionBox.classList.add('d-none');
        return;
      }

      matches.slice(0, 12).forEach(value => {
        const item = document.createElement('div');
        item.className = 'gm-address-suggestion-item';
        item.textContent = value;
        item.addEventListener('mousedown', event => {
          event.preventDefault();
          input.value = value;
          input.dispatchEvent(new Event('input', { bubbles: true }));
          input.dispatchEvent(new Event('change', { bubbles: true }));
          suggestionBox.classList.add('d-none');
        });
        suggestionBox.appendChild(item);
      });

      suggestionBox.classList.remove('d-none');
    };

    input.addEventListener('focus', () => {
      if (input.readOnly) return;
      renderSuggestions(input.value || '');
    });

    input.addEventListener('input', () => {
      if (input.readOnly) return;
      renderSuggestions(input.value || '');
    });

    input.addEventListener('blur', () => {
      hideTimer = window.setTimeout(() => {
        suggestionBox.classList.add('d-none');
      }, 120);
    });

    input.addEventListener('keydown', event => {
      if (event.key === 'Escape') {
        suggestionBox.classList.add('d-none');
      }
    });

    wrapper.addEventListener('mousedown', () => {
      if (hideTimer) {
        clearTimeout(hideTimer);
        hideTimer = null;
      }
    });
  };

  const setPackageFromText = value => {
    if (!packageSelect || !value) return false;
    const needle = String(value).trim().toLowerCase();
    if (!needle) return false;
    const normalizePackageLookup = text =>
      String(text || '')
        .toLowerCase()
        .normalize('NFD')
        .replace(/[\u0300-\u036f]/g, '')
        .replace(/[^a-z0-9]+/g, ' ')
        .replace(/\s+/g, ' ')
        .trim();
    const normalizedNeedle = normalizePackageLookup(needle);
    const needleDigits = (needle.match(/\d+/g) || []).join('');

    const options = Array.from(packageSelect.options).filter(option => !!option.value && !option.hidden && !option.disabled);
    const selectedOption = options.find(option => {
      const text = (option.textContent || '').toLowerCase();
      const normalizedText = normalizePackageLookup(text);
      if (text.includes(needle) || normalizedText.includes(normalizedNeedle)) {
        return true;
      }

      if (!needleDigits) return false;

      const optionDigits = ((option.textContent || '').match(/\d+/g) || []).join('');
      return optionDigits !== '' && optionDigits === needleDigits;
    }) || options.find(option => {
      if (!needleDigits) return false;
      const optionDigits = ((option.textContent || '').match(/\d+/g) || []).join('');
      return optionDigits !== '' && optionDigits === needleDigits;
    });

    if (!selectedOption) return false;

    packageSelect.value = selectedOption.value;
    packageSelect.dispatchEvent(new Event('change', { bubbles: true }));
    return true;
  };

  const applyResolvedAddressFields = fields => {
    if (!fields || typeof fields !== 'object') return;

    if (fields.service_country) setCountryFromMaps(fields.service_country);
    if (fields.service_department) setDepartmentFromMaps(fields.service_department);
    if (fields.locality) {
      const normalizedCity = toTitleCase(fields.locality);
      setLocalityFromMaps(normalizedCity);
      addLocationOption('locality', normalizedCity);
    }
    if (fields.service_locality) setServiceLocalityFromMaps(fields.service_locality);
    if (fields.service_sector) setServiceSectorFromMaps(fields.service_sector);
    if (fields.neighborhood) {
      const normalizedNeighborhood = toTitleCase(fields.neighborhood);
      setNeighborhoodFromMaps(normalizedNeighborhood);
      addLocationOption('neighborhood', normalizedNeighborhood);
    }
    renderLocationDatalists();
    lockLocationFields();
    updateServiceAddressMapsState();
  };

  const initGoogleAddressAutocomplete = async () => {
    const addressInput = serviceAddressInput;
    if (!addressInput || !addressSuggestionsUrl) {
      return false;
    }

    const wrapper = addressInput.parentElement;
    if (!wrapper) return false;

    let suggestionsBox = wrapper.querySelector('.gm-address-suggestions');
    if (!suggestionsBox) {
      suggestionsBox = document.createElement('div');
      suggestionsBox.className = 'gm-address-suggestions d-none';
      wrapper.appendChild(suggestionsBox);
    }

    const hideSuggestions = () => {
      suggestionsBox.classList.add('d-none');
      suggestionsBox.innerHTML = '';
    };

    const renderSuggestions = suggestions => {
      if (!Array.isArray(suggestions) || !suggestions.length) {
        hideSuggestions();
        return;
      }

      suggestionsBox.innerHTML = '';

      suggestions.forEach(suggestion => {
        const item = document.createElement('div');
        item.className = 'gm-address-suggestion-item';
        item.textContent = suggestion.description;
        item.dataset.placeId = suggestion.place_id || '';

        item.addEventListener('click', async () => {
          setFieldValue('service_address', suggestion.description);
          currentServiceAddressPlaceId = suggestion.place_id || '';
          hideSuggestions();

          if (!addressResolveUrl) return;
          try {
            const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
            const response = await fetch(addressResolveUrl, {
              method: 'POST',
              headers: {
                'Content-Type': 'application/json',
                Accept: 'application/json',
                'X-Requested-With': 'XMLHttpRequest',
                'X-CSRF-TOKEN': csrfToken
              },
              body: JSON.stringify({
                service_address: suggestion.description,
                address_additional: document.getElementById('address_additional')?.value || '',
                place_id: currentServiceAddressPlaceId,
                service_country: serviceCountryInput?.value || '',
                service_department: serviceDepartmentInput?.value || '',
                neighborhood: '',
                locality: localityInput?.value || '',
                service_locality: serviceLocalityInput?.value || '',
                service_sector: serviceSectorInput?.value || ''
              })
            });
            const data = await response.json();
            if (!response.ok || !data?.fields) return;
            setFieldValue('service_address', data.fields.service_address || suggestion.description);
            applyResolvedAddressFields(data.fields);
            updateServiceAddressMapsState(data.google_maps_url || '');
          } catch (error) {
            // no-op silencioso
          }
        });

        suggestionsBox.appendChild(item);
      });

      suggestionsBox.classList.remove('d-none');
    };

    let debounceTimer = null;

    addressInput.addEventListener('input', () => {
      const query = (addressInput.value || '').trim();

      if (debounceTimer) clearTimeout(debounceTimer);
      if (query.length < 3) {
        hideSuggestions();
        return;
      }

      debounceTimer = setTimeout(async () => {
        try {
          const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
          const response = await fetch(addressSuggestionsUrl, {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
              Accept: 'application/json',
              'X-Requested-With': 'XMLHttpRequest',
              'X-CSRF-TOKEN': csrfToken
            },
            body: JSON.stringify({
              query,
              country: googleMapsCountry
            })
          });
          const data = await response.json();
          if (!response.ok) {
            hideSuggestions();
            return;
          }
          renderSuggestions(data.suggestions || []);
        } catch (error) {
          hideSuggestions();
        }
      }, 220);
    });

    addressInput.addEventListener('blur', () => {
      setTimeout(hideSuggestions, 150);
    });

    addressInput.addEventListener('focus', () => {
      if (suggestionsBox.innerHTML.trim()) {
        suggestionsBox.classList.remove('d-none');
      }
    });

    document.addEventListener('click', event => {
      if (!wrapper.contains(event.target)) {
        hideSuggestions();
      }
    });

    return true;
  };

  if (addressSuggestionsUrl) {
    initGoogleAddressAutocomplete();
  }

  if (eventTimeInput && eventTimeDisplayInput && eventTimeModeInput) {
    const pad2 = value => String(value).padStart(2, '0');
    const formatHourLabel = hour => {
      const normalized = ((Number(hour) % 24) + 24) % 24;
      const suffix = normalized >= 12 ? 'PM' : 'AM';
      const hour12 = normalized % 12 || 12;
      return `${pad2(hour12)}:00 ${suffix}`;
    };
    const getMode = () => (eventTimeModeInput.value === 'before' ? 'before' : 'after');
    const getStoredStartHour = () => {
      const match = String(eventTimeInput.value || '').trim().match(/^(\d{1,2}):(\d{2})$/);
      if (!match) return 20;
      return Math.max(0, Math.min(23, Number(match[1]) || 20));
    };
    const getReferenceHour = () => {
      const startHour = getStoredStartHour();
      return getMode() === 'before' ? (startHour + 1) % 24 : startHour;
    };
    const getWindowRange = (referenceHour = getReferenceHour(), mode = getMode()) => {
      const normalizedRef = ((Number(referenceHour) % 24) + 24) % 24;
      const startHour = mode === 'before' ? (normalizedRef + 23) % 24 : normalizedRef;
      const endHour = (startHour + 1) % 24;

      return {
        startHour,
        endHour,
        referenceHour: normalizedRef,
        startValue: `${pad2(startHour)}:00`,
        endValue: `${pad2(endHour)}:00`,
        windowLabel: `${formatHourLabel(startHour)} - ${formatHourLabel(endHour)}`,
        referenceLabel: formatHourLabel(normalizedRef),
        meridiem: normalizedRef >= 12 ? 'PM' : 'AM'
      };
    };
    const renderModeButtons = mode => {
      [...eventTimeModeButtons, ...modalEventTimeModeButtons].forEach(button => {
        button.classList.toggle('active', button.dataset.mode === mode);
      });
    };
    const renderClockState = () => {
      const range = getWindowRange();
      if (eventTimeWindowPreview) {
        eventTimeWindowPreview.textContent = range.windowLabel;
      }
      if (eventTimeDisplayInput) {
        eventTimeDisplayInput.value = range.windowLabel;
      }
      if (modalEventTimeWindowPreview) {
        modalEventTimeWindowPreview.textContent = range.windowLabel;
      }
      if (modalEventTimeReferenceLabel) {
        modalEventTimeReferenceLabel.textContent = range.referenceLabel;
      }
      if (modalEventTimeMeridiem) {
        modalEventTimeMeridiem.textContent = range.meridiem;
      }
      if (modalEventTimeCenterLabel) {
        modalEventTimeCenterLabel.textContent = range.referenceLabel;
      }
      modalEventTimeHourButtons.forEach(button => {
        button.classList.toggle('active', Number(button.dataset.hour) === range.referenceHour);
      });
      renderModeButtons(getMode());
    };
    const setReferenceHour = hour => {
      const range = getWindowRange(hour, getMode());
      eventTimeInput.value = range.startValue;
      eventTimeInput.dispatchEvent(new Event('input', { bubbles: true }));
      eventTimeInput.dispatchEvent(new Event('change', { bubbles: true }));
      renderClockState();
    };
    const setMode = mode => {
      const currentReferenceHour = getReferenceHour();
      eventTimeModeInput.value = mode === 'before' ? 'before' : 'after';
      setReferenceHour(currentReferenceHour);
    };
    const parseManualReferenceHour = value => {
      const textValue = String(value || '').trim().toLowerCase();
      if (!textValue) return null;
      if (textValue.includes('-')) return null;

      const match = textValue.match(/\b(\d{1,2})(?::(\d{2}))?\s*(am|pm)?\b/);
      if (!match) return null;

      let hour = Number(match[1] || 0);
      const meridiem = (match[3] || '').toLowerCase();
      if (meridiem === 'pm' && hour < 12) hour += 12;
      if (meridiem === 'am' && hour === 12) hour = 0;
      if (!meridiem && hour === 24) hour = 0;
      if (hour < 0 || hour > 23) return null;

      return hour;
    };

    syncEventTimeWindowField = () => {
      renderClockState();
    };

    eventTimeInput.addEventListener('input', syncEventTimeWindowField);
    eventTimeInput.addEventListener('change', syncEventTimeWindowField);

    eventTimeModeButtons.forEach(button => {
      button.addEventListener('click', event => {
        event.preventDefault();
        setMode(button.dataset.mode || 'after');
      });
    });

    modalEventTimeModeButtons.forEach(button => {
      button.addEventListener('click', event => {
        event.preventDefault();
        setMode(button.dataset.mode || 'after');
      });
    });

    modalEventTimeHourButtons.forEach(button => {
      button.addEventListener('click', event => {
        event.preventDefault();
        setReferenceHour(Number(button.dataset.hour || 20));
      });
    });

    eventTimeDisplayInput.addEventListener('blur', () => {
      const parsedHour = parseManualReferenceHour(eventTimeDisplayInput.value);
      if (parsedHour === null) {
        renderClockState();
        return;
      }
      setReferenceHour(parsedHour);
    });

    eventTimeDisplayInput.addEventListener('focus', () => {
      eventTimeDisplayInput.select();
    });

    if (btnEventTimePicker && modalEventTimeWindowElement && window.bootstrap?.Modal) {
      btnEventTimePicker.addEventListener('click', event => {
        event.preventDefault();
        renderClockState();
        window.bootstrap.Modal.getOrCreateInstance(modalEventTimeWindowElement).show();
      });
    }

    if (btnApplyEventTimeWindow && modalEventTimeWindowElement && window.bootstrap?.Modal) {
      btnApplyEventTimeWindow.addEventListener('click', event => {
        event.preventDefault();
        renderClockState();
        window.bootstrap.Modal.getOrCreateInstance(modalEventTimeWindowElement).hide();
      });
    }

    if (!String(eventTimeInput.value || '').trim()) {
      eventTimeModeInput.value = 'after';
      eventTimeInput.value = '20:00';
    }

    renderClockState();
  }

  const setupLocationSelectors = async () => {
    const normalizeList = values =>
      [...new Set((Array.isArray(values) ? values : []).map(value => toTitleCase(String(value || '').trim())).filter(Boolean))].sort((a, b) =>
        a.localeCompare(b, 'es', { sensitivity: 'base' })
      );

    normalizeList(locationOptionsSeed.localities || []).forEach(value => addLocationOption('locality', value));
    normalizeList(locationOptionsSeed.neighborhoods || []).forEach(value => addLocationOption('neighborhood', value));

    if (locationOptionsUrl) {
      try {
        const response = await fetch(locationOptionsUrl, { headers: { Accept: 'application/json' } });
        const data = await response.json();
        if (response.ok) {
          normalizeList(data.localities || []).forEach(value => addLocationOption('locality', value));
          normalizeList(data.neighborhoods || []).forEach(value => addLocationOption('neighborhood', value));
        }
      } catch (error) {
        // no-op
      }
    }

    if (localityInput?.value) addLocationOption('locality', localityInput.value);
    if (neighborhoodInput?.value) addLocationOption('neighborhood', neighborhoodInput.value);

    renderLocationDatalists();
  };

  const initLocationSelect2 = () => {
    if (!window.jQuery || !window.jQuery.fn || !window.jQuery.fn.select2) return;
    const $ = window.jQuery;
    const dropdownParent = serenataModalElement ? $(serenataModalElement) : $(document.body);

    const setupSelect = element => {
      if (!element || element.tagName !== 'SELECT') return;
      const $element = $(element);
      if ($element.data('select2')) {
        $element.select2('destroy');
      }
      $element.select2({
        dropdownParent,
        width: '100%',
        tags: true,
        placeholder: element.dataset.placeholder || 'Selecciona o escribe',
        allowClear: true,
        minimumInputLength: 0,
        createTag(params) {
          const term = String(params.term || '').trim();
          if (!term) return null;
          return { id: toTitleCase(term), text: toTitleCase(term), newTag: true };
        },
        sorter(data) {
          return data;
        }
      });

      const select2Instance = $element.data('select2');
      if (select2Instance && select2Instance.$container) {
        select2Instance.$container.addClass('location-select2');
      }

      $element.on('select2:select', event => {
        const selected = toTitleCase(event.params?.data?.id || event.params?.data?.text || '');
        if (!selected) return;
        if (element.id === 'locality') {
          addLocationOption('locality', selected);
        } else if (element.id === 'neighborhood') {
          addLocationOption('neighborhood', selected);
        }
      });

      $element.on('select2:opening', event => {
        if (element.dataset.locked === '1') {
          event.preventDefault();
        }
      });

      $element.on('select2:selecting', event => {
        if (element.dataset.locked === '1') {
          event.preventDefault();
        }
      });
    };

    setupSelect(localityInput);
    setupSelect(neighborhoodInput);
  };

  setupLocationSelectors();
  initLocationSelect2();
  setupLocationAutocomplete(localityInput, 'locality');
  setupLocationAutocomplete(neighborhoodInput, 'neighborhood');

  if (serviceCountryInput && !String(serviceCountryInput.value || '').trim()) {
    serviceCountryInput.value = 'Colombia';
  }

  [
    serviceAddressInput,
    neighborhoodInput,
    localityInput,
    serviceCountryInput,
    serviceDepartmentInput,
    serviceLocalityInput,
    serviceSectorInput
  ].forEach(input => {
    if (!input) return;

    const syncMapsState = () => {
      currentServiceAddressMapsUrl = '';
      if (input === serviceAddressInput) {
        currentServiceAddressPlaceId = '';
      }
      updateServiceAddressMapsState();
    };

    input.addEventListener('input', syncMapsState);
    input.addEventListener('change', syncMapsState);
  });

  updateServiceAddressMapsState();

  lockLocationFields();

  if (btnEditLocality) {
    btnEditLocality.addEventListener('click', event => {
      event.preventDefault();
      toggleLocationField('locality');
    });
  }

  if (btnEditNeighborhood) {
    btnEditNeighborhood.addEventListener('click', event => {
      event.preventDefault();
      toggleLocationField('neighborhood');
    });
  }

  if (btnEditServiceCountry) {
    btnEditServiceCountry.addEventListener('click', event => {
      event.preventDefault();
      toggleLocationField('service_country');
    });
  }

  if (btnEditServiceDepartment) {
    btnEditServiceDepartment.addEventListener('click', event => {
      event.preventDefault();
      toggleLocationField('service_department');
    });
  }

  if (btnEditServiceLocality) {
    btnEditServiceLocality.addEventListener('click', event => {
      event.preventDefault();
      toggleLocationField('service_locality');
    });
  }

  if (btnEditServiceSector) {
    btnEditServiceSector.addEventListener('click', event => {
      event.preventDefault();
      toggleLocationField('service_sector');
    });
  }

  if (btnOpenServiceAddressMap) {
    btnOpenServiceAddressMap.addEventListener('click', async event => {
      event.preventDefault();
      const csrfToken =
        document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') ||
        addSerenataForm?.querySelector('input[name="_token"]')?.value ||
        '';

      const resolved = await resolveServiceAddressForMaps(csrfToken);
      const targetUrl = (resolved?.google_maps_url || '').trim() || currentServiceAddressMapsUrl || buildServiceAddressMapsUrl();
      if (!targetUrl) {
        notyf.open({ type: 'warning', message: 'Completa la dirección para abrir Google Maps.' });
        return;
      }
      window.open(targetUrl, '_blank', 'noopener,noreferrer');
    });
  }

  if (iaApplyButton && iaRawText) {
    iaApplyButton.addEventListener('click', async () => {
      const raw = iaRawText.value || '';
      const csrfToken =
        document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') ||
        addSerenataForm?.querySelector('input[name="_token"]')?.value ||
        '';

      const applyParsedFields = fields => {
        if (!fields || typeof fields !== 'object') return 0;
        let count = 0;
        const map = [
          'payer_name',
          'package_name',
          'payer_email',
          'service_address',
          'address_additional',
          'service_country',
          'service_department',
          'neighborhood',
          'service_locality',
          'service_sector',
          'contact_phone_1',
          'contact_phone_2',
          'reason',
          'from_name',
          'to_name',
          'event_date',
          'event_time',
          'internal_notes',
          'client_notes',
          'payment_method'
        ];

        map.forEach(key => {
          const value = (fields[key] || '').toString().trim();
          if (!value) return;
          if (key === 'package_name') {
            if (setPackageFromText(value)) {
              count += 1;
            }
            return;
          }
          setFieldValue(key, value);
          count += 1;
        });

        const localityValue = (fields.locality || '').toString().trim();
        if (localityValue) {
          setLocalityFromMaps(localityValue);
          count += 1;
        }

        return count;
      };

      const getFieldCurrentValue = fieldId => {
        const input = document.getElementById(fieldId);
        if (!input) return '';
        return String(input.value || '').trim();
      };

      const applyValueIfEmpty = (fieldId, value) => {
        if (getFieldCurrentValue(fieldId)) return false;
        return applyValue(fieldId, value);
      };

      const resolveAddressWithGoogle = async () => {
        if (!addressResolveUrl) return 0;

        const payload = {
          service_address: (document.getElementById('service_address')?.value || '').trim(),
          address_additional: (document.getElementById('address_additional')?.value || '').trim(),
          place_id: currentServiceAddressPlaceId,
          service_country: (document.getElementById('service_country')?.value || '').trim(),
          service_department: (document.getElementById('service_department')?.value || '').trim(),
          neighborhood: (document.getElementById('neighborhood')?.value || '').trim(),
          locality: (document.getElementById('locality')?.value || '').trim(),
          service_locality: (document.getElementById('service_locality')?.value || '').trim(),
          service_sector: (document.getElementById('service_sector')?.value || '').trim()
        };

        if (!payload.service_address && !payload.locality && !payload.neighborhood) return 0;

        try {
          const response = await fetch(addressResolveUrl, {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
              'Accept': 'application/json',
              'X-CSRF-TOKEN': csrfToken
            },
            body: JSON.stringify(payload)
          });
          const data = await response.json();
          if (!response.ok || !data?.success) return 0;

          let changed = 0;
          const resolvedFields = data.fields || {};
          ['service_address', 'address_additional', 'service_country', 'service_department', 'service_locality', 'service_sector'].forEach(key => {
            const value = (resolvedFields[key] || '').toString().trim();
            if (!value) return;
            setFieldValue(key, value);
            changed += 1;
          });

          const localityResolved = (resolvedFields.locality || '').toString().trim();
          if (localityResolved) {
            const normalized = toTitleCase(localityResolved);
            setLocalityFromMaps(normalized);
            addLocationOption('locality', normalized);
            changed += 1;
          }

          const neighborhoodResolved = (resolvedFields.neighborhood || '').toString().trim();
          if (neighborhoodResolved) {
            const normalized = toTitleCase(neighborhoodResolved);
            setNeighborhoodFromMaps(normalized);
            addLocationOption('neighborhood', normalized);
            changed += 1;
          }

          if (changed > 0) {
            renderLocationDatalists();
            lockLocationFields();
          }

          updateServiceAddressMapsState(data.google_maps_url || '');

          return changed;
        } catch (error) {
          console.error('Address resolve error', error);
          return 0;
        }
      };

      let geminiFilled = 0;
      let geminiResolved = 0;

      if (aiParseUrl && raw.trim()) {
        try {
          const response = await fetch(aiParseUrl, {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
              'Accept': 'application/json',
              'X-CSRF-TOKEN': csrfToken
            },
            body: JSON.stringify({ raw_text: raw })
          });

          const data = await response.json();
          if (response.ok && data?.success) {
            geminiFilled = applyParsedFields(data.fields || {});
            geminiResolved = await resolveAddressWithGoogle();
          } else if (data?.message) {
            notyf.open({ type: 'warning', message: data.message });
          }
        } catch (error) {
          console.error('Gemini parse error', error);
          notyf.open({ type: 'warning', message: 'Gemini no disponible en este momento. Se usará parser local.' });
        }
      }

      const text = raw
        .replace(/[✔️✨📝📞🎶💖⏰📍💰]/g, ' ')
        .replace(/\s+/g, ' ')
        .trim();
      const rawLines = raw
        .split(/\r?\n/)
        .map(line => line.trim())
        .filter(Boolean);
      const chatLines = rawLines
        .map(line =>
          line
            // Remove WhatsApp timestamp + sender prefix.
            .replace(/^\[[^\]]+\]\s*(?:\+?\d[\d\s\-]{6,}|[^:]{1,40})\s*:\s*/i, '')
            .replace(/^[•\-]+\s*/, '')
            .replace(/[✔️✨📝📞🎶💖⏰📍💰]/g, ' ')
            .replace(/\*/g, '')
            .replace(/\s+/g, ' ')
            .trim()
        )
        .filter(Boolean)
        .filter(line => !/^(muchas gracias|ya he registrado|te confirmo los datos|todo queda confirmado|unas horas antes)/i.test(line));

      const escapeRegExp = value => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
      const cleanExtracted = value =>
        (value || '')
          .replace(/^[:\-\s,/]+/, '')
          .replace(/[\s,.;:!?\-]+$/g, '')
          .replace(/\s+/g, ' ')
          .trim();
      let appliedCount = 0;
      const applyValue = (fieldId, value) => {
        let cleaned = cleanExtracted(value);
        if (['neighborhood', 'locality'].includes(fieldId)) {
          cleaned = toTitleCase(cleaned);
        }
        if (!cleaned) return false;
        setFieldValue(fieldId, cleaned);
        appliedCount += 1;
        return true;
      };

      const getSegment = (labels, nextLabels) => {
        const labelsPattern = labels.map(escapeRegExp).join('|');
        const nextPattern = nextLabels.map(escapeRegExp).join('|');
        const regex = new RegExp(`(?:${labelsPattern})\\s*[:.-]?\\s*(.+?)(?=(?:${nextPattern})\\s*[:.-]?|$)`, 'i');
        const match = text.match(regex);
        return cleanExtracted(match ? match[1] : '');
      };

      const allLabels = [
        'nombre de quien realiza el pago',
        'nombre',
        'hola',
        'dirección y barrio del servicio',
        'direccion y barrio del servicio',
        'dirección del servicio',
        'direccion del servicio',
        'lugar',
        'localidad',
        '2 números de contacto',
        '2 numeros de contacto',
        'números de contacto',
        'numeros de contacto',
        'motivo de la serenata',
        'motivo',
        'homenajeada',
        'de parte de / para quién',
        'de parte de / para quien',
        'de parte de',
        'para quién',
        'para quien',
        'fecha y horario',
        'fecha',
        'hora',
        'forma de pago',
        'paquete',
        'plan'
      ];

      const payerName =
        getSegment(['nombre de quien realiza el pago', 'nombre'], allLabels.filter(l => !['nombre de quien realiza el pago', 'nombre'].includes(l))) ||
        cleanExtracted((text.match(/hola\s+([a-záéíóúñ\s]+)/i) || [])[1]);
      const addressAndNeighborhood = getSegment(
        ['dirección y barrio del servicio', 'direccion y barrio del servicio', 'dirección del servicio', 'direccion del servicio', 'lugar'],
        allLabels.filter(l => !['dirección y barrio del servicio', 'direccion y barrio del servicio', 'dirección del servicio', 'direccion del servicio', 'lugar'].includes(l))
      );
      const serviceLocality = getSegment(['localidad'], allLabels.filter(l => l !== 'localidad'));
      const reason = getSegment(['motivo de la serenata', 'motivo'], allLabels.filter(l => !['motivo de la serenata', 'motivo'].includes(l)));
      const fromTo = getSegment(['de parte de / para quién', 'de parte de / para quien', 'de parte de'], allLabels.filter(l => !['de parte de / para quién', 'de parte de / para quien', 'de parte de'].includes(l)));
      const homenajeada = getSegment(['homenajeada', 'para quién', 'para quien'], allLabels.filter(l => !['homenajeada', 'para quién', 'para quien'].includes(l)));
      const paymentRaw = getSegment(['forma de pago'], allLabels.filter(l => l !== 'forma de pago'));
      const occasionFromSummary = getSegment(['ocasión', 'ocasion'], allLabels.filter(l => !['ocasión', 'ocasion'].includes(l)));
      const packageName = getSegment(['paquete', 'plan'], allLabels.filter(l => !['paquete', 'plan'].includes(l)));
      const firstNameLikeLine =
        chatLines.find(line => /^[a-záéíóúñ]+(?:\s+[a-záéíóúñ]+){1,4}$/i.test(line)) ||
        chatLines[0] ||
        '';

      applyValueIfEmpty('payer_name', payerName);
      applyValueIfEmpty('payer_name', firstNameLikeLine);
      if (!packageSelect?.value && packageName && setPackageFromText(packageName)) {
        appliedCount += 1;
      }
      if (!getFieldCurrentValue('service_locality') && serviceLocality) {
        setServiceLocalityFromMaps(serviceLocality);
        appliedCount += 1;
      }

      if (addressAndNeighborhood) {
        const normalizedAddressText = addressAndNeighborhood
          .replace(/\s+[–—-]\s+/g, ', ')
          .replace(/\s{2,}/g, ' ')
          .trim();

        const splitAddress = normalizedAddressText.split(/(?:\s+barri?o\s+|,?\s+barrio\s+)/i);
        const serviceAddress = cleanExtracted(splitAddress[0]);
        const neighborhood = toTitleCase(cleanExtracted(splitAddress[1] || ''));
        const addressAdditionalMatch = normalizedAddressText.match(/\b(apto|apartamento|casa|torre|interior|oficina|piso)\b[\s#-]*([a-z0-9\-]+)/i);
        const addressAdditional = cleanExtracted(addressAdditionalMatch ? `${addressAdditionalMatch[1]} ${addressAdditionalMatch[2]}` : '');
        applyValueIfEmpty('service_address', serviceAddress);
        applyValueIfEmpty('neighborhood', neighborhood);
        applyValueIfEmpty('address_additional', addressAdditional);
      }

      applyValueIfEmpty('reason', reason);
      if (!getFieldCurrentValue('reason')) {
        const reasonFromLine = chatLines.find(line => /cumple|aniversario|detalle|homenaje|ocasi[oó]n/i.test(line));
        applyValueIfEmpty('reason', reasonFromLine || occasionFromSummary);
      }

      const fromToLooseLine =
        chatLines.find(line => /^de\s+.+(?:\/|\|)\s*para\s+/i.test(line)) ||
        chatLines.find(line => /^de\s+.+\s+para\s+/i.test(line)) ||
        '';
      const normalizedFromToSource = cleanExtracted(fromTo || fromToLooseLine)
        .replace(/\s*\/\s*/g, ' | ')
        .replace(/\s+\|\s+/g, ' | ')
        .replace(/\s+para\s+/i, ' | ');

      if (normalizedFromToSource) {
        const parts = normalizedFromToSource.split('|').map(cleanExtracted).filter(Boolean);
        applyValueIfEmpty('from_name', parts[0] ? parts[0].replace(/^de\s+/, '').trim() : '');
        applyValueIfEmpty('to_name', parts[1] || '');
      }
      if (!normalizedFromToSource) {
        applyValueIfEmpty('to_name', homenajeada);
        if (!getFieldCurrentValue('payer_name')) {
          applyValueIfEmpty('payer_name', firstNameLikeLine);
        }
        const secondaryNameLine = chatLines.find(
          line =>
            /^[a-záéíóúñ]+(?:\s+[a-záéíóúñ]+){1,4}$/i.test(line) &&
            line.toLowerCase() !== getFieldCurrentValue('payer_name').toLowerCase()
        );
        if (!getFieldCurrentValue('to_name')) {
          applyValueIfEmpty('to_name', secondaryNameLine || '');
        }
      }

      const uniquePhones = extractPhoneCandidates(raw);
      applyValueIfEmpty('contact_phone_1', uniquePhones[0] || '');
      applyValueIfEmpty('contact_phone_2', uniquePhones[1] || '');

      const timeSource =
        chatLines.find(line => /\b\d{1,2}(?::\d{2})?\s*(a\.?\s*m\.?|p\.?\s*m\.?|am|pm)\b/i.test(line)) ||
        chatLines.find(line => /\bhora\b/i.test(line)) ||
        text;
      const timeMatch = timeSource.match(/\b(\d{1,2})(?::(\d{2}))?\s*(a\.?\s*m\.?|p\.?\s*m\.?|am|pm)\b/i)
        || timeSource.match(/\bhora\s*[:.-]?\s*(\d{1,2})(?::(\d{2}))?/i)
        || timeSource.match(/\b(\d{1,2}):(\d{2})\b/);
      if (timeMatch) {
        let hour = parseInt(timeMatch[1], 10);
        const minute = (timeMatch[2] || '00').padStart(2, '0');
        const meridiem = (timeMatch[3] || '').toLowerCase().replace(/\./g, '').replace(/\s/g, '');
        if (meridiem === 'pm' && hour < 12) hour += 12;
        if (meridiem === 'am' && hour === 12) hour = 0;
        if (hour >= 0 && hour <= 23) {
          if (eventTimeModeInput) {
            eventTimeModeInput.value = 'after';
          }
          applyValue('event_time', `${hour.toString().padStart(2, '0')}:${minute}`);
        }
      }

      const monthMap = {
        enero: '01',
        febrero: '02',
        marzo: '03',
        abril: '04',
        mayo: '05',
        junio: '06',
        julio: '07',
        agosto: '08',
        septiembre: '09',
        setiembre: '09',
        octubre: '10',
        noviembre: '11',
        diciembre: '12'
      };
      const monthPattern = Object.keys(monthMap).join('|');
      const dateCandidates = [...text.matchAll(new RegExp(`\\b(\\d{1,2})\\s*(?:de\\s+)?(${monthPattern})(?:\\s*(?:de\\s+)?(\\d{4}))?\\b`, 'gi'))];
      const validDateMatch = dateCandidates.find(match => {
        const day = parseInt(match[1], 10);
        return Number.isInteger(day) && day >= 1 && day <= 31 && !!monthMap[(match[2] || '').toLowerCase()];
      });
      if (validDateMatch) {
        const day = validDateMatch[1].padStart(2, '0');
        const month = monthMap[(validDateMatch[2] || '').toLowerCase()];
        const year = (validDateMatch[3] || new Date().getFullYear().toString()).toString();
        applyValueIfEmpty('event_date', `${year}-${month}-${day}`);
      } else {
        const numericDate = text.match(/(\d{1,2})\/(\d{1,2})\/(\d{2,4})/);
        if (numericDate) {
          const day = numericDate[1].padStart(2, '0');
          const month = numericDate[2].padStart(2, '0');
          const year = (numericDate[3].length === 2 ? `20${numericDate[3]}` : numericDate[3]).toString();
          applyValueIfEmpty('event_date', `${year}-${month}-${day}`);
        }
      }

      if (paymentRaw) {
        const paymentLower = paymentRaw.toLowerCase();
        let method = '';
        if (paymentLower.includes('efectivo')) method = 'Efectivo al finalizar';
        else if (paymentLower.includes('transfer')) method = 'Transferencia';
        else if (paymentLower.includes('tarjeta')) method = 'Tarjeta';
        else if (paymentLower.includes('nequi')) method = 'Nequi';
        else if (paymentLower.includes('daviplata')) method = 'Daviplata';
        const paymentSelect = document.getElementById('payment_method');
        if (paymentSelect && method) {
          const options = [...paymentSelect.options]
            .map(option => ({ value: option.value || '', text: (option.textContent || '').trim() }))
            .filter(option => option.value);
          const matched = options.find(option => option.text.toLowerCase().includes(method.toLowerCase()));
          if (matched) {
            applyValueIfEmpty('payment_method', matched.value);
          }
        }
      }

      const packageLine = chatLines.find(line => /\b(paquete|plan)\b/i.test(line)) || '';
      if (!packageSelect?.value && packageLine && setPackageFromText(packageLine)) {
        appliedCount += 1;
      }

      if (!getFieldCurrentValue('service_address')) {
        const addressLine = chatLines.find(line => /calle|carrera|cr\.?|cra\.?|av\.?|avenida|diagonal|transversal|#|via|vía/i.test(line));
        applyValueIfEmpty('service_address', addressLine || '');
      }
      if (!getFieldCurrentValue('neighborhood')) {
        const neighborhoodLine = chatLines.find(line => /barrio/i.test(line));
        if (neighborhoodLine) {
          const barrio = toTitleCase(cleanExtracted((neighborhoodLine.match(/barrio\s+(.+)/i) || [])[1] || ''));
          applyValueIfEmpty('neighborhood', barrio);
        }
      }
      if (!getFieldCurrentValue('locality')) {
        const localityLine =
          chatLines.find(line => /sibat[eé]|fontib[oó]n|ch[ií]a|bogot[aá]|soacha|mosquera|funza|madrid/i.test(line)) || '';
        if (localityLine) {
          const localityGuess = localityLine.split(/,|-/)[0];
          setLocalityFromMaps(localityGuess);
          appliedCount += 1;
        }
      }

      if (!getFieldCurrentValue('service_country')) {
        applyValueIfEmpty('service_country', 'Colombia');
      }

      if (!getFieldCurrentValue('address_additional')) {
        const additionalLine = chatLines.find(line => /\b(apto|apartamento|casa|torre|interior|oficina|piso)\b/i.test(line)) || '';
        if (additionalLine) {
          const additionalMatch = additionalLine.match(/\b(apto|apartamento|casa|torre|interior|oficina|piso)\b[\s#-]*([a-z0-9\-]+)/i);
          if (additionalMatch) {
            applyValueIfEmpty('address_additional', `${additionalMatch[1]} ${additionalMatch[2]}`);
          }
        }
      }

      if (!getFieldCurrentValue('internal_notes')) {
        const detailLine = chatLines.find(line => /detalle|sorpresa|indicaci[oó]n|referencia|nota/i.test(line)) || '';
        applyValueIfEmpty('internal_notes', detailLine);
      }

      const resolvedFallback = await resolveAddressWithGoogle();
      if (resolvedFallback > 0) {
        appliedCount += 1;
      }

      closeIaOverlay();
      const totalApplied = geminiFilled + appliedCount;
      if (totalApplied > 0) {
        const sourceLabel = geminiFilled > 0 ? 'Gemini + parser local' : 'parser local';
        notyf.open({ type: 'success', message: `Campos autocompletados (${totalApplied}) con ${sourceLabel}. Revisa y confirma.` });
      } else {
        notyf.open({ type: 'warning', message: 'No pude detectar campos con ese texto. Ajusta el formato o pega más datos.' });
      }
    });
  }
});
