import type { Dayjs } from 'dayjs';
import type { RecordBoardCalendarBookingLike } from '../../../types';
/**
 * 日历日期与时间轴纯函数：`fmtDate`、占用矩阵、月格、overlay 分段、周范围等，供 BookingCalendar 三视图共用。
 */
/** 与 {@link RecordBoardCalendarBookingLike} 一致，并支持跨日 endDate */
export declare type CalendarBookingSegmentInput = RecordBoardCalendarBookingLike & {
    endDate?: string;
};
export interface BookingSegmentForDate {
    startHour: number;
    endHour: number;
    continuesPrev: boolean;
    continuesNext: boolean;
}
export interface ViewNowReference {
    dateKey: string;
    hourFloat: number;
}
export declare function fmtDate(date: Date): string;
/** 本地当前日期键与「日历时」小数小时，用于状态徽标与当前时刻线 */
export declare function getViewNowReference(): ViewNowReference;
export declare function parseDate(str: string): Date;
export declare function addDays(date: Date, n: number): Date;
export declare function startOfWeek(date: Date): Date;
export declare function rangeDays(anchor: Date): Date[];
/**
 * 锚定日所在自然月的每一天（1 号至月末），用于资源视图横向「日」列。
 */
export declare function monthCalendarDays(anchor: Date): Date[];
export declare function isHourInTimelineSlots(hour: number, hourSlots: number[] | undefined): boolean;
export declare function formatHourLabel(hour: number): string;
/**
 * `YYYY-MM-DD` → `M/D`，跨日 booking 的 pill 内时间行用这个短日期前缀，
 * 视觉上不挤占 pill 宽度（24px 左右）。
 */
export declare function formatShortMonthDay(dateKey: string): string;
export declare function buildTimeOptions(start?: number, end?: number, sliceMinutes?: number): number[];
export declare function monthGrid(date: Date, bookings: CalendarBookingSegmentInput[], courtsList: {
    id: string;
}[] | undefined, hourSlotsIn: number[] | undefined, slotStepHours?: number): {
    date: Date;
    key: string;
    inMonth: boolean;
    bookingCount: number;
    blockCount: number;
    fullyBooked: boolean;
}[];
export declare function getBookingSegmentForDate(item: CalendarBookingSegmentInput, dateKey: string): BookingSegmentForDate | null;
/**
 * 是否跨自然日（`endDate` 存在且与 `date` 不同）。单日 booking 返回 false。
 */
export declare function isCrossDayBooking(item: CalendarBookingSegmentInput): boolean;
/**
 * 把 booking 整段转成绝对毫秒区间：
 * - `startMs = parseDate(date).setHours(...)`，按浮点小时拆出小时 / 分钟（30 min 步进）
 * - `endMs` 同理；`endHour = 24` 时归一为「次日 00:00」
 *
 * 用于跨视图统一的 lane 抢占与重叠判定，避免「按格切」假设。
 */
export declare function getBookingFullSpanMs(item: CalendarBookingSegmentInput): {
    startMs: number;
    endMs: number;
};
/**
 * 同 court 内两条 booking 的时间区间是否相交（`[startMs, endMs)` 半开区间，端点相邻不算交）。
 * 跨 court 永远不算冲突（不同资源各画各的）。
 */
export declare function bookingsOverlap(a: CalendarBookingSegmentInput, b: CalendarBookingSegmentInput): boolean;
/**
 * 把 booking 整段转成「按日」的毫秒区间：
 * - `startMs = parseDate(date).getTime()`
 * - `endMs   = parseDate(endDate ?? date).getTime() + 1 day`
 *
 * 用于资源 / 月视图这类「一格 = 一天」的视图：同一天内不同小时的两条 booking
 * 视觉上需要垂直 stack（占两个 lane），即使时间不重叠也不能合并到同一 lane。
 */
export declare function getBookingDaySpanMs(item: CalendarBookingSegmentInput): {
    startMs: number;
    endMs: number;
};
/**
 * 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 declare function bookingIntersectsDateWindow(item: {
    date?: unknown;
    endDate?: unknown;
} | null | undefined, windowFirstKey: string, windowLastKey: string): boolean;
/**
 * 将 booking 的 day-span 截断到窗口 `[windowFirstKey, windowLastKey]`，超出窗口的部分裁掉，
 * 用于月视图「按周独立 lane 抢占 + 跨周续画」：
 *   - 与窗口完全不相交 → 返回 `null`；
 *   - `date` 早于窗口 → 起点截到 `windowFirstKey`（视觉上 `clippedLeft`）；
 *   - `endDate` 晚于窗口 → 终点截到 `windowLastKey`（视觉上 `clippedRight`）；
 *   - 截断后只占一天的，回退为单日（`endDate` 设为 `undefined`）。
 *
 * 截断后的对象保留原 `id`、`courtId` 等字段，让 lane 抢占在窗口内独立判断重叠。
 */
