(function () {
  function dayKeyFromDateStr(s) {
    if (!s) return '';
    // Use noon to avoid timezone edge cases around midnight.
    var d = new Date(s + 'T12:00:00');
    if (isNaN(d.getTime())) return '';
    var map = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
    return map[d.getDay()] || '';
  }

  function formatYMD(d) {
    var y = d.getFullYear();
    var m = String(d.getMonth() + 1).padStart(2, '0');
    var day = String(d.getDate()).padStart(2, '0');
    return y + '-' + m + '-' + day;
  }


  function parseHHMM(v) {
    if (!v || typeof v !== 'string') return null;
    var m = v.trim().match(/^(\d{2}):(\d{2})$/);
    if (!m) return null;
    var hh = parseInt(m[1], 10);
    var mm = parseInt(m[2], 10);
    if (isNaN(hh) || isNaN(mm)) return null;
    if (hh < 0 || hh > 23 || mm < 0 || mm > 59) return null;
    return { hh: hh, mm: mm };
  }

  function toMinutes(hhmm) {
    var p = parseHHMM(hhmm);
    if (!p) return null;
    return (p.hh * 60) + p.mm;
  }

  function minutesToHHMM(mins) {
    mins = Math.max(0, mins | 0);
    var hh = Math.floor(mins / 60) % 24;
    var mm = mins % 60;
    return (String(hh).padStart(2, '0') + ':' + String(mm).padStart(2, '0'));
  }

  function format12(hhmm) {
    var m = /^([0-9]{2}):([0-9]{2})$/.exec(hhmm || '');
    if (!m) return hhmm || '';
    var hh = parseInt(m[1], 10);
    var mm = m[2];
    if (isNaN(hh)) return hhmm || '';
    var ampm = (hh >= 12) ? 'PM' : 'AM';
    var h = hh % 12;
    if (h === 0) h = 12;
    return String(h) + ':' + mm + ' ' + ampm;
  }


  function timeRangeOverlaps(aStart, aEnd, bStart, bEnd) {
    // Overlap if aStart < bEnd AND aEnd > bStart
    return (aStart < bEnd) && (aEnd > bStart);
  }

  function inDateRange(dateStr, start, end) {
    return (dateStr >= start && dateStr <= end);
  }

  function isFullDayBlocked(dateStr, blocks) {
    for (var i = 0; i < blocks.length; i++) {
      var b = blocks[i] || {};
      if ((b.mode || 'full') !== 'full') continue;
      if (b.start && b.end && inDateRange(dateStr, b.start, b.end)) return true;
    }
    return false;
  }

  function isTimeBlocked(dateStr, startHHMM, durationMins, blocks) {
    var startMin = toMinutes(startHHMM);
    if (startMin === null) return true;
    var endMin = startMin + (durationMins | 0);

    for (var i = 0; i < blocks.length; i++) {
      var b = blocks[i] || {};
      if (!b.start || !b.end) continue;
      if (!inDateRange(dateStr, b.start, b.end)) continue;

      var mode = (b.mode || 'full');
      if (mode !== 'range') {
        return true; // full-day block
      }

      var bf = toMinutes(b.from || '');
      var bt = toMinutes(b.to || '');
      if (bf === null || bt === null) continue;

      if (timeRangeOverlaps(startMin, endMin, bf, bt)) return true;
    }
    return false;
  }

  function computeDuration(wrap) {
    var party = 0;
    var partyInput = wrap.querySelector('input[name="party"]');
    if (partyInput) party = parseInt(partyInput.value || '0', 10) || 0;

    var defDur = parseInt(wrap.getAttribute('data-duration-default') || '0', 10) || 0;
    var largeDur = parseInt(wrap.getAttribute('data-duration-large') || '0', 10) || 0;
    var threshold = parseInt(wrap.getAttribute('data-large-threshold') || '0', 10) || 0;

    if (threshold > 0 && party >= threshold && largeDur > 0) return largeDur;
    return defDur > 0 ? defDur : 0;
  }

  function updateTimes(wrap) {
    var hoursJson = wrap.getAttribute('data-weekly-hours') || '{}';
    var stepMins = parseInt(wrap.getAttribute('data-step') || '15', 10) || 15;
    var blocksJson = wrap.getAttribute('data-blocks') || '[]';

    var hours, blocks;
    try { hours = JSON.parse(hoursJson); } catch (e) { hours = {}; }
    try { blocks = JSON.parse(blocksJson); } catch (e) { blocks = []; }
    if (!Array.isArray(blocks)) blocks = [];

    var dateInput = wrap.querySelector('input[name="date"]');
    var timeSelect = wrap.querySelector('select[name="time"]');
    var msgEl = wrap.querySelector('.dabl-rr-date-msg');

    if (!dateInput || !timeSelect) return;

    var dateStr = (dateInput.value || '').trim();
    var dur = computeDuration(wrap);

    // Reset messages and validity
    if (msgEl) msgEl.textContent = '';
    dateInput.setCustomValidity('');

    if (!dateStr) {
      timeSelect.disabled = true;
      timeSelect.innerHTML = '<option value="">Select a date first</option>';
      return;
    }

    if (isFullDayBlocked(dateStr, blocks)) {
      timeSelect.disabled = true;
      timeSelect.innerHTML = '<option value="">Date is blocked</option>';
      dateInput.setCustomValidity('This date is blocked.');
      if (msgEl) msgEl.textContent = 'This date is blocked for online reservations.';
      return;
    }

    var key = dayKeyFromDateStr(dateStr);
    var h = (hours && hours[key]) ? hours[key] : null;
    if (!h || h.closed) {
      timeSelect.disabled = true;
      timeSelect.innerHTML = '<option value="">Closed</option>';
      dateInput.setCustomValidity('This day is closed.');
      if (msgEl) msgEl.textContent = 'This day is closed.';
      return;
    }

    var openMin = toMinutes(h.open || '');
    var closeMin = toMinutes(h.close || '');
    if (openMin === null || closeMin === null || closeMin <= openMin) {
      timeSelect.disabled = true;
      timeSelect.innerHTML = '<option value="">Closed</option>';
      dateInput.setCustomValidity('This day is closed.');
      if (msgEl) msgEl.textContent = 'This day is closed.';
      return;
    }

    var opts = [];
    for (var t = openMin; t <= closeMin; t += stepMins) {
      if (t === closeMin) break; // don't allow exact close time
      var startHHMM = minutesToHHMM(t);

      // Respect duration: do not allow an end time past close.
      if (dur > 0 && (t + dur) > closeMin) continue;

      // Respect blocks (time ranges)
      if (dur > 0 && isTimeBlocked(dateStr, startHHMM, dur, blocks)) continue;
      if (dur === 0 && isTimeBlocked(dateStr, startHHMM, 1, blocks)) continue;

      opts.push(startHHMM);
    }

    if (!opts.length) {
      timeSelect.disabled = true;
      timeSelect.innerHTML = '<option value="">No times available</option>';
      if (msgEl) msgEl.textContent = 'No times available for this date.';
      return;
    }

    // Preserve existing value if still valid
    var prev = timeSelect.value;
    timeSelect.innerHTML = '<option value="">Select a time</option>' + opts.map(function (v) {
      var sel = (prev === v) ? ' selected' : '';
      return '<option value="' + v + '"' + sel + '>' + format12(v) + '</option>';
    }).join('');
    timeSelect.disabled = false;
  }

  function init() {
    document.querySelectorAll('.dabl-rr-form-wrap').forEach(function (wrap) {
      var dateInput = wrap.querySelector('input[name="date"]');
      var partyInput = wrap.querySelector('input[name="party"]');

      // jQuery UI Datepicker (disables full-day blocks so they cannot be selected)
      if (dateInput && window.jQuery && jQuery.fn && typeof jQuery.fn.datepicker === 'function') {
        var blocksJsonForPicker = wrap.getAttribute('data-blocks') || '[]';
        var blocksForPicker;
        try { blocksForPicker = JSON.parse(blocksJsonForPicker); } catch (e) { blocksForPicker = []; }
        if (!Array.isArray(blocksForPicker)) blocksForPicker = [];

        jQuery(dateInput).datepicker({
          minDate: 0,
          dateFormat: 'yy-mm-dd',
          beforeShowDay: function (d) {
            var ds = formatYMD(d);
            if (isFullDayBlocked(ds, blocksForPicker)) return [false, 'dabl-rr-date-blocked', 'Blocked'];
            return [true, '', ''];
          },
          onSelect: function () {
            updateTimes(wrap);
          }
        });

        // Sync any pre-filled value (browser autofill/back button)
        if (dateInput.value) {
          jQuery(dateInput).datepicker('setDate', dateInput.value);
        }
      }
      if (dateInput) {
        dateInput.addEventListener('change', function () { updateTimes(wrap); });
        dateInput.addEventListener('input', function () { updateTimes(wrap); });
      }
      if (partyInput) {
        partyInput.addEventListener('change', function () { updateTimes(wrap); });
        partyInput.addEventListener('input', function () { updateTimes(wrap); });
      }

      // Initialize on load (in case browser pre-fills values).
      updateTimes(wrap);

      // Submit handler: Loading state
      var form = wrap.querySelector('form.dabl-rr-form');
      if (form) {
        form.addEventListener('submit', function (e) {
          var btn = form.querySelector('.dabl-rr-btn');
          if (btn) {
            btn.disabled = true;
            btn.textContent = 'Processing...';
          }
        });
      }
    });

    // Staff portal: reveal/hide contact details
    document.querySelectorAll('.dabl-rr-reveal').forEach(function (b) {
      b.addEventListener('click', function () {
        var t = document.getElementById(b.dataset.target);
        if (!t) return;
        var isHidden = t.hasAttribute('hidden');
        if (isHidden) {
          t.removeAttribute('hidden');
          b.textContent = b.getAttribute('data-label-hide') || 'Hide contact';
        } else {
          t.setAttribute('hidden', '');
          b.textContent = b.getAttribute('data-label-show') || 'Show contact';
        }
      });
    });
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }
})();