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/solution/VenueBooking/utils/timeSlot.ts
var timeSlot_exports = {};
__export(timeSlot_exports, {
  buildTimeSlotGrid: () => buildTimeSlotGrid,
  collectEventsForDate: () => collectEventsForDate,
  computeSlotStatus: () => computeSlotStatus,
  generateTimeLabels: () => generateTimeLabels,
  isBusinessHoursCrossDay: () => isBusinessHoursCrossDay,
  isSlotOccupied: () => isSlotOccupied
});
module.exports = __toCommonJS(timeSlot_exports);
var import_dayjs = __toESM(require("dayjs"));
var import_decimal = __toESM(require("decimal.js"));
function isBusinessHoursCrossDay(config) {
  const [startH, startM] = config.businessStartTime.split(":").map(Number);
  const [endH, endM] = config.businessEndTime.split(":").map(Number);
  return endH < startH || endH === startH && endM <= startM;
}
function generateTimeLabels(config) {
  const { slotDurationMinutes, businessStartTime, businessEndTime } = config;
  const labels = [];
  const baseDate = "2000-01-01";
  let cursor = (0, import_dayjs.default)(`${baseDate} ${businessStartTime}`);
  const crossDay = isBusinessHoursCrossDay(config);
  const endBase = crossDay ? "2000-01-02" : baseDate;
  const end = (0, import_dayjs.default)(`${endBase} ${businessEndTime}`);
  while (cursor.isBefore(end)) {
    labels.push(cursor.format("HH:mm"));
    cursor = cursor.add(slotDurationMinutes, "minute");
  }
  return labels;
}
function isSlotOccupied(slotStart, slotEnd, occupiedTimes) {
  for (const occupied of occupiedTimes) {
    const occStart = (0, import_dayjs.default)(occupied.start_at);
    const occEnd = (0, import_dayjs.default)(occupied.end_at);
    if (slotStart.isBefore(occEnd) && slotEnd.isAfter(occStart)) {
      return true;
    }
  }
  return false;
}
function isSlotWithinResourceTimes(slotStart, slotEnd, resourceTimes) {
  for (const t of resourceTimes) {
    const tStart = (0, import_dayjs.default)(t.start_at);
    const tEnd = (0, import_dayjs.default)(t.end_at);
    if (slotStart.isBefore(tEnd) && slotEnd.isAfter(tStart))
      return true;
  }
  return false;
}
function getResourceTimesForDate(resource, date, config) {
  const crossDay = isBusinessHoursCrossDay(config);
  const dayStart = (0, import_dayjs.default)(`${date} ${config.businessStartTime}`);
  const dayEnd = crossDay ? (0, import_dayjs.default)(`${date} ${config.businessEndTime}`).add(1, "day") : (0, import_dayjs.default)(`${date} ${config.businessEndTime}`);
  return resource.times.filter((t) => {
    const tStart = (0, import_dayjs.default)(t.start_at);
    const tEnd = (0, import_dayjs.default)(t.end_at);
    return tStart.isBefore(dayEnd) && tEnd.isAfter(dayStart);
  });
}
function collectEventsForDate(resource, date, config) {
  const timesForDate = getResourceTimesForDate(resource, date, config);
  const events = [];
  for (const t of timesForDate) {
    if (Array.isArray(t.event_list)) {
      for (const evt of t.event_list) {
        events.push({
          start_at: evt.start_at,
          end_at: evt.end_at,
          pax: evt.pax ?? 1
        });
      }
    }
  }
  return events;
}
function computeSlotStatus(params) {
  const { slotStart, slotEnd, events, resourceType, capacity } = params;
  const overlapping = events.filter((evt) => {
    const evtStart = (0, import_dayjs.default)(evt.start_at);
    const evtEnd = (0, import_dayjs.default)(evt.end_at);
    return slotStart.isBefore(evtEnd) && slotEnd.isAfter(evtStart);
  });
  if (resourceType === "single") {
    if (overlapping.length > 0)
      return { status: "occupied", remainingCapacity: 0 };
    return { status: "available", remainingCapacity: 1 };
  }
  const usedCapacity = overlapping.reduce((sum, evt) => sum + evt.pax, 0);
  const remaining = Math.max(0, capacity - usedCapacity);
  if (remaining <= 0)
    return { status: "occupied", remainingCapacity: 0 };
  if (remaining < capacity)
    return { status: "partially_occupied", remainingCapacity: remaining };
  return { status: "available", remainingCapacity: remaining };
}
function mergeSubSlots(subSlots) {
  const priority = {
    available: 5,
    partially_occupied: 4,
    occupied: 3,
    unavailable: 2,
    past: 1
  };
  let best = subSlots[0];
  for (const slot of subSlots) {
    if ((priority[slot.status] ?? 0) > (priority[best.status] ?? 0))
      best = slot;
  }
  let minPrice = null;
  for (const slot of subSlots) {
    if (slot.price == null)
      continue;
    const decimal = new import_decimal.default(slot.price || "0");
    if (!minPrice || decimal.lt(minPrice))
      minPrice = decimal;
  }
  return {
    startTime: best.startTime,
    endTime: best.endTime,
    status: best.status,
    price: minPrice ? minPrice.toFixed(2) : null,
    resourceId: best.resourceId,
    resourceFormId: best.resourceFormId,
    capacity: best.capacity,
    remainingCapacity: best.remainingCapacity
  };
}
function buildProductSlots(params) {
  const {
    date,
    config,
    resource,
    mapping,
    timeLabels,
    timesForDate,
    events,
    resCapacity,
    now,
    crossDay,
    quotationPriceMap,
    childRawResources,
    childTimesCache,
    childEventsCache,
    config_businessStartHour
  } = params;
  const slots = [];
  for (const label of timeLabels) {
    const [h] = label.split(":").map(Number);
    const isNextDay = crossDay && h < config_businessStartHour;
    const slotDate = isNextDay ? (0, import_dayjs.default)(date).add(1, "day").format("YYYY-MM-DD") : date;
    const slotStart = (0, import_dayjs.default)(`${slotDate} ${label}`);
    const slotEnd = slotStart.add(config.slotDurationMinutes, "minute");
    let status;
    let remainingCapacity = null;
    if (slotStart.isBefore(now)) {
      status = "past";
    } else if (!isSlotWithinResourceTimes(slotStart, slotEnd, timesForDate)) {
      status = "unavailable";
    } else {
      const result = computeSlotStatus({
        slotStart,
        slotEnd,
        events,
        resourceType: mapping.resourceType,
        capacity: resCapacity
      });
      status = result.status;
      remainingCapacity = result.remainingCapacity;
      if (status !== "occupied" && childRawResources && childRawResources.length) {
        for (let i = 0; i < childRawResources.length; i++) {
          const child = childRawResources[i];
          const childTimes = (childTimesCache == null ? void 0 : childTimesCache[i]) ?? getResourceTimesForDate(child, date, config);
          const childEvents = (childEventsCache == null ? void 0 : childEventsCache[i]) ?? collectEventsForDate(child, date, config);
          if (!isSlotWithinResourceTimes(slotStart, slotEnd, childTimes)) {
            status = "unavailable";
            remainingCapacity = null;
            break;
          }
          const childType = child.type || "single";
          const childResult = computeSlotStatus({
            slotStart,
            slotEnd,
            events: childEvents,
            resourceType: childType,
            capacity: child.capacity ?? 1
          });
          if (childResult.status === "occupied") {
            status = "occupied";
            remainingCapacity = 0;
            break;
          }
          if (childResult.status === "partially_occupied" && status === "available") {
            status = "partially_occupied";
            remainingCapacity = Math.min(
              remainingCapacity ?? childResult.remainingCapacity,
              childResult.remainingCapacity
            );
          }
        }
      }
    }
    const isBookable = status === "available" || status === "partially_occupied";
    const slotStartStr = slotStart.format("YYYY-MM-DD HH:mm");
    slots.push({
      startTime: slotStartStr,
      endTime: slotEnd.format("YYYY-MM-DD HH:mm"),
      status,
      price: isBookable ? (quotationPriceMap == null ? void 0 : quotationPriceMap.get(`${mapping.productId}:${slotStartStr}`)) ?? mapping.price : null,
      resourceId: resource.resourceId,
      resourceFormId: resource.formId,
      capacity: status === "past" || status === "unavailable" ? null : resCapacity,
      remainingCapacity,
      productId: mapping.productId
    });
  }
  return slots;
}
function buildTimeSlotGrid(params) {
  const { date, config, rawResources, resourceProductMap, quotationPriceMap } = params;
  const timeLabels = generateTimeLabels(config);
  const now = (0, import_dayjs.default)();
  const crossDay = isBusinessHoursCrossDay(config);
  const config_businessStartHour = Number(config.businessStartTime.split(":")[0]);
  const rawResourceById = /* @__PURE__ */ new Map();
  for (const item of rawResources)
    rawResourceById.set(item.resourceId, item);
  const resources = [];
  for (const resource of rawResources) {
    const mappings = resourceProductMap.get(resource.resourceId);
    if (!mappings || !mappings.length)
      continue;
    const timesForDate = getResourceTimesForDate(resource, date, config);
    const events = collectEventsForDate(resource, date, config);
    const resCapacity = resource.capacity ?? 1;
    const combined = resource.combined_resource;
    const isCombined = !!(combined && combined.status === 1 && Array.isArray(combined.resource_ids) && combined.resource_ids.length);
    const childRawResources = isCombined ? combined.resource_ids.map((id) => rawResourceById.get(id)).filter((r) => !!r) : void 0;
    const childTimesCache = childRawResources == null ? void 0 : childRawResources.map((child) => getResourceTimesForDate(child, date, config));
    const childEventsCache = childRawResources == null ? void 0 : childRawResources.map((child) => collectEventsForDate(child, date, config));
    const subRows = mappings.map((mapping) => {
      const productSlots = buildProductSlots({
        date,
        config,
        resource,
        mapping,
        timeLabels,
        timesForDate,
        events,
        resCapacity,
        now,
        crossDay,
        quotationPriceMap,
        childRawResources,
        childTimesCache,
        childEventsCache,
        config_businessStartHour
      });
      return {
        productId: mapping.productId,
        productTitle: mapping.productTitle,
        price: mapping.price,
        slots: productSlots
      };
    });
    const hasMultipleProducts = subRows.length > 1;
    const primary = mappings[0];
    let outerSlots;
    if (hasMultipleProducts) {
      outerSlots = timeLabels.map((_label, slotIndex) => {
        const group = subRows.map((row2) => row2.slots[slotIndex]);
        return mergeSubSlots(group);
      });
    } else {
      outerSlots = subRows[0].slots.map((slot) => ({ ...slot, productId: void 0 }));
    }
    const row = {
      resourceId: resource.resourceId,
      resourceFormId: resource.formId,
      resourceName: resource.main_field || primary.resourceName,
      productId: primary.productId,
      productTitle: primary.productTitle,
      slots: outerSlots
    };
    if (hasMultipleProducts) {
      row.products = subRows;
      row.hasMultipleProducts = true;
    }
    if (isCombined && combined) {
      row.combinedResource = { resourceIds: [...combined.resource_ids] };
    }
    resources.push(row);
  }
  const productOrder = /* @__PURE__ */ new Map();
  for (const row of resources) {
    if (!productOrder.has(row.productId))
      productOrder.set(row.productId, productOrder.size);
  }
  resources.sort((a, b) => (productOrder.get(a.productId) ?? 0) - (productOrder.get(b.productId) ?? 0));
  return { date, timeLabels, resources };
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  buildTimeSlotGrid,
  collectEventsForDate,
  computeSlotStatus,
  generateTimeLabels,
  isBusinessHoursCrossDay,
  isSlotOccupied
});