export declare function clipBookingDaySpanToWindow(item: CalendarBookingSegmentInput, windowFirstKey: string, windowLastKey: string): CalendarBookingSegmentInput | null;
/**
 * 月 / 资源视图按日 lane：跨日 booking 在命中各日占**同一** lane；单日 booking 再逐日找空位。
 *
 * 1. 跨日优先（跨度长的先占），在 span 覆盖的每个自然日上取同一最小可用 lane；
 * 2. 单日 booking 仅在各自命中日抢剩余 lane，不影响跨日条的纵向对齐。
 */
export declare function assignPerDateLanesCrossDayUnified(items: CalendarBookingSegmentInput[], dateKeysInOrder: string[]): {
    perDateLanes: Map<string, Map<string, number>>;
    laneCount: number;
};
/**
 * 通用 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 declare function assignLanesByInterval(items: CalendarBookingSegmentInput[], granularity?: 'hour' | 'day'): {
    lanes: Map<string, number>;
    laneCount: number;
};
export declare function dayOccupancy(courtId: string, dateKey: string, bookings: CalendarBookingSegmentInput[], hourSlots: number[] | undefined, slotStepHours: number): Record<number, CalendarBookingSegmentInput | null>;
export interface SelectedSlotLike {
    courtId: string;
    date: string;
    hour: number;
}
export declare function groupSelectedSlots(slots: SelectedSlotLike[], slotStepHours?: number): Array<{
    courtId: string;
    date: string;
    startHour: number;
    endHour: number;
}>;
/**
 * 日视图空闲格：仅当全部为同一 court、同一日，且时间上首尾相接成一段时返回 true（与 {@link groupSelectedSlots} 合并规则一致）。
 */
export declare function isFreeSelectionContiguousSameResource(slots: SelectedSlotLike[], slotStepHours?: number): boolean;
export declare function hasConflict(bookings: CalendarBookingSegmentInput[], courtId: string, date: string, startHour: number, endHour: number, ignoreId?: string | null): boolean;
export declare function overlappingBookings(bookings: CalendarBookingSegmentInput[], courtId: string, date: string, startHour: number, endHour: number): CalendarBookingSegmentInput[];
export declare function courtDaySummary(courtId: string, dateKey: string, bookings: CalendarBookingSegmentInput[], hourSlots: number[] | undefined, slotStepHours: number): {
    occupiedHours: number[];
    hasBlock: boolean;
    fullyBooked: boolean;
    items: CalendarBookingSegmentInput[];
};
export declare function sameSlot(a: SelectedSlotLike, b: SelectedSlotLike): boolean;
/** 营业日 `dateStr` + 浮点小时（含半格）→ dayjs */
export declare function dayAndHourToDayjs(dateStr: string, hourFloat: number): Dayjs;
export declare function getDisplayStatusTag(item: CalendarBookingSegmentInput, dateKey: string): string | null;
export declare function getLateMinutes(item: CalendarBookingSegmentInput, dateKey: string): number | null;
export declare function currentTimeLabel(): string;
export declare function formatDateLoose(str: string | undefined): string;
export declare function formatTimelineStamp(value: string | undefined | null): {
    date: string;
    time: string;
};
/** 日视图 overlay 条（可原地写入 lane） */
export declare type DayOverlayLaneMutable = {
    courtId: string;
    start: number;
    span: number;
    lane?: number;
    laneCount?: number;
};
/**
 * 同一 court 上时间重叠的 overlay 分配纵向 lane，避免多张卡片完全叠在同一矩形里。
 * 使用按开始时间排序 + 贪心：每条预约放入「上一条已结束时间不晚于本段开始」的最小编号线。
 */
