HEX
Server: nginx/1.24.0
System: Linux DGT-WORDPRESS-VM-SERVER 6.14.0-1014-azure #14~24.04.1-Ubuntu SMP Fri Oct 3 20:52:11 UTC 2025 x86_64
User: ubuntu (1000)
PHP: 8.4.12
Disabled: NONE
Upload Files
File: /mnt/data/dreamsai-wp/wp-content/dreams-ai/assets/scripts.js
jQuery(function ($) {
  function dtShowToast(message, type) {
    var $container = $("#dt-toast-container");
    if (!$container.length) {
      $("body").append(
        '<div class="toast-container position-fixed top-50 end-0 p-0" id="dt-toast-container" style="z-index:1080;"></div>'
      );
      $container = $("#dt-toast-container");
    }
    var id = "dt-toast-" + Date.now();
    var bgClass = "text-bg-secondary";
    if (type === "success") bgClass = "text-bg-success";
    if (type === "error") bgClass = "text-bg-danger";
    var html =
      '<div id="' +
      id +
      '" class="toast align-items-center ' +
      bgClass +
      '" role="alert" aria-live="assertive" aria-atomic="true">\
        <div class="d-flex">\
          <div class="toast-body">' +
      (message || "") +
      '</div>\
          <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>\
        </div>\
      </div>';
    $container.append(html);
    var el = document.getElementById(id);
    if (window.bootstrap && window.bootstrap.Toast) {
      var toast = new window.bootstrap.Toast(el, { delay: 3000 });
      toast.show();
    } else {
      $(el)
        .fadeIn(150)
        .delay(3000)
        .fadeOut(300, function () {
          $(this).remove();
        });
    }
  }
  // Initialize Select2 if present
  function initSelect2() {
    if (!$.fn.select2) return;
    $("select.form-select, select.select").each(function () {
      var $el = $(this);
      if ($el.data("select2")) return;
      $el.select2({ width: "100%" });
    });

    // ===== Add Service: Gallery uploader =====
    var dsGalleryFrame;
    $(document).on("click", "#gallery_media_uploader", function (e) {
      e.preventDefault();
      if (dsGalleryFrame) {
        dsGalleryFrame.open();
        return;
      }
      dsGalleryFrame = wp.media({
        title: "Select Images",
        button: { text: "Use selected" },
        multiple: true,
      });
      dsGalleryFrame.on("select", function () {
        var selection = dsGalleryFrame.state().get("selection");
        var $wrap = $(".gallery-preview");
        selection.each(function (att) {
          var a = att.toJSON();
          var url =
            a.sizes && a.sizes.thumbnail ? a.sizes.thumbnail.url : a.url;
          var $box = $(
            '<div class="gallery-upload-img me-2 mb-2 position-relative">'
          );
          $box.append(
            $("<img>").attr("src", url).css({
              width: "100px",
              height: "100px",
              objectFit: "cover",
              borderRadius: "4px",
            })
          );
          $box.append(
            '<span class="trash-icon d-flex align-items-center justify-content-center text-danger gallery-trash" data-image-id="' +
              a.id +
              '"><i class="isax isax-trash"></i></span>'
          );
          $box.append(
            '<input type="hidden" name="gallery_images[]" value="' +
              a.id +
              '" />'
          );
          $wrap.append($box);
        });
      });
      dsGalleryFrame.open();
    });
    $(document).on("click", ".gallery-trash", function () {
      var $box = $(this).closest(".gallery-upload-img");
      $box.remove();
    });

    // ===== Add Service: Add-ons rows =====
    function addonRowTemplate() {
      return (
        '<div class="col-12 ds-addon-row">\
          <div class="row g-2 align-items-end">\
            <div class="col-md-4">\
              <label class="form-label">Title</label>\
              <input type="text" class="form-control" name="addons_title[]" />\
            </div>\
            <div class="col-md-3">\
              <label class="form-label">Price</label>\
              <div class="input-group">\
                <span class="input-group-text">' +
        wcCurrency.symbol +
        ' </span>\
                <input type="text" class="form-control" name="addons_price[]" />\
              </div>\
            </div>\
            <div class="col-md-2">\
              <label class="form-label">Hrs</label>\
              <select class="form-select" name="addons_dur_h[]">' +
        (function () {
          var o = "\n<option value>—</option>";
          for (var h = 0; h <= 12; h++) {
            o += '\n<option value="' + h + '">' + h + "</option>";
          }
          return o;
        })() +
        '</select>\
            </div>\
            <div class="col-md-2">\
              <label class="form-label">Mins</label>\
              <select class="form-select" name="addons_dur_m[]">' +
        (function () {
          var o = "\n<option value>—</option>";
          for (var m = 0; m <= 59; m++) {
            o += '\n<option value="' + m + '">' + m + "</option>";
          }
          return o;
        })() +
        '</select>\
            </div>\
            <div class="col-md-1">\
              <button type="button" class="btn btn-outline-danger ds-remove-addon">&times;</button>\
            </div>\
          </div>\
        </div>'
      );
    }
    $(document).on("click", "#ds-add-addon-btn", function () {
      var $list = $(".ds-addons-list");
      if (!$list.length) return;
      $list.append(addonRowTemplate());
    });
    $(document).on("click", ".ds-remove-addon", function () {
      var $row = $(this).closest(".ds-addon-row");
      $row.remove();
    });

    // ===== Add Service: FAQ add/edit =====
    var $faqEditing = null;
    $(document).on("click", "#add_faq_btn", function () {
      var q = $.trim($("#faq_question_input").val());
      var a = $.trim($("#faq_answer_input").val());
      if (!q || !a) {
        dtShowToast("Please enter both question and answer.", "error");
        return;
      }
      var $card = $(
        '<div class="card shadow-none mb-0 mt-3 faq-item">\
      <div class="card-body px-3 py-2">\
        <div class="d-flex align-items-center justify-content-between flex-wrap row-gap-3">\
          <h6><a href="javascript:void(0);" class="faq-question-text"></a></h6>\
          <div class="d-flex align-items-center">\
            <a href="javascript:void(0);" data-bs-toggle="modal" data-bs-target="#edit_faq" class="rounded-edit d-flex align-items-center justify-content-center me-2 edit-faq-btn"><i class="isax isax-edit-2"></i></a>\
            <a href="javascript:void(0);" class="trash-icon d-flex align-items-center justify-content-center remove-faq"><i class="isax isax-trash text-danger"></i></a>\
          </div>\
        </div>\
      </div>\
    </div>'
      );
      $card.find(".faq-question-text").text(q);
      var $hiddenQ = $('<input type="hidden" name="faq_question[]" />').val(q);
      var $hiddenA = $(
        '<textarea name="faq_answer[]" style="display:none;"></textarea>'
      ).text(a);
      $(".faq_append").append($card).append($hiddenQ).append($hiddenA);
      $("#add_faq").modal("hide");
      $("#faq_question_input").val("");
      $("#faq_answer_input").val("");
    });
    $(document).on("click", ".edit-faq-btn", function () {
      var $card = $(this).closest(".faq-item");
      var $qInput = $card.nextAll("input[name='faq_question[]']").first();
      var $aInput = $qInput.length
        ? $qInput.nextAll("textarea[name='faq_answer[]']").first()
        : $();
      var q = $qInput.length
        ? $qInput.val()
        : $card.find(".faq-question-text").text();
      var a = $aInput.length ? $aInput.val() : "";
      $("#edit_faq_question_input").val(q);
      $("#edit_faq_answer_input").val(a);
      $faqEditing = $card;
    });
    $(document).on("click", "#save_faq_btn", function () {
      if (!$faqEditing) return;
      var nq = $.trim($("#edit_faq_question_input").val());
      var na = $.trim($("#edit_faq_answer_input").val());
      if (!nq || !na) {
        dtShowToast("Please enter both question and answer.", "error");
        return;
      }
      $faqEditing.find(".faq-question-text").text(nq);
      var $qInput = $faqEditing.nextAll("input[name='faq_question[]']").first();
      var $aInput = $qInput.length
        ? $qInput.nextAll("textarea[name='faq_answer[]']").first()
        : $();
      if ($qInput.length) $qInput.val(nq);
      else
        $faqEditing.after(
          $('<input type="hidden" name="faq_question[]" />').val(nq)
        );
      if ($aInput.length) $aInput.val(na);
      else
        $faqEditing.after(
          $(
            '<textarea name="faq_answer[]" style="display:none;"></textarea>'
          ).text(na)
        );
      $("#edit_faq").modal("hide");
      $faqEditing = null;
    });
    $(document).on("click", ".remove-faq", function () {
      var $card = $(this).closest(".faq-item");
      var $qInput = $card.nextAll("input[name='faq_question[]']").first();
      var $aInput = $qInput.length
        ? $qInput.nextAll("textarea[name='faq_answer[]']").first()
        : $();
      $card.remove();
      if ($qInput.length) $qInput.remove();
      if ($aInput.length) $aInput.remove();
    });
  }

  function showFormErrors(messages) {
    var $box = $("#ds-form-errors");
    if (!$box.length) return;
    var $list = $("#ds-form-errors-list");
    if (!$list.length) {
      $list = $('<ul class="mb-0" id="ds-form-errors-list"></ul>');
      $box.append($list);
    }
    $list.empty();
    if (!messages || !messages.length) {
      $box.addClass("d-none");
      return;
    }
    messages.forEach(function (msg) {
      $list.append($("<li></li>").text(msg));
    });
    $box.removeClass("d-none");
    var top = $box.offset() ? $box.offset().top - 120 : 0;
    if (top >= 0) {
      window.scrollTo({ top: top, behavior: "smooth" });
    }
  }

  // Ensure global staff loader exists for Assign Staff
  function feLoadStaffGlobal() {
    var $staff = $("#fe_staff_users");
    if (!$staff.length || typeof DSAjax === "undefined") return;
    var cat = $("select[name='category']").val();
    var branches = $("select[name='branch[]']").val() || [];
    var preselected = $staff.val() || [];
    $staff.prop("disabled", true);
    $staff.trigger("change.select2");
    $.post(
      DSAjax.ajaxUrl,
      {
        action: "ds_load_staff",
        _ajax_nonce: DSAjax.nonce,
        categories: cat ? [parseInt(cat, 10)] : [],
        branches: (branches || []).map(function (v) {
          return parseInt(v, 10);
        }),
      },
      function (resp) {
        if (!resp || !resp.success) return;
        $staff.empty();
        var users = resp.data && resp.data.users ? resp.data.users : [];
        users.forEach(function (u) {
          var opt = $("<option>").val(u.ID).text(u.name);
          if (
            preselected.includes(String(u.ID)) ||
            preselected.includes(parseInt(u.ID, 10))
          ) {
            opt.attr("selected", "selected");
          }
          $staff.append(opt);
        });
        $staff.prop("disabled", false).trigger("change");
      }
    );
  }

  // Debounce helper
  function debounce(fn, wait) {
    var t;
    return function () {
      var ctx = this,
        args = arguments;
      clearTimeout(t);
      t = setTimeout(function () {
        fn.apply(ctx, args);
      }, wait);
    };
  }

  // Bind and initializations
  var feLoadStaffDebounced = debounce(feLoadStaffGlobal, 250);
  $(document).on(
    "change",
    "select[name='category'], select[name='branch[]']",
    feLoadStaffDebounced
  );
  feLoadStaffGlobal();
  initSelect2();

  function renderSlots(slots) {
    var $wrap = $("#ds_generated_slots");
    var $inputs = $("#ds_generated_slots_inputs");
    $wrap.empty();
    $inputs.empty();
    if (!slots || !slots.length) {
      $wrap.append($("<em>").text("No slots generated yet."));
      return;
    }
    $.each(slots, function (_, s) {
      $wrap.append($('<span class="ds-slot-chip">').text(s));
      $inputs.append(
        $('<input type="hidden" name="ds_generated_slots[]" />').val(s)
      );
    });

    // ===== Frontend: Load staff filtered by Category & Branches =====
    function feLoadStaff() {
      var $staff = $("#fe_staff_users");
      if (!$staff.length) return;
      var cat = $("select[name='category']").val();
      var branches = $("select[name='branch[]']").val() || [];
      var preselected = $staff.val() || [];
      var payload = {
        action: "ds_load_staff",
        _ajax_nonce: DSAjax.nonce,
        categories: cat ? [parseInt(cat, 10)] : [],
        branches: (branches || []).map(function (v) {
          return parseInt(v, 10);
        }),
      };
      $.post(DSAjax.ajaxUrl, payload, function (resp) {
        if (!resp || !resp.success) return;
        $staff.empty();
        var users = resp.data && resp.data.users ? resp.data.users : [];
        users.forEach(function (u) {
          var opt = $("<option>").val(u.ID).text(u.name);
          if (
            preselected.includes(String(u.ID)) ||
            preselected.includes(parseInt(u.ID, 10))
          ) {
            opt.attr("selected", "selected");
          }
          $staff.append(opt);
        });
        $staff.trigger("change");
      });
    }
    // Bind when category or branches change
    $(document).on(
      "change",
      "select[name='category'], select[name='branch[]']",
      feLoadStaff
    );
    // Initial load on page ready
    feLoadStaff();
    initSelect2();
  }

  function generateSlots() {
    var start = $("#ds_start_time").val();
    var end = $("#ds_end_time").val();
    var interval = parseInt($("#ds_interval").val(), 10) || 30;
    if (!start || !end) return; // wait until both are set
    $.post(DSAjax.ajaxUrl, {
      action: "ds_generate_slots",
      _ajax_nonce: DSAjax.nonce,
      start: start,
      end: end,
      interval: interval,
    }).done(function (resp) {
      if (resp && resp.success) {
        renderSlots(resp.data.slots || []);
      }
    });
  }

  // Button
  $(document).on("click", "#ds_generate_slots_btn", function (e) {
    e.preventDefault();
    generateSlots();
  });
  // Auto-generate on change if both set
  $(document).on(
    "change",
    "#ds_start_time, #ds_end_time, #ds_interval",
    function () {
      generateSlots();
    }
  );

  // ===== Frontend Add Service: Weekly Slots UI =====
  function parseHM(v) {
    if (!v) return null;
    var p = v.split(":");
    if (p.length < 2) return null;
    var h = parseInt(p[0], 10);
    var m = parseInt(p[1], 10);
    if (isNaN(h) || isNaN(m)) return null;
    return h * 60 + m;
  }
  function fmtAMPM(total) {
    var h = Math.floor(total / 60),
      m = total % 60;
    var suf = h >= 12 ? "PM" : "AM";
    var h12 = h % 12;
    if (h12 === 0) h12 = 12;
    var mm = (m < 10 ? "0" : "") + m;
    return h12 + ":" + mm + suf;
  }
  function buildDaySlotsWithGap(start, end, durMin, interval) {
    var out = [];
    var s = parseHM(start),
      e = parseHM(end);
    var D = parseInt(durMin, 10) || 0;
    var I = parseInt(interval, 10) || 0;
    if (s == null || e == null || e <= s || D <= 0) return out;
    var t = s;
    while (t + D <= e) {
      var st = t,
        en = t + D;
      out.push(fmtAMPM(st) + " to " + fmtAMPM(en));
      t = I <= 0 ? en : en + I;
    }
    return out;
  }
  function renderDayGenerated(day, slots) {
    var $wrap = $("#fe-day-slots-" + day);
    if (!$wrap.length) return;
    $wrap.empty();
    if (!slots.length) {
      $wrap.append("<em>No slots generated yet.</em>");
      return;
    }
    slots.forEach(function (s) {
      $wrap.append(
        '<span class="badge bg-light text-dark border position-relative pe-4">' +
          s +
          '<button type="button" class="btn btn-link p-0 position-absolute top-0 end-0 fe-remove-slot" data-day="' +
          day +
          '" data-slot="' +
          s +
          '">&times;</button></span>'
      );
      $wrap.append(
        '<input type="hidden" name="service_day[' +
          day +
          '][time_slots][]" value="' +
          s +
          '" />'
      );
    });
  }
  function generateForDay(day) {
    var s = $(".fe-day-start[data-day='" + day + "']").val();
    var e = $(".fe-day-end[data-day='" + day + "']").val();
    var iv = $(".fe-day-interval[data-day='" + day + "']").val();
    var dh = parseInt($("select[name='duration_hours']").val(), 10) || 0;
    var dm = parseInt($("select[name='duration_minutes']").val(), 10) || 0;
    var D = dh * 60 + dm;
    var slots = buildDaySlotsWithGap(s, e, D, iv);
    renderDayGenerated(day, slots);
  }

  // Accordion init
  (function initWeeklySlots() {
    $(".dc-subpanelcontent_2").hide();
    var $first = $(".dc-childaccordion_2 .dc-subpaneltitle_2").first();
    if ($first.length) {
      $first.addClass("active");
      var tg = $first.attr("data-bs-target");
      $first.find("input.day-toggle").prop("checked", true);
      if (tg) $(tg).show();
    }
    $(".dc-childaccordion_2").on("change", "input.day-toggle", function () {
      var header = $(this).closest(".dc-subpaneltitle_2");
      var target = header.attr("data-bs-target");
      var checked = $(this).is(":checked");
      header.toggleClass("active", checked);
      if (target) {
        var $content = $(target);
        if (checked) $content.stop(true, true).slideDown("fast");
        else $content.stop(true, true).slideUp("fast");
      }
    });
    $(".dc-childaccordion_2").on("click", ".dc-subpaneltitle_2", function (e) {
      if ($(e.target).is("input,button,a,label,.slider")) return;
      var $cb = $(this).find("input.day-toggle");
      $cb.prop("checked", !$cb.prop("checked")).trigger("change");
    });
  })();

  // ===== Dashboard Agent Reviews: Reply toggle + AJAX =====
  (function initAgentReviews() {
    // Toggle reply form
    $(document).on("click", ".add-reply", function (e) {
      e.preventDefault();
      var target = $(this).data("target");
      if (!target) return;
      var $box = $("#" + target);
      if ($box.length) {
        $box.toggle();
      }
    });

    // AJAX submit reply
    $(document).on("submit", ".reply-form", function (e) {
      e.preventDefault();
      var $form = $(this);
      var content = $.trim(
        $form.find('textarea[name="reply_content"]').val() || ""
      );
      var parentId =
        parseInt($form.find('input[name="parent_comment_id"]').val(), 10) || 0;
      var postId =
        parseInt($form.find('input[name="salon_post_id"]').val(), 10) || 0;
      if (!content || !parentId || !postId) {
        dtShowToast("Please enter a reply.", "error");
        return;
      }
      var payload = {
        action: "ds_reply_review",
        _ajax_nonce: window.DSAjax && DSAjax.nonce ? DSAjax.nonce : "",
        parent_comment_id: parentId,
        post_id: postId,
        content: content,
      };
      $form.find('button[type="submit"]').prop("disabled", true);
      $.post(window.DSAjax && DSAjax.ajaxUrl ? DSAjax.ajaxUrl : "", payload)
        .done(function (resp) {
          if (resp && resp.success) {
            dtShowToast(
              resp.data && resp.data.message
                ? resp.data.message
                : "Reply posted.",
              "success"
            );
            window.location.reload();
          } else {
            dtShowToast(
              resp && resp.data && resp.data.message
                ? resp.data.message
                : "Failed",
              "error"
            );
          }
        })
        .fail(function () {
          dtShowToast("Failed", "error");
        })
        .always(function () {
          $form.find('button[type="submit"]').prop("disabled", false);
        });
    });
  })();

  // ===== Customer Reviews: Edit/Delete via AJAX =====
  (function initCustomerReviews() {
    // Fill Edit modal
    $(document).on("click", ".js-open-edit", function () {
      var id = $(this).data("comment-id");
      var content = $(this).data("content") || "";
      var rating = $(this).data("rating") || "";
      $("#edit-comment-id").val(id);
      $("#edit-content").val(content);
      $("#edit-rating").val(rating);
    });
    // Submit Edit
    $(document).on("click", "#edit-review-submit", function () {
      var id = $("#edit-comment-id").val();
      var content = $("#edit-content").val();
      var rating = $("#edit-rating").val();
      var payload = {
        action: "ds_update_review",
        _ajax_nonce: window.DSAjax && DSAjax.nonce ? DSAjax.nonce : "",
        comment_id: id,
        content: content,
        rating: rating,
      };
      var $btn = $(this).prop("disabled", true);
      $.post(window.DSAjax && DSAjax.ajaxUrl ? DSAjax.ajaxUrl : "", payload)
        .done(function (resp) {
          if (resp && resp.success) {
            location.reload();
          } else {
            dtShowToast(
              resp && resp.data && resp.data.message
                ? resp.data.message
                : "Update failed",
              "error"
            );
          }
        })
        .fail(function () {
          dtShowToast("Update failed", "error");
        })
        .always(function () {
          $btn.prop("disabled", false);
        });
    });
    // Fill Delete modal
    $(document).on("click", ".js-open-delete", function () {
      var id = $(this).data("comment-id");
      $("#delete-comment-id").val(id);
    });
    // Submit Delete
    $(document).on("click", "#delete-review-submit", function () {
      var id = $("#delete-comment-id").val();
      var payload = {
        action: "ds_delete_review",
        _ajax_nonce: window.DSAjax && DSAjax.nonce ? DSAjax.nonce : "",
        comment_id: id,
      };
      var $btn = $(this).prop("disabled", true);
      $.post(window.DSAjax && DSAjax.ajaxUrl ? DSAjax.ajaxUrl : "", payload)
        .done(function (resp) {
          if (resp && resp.success) {
            location.reload();
          } else {
            dtShowToast(
              resp && resp.data && resp.data.message
                ? resp.data.message
                : "Delete failed",
              "error"
            );
          }
        })
        .fail(function () {
          dtShowToast("Delete failed", "error");
        })
        .always(function () {
          $btn.prop("disabled", false);
        });
    });
  })();

  // ===== Agent Replies: Inline Edit/Delete via AJAX =====
  (function initAgentReplyActions() {
    $(document).on("click", ".js-reply-edit", function (e) {
      e.preventDefault();
      var id = $(this).data("comment-id");
      var curr = $(this).data("content") || "";
      var content = window.prompt("Edit reply", curr);
      if (content === null) return;
      $.post(window.DSAjax && DSAjax.ajaxUrl ? DSAjax.ajaxUrl : "", {
        action: "ds_update_review",
        _ajax_nonce: window.DSAjax && DSAjax.nonce ? DSAjax.nonce : "",
        comment_id: id,
        content: content,
      })
        .done(function (resp) {
          if (resp && resp.success) location.reload();
          else
            dtShowToast(
              (resp && resp.data && resp.data.message) || "Failed",
              "error"
            );
        })
        .fail(function () {
          dtShowToast("Failed", "error");
        });
    });
    $(document).on("click", ".js-reply-delete", function (e) {
      e.preventDefault();
      if (!window.confirm("Delete this reply?")) return;
      var id = $(this).data("comment-id");
      $.post(window.DSAjax && DSAjax.ajaxUrl ? DSAjax.ajaxUrl : "", {
        action: "ds_delete_review",
        _ajax_nonce: window.DSAjax && DSAjax.nonce ? DSAjax.nonce : "",
        comment_id: id,
      })
        .done(function (resp) {
          if (resp && resp.success) location.reload();
          else
            dtShowToast(
              (resp && resp.data && resp.data.message) || "Failed",
              "error"
            );
        })
        .fail(function () {
          dtShowToast("Failed", "error");
        });
    });
  })();

  // ===== Single Service: Review & Enquiry submissions =====
  (function initSingleServiceForms() {
    // Review form
    $(document).on("submit", "#ds-add-review-form", function (e) {
      e.preventDefault();
      var $form = $(this);
      var data = {
        action: "ds_submit_review",
        _ajax_nonce:
          $form.find('input[name="_ajax_nonce"]').val() ||
          (window.DSAjax && DSAjax.nonce) ||
          "",
        post_id: $form.find('input[name="post_id"]').val(),
        rating: $form.find('input[name="rating"]:checked').val(),
        author: $form.find('input[name="author"]').val(),
        email: $form.find('input[name="email"]').val(),
        content: $form.find('textarea[name="content"]').val(),
      };
      $.post(window.DSAjax && DSAjax.ajaxUrl ? DSAjax.ajaxUrl : "", data)
        .done(function (resp) {
          if (resp && resp.success) {
            try {
              $("#add_review").modal
                ? $("#add_review").modal("hide")
                : window.bootstrap
                ? new window.bootstrap.Modal(
                    document.getElementById("add_review")
                  ).hide()
                : null;
            } catch (_) {}
            dtShowToast(
              resp.data && resp.data.message ? resp.data.message : "Submitted",
              "success"
            );
            window.location.reload();
          } else {
            dtShowToast(
              resp && resp.data && resp.data.message
                ? resp.data.message
                : "Failed",
              "error"
            );
          }
        })
        .fail(function () {
          dtShowToast("Failed", "error");
        });
    });

    // Enquiry form
    $(document).on("submit", "#ds-enquire-form", function (e) {
      e.preventDefault();
      var $form = $(this);
      var phoneInput = $form.find('input[name="phone"]');
      var phone = $.trim(phoneInput.val());
      var phonePattern = /^\+?[0-9\s-]{7,15}$/;
      if (!phonePattern.test(phone)) {
        dtShowToast(
          window.DSAjax && DSAjax.i18n && DSAjax.i18n.invalidPhone
            ? DSAjax.i18n.invalidPhone
            : "Please enter a valid phone number (7-15 digits).",
          "error"
        );
        phoneInput.focus();
        return;
      }
      var data = {
        action: "ds_submit_enquiry",
        _ajax_nonce:
          $form.find('input[name="_ajax_nonce"]').val() ||
          (window.DSAjax && DSAjax.nonce) ||
          "",
        service_id: $form.find('input[name="service_id"]').val(),
        name: $form.find('input[name="name"]').val(),
        email: $form.find('input[name="email"]').val(),
        phone: phone,
        message: $form.find('textarea[name="message"]').val(),
      };
      $.post(window.DSAjax && DSAjax.ajaxUrl ? DSAjax.ajaxUrl : "", data)
        .done(function (resp) {
          if (resp && resp.success) {
            try {
              $("#enquire_modal").modal
                ? $("#enquire_modal").modal("hide")
                : window.bootstrap
                ? new window.bootstrap.Modal(
                    document.getElementById("enquire_modal")
                  ).hide()
                : null;
            } catch (_) {}
            dtShowToast(
              resp.data && resp.data.message ? resp.data.message : "Submitted",
              "success"
            );
            if (typeof $form[0].reset === "function") {
              $form[0].reset();
            }
            // restore logged-in autofill if needed
            var nameVal = $form.data("prefill-name");
            var emailVal = $form.data("prefill-email");
            if (nameVal) {
              $form.find('input[name="name"]').val(nameVal);
            }
            if (emailVal) {
              $form.find('input[name="email"]').val(emailVal);
            }
          } else {
            dtShowToast(
              resp && resp.data && resp.data.message
                ? resp.data.message
                : "Failed",
              "error"
            );
          }
        })
        .fail(function () {
          dtShowToast("Failed", "error");
        });
    });
    // View Details: force open modal even if Bootstrap JS not auto-wired
    $(document).on("click", "[data-view-details]", function (e) {
      var $a = $(this);
      // allow default data-bs-toggle to work if Bootstrap is present
      // if not, manually open modal
      try {
        if (window.bootstrap && typeof window.bootstrap.Modal === "function") {
          e.preventDefault();
          var el = document.getElementById("schedule_modal");
          if (el) {
            new window.bootstrap.Modal(el).show();
          }
        } else if ($.fn.modal) {
          e.preventDefault();
          $("#schedule_modal").modal("show");
        }
      } catch (_) {}
    });

    // Helper: get query string parameter from current URL
    function getQueryParam(name) {
      try {
        var url = window.location.href;
        if (window.URL && URL.prototype.searchParams) {
          var u = new URL(url);
          return u.searchParams.get(name);
        }
        var query = url.split("?")[1] || "";
        var pairs = query.split("&");
        for (var i = 0; i < pairs.length; i++) {
          var p = pairs[i].split("=");
          if (decodeURIComponent(p[0]) === name) {
            return typeof p[1] !== "undefined"
              ? decodeURIComponent(p[1].replace(/\+/g, " "))
              : "";
          }
        }
        return null;
      } catch (e) {
        return null;
      }
    }

    // Load More (dashboard bookings only: requires data-role)
    $(document).on("click", ".load-more-custom", function (e) {
      var $btn = $(this);
      var role = $btn.data("role");
      // If no role attribute, this is likely a normal front-end Load More link; let browser handle navigation
      if (!role) {
        return;
      }
      e.preventDefault();
      if ($btn.prop("disabled")) return;
      var page = parseInt($btn.data("page"), 10) || 1;
      var nextPage = page + 1;
      var payload = {
        action: "ds_dashboard_load_more",
        _ajax_nonce: window.DSAjax && DSAjax.nonce ? DSAjax.nonce : "",
        role: role,
        page: nextPage,
        search: getQueryParam("search") || getQueryParam("s") || "",
        status: getQueryParam("status") || "",
        from: getQueryParam("from") || "",
        to: getQueryParam("to") || "",
      };
      if (role === "agent" || role === "staff") {
        payload.sort = getQueryParam("sort") || "newest";
      }
      if (role === "agent") {
        delete payload.search;
        delete payload.from;
        delete payload.to;
      }
      $btn.prop("disabled", true).addClass("opacity-50");
      $.post(window.DSAjax && DSAjax.ajaxUrl ? DSAjax.ajaxUrl : "", payload)
        .done(function (resp) {
          if (!resp || !resp.success) {
            return;
          }
          var html = resp.data && resp.data.html ? resp.data.html : "";
          if (html) {
            $("#ds-bookings-list").append(html);
          }
          var hasMore = !!(resp.data && resp.data.has_more);
          if (hasMore) {
            $btn
              .data("page", nextPage)
              .prop("disabled", false)
              .removeClass("opacity-50");
          } else {
            $btn.closest(".load-more").remove();
          }
        })
        .fail(function () {
          $btn.prop("disabled", false).removeClass("opacity-50");
        });
    });
  })();

  // Events
  $(document).on("click", ".fe-day-generate", function () {
    var day = $(this).data("day");
    generateForDay(day);
  });
  $(document).on(
    "change",
    ".fe-day-start, .fe-day-end, .fe-day-interval",
    function () {
      var day = $(this).data("day");
      generateForDay(day);
    }
  );
  $(document).on(
    "change",
    "select[name='duration_hours'], select[name='duration_minutes']",
    function () {
      $(".fe-day-generate").each(function () {
        generateForDay($(this).data("day"));
      });
    }
  );
  $(document).on("click", ".fe-day-delete-all", function () {
    var day = $(this).data("day");
    var $wrap = $("#fe-day-slots-" + day);
    $wrap.empty();
    $(
      "#ds-add-service-form input[name='service_day[" +
        day +
        "][time_slots][]']"
    ).remove();
    var $start = $(".fe-day-start[data-day='" + day + "']");
    var $end = $(".fe-day-end[data-day='" + day + "']");
    var $interval = $(".fe-day-interval[data-day='" + day + "']");
    var $spaces = $(".fe-day-spaces[data-day='" + day + "']");
    $start.val("").trigger("change");
    $end.val("").trigger("change");
    if (!$interval.find("option[value='']").length) {
      $interval.prepend('<option value="">—</option>');
    }
    $interval.val("").trigger("change");
    $spaces.val("");
    $wrap.append('<em class="text-muted">No slots generated yet.</em>');
  });
  $(document).on("click", ".fe-remove-slot", function () {
    var day = $(this).data("day");
    var slot = $(this).data("slot");
    $(this).closest("span.badge").remove();
    $(
      "input[name='service_day[" +
        day +
        "][time_slots][]'][value='" +
        slot +
        "']"
    ).remove();
    var $wrap = $("#fe-day-slots-" + day);
    if (
      !$wrap.find("input[name='service_day[" + day + "][time_slots][]']").length
    ) {
      $wrap.append("<em>No slots generated yet.</em>");
    }
  });

  // Validation on submit (only for Add Service form)
  $(document).on("submit", "#ds-add-service-form", function (e) {
    var errors = [];

    var serviceName = $.trim($("input[name='service_name']").val());
    if (!serviceName) {
      errors.push("Service name is required.");
    }

    var descEditor =
      window.tinyMCE && window.tinyMCE.get("service_description")
        ? window.tinyMCE.get("service_description")
        : null;
    var descText = descEditor
      ? descEditor.getContent({ format: "text" })
      : $("#service_description").val();
    if (!descText || !descText.replace(/\s+/g, "").length) {
      errors.push("Description is required.");
    }

    var price = $.trim($("input[name='price']").val());
    if (!price) {
      errors.push("Price is required.");
    }

    var category = $("select[name='category']").val();
    if (!category) {
      errors.push("Please select a category.");
    }

    var branches = $("select[name='branch[]']").val() || [];
    var hasBranch = branches.some(function (branch) {
      return branch !== null && String(branch).trim() !== "";
    });
    if (!hasBranch) {
      errors.push("Please select at least one branch.");
    }

    var dh = parseInt($("select[name='duration_hours']").val(), 10) || 0;
    var dm = parseInt($("select[name='duration_minutes']").val(), 10) || 0;
    if (dh + dm === 0) {
      errors.push("Please select a service duration (hours/minutes).");
    }
    var missingTime = false;
    var missingDuration = false;
    var invalidSlots = false;
    var hasAtLeastOneSlot = false;
    $(".dc-childaccordion_2 .dc-subpaneltitle_2").each(function () {
      var header = $(this);
      var day = (header.attr("id") || "").replace("heading-", "");
      var checked = header.find("input.day-toggle").is(":checked");
      if (!day || !checked) return;
      var s = $(".fe-day-start[data-day='" + day + "']").val();
      var e2 = $(".fe-day-end[data-day='" + day + "']").val();
      var iv = $(".fe-day-interval[data-day='" + day + "']").val();
      if (!s || !e2) {
        missingTime = true;
        return;
      }
      var D = dh * 60 + dm;
      if (D <= 0) {
        missingDuration = true;
        return;
      }
      var $wrap = $("#fe-day-slots-" + day);
      var slotInputs = $wrap.find(
        "input[name='service_day[" + day + "][time_slots][]']"
      );
      if (slotInputs.length) {
        hasAtLeastOneSlot = true;
        return;
      }
      var start = parseHM(s),
        end = parseHM(e2),
        I = parseInt(iv, 10) || 0;
      var out = [];
      if (!(start == null || end == null || end <= start)) {
        var t = start;
        while (t + D <= end) {
          var st = t,
            en = t + D;
          out.push(st + "-" + en);
          t = I <= 0 ? en : en + I;
        }
      }
      if (!out.length) {
        invalidSlots = true;
        return;
      }
      hasAtLeastOneSlot = true;
      generateForDay(day);
    });
    if (missingTime) {
      errors.push("Please set start and end time for each enabled day.");
    }
    if (missingDuration) {
      errors.push("Duration must be greater than 0 before generating slots.");
    }
    if (invalidSlots) {
      errors.push("Please generate valid slots for the enabled days.");
    }
    if (!hasAtLeastOneSlot) {
      errors.push("Please add slots for at least one day.");
    }
    if (errors.length) {
      e.preventDefault();
      showFormErrors(errors);
      return false;
    }
    showFormErrors([]);
  });

  // ===== Frontend Booking Page: Branch -> Categories -> Services (AJAX) =====
  (function initFrontendBooking() {
    var $branch = $("#ds-branch-select");
    if (typeof DSFE === "undefined") return;
    // If not on bookings page, still allow running on cart page
    var onCartPage = (function () {
      try {
        if (document.querySelector("form.woocommerce-cart-form")) return true;
        var href = window.location.href;
        var path =
          window.location.pathname +
          window.location.search +
          window.location.hash;
        if (window.DSFE && DSFE.cartUrl && href.indexOf(DSFE.cartUrl) === 0)
          return true;
        return /(\/|^)cart(\/|\?|#|$)/i.test(path);
      } catch (e) {
        return false;
      }
    })();
    if (!$branch.length && !onCartPage) return;

    var $tabs = $("#ds-category-tabs");
    var $grid = $("#ds-services-grid");
    var $categoryLoader = $("#ds-category-loader");
    var $servicesLoader = $("#ds-services-loader");
    var $drawer = $("#ds-booking-drawer");
    var $drawerBody = $("#ds-booking-drawer-body");
    var $overlay = $("#ds-drawer-overlay");
    var $branchTitle = $("#ds-branch-title");
    var $branchLocation = $("#ds-branch-location");

    function getSelectedBranchData() {
      if (!$branch.length) {
        return { id: 0, title: "", location: "", image: "" };
      }
      var $opt = $branch.find("option:selected");
      if (!$opt.length) {
        return { id: 0, title: "", location: "", image: "" };
      }
      return {
        id: parseInt($opt.val(), 10) || 0,
        title: $opt.data("title") || $opt.text() || "",
        location: $opt.data("location") || "",
        image: $opt.data("image") || "",
      };
    }

    var currentBranch = getSelectedBranchData();

    function setLoading($el, on) {
      if (on) {
        $el.addClass("opacity-50 pointer-events-none");
      } else {
        $el.removeClass("opacity-50 pointer-events-none");
      }
    }

    function toggleOverlay($overlay, on) {
      if (!$overlay.length) return;
      if (on) {
        $overlay.removeClass("d-none");
      } else {
        $overlay.addClass("d-none");
      }
    }

    function renderInlineLoader(message) {
      var text = message
        ? '<span class="mt-2 text-muted">' + message + "</span>"
        : "";
      return (
        '<div class="ds-inline-loader" role="status">' +
        '<span class="ds-spinner" aria-hidden="true"></span>' +
        text +
        "</div>"
      );
    }

    function loadCategories(branchId) {
      setLoading($tabs, true);
      toggleOverlay($categoryLoader, true);
      $.post(DSFE.ajaxUrl, {
        action: "ds_get_branch_categories",
        _ajax_nonce: DSFE.nonce,
        branch_id: parseInt(branchId, 10) || 0,
      })
        .done(function (resp) {
          $tabs.empty();
          if (!resp || !resp.success) {
            $tabs.append(
              '<div class="nav-item"><span class="category-nav-link disabled">No categories</span></div>'
            );
            $grid.html(
              '<div class="row g-3"><div class="col-12"><em>Unable to load services for this branch.</em></div></div>'
            );
            return;
          }
          var cats =
            resp.data && resp.data.categories ? resp.data.categories : [];
          if (!cats.length) {
            $tabs.append(
              '<div class="nav-item"><span class="category-nav-link disabled">No categories</span></div>'
            );
            $grid.html(
              '<div class="row g-3"><div class="col-12"><em>No services found for this branch.</em></div></div>'
            );
            return;
          }
          cats.forEach(function (c, idx) {
            var active = idx === 0 ? "active" : "";
            var li = $(
              '<div class="card category-booking-card d-flex justify-content-center align-items-center mb-0 ' +
                active +
                '"><div class="nav-item card-body text-center p-3"></div></div>'
            );
            var a = $(
              '<a class="category-nav-link text-center ' +
                active +
                '" href="#" data-cat-id="' +
                c.id +
                '">'
            );
            a.html(
              $("<h5>").text(c.name)[0].outerHTML +
                '<small class="text-muted">' +
                (c.count || 0) +
                " Services</small>"
            );
            li.find(".card-body").append(a);
            $tabs.append(li);
          });
          // auto-load first category
          var first = cats[0];
          if (first) {
            loadServices(branchId, first.id);
          }
        })
        .fail(function () {
          $tabs
            .empty()
            .append(
              '<div class="nav-item"><span class="category-nav-link disabled">Failed to load categories.</span></div>'
            );
        })
        .always(function () {
          setLoading($tabs, false);
          toggleOverlay($categoryLoader, false);
        });
    }

    function loadServices(branchId, categoryId) {
      setLoading($grid, true);
      toggleOverlay($servicesLoader, true);
      $.post(DSFE.ajaxUrl, {
        action: "ds_get_services",
        _ajax_nonce: DSFE.nonce,
        branch_id: parseInt(branchId, 10) || 0,
        category_id: parseInt(categoryId, 10) || 0,
      })
        .done(function (resp) {
          if (!resp || !resp.success) {
            $grid.html(
              '<div class="col-12"><em>Failed to load services.</em></div>'
            );
            return;
          }
          $grid.html(resp.data && resp.data.html ? resp.data.html : "");
          // Mark already selected services as Edit with tick
          if (window.DSBookingCart) {
            Object.keys(window.DSBookingCart).forEach(function (key) {
              var sid = parseInt(key, 10);
              if (sid) {
                markServiceCardSelected(sid, true);
              }
            });
          }
          updateSummaryBar();
        })
        .fail(function () {
          $grid.html(
            '<div class="col-12"><em>Failed to load services.</em></div>'
          );
        })
        .always(function () {
          setLoading($grid, false);
          toggleOverlay($servicesLoader, false);
        });
    }

    // Branch change
    $branch.on("change", function () {
      var bid = $(this).val();
      // Update header title/location from selected option data attributes
      var $opt = $(this).find("option:selected");
      var t = $opt.data("title") || $opt.text();
      var loc = $opt.data("location") || "";
      var img = $opt.data("image") || "";
      if ($branchTitle.length) $branchTitle.text(t);
      if ($branchLocation.length) $branchLocation.text(loc);
      if ($("#ds-branch-image").length && img) {
        $("#ds-branch-image").attr("src", img).attr("alt", t);
      }
      currentBranch = getSelectedBranchData();
      loadCategories(bid);
    });

    // Category click
    $tabs.on("click", "a.category-nav-link", function (e) {
      e.preventDefault();
      var $a = $(this);
      $tabs.find("div.category-booking-card").removeClass("active");
      $a.closest("div.category-booking-card").addClass("active");
      $tabs.find("a.category-nav-link").removeClass("active");
      $a.addClass("active");
      var catId = $a.data("cat-id");
      var bid = $branch.val();
      loadServices(bid, catId);
    });

    // Drawer helpers
    function openDrawer() {
      $overlay.show();
      requestAnimationFrame(function () {
        $drawer.css("right", "0");
      });
    }
    function closeDrawer() {
      $drawer.css("right", "-420px");
      $overlay.hide();
    }
    $(document).on("click", ".ds-drawer-close", function () {
      closeDrawer();
    });
    $overlay.on("click", function () {
      closeDrawer();
    });

    // In-memory cart for multi-service selection
    var DSBookingCart = window.DSBookingCart || {};
    // Restore from localStorage
    try {
      var saved = localStorage.getItem("DSBookingCart");
      if (saved) {
        DSBookingCart = JSON.parse(saved) || {};
        window.DSBookingCart = DSBookingCart;
      } else {
        window.DSBookingCart = DSBookingCart;
      }
    } catch (e) {
      window.DSBookingCart = DSBookingCart;
    }

    function parseNumber(v) {
      var n = parseFloat(String(v).replace(/[^0-9.\-]/g, ""));
      return isNaN(n) ? 0 : n;
    }
    function formatMoney(n) {
      var sym = window.DSFE && DSFE.currencySymbol ? DSFE.currencySymbol : "$";
      return sym + (Math.round(n * 100) / 100).toFixed(2);
    }
    function formatDuration(mins) {
      mins = parseInt(mins, 10) || 0;
      var h = Math.floor(mins / 60);
      var m = mins % 60;
      return h + " Hrs " + m + " Mins";
    }
    $(document).on("click", "#ds-summary-continue", function (e) {
      e.preventDefault();
      e.stopPropagation();
      try {
        var payload = JSON.stringify(DSBookingCart || {});
        var keys = Object.keys(DSBookingCart || {});
        if (!keys.length) {
          window.location.href =
            window.DSFE && DSFE.cartUrl ? DSFE.cartUrl : window.location.href;
          return;
        }
        for (var i = 0; i < keys.length; i++) {
          var it = DSBookingCart[keys[i]];
          if (!it || !it.date || !it.slot) {
            dtShowToast(
              "Please select a date and time slot for all selected services before continuing.",
              "error"
            );
            return;
          }
        }
        $.post(DSFE.ajaxUrl, {
          action: "ds_wc_add_cart",
          _ajax_nonce: DSFE.nonce,
          cart: payload,
        })
          .done(function (resp) {
            if (resp && resp.success) {
              try {
                localStorage.removeItem("DSBookingCart");
              } catch (_) {}
              window.DSBookingCart = {};
              if (window.DSFE && DSFE.cartUrl)
                window.location.href = DSFE.cartUrl;
              else window.location.reload();
            } else {
              var msg =
                resp && resp.data && resp.data.message
                  ? resp.data.message
                  : "Failed to add to cart.";
              dtShowToast(msg, "error");
            }
          })
          .fail(function () {
            dtShowToast("Failed to add to cart. Please try again.", "error");
          });
      } catch (err) {
        dtShowToast("Failed to add to cart.", "error");
      }
    });
    function computeTotals() {
      var count = 0,
        totalMin = 0,
        totalCost = 0;
      Object.keys(DSBookingCart || {}).forEach(function (key) {
        var it = DSBookingCart[key];
        if (!it) return;
        count++;
        var basePrice = parseNumber(it.meta && it.meta.price);
        var durMin = parseInt(it.meta && it.meta.durMin, 10) || 0;
        var addonsPrice = 0,
          addonsMin = 0;
        (it.addonsCustom || []).forEach(function (a) {
          addonsPrice += parseNumber(a.price);
          addonsMin +=
            (parseInt(a.dur_h, 10) || 0) * 60 + (parseInt(a.dur_m, 10) || 0);
        });
        totalMin += durMin + addonsMin;
        totalCost += basePrice + addonsPrice;
      });
      return { count: count, totalMin: totalMin, totalCost: totalCost };
    }
    function updateSummaryBar() {
      var elCount = document.getElementById("ds-summary-count");
      var elDur = document.getElementById("ds-summary-duration");
      var elTot = document.getElementById("ds-summary-total");
      if (!(elCount && elDur && elTot)) return;
      var t = computeTotals();
      elCount.textContent = String(t.count);
      elDur.textContent = formatDuration(t.totalMin);
      elTot.textContent = formatMoney(t.totalCost);
      var bar = document.getElementById("ds-summary-bar");
      if (bar) {
        if (t.count > 0) {
          bar.style.display = ""; // Removes inline display to show the element
          bar.classList.add("d-flex"); // Ensure d-flex class is applied
        } else {
          bar.style.display = "none"; // Hide the element
          bar.classList.remove("d-flex"); // Remove d-flex class when hiding
        }
      }
    }

    function todayISO() {
      var d = new Date();
      var mm = (d.getMonth() + 1).toString().padStart(2, "0");
      var dd = d.getDate().toString().padStart(2, "0");
      return d.getFullYear() + "-" + mm + "-" + dd;
    }

    function setDrawerDefaults(sid) {
      // Default date
      var $date = $drawerBody.find(".ds-date-picker");
      if ($date.length) {
        var iso = todayISO();
        if (!$date.val()) $date.val(iso);
        $date.attr("min", iso);
      }
      // Prefill from cart
      var item = DSBookingCart[sid];
      if (!item) return;
      // Addons (custom)
      if (item.addonsCustom && item.addonsCustom.length) {
        $drawerBody.find(".ds-addon-chip[data-addon]").each(function () {
          try {
            var data = $(this).data("addon");
            if (typeof data === "string") data = JSON.parse(data);
            var title = data && data.title ? data.title : null;
            if (
              title &&
              item.addonsCustom.some(function (a) {
                return a.title === title;
              })
            ) {
              $(this).addClass("active");
            }
          } catch (e) {}
        });
      }
      // Amenities (taxonomy)
      if (item.amenities && item.amenities.length) {
        $drawerBody.find(".ds-addon-chip[data-term-id]").each(function () {
          var tid = parseInt($(this).data("term-id"), 10);
          if (item.amenities.indexOf(tid) !== -1) $(this).addClass("active");
        });
      }
      // Staff
      if (item.staff) {
        $drawerBody.find(".ds-staff-chip").each(function () {
          var uid = parseInt($(this).data("user-id"), 10);
          if (uid === item.staff) $(this).addClass("active");
        });
      }
      // Date
      if (item.date && $date.length) {
        $date.val(item.date);
      }
      // Slot
      if (item.slot) {
        $drawerBody.find(".ds-slot-chip").each(function () {
          if ($(this).data("slot") === item.slot) $(this).addClass("active");
        });
      }
      // After defaults, if date exists, load slots
      var chosen = $drawerBody.find(".ds-date-picker").val();
      if (chosen) {
        fetchSlotsForDate(sid, chosen);
      }
    }

    function markServiceCardSelected(sid, selected) {
      var $card = $grid
        .find('.ds-add-to-book[data-service-id="' + sid + '"]')
        .closest(".card");
      if (!$card.length) return;
      var $btn = $card.find(".ds-add-to-book");
      if (selected) {
        $card.addClass("ds-selected");
        // add tick badge if not exists
        if (!$card.find(".ds-selected-badge").length) {
          var badge = $('<span class="ds-selected-badge" />')
            .html("&#10003;")
            .css({
              position: "absolute",
              top: "8px",
              right: "8px",
              background: "#16a34a",
              color: "#fff",
              width: "22px",
              height: "22px",
              borderRadius: "50%",
              display: "inline-flex",
              alignItems: "center",
              justifyContent: "center",
              fontSize: "12px",
            });
          $card.css("position", "relative");
          $card.append(badge);
        }
        //$btn.text("Edit");
        $btn.html('<i class="ti ti-edit"></i>');
      } else {
        $card.removeClass("ds-selected");
        $card.find(".ds-selected-badge").remove();
        $btn.text("+");
      }
    }

    // Helper: read query param
    function getQuery(name) {
      try {
        return new URLSearchParams(window.location.search).get(name);
      } catch (e) {
        return null;
      }
    }

    // Reusable: open drawer and load booking content for a service id
    function openServiceDrawer(sid) {
      sid = parseInt(sid, 10) || 0;
      if (!sid) return;
      $drawerBody.html(renderInlineLoader("Loading service details..."));
      openDrawer();
      $.post(
        DSFE.ajaxUrl,
        {
          action: "ds_get_service_booking",
          _ajax_nonce: DSFE.nonce,
          service_id: sid,
        },
        function (resp) {
          if (!resp || !resp.success) {
            $drawerBody.html(
              renderInlineLoader("Failed to load service. Please try again.")
            );
            return;
          }
          $drawerBody.html(resp.data && resp.data.html ? resp.data.html : "");
          setDrawerDefaults(sid);
          $drawerBody.data("service-id", sid);
          // Stamp currently selected branch metadata onto hidden meta block for later persistence
          var $meta = $drawerBody.find("#ds-service-meta");
          if ($meta.length && currentBranch) {
            $meta.attr("data-branch-id", currentBranch.id || 0);
            $meta.attr("data-branch-title", currentBranch.title || "");
            $meta.attr("data-branch-location", currentBranch.location || "");
          }
          // init flatpickr if available
          if (window.flatpickr) {
            var $inline = $drawerBody.find(".ds-calendar-inline");
            var $dp = $drawerBody.find(".ds-date-picker");
            var today = todayISO();
            if ($inline.length) {
              var inlineFp = flatpickr($inline[0], {
                inline: true,
                dateFormat: "Y-m-d",
                minDate: today,
                defaultDate: $dp.val() || today,
                monthSelectorType: "static",
                disableMobile: true,
                onChange: function (selectedDates, dateStr) {
                  if (dateStr) {
                    $dp.val(dateStr);
                    fetchSlotsForDate(sid, dateStr);
                  }
                },
              });
              try {
                var initDate = $dp.val() || today;
                if (inlineFp && inlineFp.setDate)
                  inlineFp.setDate(initDate, true);
              } catch (_) {}
            } else if ($dp.length) {
              var fp = flatpickr($dp[0], {
                dateFormat: "Y-m-d",
                minDate: today,
                defaultDate: $dp.val() || today,
                monthSelectorType: "static",
                disableMobile: true,
                onChange: function (selectedDates, dateStr) {
                  if (dateStr) {
                    fetchSlotsForDate(sid, dateStr);
                  }
                },
              });
              try {
                var initDate2 = $dp.val() || today;
                if (fp && fp.setDate) {
                  fp.setDate(initDate2, true);
                } else {
                  $dp.val(initDate2);
                  fetchSlotsForDate(sid, initDate2);
                }
              } catch (_) {}
            }
          }
          // Show proper step
          var $activeTab = $drawerBody.find(".btn-group [data-step].active");
          $drawerBody.find(".ds-step").hide();
          if ($activeTab.length) {
            var step = $activeTab.data("step");
            if (step === "addons") $drawerBody.find(".ds-step-addons").show();
            else $drawerBody.find(".ds-step-datetime").show();
          } else {
            $drawerBody.find(".ds-step-addons").show();
          }
        }
      );
    }

    // Open drawer on + click using helper
    $grid.on("click", ".ds-add-to-book", function () {
      var sid = $(this).data("service-id");
      openServiceDrawer(sid);
    });

    // Allow image/title triggers to open drawer as well
    function isDrawerTriggerKey(evt) {
      var code = evt.key || evt.keyCode;
      return code === "Enter" || code === " " || code === 13 || code === 32;
    }

    $grid.on("click", ".ds-service-trigger", function (e) {
      e.preventDefault();
      var sid = $(this).data("service-id");
      openServiceDrawer(sid);
    });

    $grid.on("keydown", ".ds-service-trigger", function (e) {
      if (!isDrawerTriggerKey(e)) return;
      e.preventDefault();
      var sid = $(this).data("service-id");
      openServiceDrawer(sid);
    });

    // Helper: format slot label like '09:00 AM' from '09:00-09:30'
    function formatSlotLabel(s) {
      try {
        if (!s) return "";
        var raw = (s + "").trim();
        // Normalize common range separators to '-'
        raw = raw.replace(/\s+to\s+/i, "-").replace(/[–—]/g, "-");
        // Extract the first time token (supports 9, 09, 9:00, 09:00, with or without AM/PM)
        var match = raw.match(/(\d{1,2})(?::?(\d{2}))?\s*(AM|PM)?/i);
        if (!match) return raw;
        var h = parseInt(match[1], 10);
        var m = match[2] ? match[2] : "00";
        var ap = match[3] ? match[3].toUpperCase() : null;
        if (!ap) {
          ap = h >= 12 ? "PM" : "AM";
        }
        if (h === 0) h = 12;
        else if (h > 12) h = h - 12;
        var hh = h < 10 ? "0" + h : "" + h;
        return hh + ":" + (m.length === 1 ? "0" + m : m) + " " + ap;
      } catch (e) {
        return s;
      }
    }

    function formatDateLabel(dateStr) {
      if (!dateStr) return "";
      try {
        var parts = dateStr.split("-");
        if (parts.length !== 3) return dateStr;
        var dateObj = new Date(parts[0], parts[1] - 1, parts[2]);
        return dateObj.toLocaleDateString(undefined, {
          year: "numeric",
          month: "short",
          day: "numeric",
        });
      } catch (e) {
        return dateStr;
      }
    }

    function syncDrawerDateInputs(dateStr) {
      if (!dateStr) return;
      var $dateInput = $drawerBody.find(".ds-date-picker");
      if ($dateInput.length && $dateInput.val() !== dateStr) {
        $dateInput.val(dateStr);
      }
      var inlineCal = $drawerBody.find(".ds-calendar-inline");
      if (inlineCal.length && inlineCal[0]._flatpickr) {
        inlineCal[0]._flatpickr.setDate(dateStr, false);
      } else if (
        $dateInput.length &&
        $dateInput[0] &&
        $dateInput[0]._flatpickr
      ) {
        $dateInput[0]._flatpickr.setDate(dateStr, false);
      }
    }

    // Fetch slots for a given date
    function fetchSlotsForDate(sid, dateStr) {
      var $wrap = $drawerBody.find(".ds-slots-wrap");
      if (!$wrap.length) return;
      $wrap.empty().append(renderInlineLoader("Loading slots..."));
      var staffId = 0;
      var $activeStaff = $drawerBody.find(".ds-staff-chip.active");
      if ($activeStaff.length) {
        staffId = parseInt($activeStaff.first().data("user-id"), 10) || 0;
      }
      $.post(DSFE.ajaxUrl, {
        action: "ds_get_service_slots_for_date",
        _ajax_nonce: DSFE.nonce,
        service_id: sid,
        date: dateStr,
        staff_id: staffId,
      }).done(function (resp) {
        $wrap.empty();
        if (!resp || !resp.success) {
          $wrap.append("<em>No slots available.</em>");
          return;
        }
        var data = resp.data || {};
        var slots = data.slots || [];
        var assignedStaff = data.assigned_staff_id || 0;
        var fallbackDate = data.next_available_date || "";
        if (fallbackDate && fallbackDate !== dateStr) {
          var info =
            '<div class="alert alert-info w-100 small">' +
            "No slots on " +
            formatDateLabel(dateStr) +
            ". Showing next available on " +
            formatDateLabel(fallbackDate) +
            " instead." +
            "</div>";
          $wrap.append(info);
          syncDrawerDateInputs(fallbackDate);
          dateStr = fallbackDate;
        }
        if (!staffId && assignedStaff) {
          var $chips = $drawerBody.find(".ds-staff-chip");
          if ($chips.length) {
            $chips.removeClass("active");
            var $match = $chips.filter(
              '[data-user-id="' + assignedStaff + '"]'
            );
            if ($match.length) {
              $match.addClass("active");
            }
          }
        }
        if (!slots.length) {
          if (!fallbackDate) {
            $wrap.append("<em>No slots available.</em>");
          }
          return;
        }
        slots.forEach(function (s) {
          var label = formatSlotLabel(s);
          $wrap.append(
            '<button type="button" class="btn border col-4 ds-slot-chip" data-slot="' +
              s +
              '">' +
              label +
              "</button>"
          );
        });
      });
    }

    // Bind date change
    $(document).on("change", "#ds-booking-drawer .ds-date-picker", function () {
      var sid = $drawerBody.data("service-id");
      var dateStr = $(this).val();
      if (sid && dateStr) {
        fetchSlotsForDate(sid, dateStr);
      }
    });

    // Step switching inside drawer
    $(document).on(
      "click",
      "#ds-booking-drawer .btn-group [data-step]",
      function () {
        var $btn = $(this);
        var step = $btn.data("step");
        var $grp = $btn.closest(".btn-group");
        $grp.find(".btn").removeClass("active");
        $btn.addClass("active");
        var $wrap = $btn.closest("#ds-booking-drawer");
        $wrap.find(".ds-step").hide();
        if (step === "addons") {
          $wrap.find(".ds-step-addons").show();
        } else {
          var $staffChips = $wrap.find(".ds-staff-chip");
          if (
            $staffChips.length &&
            !$wrap.find(".ds-staff-chip.active").length
          ) {
            if (typeof dtShowToast === "function") {
              dtShowToast(
                "Please select a staff member or tap Auto Assign before continuing.",
                "info"
              );
            }
            $grp.find(".btn[data-step='addons']").addClass("active");
            $btn.removeClass("active");
            $wrap.find(".ds-step-addons").show();
            return;
          }
          $wrap.find(".ds-step-datetime").show();
          // Ensure date defaults and slots load when switching to Date & Time
          try {
            var sid = $drawerBody.data("service-id");
            var $dp = $wrap.find(".ds-date-picker");
            var $inline = $wrap.find(".ds-calendar-inline");
            var today = todayISO();
            if ($inline.length && $inline[0]._flatpickr) {
              var inlineInst = $inline[0]._flatpickr;
              var current = $dp.val() || today;
              inlineInst.setDate(current, true);
            } else if ($dp.length) {
              var inst = $dp[0] && $dp[0]._flatpickr ? $dp[0]._flatpickr : null;
              var val = $dp.val();
              var targetDate = val || today;
              if (inst && inst.setDate) {
                inst.setDate(targetDate, true);
              } else {
                $dp.val(targetDate).attr("min", today);
                if (sid && targetDate) fetchSlotsForDate(sid, targetDate);
              }
            }
          } catch (e) {}
        }
      }
    );

    // Basic interactions
    // Addons: multi-select toggle
    $(document).on("click", "#ds-booking-drawer .ds-addon-chip", function () {
      $(this).toggleClass("active");
    });
    // Slots: single-select
    $(document).on("click", "#ds-booking-drawer .ds-slot-chip", function () {
      var $chip = $(this);
      var $wrap = $chip.closest(".ds-slots-wrap");
      $wrap.find(".ds-slot-chip").removeClass("active");
      $chip.addClass("active");
    });
    // Single-select staff chips and refresh slots
    $(document).on("click", "#ds-booking-drawer .ds-staff-chip", function () {
      var $chip = $(this);
      var wasActive = $chip.hasClass("active");
      $chip
        .closest("#ds-booking-drawer")
        .find(".ds-staff-chip")
        .removeClass("active");
      if (!wasActive) $chip.addClass("active");
      var sid = $drawerBody.data("service-id");
      var dateStr = $drawerBody.find(".ds-date-picker").val();
      if (sid && dateStr) fetchSlotsForDate(sid, dateStr);
    });

    // Auto-assign: pick first staff with available slots for chosen date
    $(document).on("click", "#ds-booking-drawer .ds-auto-assign", function () {
      var $btn = $(this);
      var sid = $drawerBody.data("service-id");
      var dateStr = $drawerBody.find(".ds-date-picker").val() || todayISO();
      var $chips = $drawerBody.find(".ds-staff-chip");
      if (!$chips.length || !sid) return;
      var idx = 0;
      var found = false;
      var originalHtml = $btn.html();
      $btn.prop("disabled", true).html("Finding...");
      var $slotsWrap = $drawerBody.find(".ds-slots-wrap");
      $slotsWrap
        .empty()
        .append(renderInlineLoader("Checking staff availability..."));
      function finish() {
        $btn.prop("disabled", false).html(originalHtml);
      }
      function tryNext() {
        if (found) {
          finish();
          return;
        }
        if (idx >= $chips.length) {
          // none found; clear selection and refresh generic slots
          $drawerBody.find(".ds-staff-chip").removeClass("active");
          fetchSlotsForDate(sid, dateStr);
          finish();
          return;
        }
        var $c = $chips.eq(idx++);
        var staffId = parseInt($c.data("user-id"), 10) || 0;
        $.post(DSFE.ajaxUrl, {
          action: "ds_get_service_slots_for_date",
          _ajax_nonce: DSFE.nonce,
          service_id: sid,
          date: dateStr,
          staff_id: staffId,
        })
          .done(function (resp) {
            var slots =
              resp && resp.success && resp.data && resp.data.slots
                ? resp.data.slots
                : [];
            if (slots && slots.length) {
              found = true;
              // Trigger the normal selection flow so all handlers run
              $drawerBody.find(".ds-staff-chip").removeClass("active");
              $c.trigger("click");
              // Render slots immediately for responsiveness
              $slotsWrap.empty();
              slots.forEach(function (s) {
                var label = formatSlotLabel(s);
                $slotsWrap.append(
                  '<button type="button" class="btn border ds-slot-chip" data-slot="' +
                    s +
                    '">' +
                    label +
                    "</button>"
                );
              });
              finish();
            } else {
              tryNext();
            }
          })
          .fail(function () {
            tryNext();
          });
      }
      tryNext();
    });
    $(document).on("click", "#ds-booking-drawer .ds-reset", function () {
      $(
        "#ds-booking-drawer .ds-addon-chip, #ds-booking-drawer .ds-staff-chip, #ds-booking-drawer .ds-slot-chip"
      ).removeClass("active");
    });
    $(document).on("click", "#ds-booking-drawer .ds-add-to-cart", function () {
      var sid = $drawerBody.data("service-id");
      if (!sid) return;
      var item = {
        serviceId: sid,
        addonsCustom: [],
        amenities: [],
        staff: null,
        date: null,
        slot: null,
        meta: {},
      };
      // meta from drawer
      var metaEl = $drawerBody.find("#ds-service-meta");
      if (metaEl.length) {
        item.meta = {
          title: metaEl.data("title") || "",
          price: metaEl.data("price") || 0,
          durMin: metaEl.data("dur-min") || 0,
          thumb: metaEl.data("thumb") || "",
        };
      }
      // collect add-ons custom
      $drawerBody.find(".ds-addon-chip.active[data-addon]").each(function () {
        try {
          var data = $(this).data("addon");
          if (typeof data === "string") data = JSON.parse(data);
          if (data && data.title)
            item.addonsCustom.push({
              title: data.title,
              price: data.price || "",
              dur_h: data.dur_h || 0,
              dur_m: data.dur_m || 0,
            });
        } catch (e) {}
      });
      // collect amenities
      $drawerBody.find(".ds-addon-chip.active[data-term-id]").each(function () {
        var tid = parseInt($(this).data("term-id"), 10);
        if (tid) item.amenities.push(tid);
      });
      // staff
      var $st = $drawerBody.find(".ds-staff-chip.active").first();
      if ($st.length) item.staff = parseInt($st.data("user-id"), 10);
      // date
      var $date = $drawerBody.find(".ds-date-picker");
      var dateVal = $date.length ? $date.val() : "";
      var $slot = $drawerBody.find(".ds-slot-chip.active").first();
      if (!dateVal) {
        dtShowToast(
          "Please select a date before adding this service.",
          "error"
        );
        return;
      }
      if (!$slot.length) {
        dtShowToast(
          "Please select a time slot before adding this service.",
          "error"
        );
        return;
      }
      item.date = dateVal;
      item.slot = $slot.data("slot");
      if (!currentBranch || !currentBranch.id) {
        currentBranch = getSelectedBranchData();
      }
      item.branchId = currentBranch.id;
      item.branchTitle = currentBranch.title;
      item.branchLocation = currentBranch.location;

      DSBookingCart[sid] = item;
      try {
        localStorage.setItem("DSBookingCart", JSON.stringify(DSBookingCart));
      } catch (e) {}
      markServiceCardSelected(sid, true);
      closeDrawer();
      updateSummaryBar();
    });

    // initial load
    var initBranch = $branch.val();
    if (initBranch) {
      loadCategories(initBranch);
    }
    // On initial load, update summary from persisted cart
    updateSummaryBar();

    // Auto-open drawer if URL has ?service_id=ID
    var preselectSid = getQuery("service_id");
    if (preselectSid) {
      openServiceDrawer(preselectSid);
    }

    // ===== Cart Page Render =====
    function isCartPage() {
      try {
        if (document.querySelector("form.woocommerce-cart-form")) return true;
        var href = window.location.href;
        var path =
          window.location.pathname +
          window.location.search +
          window.location.hash;
        if (window.DSFE && DSFE.cartUrl && href.indexOf(DSFE.cartUrl) === 0)
          return true;
        return /(\/|^)cart(\/|\?|#|$)/i.test(path);
      } catch (e) {
        return false;
      }
    }
    function renderAppointmentsCart() {
      if (!isCartPage()) return;
      var host = document.querySelector("#ds-appointments-cart");
      if (!host) {
        host = document.createElement("div");
        host.id = "ds-appointments-cart";
        var target =
          document.querySelector(".woocommerce") ||
          document.querySelector("#primary") ||
          document.querySelector("main") ||
          document.body;
        target.insertBefore(host, target.firstChild);
      }
      var t = computeTotals();
      var sym = window.DSFE && DSFE.currencySymbol ? DSFE.currencySymbol : "$";
      var html = "";
      html +=
        '<a href="javascript:history.back()" class="d-inline-block mb-3">&larr; Back</a>';
      html += '<h3 class="mb-3">Your Appointments</h3>';
      var keys = Object.keys(DSBookingCart || {});
      if (!keys.length) {
        html +=
          '<div class="alert alert-info">No appointments in your cart yet.</div>';
      }
      keys.forEach(function (key) {
        var it = DSBookingCart[key];
        if (!it) return;
        var dur = parseInt(it.meta && it.meta.durMin, 10) || 0;
        (it.addonsCustom || []).forEach(function (a) {
          dur +=
            (parseInt(a.dur_h, 10) || 0) * 60 + (parseInt(a.dur_m, 10) || 0);
        });
        var price = parseNumber(it.meta && it.meta.price);
        (it.addonsCustom || []).forEach(function (a) {
          price += parseNumber(a.price);
        });
        html +=
          '\n<div class="card mb-3">\n  <div class="card-body d-flex justify-content-between align-items-start">';
        html += '<div class="d-flex">';
        if (it.meta && it.meta.thumb) {
          html +=
            '<img src="' +
            it.meta.thumb +
            '" width="72" height="72" class="rounded me-3" style="object-fit:cover;" />';
        }
        html += "<div>";
        html +=
          '<div class="fw-semibold">' +
          (it.meta && it.meta.title ? it.meta.title : "Service") +
          "</div>";
        if (it.staff) {
          html += '<div class="text-muted small">Staff #' + it.staff + "</div>";
        }
        if (it.date) {
          html += '<div class="text-muted small">' + it.date + "</div>";
        }
        if (it.slot) {
          html += '<div class="text-muted small">' + it.slot + "</div>";
        }
        html +=
          '<div class="text-muted small">' + formatDuration(dur) + "</div>";
        html += "</div></div>";
        html += '<div class="text-end">';
        html +=
          '<div class="fw-semibold">' +
          sym +
          (Math.round(price * 100) / 100).toFixed(2) +
          "</div>";
        html += '<div class="mt-2">';
        html +=
          '<button class="btn btn-sm btn-outline-danger" data-ds-remove="' +
          key +
          '">Delete</button>';
        html += "</div></div>";
        html += "</div></div>";
      });
      html +=
        '<div class="border-top pt-3 d-flex justify-content-between align-items-center">';
      html +=
        "<div>Services : " +
        t.count +
        " &nbsp; Duration : " +
        formatDuration(t.totalMin) +
        " &nbsp; Est. Total : " +
        formatMoney(t.totalCost) +
        "</div>";
      html +=
        '<a href="' +
        (window.DSFE && DSFE.checkoutUrl ? DSFE.checkoutUrl : "#") +
        '" class="btn btn-warning">Checkout</a>';
      html += "</div>";
      host.innerHTML = html;
    }
    document.addEventListener("click", function (e) {
      var btn = e.target.closest("[data-ds-remove]");
      if (!btn) return;
      var sid = btn.getAttribute("data-ds-remove");
      if (sid && DSBookingCart[sid]) {
        delete DSBookingCart[sid];
        try {
          localStorage.setItem("DSBookingCart", JSON.stringify(DSBookingCart));
        } catch (err) {}
        updateSummaryBar();
        // renderAppointmentsCart();
      }
    });
    // Render if on cart page
    // renderAppointmentsCart();
  })();
});