import React, { useEffect, useMemo, useRef, useState } from 'react';
import { __, sprintf } from '@wordpress/i18n';
import { formatTime, siteToday, splitDateTime } from '../../core/format';
import { hhmm, layoutOverlaps, minutesOf } from './dates';

const HOUR_PX = 56;
const SNAP = 15;

/**
 * Time grid used by the Day, Week and Staff views.
 *
 * columns: [{ id, date, label, sub?, agentId? }]
 * events:  bookings ({ id, start, end, status, customer_name, service_name, agent_name, agent_id })
 * Each event belongs to the column selected by `columnOf(event)`.
 *
 * onCreate(column, 'HH:MM')            click on empty space
 * onOpen(event)                         click on an event
 * onMove(event, column, 'HH:MM')        drag & drop onto another time / column
 */
export default function TimeGrid({ columns, events, columnOf, onCreate, onOpen, onMove, minColWidth = 132 }) {
	const bodyRef = useRef(null);
	const [drag, setDrag] = useState(null); // { id, offsetMin }
	const [drop, setDrop] = useState(null); // { colId, top }
	const today = siteToday();

	const { startHour, endHour } = useMemo(() => {
		let lo = 8;
		let hi = 19;
		events.forEach((e) => {
			lo = Math.min(lo, Math.floor(minutesOf(splitDateTime(e.start).time) / 60));
			hi = Math.max(hi, Math.ceil(minutesOf(splitDateTime(e.end).time || splitDateTime(e.start).time) / 60));
		});
		return { startHour: Math.max(0, lo), endHour: Math.min(24, Math.max(hi, lo + 4)) };
	}, [events]);

	const gridMinutes = (endHour - startHour) * 60;
	const height = (gridMinutes / 60) * HOUR_PX;
	const hours = Array.from({ length: endHour - startHour }, (_, i) => startHour + i);

	// Group events per column and lay out overlaps.
	const byColumn = useMemo(() => {
		const map = new Map(columns.map((c) => [c.id, []]));
		events.forEach((e) => {
			const cid = columnOf(e);
			if (map.has(cid)) map.get(cid).push(e);
		});
		const out = new Map();
		map.forEach((list, cid) => {
			const items = list.map((e) => {
				const s = minutesOf(splitDateTime(e.start).time);
				const en = e.end ? minutesOf(splitDateTime(e.end).time) : s + 30;
				return { id: e.id, startMin: s, endMin: Math.max(en, s + 15) };
			});
			const lay = layoutOverlaps(items);
			out.set(
				cid,
				list.map((e, i) => ({ event: e, ...items[i], ...lay.get(e.id) }))
			);
		});
		return out;
	}, [columns, events, columnOf]);

	// Keep "now" roughly current and scroll it into view once.
	const [now, setNow] = useState(() => new Date());
	useEffect(() => {
		const t = setInterval(() => setNow(new Date()), 60000);
		return () => clearInterval(t);
	}, []);
	const nowMin = now.getHours() * 60 + now.getMinutes();
	useEffect(() => {
		const el = bodyRef.current;
		if (!el || !columns.some((c) => c.date === today)) return;
		const top = ((nowMin - startHour * 60) / 60) * HOUR_PX - 120;
		if (top > 0) el.scrollTop = top;
		// eslint-disable-next-line react-hooks/exhaustive-deps
	}, [columns.length, startHour]);

	const yToMinutes = (clientY, colEl) => {
		const rect = colEl.getBoundingClientRect();
		const y = Math.max(0, Math.min(height, clientY - rect.top));
		return startHour * 60 + (y / HOUR_PX) * 60;
	};
	const snap = (min) => Math.round(min / SNAP) * SNAP;

	const onColClick = (col, e) => {
		if (e.target.closest('.bp-cal-event')) return;
		onCreate && onCreate(col, hhmm(Math.floor(yToMinutes(e.clientY, e.currentTarget) / SNAP) * SNAP));
	};

	const onDragOver = (col, e) => {
		if (!drag) return;
		e.preventDefault();
		e.dataTransfer.dropEffect = 'move';
		const min = snap(yToMinutes(e.clientY, e.currentTarget) - drag.offsetMin);
		setDrop({ colId: col.id, top: ((min - startHour * 60) / 60) * HOUR_PX });
	};
	const onDropCol = (col, e) => {
		if (!drag) return;
		e.preventDefault();
		const min = snap(yToMinutes(e.clientY, e.currentTarget) - drag.offsetMin);
		const ev = events.find((x) => x.id === drag.id);
		setDrag(null);
		setDrop(null);
		if (ev && onMove) onMove(ev, col, hhmm(Math.max(0, min)));
	};

	const template = { gridTemplateColumns: `52px repeat(${columns.length}, minmax(${minColWidth}px, 1fr))` };

	return (
		<div className="bp-cal-grid" role="group" aria-label={__('Calendar', 'pointly-booking')}>
			<div className="bp-cal-grid__scroll" ref={bodyRef}>
				<div className="bp-cal-grid__head" style={template}>
					<div className="bp-cal-grid__corner" />
					{columns.map((c) => (
						<div key={c.id} className={['bp-cal-grid__colhead', c.date === today ? 'is-today' : ''].filter(Boolean).join(' ')}>
							<span className="bp-cal-grid__dow">{c.label}</span>
							{c.sub ? <span className="bp-cal-grid__daynum">{c.sub}</span> : null}
						</div>
					))}
				</div>

				<div className="bp-cal-grid__body" style={{ ...template, height }}>
					<div className="bp-cal-grid__hours" aria-hidden="true">
						{hours.map((h) => (
							<div key={h} className="bp-cal-grid__hour" style={{ height: HOUR_PX }}>
								<span>{formatTime(hhmm(h * 60))}</span>
							</div>
						))}
					</div>

					{columns.map((col) => {
						const items = byColumn.get(col.id) || [];
						const isToday = col.date === today;
						return (
							<div
								key={col.id}
								className={['bp-cal-grid__col', isToday ? 'is-today' : ''].filter(Boolean).join(' ')}
								style={{ backgroundSize: `100% ${HOUR_PX}px` }}
								onClick={(e) => onColClick(col, e)}
								onDragOver={(e) => onDragOver(col, e)}
								onDragLeave={() => setDrop((d) => (d && d.colId === col.id ? null : d))}
								onDrop={(e) => onDropCol(col, e)}
								role="presentation"
							>
								{isToday && nowMin >= startHour * 60 && nowMin <= endHour * 60 ? <div className="bp-cal-grid__now" style={{ top: ((nowMin - startHour * 60) / 60) * HOUR_PX }} aria-hidden="true" /> : null}

								{drop && drop.colId === col.id && drag ? (
									<div className="bp-cal-grid__ghost" style={{ top: drop.top, height: Math.max(20, ((byColumn.get(drag.colId)?.find((i) => i.id === drag.id)?.endMin || 30) - (byColumn.get(drag.colId)?.find((i) => i.id === drag.id)?.startMin || 0)) / 60 * HOUR_PX) }} aria-hidden="true" />
								) : null}

								{items.map(({ event: e, startMin, endMin, lane, lanes }) => {
									const top = ((startMin - startHour * 60) / 60) * HOUR_PX;
									const h = Math.max(22, ((endMin - startMin) / 60) * HOUR_PX - 2);
									const { time } = splitDateTime(e.start);
									const cancelled = e.status === 'cancelled';
									return (
										<button
											key={e.id}
											type="button"
											draggable={!!onMove && !cancelled}
											className={['bp-cal-event', `is-${e.status || 'pending'}`, h < 40 ? 'is-short' : ''].join(' ')}
											style={{ top, height: h, insetInlineStart: `calc(${(lane / lanes) * 100}% + 2px)`, width: `calc(${100 / lanes}% - 4px)` }}
											onClick={() => onOpen && onOpen(e)}
											onDragStart={(ev) => {
												ev.dataTransfer.effectAllowed = 'move';
												ev.dataTransfer.setData('text/plain', String(e.id));
												const rect = ev.currentTarget.getBoundingClientRect();
												setDrag({ id: e.id, colId: col.id, offsetMin: ((ev.clientY - rect.top) / HOUR_PX) * 60 });
											}}
											onDragEnd={() => {
												setDrag(null);
												setDrop(null);
											}}
											aria-label={sprintf(
												/* translators: 1: time, 2: service, 3: customer, 4: status. */
												__('%1$s, %2$s, %3$s (%4$s)', 'pointly-booking'),
												formatTime(time),
												e.service_name || '',
												e.customer_name || '',
												e.status || ''
											)}
										>
											<span className="bp-cal-event__time">{formatTime(time)}</span>
											<span className="bp-cal-event__title">{e.customer_name || e.service_name}</span>
											{h >= 40 ? <span className="bp-cal-event__sub">{e.service_name}</span> : null}
										</button>
									);
								})}
							</div>
						);
					})}
				</div>
			</div>
		</div>
	);
}

export { HOUR_PX };