export declare function assignDayOverlapLanesInPlace(overlays: DayOverlayLaneMutable[]): void;
/** 扫描时间轴：同一 court 在任一时刻的最大并行条数（用于行高，与分 lane 一致） */
export declare function maxConcurrentBookingsOnCourt(overlays: Array<{
    courtId: string;
    start: number;
    span: number;
}>, courtId: string): number;
/** 并行超过 cap 时：cap 条内容 + 1 行「+N」占位 */
export declare function effectiveDayOverlayDisplayLaneCount(rawConcurrent: number): number;
/** 同一 court 下时间轴区间相交的 overlay 归为一簇（用于 3+N 合并展示） */
export declare function clusterDayCourtOverlaysByOverlap<T extends {
    courtId: string;
    start: number;
    span: number;
}>(overlays: T[], courtId: string): T[][];
export declare function dayOverlayHiddenKey(item: {
    id?: string | number;
} | null | undefined, dayKey: string, courtId: string): string;
/**
 * 将 totalInner 像素整数分给 n 条竖向 lane（含缝），保证 Σheight + Σgap === totalInner，消除底部留白缝。
 */
export declare function dayOverlayLaneMetrics(totalInner: number, laneCount: number, gap: number): {
    topFor: (lane: number) => number;
    heightFor: (lane: number) => number;
};
export interface BuildOverlayBookingRow {
    kind: string;
    id: string;
}
export interface BuiltDayOverlay {
    item: CalendarBookingSegmentInput;
    rowIndex: number;
    rowTop: number;
    rowHeight: number;
    courtId: string;
    start: number;
    end: number;
    span: number;
    continuesNext?: boolean;
    continuesPrev?: boolean;
    lane?: number;
    laneCount?: number;
}
export declare function buildOverlayBookings(visibleCourtRows: BuildOverlayBookingRow[], dayKey: string, bookings: CalendarBookingSegmentInput[], rowTopMap: Record<string, number> | undefined, hourSlots: number[] | undefined, dayRowHeights: number[], slotStepHours?: number): BuiltDayOverlay[];
/** 日视图：按行高累加每行 rowTop（court / group） */
export declare function buildDayRowTopMap(visibleCourtRows: Array<{
    id: string;
}>, heights: number[]): Record<string, number>;
/** 日历状态徽标：返回 i18n key + className；文案在 UI 层通过 tCal 解析，便于 utils 单测 */
export declare function statusTagMetaParts(tag: string | null | undefined): {
    labelI18nKey: string;
    className: string;
} | null;
export declare type CalendarCourtNameRow = {
    id: string;
    name?: string | null;
};
export declare function getBookingDetailData(item: CalendarBookingSegmentInput, courtsList?: CalendarCourtNameRow[]): {
    rawSales: null;
    bookingId: string;
    orderNumber: string;
    shopOrderNumber: string;
    orderStatus: string;
    appointmentStatus: string;
    paymentStatus: string;
    customer: {
        id: null;
        name: string;
        email: string;
        phone: string;
    };
    guestCount: number;
    checkedInCount: number;
    guests: never[];
    schedule: {
        startDate: string;
        startTime: string;
        endDate: string;
        endTime: string;
        duration: number;
        arrivedAt: null;
        completedAt: null;
    };
    resources: never[];
    summaryItems: never[];
    items: never[];
    note: string;
    files: {
        form: never[];
        uploaded: never[];
        invoice: never[];
    };
    payments: never[];
    history: never[];
    summary: {};
    paymentSummary: {
        totalAmount: number;
        paidAmount: number;
        balanceAmount: number;
        currencySymbol: string;
        isFullyPaid: boolean;
    };
    itemSummary: {
        itemCount: number;
        itemQtyTotal: number;
    };
    headerSummary: {
        customerName: string;
        guestCount: number;
        checkedInCount: number;
        timeLabel: string;
        resourceLabel: string;
    };
    headerTags: {
        paymentTag: string;
        fulfillmentTag: string;
        hasNote: boolean;
        paymentStatus: string;
        orderStatus: string;
    };
    fileCount: number;
    canChangeCustomer: boolean;
    noteCount: number;
    noteGroups: {
        internal: never[];
        order: {
            id: string;
            label: string;
            text: string;
        }[];
        item: never[];
    };
    metaFlags: {
        itemsChanged: boolean;
        paymentsChanged: boolean;
        guestChanged: boolean;
        filesChanged: boolean;
        notesChanged: boolean;
        formChanged: boolean;
        uploadedChanged: boolean;
        invoiceChanged: boolean;
        historyChanged: boolean;
    };
    actions: never[];
};
