function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
import dayjs from 'dayjs';
import { buildCalendarTimelineSlotStartsFromRange } from "../../../../pisellTimeNavigator/utils";
import { tCal } from "../calendarI18n";
import { DAY_OVERLAY_VISIBLE_BOOKING_CAP, dayRowHeight, DEFAULT_CALENDAR_TIMELINE, timeSliceMinutes } from "../const/bookingCalendarConstants";

/**
 * 日历日期与时间轴纯函数：`fmtDate`、占用矩阵、月格、overlay 分段、周范围等，供 BookingCalendar 三视图共用。
 */

/** 与 {@link RecordBoardCalendarBookingLike} 一致，并支持跨日 endDate */

export function fmtDate(date) {
  var y = date.getFullYear();
  var m = "".concat(date.getMonth() + 1).padStart(2, '0');
  var d = "".concat(date.getDate()).padStart(2, '0');
  return "".concat(y, "-").concat(m, "-").concat(d);
}

/** 本地当前日期键与「日历时」小数小时，用于状态徽标与当前时刻线 */
export function getViewNowReference() {
  var d = new Date();
  return {
    dateKey: fmtDate(d),
    hourFloat: d.getHours() + d.getMinutes() / 60 + d.getSeconds() / 3600
  };
}
export function parseDate(str) {
  var _str$split$map = str.split('-').map(Number),
    _str$split$map2 = _slicedToArray(_str$split$map, 3),
    y = _str$split$map2[0],
    m = _str$split$map2[1],
    d = _str$split$map2[2];
  return new Date(y, m - 1, d);
}
export function addDays(date, n) {
  var next = new Date(date);
  next.setDate(next.getDate() + n);
  return next;
}
export function startOfWeek(date) {
  var d = new Date(date);
  var day = d.getDay();
  var diff = day === 0 ? -6 : 1 - day;
  d.setDate(d.getDate() + diff);
  d.setHours(0, 0, 0, 0);
  return d;
}
export function rangeDays(anchor) {
  var start = startOfWeek(anchor);
  return Array.from({
    length: 7
  }, function (_, i) {
    return addDays(start, i);
  });
}

/**
 * 锚定日所在自然月的每一天（1 号至月末），用于资源视图横向「日」列。
 */
export function monthCalendarDays(anchor) {
  var base = anchor instanceof Date && !Number.isNaN(anchor.getTime()) ? anchor : new Date();
  var y = base.getFullYear();
  var m = base.getMonth();
  var last = new Date(y, m + 1, 0).getDate();
  return Array.from({
    length: last
  }, function (_, i) {
    return new Date(y, m, i + 1);
  });
}
export function isHourInTimelineSlots(hour, hourSlots) {
  if (!(hourSlots !== null && hourSlots !== void 0 && hourSlots.length)) return false;
  return hourSlots.some(function (x) {
    return Math.abs(x - hour) < 1e-6;
  });
}
export function formatHourLabel(hour) {
  var totalMin = Math.round(hour * 60);
  var h = Math.floor(totalMin / 60);
  var m = totalMin % 60;
  var clockH = (h % 24 + 24) % 24;
  return "".concat(String(clockH).padStart(2, '0'), ":").concat(String(m).padStart(2, '0'));
}

/**
 * `YYYY-MM-DD` → `M/D`，跨日 booking 的 pill 内时间行用这个短日期前缀，
 * 视觉上不挤占 pill 宽度（24px 左右）。
 */
export function formatShortMonthDay(dateKey) {
  if (!dateKey) return '';
  var _dateKey$split$map = dateKey.split('-').map(Number),
    _dateKey$split$map2 = _slicedToArray(_dateKey$split$map, 3),
    m = _dateKey$split$map2[1],
    d = _dateKey$split$map2[2];
  if (!m || !d) return dateKey;
  return "".concat(m, "/").concat(d);
}
export function buildTimeOptions() {
  var start = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 8;
  var end = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 20;
  var sliceMinutes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : timeSliceMinutes;
  var out = [];
  for (var m = start * 60; m <= end * 60; m += sliceMinutes) out.push(m / 60);
  return out;
}
export function monthGrid(date, bookings) {
  var courtsList = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
  var hourSlotsIn = arguments.length > 3 ? arguments[3] : undefined;
  var slotStepHours = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;
  var hourSlots = hourSlotsIn && hourSlotsIn.length ? hourSlotsIn : buildCalendarTimelineSlotStartsFromRange(DEFAULT_CALENDAR_TIMELINE, date, 60);
  var year = date.getFullYear();
  var month = date.getMonth();
  var first = new Date(year, month, 1);
  var start = startOfWeek(first);
  var days = Array.from({
    length: 35
  }, function (_, i) {
    return addDays(start, i);
  });
  return days.map(function (day) {
    var key = fmtDate(day);
    var dayBookings = bookings.filter(function (b) {
      return b.kind === 'booking' && getBookingSegmentForDate(b, key) != null;
    });
    var blocks = bookings.filter(function (b) {
      return b.kind === 'block' && getBookingSegmentForDate(b, key) != null;
    });
    var fullyBookedCourts = courtsList.filter(function (court) {
      var _hourSlots$length;
      var items = bookings.filter(function (b) {
        return b.courtId === court.id && getBookingSegmentForDate(b, key) != null;
      });
      var occupied = new Set();
      items.forEach(function (b) {
        (hourSlots || []).forEach(function (slotStart) {
          var seg = getBookingSegmentForDate(b, key);
          if (seg && seg.startHour < slotStart + slotStepHours && seg.endHour > slotStart) {
            occupied.add(slotStart);
          }
        });
      });
      var slotCount = (_hourSlots$length = hourSlots === null || hourSlots === void 0 ? void 0 : hourSlots.length) !== null && _hourSlots$length !== void 0 ? _hourSlots$length : 14;
      return occupied.size >= slotCount;
    }).length;
    return {
      date: day,
      key: key,
      inMonth: day.getMonth() === month,
      bookingCount: dayBookings.length,
      blockCount: blocks.length,
      fullyBooked: fullyBookedCourts >= courtsList.length
    };
  });
}
export 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
  };
}

