import { useEffect, useState } from 'react';
import {
    dayNumber,
    eachDay,
    endOfWeek,
    formatWeekday,
    isWeekend,
    minutesOfDay,
    startOfWeek,
    todayIso,
} from '@/lib/date';
import { cn } from '@/lib/utils';
import type { CalendarMeeting } from '@/types';
import MeetingChip from './meeting-chip';

const dayStartsAt = 7 * 60;
const dayEndsAt = 21 * 60;
const hourHeight = 56;
const minBlockHeight = 22;

const hours = Array.from(
    { length: (dayEndsAt - dayStartsAt) / 60 + 1 },
    (_, index) => dayStartsAt / 60 + index,
);

type Span = {
    meeting: CalendarMeeting;
    from: number;
    to: number;
};

type Placed = {
    meeting: CalendarMeeting;
    top: number;
    height: number;
    lane: number;
    lanes: number;
};

/**
 * Places a day's lessons on the time axis. Lessons that overlap share the width of
 * the column, so a double booking is visible rather than hidden underneath.
 */
function placeDay(meetings: CalendarMeeting[]): {
    placed: Placed[];
    outside: CalendarMeeting[];
} {
    const outside: CalendarMeeting[] = [];
    const spans: Span[] = [];

    for (const meeting of meetings) {
        const start = minutesOfDay(meeting.scheduled_at);
        const from = Math.max(start, dayStartsAt);
        const to = Math.min(start + meeting.duration_minutes, dayEndsAt);

        if (to <= from) {
            outside.push(meeting);
            continue;
        }

        spans.push({ meeting, from, to });
    }

    spans.sort((left, right) => left.from - right.from || left.to - right.to);

    const placed: Placed[] = [];
    let cluster: Span[] = [];
    let clusterEnd = -1;

    const flush = () => {
        const laneEnds: number[] = [];
        const assignments = cluster.map((span) => {
            let lane = laneEnds.findIndex((end) => end <= span.from);

            if (lane === -1) {
                lane = laneEnds.length;
            }

            laneEnds[lane] = span.to;

            return { span, lane };
        });

        for (const { span, lane } of assignments) {
            placed.push({
                meeting: span.meeting,
                top: ((span.from - dayStartsAt) / 60) * hourHeight,
                height: Math.max(
                    ((span.to - span.from) / 60) * hourHeight,
                    minBlockHeight,
                ),
                lane,
                lanes: laneEnds.length,
            });
        }

        cluster = [];
        clusterEnd = -1;
    };

    for (const span of spans) {
        if (cluster.length > 0 && span.from >= clusterEnd) {
            flush();
        }

        cluster.push(span);
        clusterEnd = Math.max(clusterEnd, span.to);
    }

    if (cluster.length > 0) {
        flush();
    }

    return { placed, outside };
}

function useCurrentMinute(): number {
    const [minute, setMinute] = useState(() =>
        minutesOfDay(new Date().toISOString()),
    );

    useEffect(() => {
        const timer = window.setInterval(() => {
            setMinute(minutesOfDay(new Date().toISOString()));
        }, 60_000);

        return () => window.clearInterval(timer);
    }, []);

    return minute;
}

type Props = {
    range: { from: string; to: string };
    meetingsByDay: Map<string, CalendarMeeting[]>;
};

export default function WeekView({ range, meetingsByDay }: Props) {
    const days = eachDay(startOfWeek(range.from), endOfWeek(range.to)).slice(
        0,
        7,
    );
    const today = todayIso();
    const currentMinute = useCurrentMinute();
    const showNowLine =
        days.includes(today) &&
        currentMinute >= dayStartsAt &&
        currentMinute <= dayEndsAt;

    return (
        <div className="flex min-h-0 flex-1 flex-col overflow-auto">
            <div className="bg-background sticky top-0 z-20 grid grid-cols-[3.5rem_repeat(7,minmax(0,1fr))] border-b">
                <div />
                {days.map((day) => (
                    <div
                        key={day}
                        className={cn(
                            'flex items-baseline gap-1.5 border-l px-2 py-2',
                            isWeekend(day) && 'bg-muted/20',
                        )}
                    >
                        <span className="text-muted-foreground text-xs">
                            {formatWeekday(day, 'short')}
                        </span>
                        <span
                            className={cn(
                                'flex size-6 items-center justify-center text-sm tabular-nums',
                                day === today &&
                                    'bg-primary text-primary-foreground rounded-full font-semibold',
                            )}
                        >
                            {dayNumber(day)}
                        </span>
                    </div>
                ))}
            </div>

            <div className="grid grid-cols-[3.5rem_repeat(7,minmax(0,1fr))] pt-3">
                <div className="relative">
                    {hours.map((hour) => (
                        <div
                            key={hour}
                            style={{
                                top:
                                    ((hour * 60 - dayStartsAt) / 60) *
                                    hourHeight,
                            }}
                            className="text-muted-foreground absolute right-0 -translate-y-1/2 pr-2 text-[11px] tabular-nums"
                        >
                            {String(hour).padStart(2, '0')}:00
                        </div>
                    ))}
                </div>

                {days.map((day) => {
                    const { placed, outside } = placeDay(
                        meetingsByDay.get(day) ?? [],
                    );

                    return (
                        <div
                            key={day}
                            className={cn(
                                'relative border-l',
                                isWeekend(day) && 'bg-muted/20',
                            )}
                            style={{
                                height:
                                    ((dayEndsAt - dayStartsAt) / 60) *
                                    hourHeight,
                            }}
                        >
                            {hours.slice(1).map((hour) => (
                                <div
                                    key={hour}
                                    className="border-border/60 absolute inset-x-0 border-t"
                                    style={{
                                        top:
                                            ((hour * 60 - dayStartsAt) / 60) *
                                            hourHeight,
                                    }}
                                />
                            ))}

                            {showNowLine && day === today && (
                                <div
                                    className="border-primary absolute inset-x-0 z-10 border-t-2"
                                    style={{
                                        top:
                                            ((currentMinute - dayStartsAt) /
                                                60) *
                                            hourHeight,
                                    }}
                                    aria-hidden
                                />
                            )}

                            {placed.map(
                                ({ meeting, top, height, lane, lanes }) => (
                                    <div
                                        key={meeting.id}
                                        className="absolute px-0.5"
                                        style={{
                                            top,
                                            height,
                                            left: `${(lane / lanes) * 100}%`,
                                            width: `${100 / lanes}%`,
                                        }}
                                    >
                                        <MeetingChip
                                            meeting={meeting}
                                            variant="block"
                                        />
                                    </div>
                                ),
                            )}

                            {outside.length > 0 && (
                                <div className="absolute inset-x-0 bottom-0 px-0.5 pb-0.5">
                                    <p className="text-muted-foreground bg-background/80 rounded-sm px-1 text-[11px]">
                                        {outside.length} fuori orario
                                    </p>
                                </div>
                            )}
                        </div>
                    );
                })}
            </div>
        </div>
    );
}
