(function () {
  const datesModule = (typeof require === 'function')
    ? require('./dates.js')
    : (typeof window !== 'undefined' ? window.dates : null);
  const { timeToMinutes, minutesToTime, parseDate, formatDate, addDays } = datesModule;

  const MINUTES_PER_SLOT = 30;
  const PIXELS_PER_SLOT = 60; // 1px per minute, matches the 60-minute slot duration

  function floorToSlot(minutes, slotMinutes) {
    const slot = Math.floor(minutes / slotMinutes) * slotMinutes;
    return slot === minutes ? slot - slotMinutes : slot;
  }

  function ceilToSlot(minutes, slotMinutes) {
    const slot = Math.ceil(minutes / slotMinutes) * slotMinutes;
    return slot === minutes ? slot + slotMinutes : slot;
  }

  function computeRange(records, defaultStart, defaultEnd, slotMinutes) {
    let startMin = timeToMinutes(defaultStart);
    let endMin = timeToMinutes(defaultEnd);

    for (const r of records) {
      if (!r.start_time || !r.end_time) continue;
      const s = floorToSlot(timeToMinutes(r.start_time), slotMinutes);
      const e = ceilToSlot(timeToMinutes(r.end_time), slotMinutes);
      if (s < startMin) startMin = s;
      if (e > endMin) endMin = e;
    }

    return { start: minutesToTime(startMin), end: minutesToTime(endMin) };
  }

  function computeLayout(range, records) {
    const startMin = timeToMinutes(range.start);
    const endMin = timeToMinutes(range.end);
    const totalSlots = (endMin - startMin) / range.slot;

    return records.map((r) => {
      const s = Math.max(timeToMinutes(r.start_time), startMin);
      const e = Math.min(timeToMinutes(r.end_time), endMin);
      const topSlots = (s - startMin) / range.slot;
      const heightSlots = Math.max((e - s) / range.slot, 0.5);
      return {
        ...r,
        top: topSlots * PIXELS_PER_SLOT,
        height: heightSlots * PIXELS_PER_SLOT,
        totalHeight: totalSlots * PIXELS_PER_SLOT
      };
    });
  }

  function groupByDay(records) {
    return records.reduce((acc, r) => {
      if (!r.schedule_date) return acc;
      (acc[r.schedule_date] ||= []).push(r);
      return acc;
    }, {});
  }

  function describeWeek(weekStart) {
    const start = parseDate(weekStart);
    const end = parseDate(addDays(weekStart, 6));
    return `${formatDate(start)} 至 ${formatDate(end)}`;
  }

  const exports_ = {
    computeRange,
    computeLayout,
    groupByDay,
    describeWeek,
    PIXELS_PER_SLOT
  };

  if (typeof module !== 'undefined' && module.exports) {
    module.exports = exports_;
  }

  if (typeof window !== 'undefined') {
    window.timeline = exports_;
  }
})();