/**
 * 是否跨自然日（`endDate` 存在且与 `date` 不同）。单日 booking 返回 false。
 */
export function isCrossDayBooking(item) {
  return !!item.endDate && item.endDate !== item.date;
}

/**
 * 把 booking 整段转成绝对毫秒区间：
 * - `startMs = parseDate(date).setHours(...)`，按浮点小时拆出小时 / 分钟（30 min 步进）
 * - `endMs` 同理；`endHour = 24` 时归一为「次日 00:00」
 *
 * 用于跨视图统一的 lane 抢占与重叠判定，避免「按格切」假设。
 */
export function getBookingFullSpanMs(item) {
  var _item$endDate;
  var startBase = parseDate(item.date).getTime();
  var startMs = startBase + Math.round(item.startHour * 60) * 60 * 1000;
  var endDateKey = (_item$endDate = item.endDate) !== null && _item$endDate !== void 0 ? _item$endDate : item.date;
  var endBase = parseDate(endDateKey).getTime();
  var endMs = endBase + Math.round(item.endHour * 60) * 60 * 1000;
  return {
    startMs: startMs,
    endMs: endMs
  };
}

/**
 * 同 court 内两条 booking 的时间区间是否相交（`[startMs, endMs)` 半开区间，端点相邻不算交）。
 * 跨 court 永远不算冲突（不同资源各画各的）。
 */
export function bookingsOverlap(a, b) {
  if (a.courtId !== b.courtId) return false;
  var sa = getBookingFullSpanMs(a);
  var sb = getBookingFullSpanMs(b);
  return sa.startMs < sb.endMs && sb.startMs < sa.endMs;
}

/**
 * 把 booking 整段转成「按日」的毫秒区间：
 * - `startMs = parseDate(date).getTime()`
 * - `endMs   = parseDate(endDate ?? date).getTime() + 1 day`
 *
 * 用于资源 / 月视图这类「一格 = 一天」的视图：同一天内不同小时的两条 booking
 * 视觉上需要垂直 stack（占两个 lane），即使时间不重叠也不能合并到同一 lane。
 */
export function getBookingDaySpanMs(item) {
  var _item$endDate2;
  var startMs = parseDate(item.date).getTime();
  var endDateKey = (_item$endDate2 = item.endDate) !== null && _item$endDate2 !== void 0 ? _item$endDate2 : item.date;
  var endMs = parseDate(endDateKey).getTime() + 24 * 60 * 60 * 1000;
  return {
    startMs: startMs,
    endMs: endMs
  };
}

/**
 * booking 的 day-span `[date, endDate]` 是否与 `[windowFirstKey, windowLastKey]` 相交。
 *
 * 用于 lane 抢占 / 行高计算阶段过滤掉与当前视口不相交的 booking：
 * 后端为支持跨日 banner 续画把 `booking_time_start_between` 的下界外推 30 天，
 * 这会把上月／前周的 booking 也带回 `bookings` 列表；视图层若直接用全量算 lane，
 * 切到「当月没数据但上月有」的资源行时会保留上月 lane 数 → 行高被撑高。
 *
 * 只比较 `YYYY-MM-DD` 字符串（同 `monthGrid` 的 key 格式），不构造 Date 对象。
 */
export function bookingIntersectsDateWindow(item, windowFirstKey, windowLastKey) {
  var _item$date, _ref, _item$endDate3;
  if (!item) return false;
  var startStr = String((_item$date = item.date) !== null && _item$date !== void 0 ? _item$date : '');
  var endStr = String((_ref = (_item$endDate3 = item.endDate) !== null && _item$endDate3 !== void 0 ? _item$endDate3 : item.date) !== null && _ref !== void 0 ? _ref : '');
  if (!startStr || !endStr) return false;
  return endStr >= windowFirstKey && startStr <= windowLastKey;
}

