/**
 * Timetable model — the package's core surface.
 *
 * Generic over `T`. Takes a list of timed items + a viewport; returns a
 * day-by-day layout with overlap-packed lanes and percent-based positions
 * plus a diagnostics list for items dropped during processing.
 *
 * The function is pure: given identical input (including `now`), it returns
 * the same output. The model has no opinion on what an item is — pass
 * accessors.
 */
import { type WeekStart } from './grid';
export type TimetableBounds = Readonly<{
    /** Hour ticks below this are clipped. Default 5. */
    minStartHour?: number;
    /** Hour ticks above this are clipped. Default 23. */
    maxEndHour?: number;
    /** Used when the items list is empty. Default 6. */
    defaultStartHour?: number;
    /** Used when the items list is empty. Default 21. */
    defaultEndHour?: number;
    /** Visual minimum so very short items stay visible/clickable. Default 2.5. */
    minHeightPercent?: number;
}>;
export type TimetableInput<T> = Readonly<{
    items: readonly T[];
    viewDate: Date;
    view?: TimetableView;
    startOfWeek: WeekStart;
    getId: (item: T) => string;
    getStartsAt: (item: T) => string;
    getEndsAt: (item: T) => string;
    getTimeZone: (item: T) => string;
    getColor?: (item: T) => string | null;
    /** Sort tiebreak when two items have identical start+end. Defaults to getId. */
    getOrderKey?: (item: T) => string;
    bounds?: TimetableBounds;
    /**
     * "Now" reference for the `isToday` flag on each day. Defaults to
     * `new Date()`; pass an explicit value to keep the model fully deterministic
     * for snapshot tests.
     */
    now?: Date;
}>;
export type InvalidItemReason = 'invalid-start' | 'invalid-end' | 'invalid-timezone' | 'non-positive-duration' | 'out-of-visible-hours';
export type TimetableDiagnostic<T> = Readonly<{
    item: T;
    reason: InvalidItemReason;
}>;
export type TimetableCell<T> = Readonly<{
    item: T;
    /** Local date key this rendered segment belongs to. */
    dateKey: string;
    /** Raw item bounds in minutes since local midnight, before clipping. */
    startsAtMinutes: number;
    endsAtMinutes: number;
    /** Bounds clipped to [bounds.minStartHour..bounds.maxEndHour]; used for layout. */
    visibleStartsAtMinutes: number;
    visibleEndsAtMinutes: number;
    /** Computed from visible bounds. Guaranteed: 0 <= topPercent and topPercent + heightPercent <= 100. */
    topPercent: number;
    heightPercent: number;
    /** True when the item extends outside the visible window in either direction. */
    isClippedStart: boolean;
    isClippedEnd: boolean;
    laneIndex: number;
    laneCount: number;
    color: string | null;
}>;
export type TimetableDay<T> = Readonly<{
    date: Date;
    dateKey: string;
    isToday: boolean;
    cells: readonly TimetableCell<T>[];
}>;
export type TimetableModel<T> = Readonly<{
    days: readonly TimetableDay<T>[];
    hourTicks: readonly number[];
    startHour: number;
    endHour: number;
    rangeMinutes: number;
    hasItems: boolean;
    /**
     * Items dropped during parsing or clipping, with a closed reason tag.
     * The model never throws; callers iterate this for logging or UI hints.
     */
    diagnostics: readonly TimetableDiagnostic<T>[];
}>;
export type TimetableView = 'day' | 'week';
export declare function buildTimetableModel<T>(input: TimetableInput<T>): TimetableModel<T>;
