var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

// src/components/pisellRecordBoard/shellFrame/Calendar/utils/bookingCalendarDateUtils.ts
var bookingCalendarDateUtils_exports = {};
__export(bookingCalendarDateUtils_exports, {
  addDays: () => addDays,
  assignDayOverlapLanesInPlace: () => assignDayOverlapLanesInPlace,
  buildDayRowTopMap: () => buildDayRowTopMap,
  buildOverlayBookings: () => buildOverlayBookings,
  buildTimeOptions: () => buildTimeOptions,
  clusterDayCourtOverlaysByOverlap: () => clusterDayCourtOverlaysByOverlap,
  courtDaySummary: () => courtDaySummary,
  currentTimeLabel: () => currentTimeLabel,
  dayAndHourToDayjs: () => dayAndHourToDayjs,
  dayOccupancy: () => dayOccupancy,
  dayOverlayHiddenKey: () => dayOverlayHiddenKey,
  dayOverlayLaneMetrics: () => dayOverlayLaneMetrics,
  effectiveDayOverlayDisplayLaneCount: () => effectiveDayOverlayDisplayLaneCount,
  fmtDate: () => fmtDate,
  formatDateLoose: () => formatDateLoose,
  formatHourLabel: () => formatHourLabel,
  formatTimelineStamp: () => formatTimelineStamp,
  getBookingDetailData: () => getBookingDetailData,
  getBookingSegmentForDate: () => getBookingSegmentForDate,
  getDisplayStatusTag: () => getDisplayStatusTag,
  getLateMinutes: () => getLateMinutes,
  getViewNowReference: () => getViewNowReference,
  groupSelectedSlots: () => groupSelectedSlots,
  hasConflict: () => hasConflict,
  isFreeSelectionContiguousSameResource: () => isFreeSelectionContiguousSameResource,
  isHourInTimelineSlots: () => isHourInTimelineSlots,
  maxConcurrentBookingsOnCourt: () => maxConcurrentBookingsOnCourt,
  monthCalendarDays: () => monthCalendarDays,
  monthGrid: () => monthGrid,
  overlappingBookings: () => overlappingBookings,
  parseDate: () => parseDate,
  rangeDays: () => rangeDays,
  sameSlot: () => sameSlot,
  startOfWeek: () => startOfWeek,
  statusTagMetaParts: () => statusTagMetaParts
});
module.exports = __toCommonJS(bookingCalendarDateUtils_exports);
var import_dayjs = __toESM(require("dayjs"));
var import_utils = require("../../../../pisellTimeNavigator/utils");
var import_calendarI18n = require("../calendarI18n");
var import_bookingCalendarConstants = require("../const/bookingCalendarConstants");
function fmtDate(date) {
  const y = date.getFullYear();
  const m = `${date.getMonth() + 1}`.padStart(2, "0");
  const d = `${date.getDate()}`.padStart(2, "0");
  return `${y}-${m}-${d}`;
}
function getViewNowReference() {
  const d = /* @__PURE__ */ new Date();
  return {
    dateKey: fmtDate(d),
    hourFloat: d.getHours() + d.getMinutes() / 60 + d.getSeconds() / 3600
  };
}
function parseDate(str) {
  const [y, m, d] = str.split("-").map(Number);
  return new Date(y, m - 1, d);
}
function addDays(date, n) {
  const next = new Date(date);
  next.setDate(next.getDate() + n);
  return next;
}
function startOfWeek(date) {
  const d = new Date(date);
  const day = d.getDay();
  const diff = day === 0 ? -6 : 1 - day;
  d.setDate(d.getDate() + diff);
  d.setHours(0, 0, 0, 0);
  return d;
}
function rangeDays(anchor) {
  const start = startOfWeek(anchor);
  return Array.from({ length: 7 }, (_, i) => addDays(start, i));
}
function monthCalendarDays(anchor) {
  const base = anchor instanceof Date && !Number.isNaN(anchor.getTime()) ? anchor : /* @__PURE__ */ new Date();
  const y = base.getFullYear();
  const m = base.getMonth();
  const last = new Date(y, m + 1, 0).getDate();
  return Array.from({ length: last }, (_, i) => new Date(y, m, i + 1));
}
function isHourInTimelineSlots(hour, hourSlots) {
  if (!(hourSlots == null ? void 0 : hourSlots.length)) return false;
  return hourSlots.some((x) => Math.abs(x - hour) < 1e-6);
}
function formatHourLabel(hour) {
  const totalMin = Math.round(hour * 60);
  const h = Math.floor(totalMin / 60);
  const m = totalMin % 60;
  const clockH = (h % 24 + 24) % 24;
  return `${String(clockH).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
}
function buildTimeOptions(start = 8, end = 20, sliceMinutes = import_bookingCalendarConstants.timeSliceMinutes) {
  const out = [];
  for (let m = start * 60; m <= end * 60; m += sliceMinutes) out.push(m / 60);
  return out;
}
function monthGrid(date, bookings, courtsList = [], hourSlotsIn, slotStepHours = 1) {
  const hourSlots = hourSlotsIn && hourSlotsIn.length ? hourSlotsIn : (0, import_utils.buildCalendarTimelineSlotStartsFromRange)(
    import_bookingCalendarConstants.DEFAULT_CALENDAR_TIMELINE,
    date,
    60
  );
  const year = date.getFullYear();
  const month = date.getMonth();
  const first = new Date(year, month, 1);
  const start = startOfWeek(first);
  const days = Array.from({ length: 35 }, (_, i) => addDays(start, i));
  return days.map((day) => {
    const key = fmtDate(day);
    const dayBookings = bookings.filter(
      (b) => b.date === key && b.kind === "booking"
    );
    const blocks = bookings.filter((b) => b.date === key && b.kind === "block");
    const fullyBookedCourts = courtsList.filter((court) => {
      const items = bookings.filter(
        (b) => b.courtId === court.id && b.date === key
      );
      const occupied = /* @__PURE__ */ new Set();
      items.forEach((b) => {
        (hourSlots || []).forEach((slotStart) => {
          const seg = getBookingSegmentForDate(b, key);
          if (seg && seg.startHour < slotStart + slotStepHours && seg.endHour > slotStart) {
            occupied.add(slotStart);
          }
        });
      });
      const slotCount = (hourSlots == null ? void 0 : hourSlots.length) ?? 14;
      return occupied.size >= slotCount;
    }).length;
    return {
      date: day,
      key,
      inMonth: day.getMonth() === month,
      bookingCount: dayBookings.length,
      blockCount: blocks.length,
      fullyBooked: fullyBookedCourts >= courtsList.length
    };
  });
}
function getBookingSegmentForDate(item, dateKey) {
  if (!item.endDate || item.endDate === item.date) {
    if (item.date !== dateKey) return null;
    return {
      startHour: item.startHour,
      endHour: item.endHour,
      continuesPrev: false,
      continuesNext: false
    };
  }
  if (dateKey < item.date || dateKey > item.endDate) return null;
  if (dateKey === item.date)
    return {
      startHour: item.startHour,
      endHour: 24,
      continuesPrev: false,
      continuesNext: true
    };
  if (dateKey === item.endDate)
    return {
      startHour: 0,
      endHour: item.endHour,
      continuesPrev: true,
      continuesNext: false
    };
  return {
    startHour: 0,
    endHour: 24,
    continuesPrev: true,
    continuesNext: true
  };
}
function dayOccupancy(courtId, dateKey, bookings, hourSlots, slotStepHours) {
  const items = bookings.filter(
    (b) => b.courtId === courtId && getBookingSegmentForDate(b, dateKey)
  );
  const map = {};
  const stepH = slotStepHours > 0 ? slotStepHours : 1;
  (hourSlots || []).forEach((hour) => {
    map[hour] = items.find((b) => {
      const seg = getBookingSegmentForDate(b, dateKey);
      return seg && seg.startHour < hour + stepH && seg.endHour > hour;
    }) || null;
  });
  return map;
}
function groupSelectedSlots(slots, slotStepHours = 1) {
  const stepH = slotStepHours > 0 ? slotStepHours : 1;
  const sorted = [...slots].sort((a, b) => {
    if (a.date !== b.date) return a.date.localeCompare(b.date);
    if (a.courtId !== b.courtId) return a.courtId.localeCompare(b.courtId);
    return a.hour - b.hour;
  });
  const groups = [];
  sorted.forEach((slot) => {
    const prev = groups[groups.length - 1];
    if (prev && prev.courtId === slot.courtId && prev.date === slot.date && Math.abs(prev.endHour - slot.hour) < 1e-6)
      prev.endHour = slot.hour + stepH;
    else
      groups.push({
        courtId: slot.courtId,
        date: slot.date,
        startHour: slot.hour,
        endHour: slot.hour + stepH
      });
  });
  return groups;
}
function isFreeSelectionContiguousSameResource(slots, slotStepHours = 1) {
  if (!Array.isArray(slots) || slots.length === 0) return false;
  return groupSelectedSlots(slots, slotStepHours).length === 1;
}
function hasConflict(bookings, courtId, date, startHour, endHour, ignoreId = null) {
  return bookings.some((b) => {
    if (b.id === ignoreId) return false;
    if (b.kind !== "booking") return false;
    if (b.courtId !== courtId || b.date !== date) return false;
    return !(endHour <= b.startHour || startHour >= b.endHour);
  });
}
function overlappingBookings(bookings, courtId, date, startHour, endHour) {
  return bookings.filter((b) => {
    if (b.kind !== "booking") return false;
    if (b.courtId !== courtId || b.date !== date) return false;
    return !(endHour <= b.startHour || startHour >= b.endHour);
  });
}
function courtDaySummary(courtId, dateKey, bookings, hourSlots, slotStepHours) {
  const occ = dayOccupancy(
    courtId,
    dateKey,
    bookings,
    hourSlots,
    slotStepHours
  );
  const items = bookings.filter((b) => b.courtId === courtId && b.date === dateKey).sort((a, b) => a.startHour - b.startHour);
  const occupiedHours = (hourSlots || []).filter(
    (h) => occ[h] && isHourInTimelineSlots(h, hourSlots)
  );
  const hasBlock = occupiedHours.some((h) => {
    var _a;
    return ((_a = occ[h]) == null ? void 0 : _a.kind) === "block";
  });
  const slotCount = (hourSlots == null ? void 0 : hourSlots.length) ?? 0;
  const fullyBooked = slotCount > 0 && occupiedHours.length >= slotCount;
  return { occupiedHours, hasBlock, fullyBooked, items };
}
function sameSlot(a, b) {
  return a.courtId === b.courtId && a.date === b.date && a.hour === b.hour;
}
function dayAndHourToDayjs(dateStr, hourFloat) {
  const h = Math.floor(hourFloat);
  const m = Math.round((hourFloat - h) * 60);
  const mm = Math.min(59, Math.max(0, m));
  return (0, import_dayjs.default)(
    `${dateStr} ${String(h).padStart(2, "0")}:${String(mm).padStart(2, "0")}`,
    "YYYY-MM-DD HH:mm"
  );
}
function getDisplayStatusTag(item, dateKey) {
  if (item.kind !== "booking") return item.statusTag || null;
  if (item.statusTag === "checked_in" || item.statusTag === "cancel")
    return item.statusTag;
  const { dateKey: todayKey, hourFloat: nowHour } = getViewNowReference();
  if (dateKey !== todayKey) return item.statusTag || null;
  const seg = getBookingSegmentForDate(item, dateKey);
  if (!seg) return item.statusTag || null;
  const lateThreshold = item.startHour + 5 / 60;
  if (nowHour > seg.endHour) return "no_show";
  if (nowHour > lateThreshold) return "late";
  return item.statusTag || null;
}
function getLateMinutes(item, dateKey) {
  if (getDisplayStatusTag(item, dateKey) !== "late") return null;
  const seg = getBookingSegmentForDate(item, dateKey);
  if (!seg) return null;
  const lateThreshold = seg.startHour + 5 / 60;
  const { hourFloat: nowHour } = getViewNowReference();
  return Math.max(1, Math.floor((nowHour - lateThreshold) * 60));
}
function currentTimeLabel() {
  const d = /* @__PURE__ */ new Date();
  return `${String(d.getHours()).padStart(2, "0")}:${String(
    d.getMinutes()
  ).padStart(2, "0")}`;
}
function formatDateLoose(str) {
  if (!str) return "";
  const d = parseDate(str);
  return `${d.getFullYear()} ${String(d.getMonth() + 1).padStart(
    2,
    "0"
  )} ${String(d.getDate()).padStart(2, "0")}`;
}
function formatTimelineStamp(value) {
  if (!value) return { date: "--", time: "--" };
  const normalized = String(value).replace("T", " ");
  const [date = "--", time = "--"] = normalized.split(" ");
  return { date, time: time.slice(0, 5) || "--" };
}
function assignDayOverlapLanesInPlace(overlays) {
  const byCourt = /* @__PURE__ */ new Map();
  for (const o of overlays) {
    const list = byCourt.get(o.courtId);
    if (list) list.push(o);
    else byCourt.set(o.courtId, [o]);
  }
  for (const list of Array.from(byCourt.values())) {
    list.sort((a, b) => {
      if (a.start !== b.start) return a.start - b.start;
      return b.span - a.span;
    });
    const laneEnds = [];
    for (const o of list) {
      const oEnd = o.start + o.span;
      let lane = 0;
      while (lane < laneEnds.length && laneEnds[lane] > o.start) {
        lane++;
      }
      if (lane === laneEnds.length) {
        laneEnds.push(oEnd);
      } else {
        laneEnds[lane] = oEnd;
      }
      o.lane = lane;
    }
    let maxL = 0;
    for (const o of list) {
      maxL = Math.max(maxL, o.lane ?? 0);
    }
    const laneCount = Math.max(1, maxL + 1);
    for (const o of list) {
      o.laneCount = laneCount;
    }
  }
}
function maxConcurrentBookingsOnCourt(overlays, courtId) {
  const ev = [];
  for (const o of overlays) {
    if (o.courtId !== courtId) continue;
    ev.push({ t: o.start, d: 1 });
    ev.push({ t: o.start + o.span, d: -1 });
  }
  ev.sort((a, b) => {
    if (a.t !== b.t) return a.t - b.t;
    return a.d - b.d;
  });
  let c = 0;
  let m = 0;
  for (const e of ev) {
    c += e.d;
    m = Math.max(m, c);
  }
  return Math.max(1, m);
}
function effectiveDayOverlayDisplayLaneCount(rawConcurrent) {
  if (rawConcurrent <= import_bookingCalendarConstants.DAY_OVERLAY_VISIBLE_BOOKING_CAP)
    return Math.max(1, rawConcurrent);
  return import_bookingCalendarConstants.DAY_OVERLAY_VISIBLE_BOOKING_CAP + 1;
}
function clusterDayCourtOverlaysByOverlap(overlays, courtId) {
  const list = overlays.filter((o) => o.courtId === courtId);
  if (!list.length) return [];
  const sorted = [...list].sort((a, b) => {
    if (a.start !== b.start) return a.start - b.start;
    return a.start + a.span - (b.start + b.span);
  });
  const clusters = [];
  let cur = [];
  let curMaxEnd = -Infinity;
  for (const o of sorted) {
    const oe = o.start + o.span;
    if (!cur.length) {
      cur = [o];
      curMaxEnd = oe;
      continue;
    }
    if (o.start < curMaxEnd - 1e-9) {
      cur.push(o);
      curMaxEnd = Math.max(curMaxEnd, oe);
    } else {
      clusters.push(cur);
      cur = [o];
      curMaxEnd = oe;
    }
  }
  if (cur.length) clusters.push(cur);
  return clusters;
}
function dayOverlayHiddenKey(item, dayKey, courtId) {
  const id = (item == null ? void 0 : item.id) != null ? String(item.id) : "";
  return `${String(courtId)}\0${id}\0${dayKey}`;
}
function dayOverlayLaneMetrics(totalInner, laneCount, gap) {
  const n = Math.max(1, laneCount);
  if (n === 1) {
    return {
      topFor: () => 0,
      heightFor: () => totalInner
    };
  }
  const gapsTotal = (n - 1) * gap;
  const body = Math.max(0, totalInner - gapsTotal);
  const base = Math.floor(body / n);
  const rem = body % n;
  const heights = [];
  for (let i = 0; i < n; i += 1) {
    heights.push(base + (i < rem ? 1 : 0));
  }
  const offsets = [0];
  for (let i = 1; i < n; i += 1) {
    offsets.push(offsets[i - 1] + heights[i - 1] + gap);
  }
  return {
    topFor: (lane) => offsets[Math.min(lane, n - 1)] ?? 0,
    heightFor: (lane) => heights[Math.min(lane, n - 1)] ?? base
  };
}
function buildOverlayBookings(visibleCourtRows, dayKey, bookings, rowTopMap = {}, hourSlots, dayRowHeights, slotStepHours = 1) {
  const courtRowIndexMap = /* @__PURE__ */ new Map();
  visibleCourtRows.forEach((row, index) => {
    if (row.kind === "court") courtRowIndexMap.set(row.id, index);
  });
  const stepH = slotStepHours > 0 ? slotStepHours : 1;
  const gridMin = (hourSlots == null ? void 0 : hourSlots.length) ? hourSlots[0] : 0;
  const gridMaxExclusive = (hourSlots == null ? void 0 : hourSlots.length) ? hourSlots[hourSlots.length - 1] + stepH : 24;
  const seen = /* @__PURE__ */ new Set();
  const out = [];
  for (let i = 0; i < visibleCourtRows.length; i++) {
    const row = visibleCourtRows[i];
    if (row.kind !== "court") continue;
    const rowHeight = dayRowHeights[i] ?? import_bookingCalendarConstants.dayRowHeight;
    for (const item of bookings.filter((b) => b.courtId === row.id)) {
      const dedupeKey = `${row.id}\0${item.id ?? ""}\0${dayKey}`;
      if (seen.has(dedupeKey)) continue;
      const seg = getBookingSegmentForDate(item, dayKey);
      if (!seg) continue;
      const start = Math.max(seg.startHour, gridMin);
      const end = Math.min(seg.endHour, gridMaxExclusive);
      if (end <= start) continue;
      seen.add(dedupeKey);
      out.push({
        item,
        rowIndex: courtRowIndexMap.get(row.id) || 0,
        rowTop: rowTopMap[row.id] || 0,
        rowHeight,
        courtId: row.id,
        start,
        end,
        span: end - start,
        continuesNext: seg.continuesNext,
        continuesPrev: seg.continuesPrev
      });
    }
  }
  assignDayOverlapLanesInPlace(out);
  return out;
}
function buildDayRowTopMap(visibleCourtRows, heights) {
  let acc = 0;
  const map = {};
  visibleCourtRows.forEach((row, index) => {
    map[row.id] = acc;
    acc += heights[index] ?? import_bookingCalendarConstants.dayRowHeight;
  });
  return map;
}
function statusTagMetaParts(tag) {
  switch (tag) {
    case "cancel":
      return {
        labelI18nKey: "pisell2.recordBoard.calendar.status.cancel",
        className: "bg-red-600/88 text-white border border-red-300/35"
      };
    case "change":
      return {
        labelI18nKey: "pisell2.recordBoard.calendar.status.change",
        className: "bg-amber-500/88 text-white border border-amber-200/35"
      };
    case "checked_in":
      return {
        labelI18nKey: "pisell2.recordBoard.calendar.status.checkedIn",
        className: "bg-zinc-500/88 text-white border border-zinc-300/30"
      };
    case "pending":
      return {
        labelI18nKey: "pisell2.recordBoard.calendar.status.pending",
        className: "bg-cyan-600/88 text-white border border-cyan-300/35"
      };
    case "new":
      return {
        labelI18nKey: "pisell2.recordBoard.calendar.status.new",
        className: "bg-emerald-600/88 text-white border border-emerald-300/35"
      };
    case "late":
      return {
        labelI18nKey: "pisell2.recordBoard.calendar.status.late",
        className: "bg-amber-500/88 text-white border border-amber-200/35"
      };
    case "no_show":
      return {
        labelI18nKey: "pisell2.recordBoard.calendar.status.noShow",
        className: "bg-red-700/92 text-white border border-red-300/35"
      };
    default:
      return null;
  }
}
function getBookingDetailData(item, courtsList = []) {
  const court = courtsList.find((c) => c.id === item.courtId);
  const resourceLabel = (court == null ? void 0 : court.name) != null && String(court.name).trim() !== "" ? String(court.name) : String(item.courtId ?? "—");
  const startTime = formatHourLabel(item.startHour);
  const endTime = formatHourLabel(item.endHour);
  const endDate = item.endDate || item.date;
  const isBlock = item.kind === "block";
  const customerName = isBlock ? item.blockBy || (0, import_calendarI18n.tCal)("pisell2.recordBoard.calendar.detail.staff") : item.name || "—";
  const noteText = item.note ? String(item.note) : "";
  const orderNotes = noteText ? [
    {
      id: `${item.id}-note-order`,
      label: (0, import_calendarI18n.tCal)("pisell2.recordBoard.calendar.detail.orderNote"),
      text: noteText
    }
  ] : [];
  return {
    rawSales: null,
    bookingId: String(item.id ?? "").toUpperCase(),
    orderNumber: String(item.id ?? ""),
    shopOrderNumber: String(item.id ?? ""),
    orderStatus: "normal",
    appointmentStatus: isBlock ? "blocked" : "confirmed",
    paymentStatus: "payment_processing",
    customer: {
      id: null,
      name: customerName,
      email: "",
      phone: item.phone ? String(item.phone) : ""
    },
    guestCount: 0,
    checkedInCount: 0,
    guests: [],
    schedule: {
      startDate: item.date,
      startTime,
      endDate,
      endTime,
      duration: Math.max(0, (item.endHour - item.startHour) * 60),
      arrivedAt: null,
      completedAt: null
    },
    resources: [],
    summaryItems: [],
    items: [],
    note: noteText,
    files: { form: [], uploaded: [], invoice: [] },
    payments: [],
    history: [],
    summary: {},
    paymentSummary: {
      totalAmount: 0,
      paidAmount: 0,
      balanceAmount: 0,
      currencySymbol: "$",
      isFullyPaid: false
    },
    itemSummary: {
      itemCount: 0,
      itemQtyTotal: 0
    },
    headerSummary: {
      customerName,
      guestCount: 0,
      checkedInCount: 0,
      timeLabel: `${item.date} · ${startTime} - ${endTime}`,
      resourceLabel
    },
    headerTags: {
      paymentTag: "Unpaid",
      fulfillmentTag: "Open",
      hasNote: Boolean(noteText),
      paymentStatus: "payment_processing",
      orderStatus: "normal"
    },
    fileCount: 0,
    canChangeCustomer: !isBlock,
    noteCount: orderNotes.length,
    noteGroups: {
      internal: [],
      order: orderNotes,
      item: []
    },
    metaFlags: {
      itemsChanged: false,
      paymentsChanged: false,
      guestChanged: false,
      filesChanged: false,
      notesChanged: false,
      formChanged: false,
      uploadedChanged: false,
      invoiceChanged: false,
      historyChanged: false
    },
    actions: []
  };
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  addDays,
  assignDayOverlapLanesInPlace,
  buildDayRowTopMap,
  buildOverlayBookings,
  buildTimeOptions,
  clusterDayCourtOverlaysByOverlap,
  courtDaySummary,
  currentTimeLabel,
  dayAndHourToDayjs,
  dayOccupancy,
  dayOverlayHiddenKey,
  dayOverlayLaneMetrics,
  effectiveDayOverlayDisplayLaneCount,
  fmtDate,
  formatDateLoose,
  formatHourLabel,
  formatTimelineStamp,
  getBookingDetailData,
  getBookingSegmentForDate,
  getDisplayStatusTag,
  getLateMinutes,
  getViewNowReference,
  groupSelectedSlots,
  hasConflict,
  isFreeSelectionContiguousSameResource,
  isHourInTimelineSlots,
  maxConcurrentBookingsOnCourt,
  monthCalendarDays,
  monthGrid,
  overlappingBookings,
  parseDate,
  rangeDays,
  sameSlot,
  startOfWeek,
  statusTagMetaParts
});