/**
 * 将 booking 的 day-span 截断到窗口 `[windowFirstKey, windowLastKey]`，超出窗口的部分裁掉，
 * 用于月视图「按周独立 lane 抢占 + 跨周续画」：
 *   - 与窗口完全不相交 → 返回 `null`；
 *   - `date` 早于窗口 → 起点截到 `windowFirstKey`（视觉上 `clippedLeft`）；
 *   - `endDate` 晚于窗口 → 终点截到 `windowLastKey`（视觉上 `clippedRight`）；
 *   - 截断后只占一天的，回退为单日（`endDate` 设为 `undefined`）。
 *
 * 截断后的对象保留原 `id`、`courtId` 等字段，让 lane 抢占在窗口内独立判断重叠。
 */
export function clipBookingDaySpanToWindow(item, windowFirstKey, windowLastKey) {
  var _item$endDate4;
  var startStr = String(item.date);
  var endStr = String((_item$endDate4 = item.endDate) !== null && _item$endDate4 !== void 0 ? _item$endDate4 : item.date);
  if (endStr < windowFirstKey || startStr > windowLastKey) return null;
  var newStart = startStr < windowFirstKey ? windowFirstKey : startStr;
  var newEnd = endStr > windowLastKey ? windowLastKey : endStr;
  return _objectSpread(_objectSpread({}, item), {}, {
    date: newStart,
    endDate: newEnd === newStart ? undefined : newEnd
  });
}

/** 跨日 / 单日 lane 排序（与 {@link assignLanesByInterval} 一致） */
function compareBookingLanePriority(a, b) {
  var crossA = isCrossDayBooking(a);
  var crossB = isCrossDayBooking(b);
  if (crossA !== crossB) return crossA ? -1 : 1;
  var sa = getBookingDaySpanMs(a);
  var sb = getBookingDaySpanMs(b);
  var aDur = sa.endMs - sa.startMs;
  var bDur = sb.endMs - sb.startMs;
  if (aDur !== bDur) return bDur - aDur;
  if (sa.startMs !== sb.startMs) return sa.startMs - sb.startMs;
  return String(a.id).localeCompare(String(b.id));
}
function dedupeBookingsById(items) {
  var seen = new Set();
  var out = [];
  var _iterator = _createForOfIteratorHelper(items),
    _step;
  try {
    for (_iterator.s(); !(_step = _iterator.n()).done;) {
      var it = _step.value;
      if ((it === null || it === void 0 ? void 0 : it.id) == null) continue;
      var idKey = String(it.id);
      if (seen.has(idKey)) continue;
      seen.add(idKey);
      out.push(it);
    }
  } catch (err) {
    _iterator.e(err);
  } finally {
    _iterator.f();
  }
  return out;
}
function maxLaneCountForPerDateLanes(perDateLanes, dateKeysInOrder) {
  var max = 0;
  var _iterator2 = _createForOfIteratorHelper(dateKeysInOrder),
    _step2;
  try {
    for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
      var _perDateLanes$get$val, _perDateLanes$get;
      var dateKey = _step2.value;
      var dayMax = 0;
      var _iterator3 = _createForOfIteratorHelper((_perDateLanes$get$val = (_perDateLanes$get = perDateLanes.get(dateKey)) === null || _perDateLanes$get === void 0 ? void 0 : _perDateLanes$get.values()) !== null && _perDateLanes$get$val !== void 0 ? _perDateLanes$get$val : []),
        _step3;
      try {
        for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
          var _lane = _step3.value;
          dayMax = Math.max(dayMax, _lane + 1);
        }
      } catch (err) {
        _iterator3.e(err);
      } finally {
        _iterator3.f();
      }
      max = Math.max(max, dayMax);
    }
  } catch (err) {
    _iterator2.e(err);
  } finally {
    _iterator2.f();
  }
  return max;
}

/**
 * 月 / 资源视图按日 lane：跨日 booking 在命中各日占**同一** lane；单日 booking 再逐日找空位。
 *
 * 1. 跨日优先（跨度长的先占），在 span 覆盖的每个自然日上取同一最小可用 lane；
 * 2. 单日 booking 仅在各自命中日抢剩余 lane，不影响跨日条的纵向对齐。
 */
export function assignPerDateLanesCrossDayUnified(items, dateKeysInOrder) {
  var perDateLanes = new Map();
  var occupiedByDate = new Map();
  var _iterator4 = _createForOfIteratorHelper(dateKeysInOrder),
    _step4;
  try {
    for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
      var dateKey = _step4.value;
      perDateLanes.set(dateKey, new Map());
      occupiedByDate.set(dateKey, new Set());
    }
  } catch (err) {
    _iterator4.e(err);
  } finally {
    _iterator4.f();
  }
  var unique = dedupeBookingsById(items);
  var crossDayItems = unique.filter(isCrossDayBooking).sort(compareBookingLanePriority);
  var singleDayItems = unique.filter(function (it) {
    return !isCrossDayBooking(it);
  }).sort(compareBookingLanePriority);
  var _iterator5 = _createForOfIteratorHelper(crossDayItems),
    _step5;
  try {
    var _loop = function _loop() {
      var item = _step5.value;
      var idKey = String(item.id);
      var hitDates = dateKeysInOrder.filter(function (dk) {
        return getBookingSegmentForDate(item, dk) != null;
      });
      if (hitDates.length === 0) return 1; // continue
      var lane = 0;
      outer: while (true) {
        var _iterator7 = _createForOfIteratorHelper(hitDates),
          _step7;
        try {
          for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
            var _occupiedByDate$get;
            var dk = _step7.value;
            if ((_occupiedByDate$get = occupiedByDate.get(dk)) !== null && _occupiedByDate$get !== void 0 && _occupiedByDate$get.has(lane)) {
              lane += 1;
              continue outer;
            }
          }
        } catch (err) {
          _iterator7.e(err);
        } finally {
          _iterator7.f();
        }
        break;
      }
      var _iterator8 = _createForOfIteratorHelper(hitDates),
        _step8;
      try {
        for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
          var _dk = _step8.value;
          occupiedByDate.get(_dk).add(lane);
          perDateLanes.get(_dk).set(idKey, lane);
        }
      } catch (err) {
        _iterator8.e(err);
      } finally {
        _iterator8.f();
      }
    };
    for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
      if (_loop()) continue;
    }
  } catch (err) {
    _iterator5.e(err);
  } finally {
    _iterator5.f();
  }
  var _iterator6 = _createForOfIteratorHelper(singleDayItems),
    _step6;
  try {
    var _loop2 = function _loop2() {
      var item = _step6.value;
      var idKey = String(item.id);
      var hitDates = dateKeysInOrder.filter(function (dk) {
        return getBookingSegmentForDate(item, dk) != null;
      });
      var _iterator9 = _createForOfIteratorHelper(hitDates),
        _step9;
      try {
        for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
          var dk = _step9.value;
          var occupied = occupiedByDate.get(dk);
          var _lane2 = 0;
          while (occupied.has(_lane2)) _lane2 += 1;
          occupied.add(_lane2);
          perDateLanes.get(dk).set(idKey, _lane2);
        }
      } catch (err) {
        _iterator9.e(err);
      } finally {
        _iterator9.f();
      }
    };
    for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
      _loop2();
    }
  } catch (err) {
    _iterator6.e(err);
  } finally {
    _iterator6.f();
  }
  return {
    perDateLanes: perDateLanes,
    laneCount: maxLaneCountForPerDateLanes(perDateLanes, dateKeysInOrder)
  };
}

/**
 * 通用 lane 分配：返回 `Map<itemId, laneIndex>` 与 `laneCount`。
 *
 * @param granularity
 *   - `'hour'`（默认，周 / 日视图）：用 `getBookingFullSpanMs`，同小时内不重叠的两条
 *     booking 可以共享同一 lane（`bookingTime` 紧排）。
 *   - `'day'`（资源 / 月视图）：用 `getBookingDaySpanMs`，同一天的两条 booking 一定占两 lane，
 *     即使小时不重叠也要垂直 stack（一格只有一天宽，无法在水平方向区分）。
 *
 * 排序优先级（与设计约定一致）：
 * 1. 跨日 booking 优先（先排，整段占顶 lane）；
 * 2. 同类内按「跨度（endMs - startMs）降序」：跨度更长的先占 lane 0/1，
 *    保证视觉上"长 pill 顶贴"，避免短 pill 把长 pill 推到下方造成"上方一周空白"；
 * 3. 同跨度按 `startMs` 升序（更早开始的先占）；
 * 4. 仍相同则按 `id` 字典序，保证渲染稳定。
 *
 * 然后逐条分配到第一个 `lastEndMs <= 当前 startMs` 的 lane（贪心区间着色）。
 */
export function assignLanesByInterval(items) {
  var granularity = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'hour';
  var lanes = new Map();
  if (items.length === 0) return {
    lanes: lanes,
    laneCount: 0
  };
  var spanFn = granularity === 'day' ? getBookingDaySpanMs : getBookingFullSpanMs;
  var enriched = items.map(function (it) {
    return {
      item: it,
      span: spanFn(it),
      cross: isCrossDayBooking(it)
    };
  });
  enriched.sort(function (a, b) {
    if (a.cross !== b.cross) return a.cross ? -1 : 1;
    var aDur = a.span.endMs - a.span.startMs;
    var bDur = b.span.endMs - b.span.startMs;
    if (aDur !== bDur) return bDur - aDur;
    if (a.span.startMs !== b.span.startMs) return a.span.startMs - b.span.startMs;
    return String(a.item.id).localeCompare(String(b.item.id));
  });
  var laneEnds = [];
  var _iterator10 = _createForOfIteratorHelper(enriched),
    _step10;
  try {
    for (_iterator10.s(); !(_step10 = _iterator10.n()).done;) {
      var e = _step10.value;
      var placed = false;
      for (var i = 0; i < laneEnds.length; i += 1) {
        if (laneEnds[i] <= e.span.startMs) {
          laneEnds[i] = e.span.endMs;
          lanes.set(String(e.item.id), i);
          placed = true;
          break;
        }
      }
      if (!placed) {
        lanes.set(String(e.item.id), laneEnds.length);
        laneEnds.push(e.span.endMs);
      }
    }
  } catch (err) {
    _iterator10.e(err);
  } finally {
    _iterator10.f();
  }
  return {
    lanes: lanes,
    laneCount: laneEnds.length
  };
}
export function dayOccupancy(courtId, dateKey, bookings, hourSlots, slotStepHours) {
  var items = bookings.filter(function (b) {
    return b.courtId === courtId && getBookingSegmentForDate(b, dateKey);
  });
  var map = {};
  var stepH = slotStepHours > 0 ? slotStepHours : 1;
  (hourSlots || []).forEach(function (hour) {
    map[hour] = items.find(function (b) {
      var seg = getBookingSegmentForDate(b, dateKey);
      return seg && seg.startHour < hour + stepH && seg.endHour > hour;
    }) || null;
  });
  return map;
}
export function groupSelectedSlots(slots) {
  var slotStepHours = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
  var stepH = slotStepHours > 0 ? slotStepHours : 1;
  var sorted = _toConsumableArray(slots).sort(function (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;
  });
  var groups = [];
  sorted.forEach(function (slot) {
    var 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;
}

/**
 * 日视图空闲格：仅当全部为同一 court、同一日，且时间上首尾相接成一段时返回 true（与 {@link groupSelectedSlots} 合并规则一致）。
 */
export function isFreeSelectionContiguousSameResource(slots) {
  var slotStepHours = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
  if (!Array.isArray(slots) || slots.length === 0) return false;
  return groupSelectedSlots(slots, slotStepHours).length === 1;
}
export function hasConflict(bookings, courtId, date, startHour, endHour) {
  var ignoreId = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : null;
  return bookings.some(function (b) {
    if (b.id === ignoreId) return false;
    if (b.kind !== 'booking') return false;
    if (b.courtId !== courtId) return false;
    var seg = getBookingSegmentForDate(b, date);
    if (!seg) return false;
    return !(endHour <= seg.startHour || startHour >= seg.endHour);
  });
}
export function overlappingBookings(bookings, courtId, date, startHour, endHour) {
  return bookings.filter(function (b) {
    if (b.kind !== 'booking') return false;
    if (b.courtId !== courtId) return false;
    var seg = getBookingSegmentForDate(b, date);
    if (!seg) return false;
    return !(endHour <= seg.startHour || startHour >= seg.endHour);
  });
}
export function courtDaySummary(courtId, dateKey, bookings, hourSlots, slotStepHours) {
  var _hourSlots$length2;
  var occ = dayOccupancy(courtId, dateKey, bookings, hourSlots, slotStepHours);
  var items = bookings.filter(function (b) {
    return b.courtId === courtId && getBookingSegmentForDate(b, dateKey) != null;
  }).sort(function (a, b) {
    var _segA$startHour, _segB$startHour;
    var segA = getBookingSegmentForDate(a, dateKey);
    var segB = getBookingSegmentForDate(b, dateKey);
    return ((_segA$startHour = segA === null || segA === void 0 ? void 0 : segA.startHour) !== null && _segA$startHour !== void 0 ? _segA$startHour : a.startHour) - ((_segB$startHour = segB === null || segB === void 0 ? void 0 : segB.startHour) !== null && _segB$startHour !== void 0 ? _segB$startHour : b.startHour);
  });
  var occupiedHours = (hourSlots || []).filter(function (h) {
    return occ[h] && isHourInTimelineSlots(h, hourSlots);
  });
  var hasBlock = occupiedHours.some(function (h) {
    var _occ$h;
    return ((_occ$h = occ[h]) === null || _occ$h === void 0 ? void 0 : _occ$h.kind) === 'block';
  });
  var slotCount = (_hourSlots$length2 = hourSlots === null || hourSlots === void 0 ? void 0 : hourSlots.length) !== null && _hourSlots$length2 !== void 0 ? _hourSlots$length2 : 0;
  var fullyBooked = slotCount > 0 && occupiedHours.length >= slotCount;
  return {
    occupiedHours: occupiedHours,
    hasBlock: hasBlock,
    fullyBooked: fullyBooked,
    items: items
  };
}
export function sameSlot(a, b) {
  return a.courtId === b.courtId && a.date === b.date && a.hour === b.hour;
}

/** 营业日 `dateStr` + 浮点小时（含半格）→ dayjs */
export function dayAndHourToDayjs(dateStr, hourFloat) {
  var h = Math.floor(hourFloat);
  var m = Math.round((hourFloat - h) * 60);
  var mm = Math.min(59, Math.max(0, m));
  return dayjs("".concat(dateStr, " ").concat(String(h).padStart(2, '0'), ":").concat(String(mm).padStart(2, '0')), 'YYYY-MM-DD HH:mm');
}
export function getDisplayStatusTag(item, dateKey) {
  if (item.kind !== 'booking') return item.statusTag || null;
  if (item.statusTag === 'checked_in' || item.statusTag === 'cancel') return item.statusTag;
  var _getViewNowReference = getViewNowReference(),
    todayKey = _getViewNowReference.dateKey,
    nowHour = _getViewNowReference.hourFloat;
  if (dateKey !== todayKey) return item.statusTag || null;
  var seg = getBookingSegmentForDate(item, dateKey);
  if (!seg) return item.statusTag || null;
  var lateThreshold = item.startHour + 5 / 60;
  if (nowHour > seg.endHour) return 'no_show';
  if (nowHour > lateThreshold) return 'late';
  return item.statusTag || null;
}
export function getLateMinutes(item, dateKey) {
  if (getDisplayStatusTag(item, dateKey) !== 'late') return null;
  var seg = getBookingSegmentForDate(item, dateKey);
  if (!seg) return null;
  var lateThreshold = seg.startHour + 5 / 60;
  var _getViewNowReference2 = getViewNowReference(),
    nowHour = _getViewNowReference2.hourFloat;
  return Math.max(1, Math.floor((nowHour - lateThreshold) * 60));
}
export function currentTimeLabel() {
  var d = new Date();
  return "".concat(String(d.getHours()).padStart(2, '0'), ":").concat(String(d.getMinutes()).padStart(2, '0'));
}
export function formatDateLoose(str) {
  if (!str) return '';
  var d = parseDate(str);
  return "".concat(d.getFullYear(), " ").concat(String(d.getMonth() + 1).padStart(2, '0'), " ").concat(String(d.getDate()).padStart(2, '0'));
}
export function formatTimelineStamp(value) {
  if (!value) return {
    date: '--',
    time: '--'
  };
  var normalized = String(value).replace('T', ' ');
  var _normalized$split = normalized.split(' '),
    _normalized$split2 = _slicedToArray(_normalized$split, 2),
    _normalized$split2$ = _normalized$split2[0],
    date = _normalized$split2$ === void 0 ? '--' : _normalized$split2$,
    _normalized$split2$2 = _normalized$split2[1],
    time = _normalized$split2$2 === void 0 ? '--' : _normalized$split2$2;
  return {
    date: date,
    time: time.slice(0, 5) || '--'
  };
}

/** 日视图 overlay 条（可原地写入 lane） */

/**
 * 同一 court 上时间重叠的 overlay 分配纵向 lane，避免多张卡片完全叠在同一矩形里。
 * 使用按开始时间排序 + 贪心：每条预约放入「上一条已结束时间不晚于本段开始」的最小编号线。
 */
export function assignDayOverlapLanesInPlace(overlays) {
  var byCourt = new Map();
  var _iterator11 = _createForOfIteratorHelper(overlays),
    _step11;
  try {
    for (_iterator11.s(); !(_step11 = _iterator11.n()).done;) {
      var _o3 = _step11.value;
      var _list = byCourt.get(_o3.courtId);
      if (_list) _list.push(_o3);else byCourt.set(_o3.courtId, [_o3]);
    }
  } catch (err) {
    _iterator11.e(err);
  } finally {
    _iterator11.f();
  }
  for (var _i = 0, _Array$from = Array.from(byCourt.values()); _i < _Array$from.length; _i++) {
    var list = _Array$from[_i];
    list.sort(function (a, b) {
      if (a.start !== b.start) return a.start - b.start;
      return b.span - a.span;
    });
    var laneEnds = [];
    var _iterator12 = _createForOfIteratorHelper(list),
      _step12;
    try {
      for (_iterator12.s(); !(_step12 = _iterator12.n()).done;) {
        var o = _step12.value;
        var oEnd = o.start + o.span;
        var _lane3 = 0;
        while (_lane3 < laneEnds.length && laneEnds[_lane3] > o.start) {
          _lane3++;
        }
        if (_lane3 === laneEnds.length) {
          laneEnds.push(oEnd);
        } else {
          laneEnds[_lane3] = oEnd;
        }
        o.lane = _lane3;
      }
    } catch (err) {
      _iterator12.e(err);
    } finally {
      _iterator12.f();
    }
    var maxL = 0;
    var _iterator13 = _createForOfIteratorHelper(list),
      _step13;
    try {
      for (_iterator13.s(); !(_step13 = _iterator13.n()).done;) {
        var _o$lane;
        var _o = _step13.value;
        maxL = Math.max(maxL, (_o$lane = _o.lane) !== null && _o$lane !== void 0 ? _o$lane : 0);
      }
    } catch (err) {
      _iterator13.e(err);
    } finally {
      _iterator13.f();
    }
    var laneCount = Math.max(1, maxL + 1);
    var _iterator14 = _createForOfIteratorHelper(list),
      _step14;
    try {
      for (_iterator14.s(); !(_step14 = _iterator14.n()).done;) {
        var _o2 = _step14.value;
        _o2.laneCount = laneCount;
      }
    } catch (err) {
      _iterator14.e(err);
    } finally {
      _iterator14.f();
    }
  }
}

/** 扫描时间轴：同一 court 在任一时刻的最大并行条数（用于行高，与分 lane 一致） */
export function maxConcurrentBookingsOnCourt(overlays, courtId) {
  var ev = [];
  var _iterator15 = _createForOfIteratorHelper(overlays),
    _step15;
  try {
    for (_iterator15.s(); !(_step15 = _iterator15.n()).done;) {
      var o = _step15.value;
      if (o.courtId !== courtId) continue;
      ev.push({
        t: o.start,
        d: 1
      });
      ev.push({
        t: o.start + o.span,
        d: -1
      });
    }
    /** 同一时刻先处理结束 (-1) 再处理开始 (+1)，区间按 [start, end) 半开，避免「上一段 end == 下一段 start」被算成重叠 */
  } catch (err) {
    _iterator15.e(err);
  } finally {
    _iterator15.f();
  }
  ev.sort(function (a, b) {
    if (a.t !== b.t) return a.t - b.t;
    return a.d - b.d;
  });
  var c = 0;
  var m = 0;
  for (var _i2 = 0, _ev = ev; _i2 < _ev.length; _i2++) {
    var e = _ev[_i2];
    c += e.d;
    m = Math.max(m, c);
  }
  return Math.max(1, m);
}

/** 并行超过 cap 时：cap 条内容 + 1 行「+N」占位 */
export function effectiveDayOverlayDisplayLaneCount(rawConcurrent) {
  if (rawConcurrent <= DAY_OVERLAY_VISIBLE_BOOKING_CAP) return Math.max(1, rawConcurrent);
  return DAY_OVERLAY_VISIBLE_BOOKING_CAP + 1;
}

/** 同一 court 下时间轴区间相交的 overlay 归为一簇（用于 3+N 合并展示） */
export function clusterDayCourtOverlaysByOverlap(overlays, courtId) {
  var list = overlays.filter(function (o) {
    return o.courtId === courtId;
  });
  if (!list.length) return [];
  var sorted = _toConsumableArray(list).sort(function (a, b) {
    if (a.start !== b.start) return a.start - b.start;
    return a.start + a.span - (b.start + b.span);
  });
  var clusters = [];
  var cur = [];
  var curMaxEnd = -Infinity;
  var _iterator16 = _createForOfIteratorHelper(sorted),
    _step16;
  try {
    for (_iterator16.s(); !(_step16 = _iterator16.n()).done;) {
      var o = _step16.value;
      var 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;
      }
    }
  } catch (err) {
    _iterator16.e(err);
  } finally {
    _iterator16.f();
  }
  if (cur.length) clusters.push(cur);
  return clusters;
}
export function dayOverlayHiddenKey(item, dayKey, courtId) {
  var id = (item === null || item === void 0 ? void 0 : item.id) != null ? String(item.id) : '';
  return "".concat(String(courtId), "\0").concat(id, "\0").concat(dayKey);
}

/**
 * 将 totalInner 像素整数分给 n 条竖向 lane（含缝），保证 Σheight + Σgap === totalInner，消除底部留白缝。
 */
export function dayOverlayLaneMetrics(totalInner, laneCount, gap) {
  var n = Math.max(1, laneCount);
  if (n === 1) {
    return {
      topFor: function topFor() {
        return 0;
      },
      heightFor: function heightFor() {
        return totalInner;
      }
    };
  }
  var gapsTotal = (n - 1) * gap;
  var body = Math.max(0, totalInner - gapsTotal);
  var base = Math.floor(body / n);
  var rem = body % n;
  var heights = [];
  for (var i = 0; i < n; i += 1) {
    heights.push(base + (i < rem ? 1 : 0));
  }
  var offsets = [0];
  for (var _i3 = 1; _i3 < n; _i3 += 1) {
    offsets.push(offsets[_i3 - 1] + heights[_i3 - 1] + gap);
  }
  return {
    topFor: function topFor(lane) {
      var _offsets$Math$min;
      return (_offsets$Math$min = offsets[Math.min(lane, n - 1)]) !== null && _offsets$Math$min !== void 0 ? _offsets$Math$min : 0;
    },
    heightFor: function heightFor(lane) {
      var _heights$Math$min;
      return (_heights$Math$min = heights[Math.min(lane, n - 1)]) !== null && _heights$Math$min !== void 0 ? _heights$Math$min : base;
    }
  };
}
export function buildOverlayBookings(visibleCourtRows, dayKey, bookings) {
  var rowTopMap = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
  var hourSlots = arguments.length > 4 ? arguments[4] : undefined;
  var dayRowHeights = arguments.length > 5 ? arguments[5] : undefined;
  var slotStepHours = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : 1;
  var courtRowIndexMap = new Map();
  visibleCourtRows.forEach(function (row, index) {
    if (row.kind === 'court') courtRowIndexMap.set(row.id, index);
  });
  var stepH = slotStepHours > 0 ? slotStepHours : 1;
  var gridMin = hourSlots !== null && hourSlots !== void 0 && hourSlots.length ? hourSlots[0] : 0;
  var gridMaxExclusive = hourSlots !== null && hourSlots !== void 0 && hourSlots.length ? hourSlots[hourSlots.length - 1] + stepH : 24;
  var seen = new Set();
  var out = [];
  var _loop3 = function _loop3() {
    var _dayRowHeights$i;
    var row = visibleCourtRows[i];
    if (row.kind !== 'court') return 1; // continue
    var rowHeight = (_dayRowHeights$i = dayRowHeights[i]) !== null && _dayRowHeights$i !== void 0 ? _dayRowHeights$i : dayRowHeight;
    var _iterator17 = _createForOfIteratorHelper(bookings.filter(function (b) {
        return b.courtId === row.id;
      })),
      _step17;
    try {
      for (_iterator17.s(); !(_step17 = _iterator17.n()).done;) {
        var _item$id;
        var item = _step17.value;
        var dedupeKey = "".concat(row.id, "\0").concat((_item$id = item.id) !== null && _item$id !== void 0 ? _item$id : '', "\0").concat(dayKey);
        if (seen.has(dedupeKey)) continue;
        var seg = getBookingSegmentForDate(item, dayKey);
        if (!seg) continue;
        /**
         * 营业日跨自然日补偿（hourSlots 起点 = boundaryClock）：
         * 当 booking 自然日 hour 段落在 [0, gridMin) 之外（即 boundary > 0 时晚于 23:59
         * 之前的部分），整体 +24 让其落入 grid 的"次日"区段 [gridMin, gridMin+24)。
         * 例：boundary=23:59 → grid=[23.98, 47.98)；booking 4/16 20:00（seg=[20.16, 20.5]）
         * → +24 → [44.16, 44.5] ⊂ grid，显示在 grid 上"20:10 next day"位置，与用户对
         * 营业日时间轴的认知一致（4/16 整天的预约都在 grid 上半段之后的 24h 内）。
         */
        var segStart = seg.startHour;
        var segEnd = seg.endHour;
        if (gridMin > 0 && segEnd <= gridMin) {
          segStart += 24;
          segEnd += 24;
        }
        var start = Math.max(segStart, gridMin);
        var end = Math.min(segEnd, gridMaxExclusive);
        if (end <= start) continue;
        seen.add(dedupeKey);
        out.push({
          item: item,
          rowIndex: courtRowIndexMap.get(row.id) || 0,
          rowTop: rowTopMap[row.id] || 0,
          rowHeight: rowHeight,
          courtId: row.id,
          start: start,
          end: end,
          span: end - start,
          continuesNext: seg.continuesNext,
          continuesPrev: seg.continuesPrev
        });
      }
    } catch (err) {
      _iterator17.e(err);
    } finally {
      _iterator17.f();
    }
  };
  for (var i = 0; i < visibleCourtRows.length; i++) {
    if (_loop3()) continue;
  }
  assignDayOverlapLanesInPlace(out);
  return out;
}

/** 日视图：按行高累加每行 rowTop（court / group） */
export function buildDayRowTopMap(visibleCourtRows, heights) {
  var acc = 0;
  var map = {};
  visibleCourtRows.forEach(function (row, index) {
    var _heights$index;
    map[row.id] = acc;
    acc += (_heights$index = heights[index]) !== null && _heights$index !== void 0 ? _heights$index : dayRowHeight;
  });
  return map;
}

/** 日历状态徽标：返回 i18n key + className；文案在 UI 层通过 tCal 解析，便于 utils 单测 */
export 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;
  }
}
export function getBookingDetailData(item) {
  var _item$courtId, _item$id2, _item$id3, _item$id4;
  var courtsList = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
  var court = courtsList.find(function (c) {
    return c.id === item.courtId;
  });
  var resourceLabel = (court === null || court === void 0 ? void 0 : court.name) != null && String(court.name).trim() !== '' ? String(court.name) : String((_item$courtId = item.courtId) !== null && _item$courtId !== void 0 ? _item$courtId : '—');
  var startTime = formatHourLabel(item.startHour);
  var endTime = formatHourLabel(item.endHour);
  var endDate = item.endDate || item.date;
  var isBlock = item.kind === 'block';
  var customerName = isBlock ? item.blockBy || tCal('pisell2.recordBoard.calendar.detail.staff') : item.name || '—';
  var noteText = item.note ? String(item.note) : '';
  var orderNotes = noteText ? [{
    id: "".concat(item.id, "-note-order"),
    label: tCal('pisell2.recordBoard.calendar.detail.orderNote'),
    text: noteText
  }] : [];
  return {
    rawSales: null,
    bookingId: String((_item$id2 = item.id) !== null && _item$id2 !== void 0 ? _item$id2 : '').toUpperCase(),
    orderNumber: String((_item$id3 = item.id) !== null && _item$id3 !== void 0 ? _item$id3 : ''),
    shopOrderNumber: String((_item$id4 = item.id) !== null && _item$id4 !== void 0 ? _item$id4 : ''),
    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: startTime,
      endDate: endDate,
      endTime: 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: customerName,
      guestCount: 0,
      checkedInCount: 0,
      timeLabel: "".concat(item.date, " \xB7 ").concat(startTime, " - ").concat(endTime),
      resourceLabel: 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: []
  };
}