import React, { useEffect, useMemo, useRef, useState } from 'react';
import { __ } from '@wordpress/i18n';
import Icon from '../icons/Icon';
import { IconButton } from './Button';
import { useEscape, useOnClickOutside } from './hooks';

/* ---- Pure date helpers (dates are 'YYYY-MM-DD' strings, computed in UTC) ---- */
const pad = (n) => String(n).padStart(2, '0');
export const toYmd = (d) => `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}`;
export const fromYmd = (s) => {
	const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(s || ''));
	return m ? new Date(Date.UTC(+m[1], +m[2] - 1, +m[3], 12)) : null;
};
export const addDays = (ymd, n) => {
	const d = fromYmd(ymd);
	d.setUTCDate(d.getUTCDate() + n);
	return toYmd(d);
};
export const addMonths = (ym, n) => {
	const [y, m] = ym.split('-').map(Number);
	const d = new Date(Date.UTC(y, m - 1 + n, 1, 12));
	return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}`;
};
export const monthOf = (ymd) => String(ymd).slice(0, 7);
export const localToday = () => {
	const n = new Date();
	return `${n.getFullYear()}-${pad(n.getMonth() + 1)}-${pad(n.getDate())}`;
};

function weekdayLabels(locale, weekStartsOn) {
	const fmt = new Intl.DateTimeFormat(locale || undefined, { weekday: 'short', timeZone: 'UTC' });
	const labels = [];
	// 2023-01-01 was a Sunday.
	for (let i = 0; i < 7; i++) {
		const d = new Date(Date.UTC(2023, 0, 1 + ((weekStartsOn + i) % 7), 12));
		labels.push({ short: fmt.format(d), long: new Intl.DateTimeFormat(locale || undefined, { weekday: 'long', timeZone: 'UTC' }).format(d) });
	}
	return labels;
}

export function formatDateLong(ymd, locale) {
	const d = fromYmd(ymd);
	if (!d) return '';
	return new Intl.DateTimeFormat(locale || undefined, { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric', timeZone: 'UTC' }).format(d);
}

export function formatDateShort(ymd, locale) {
	const d = fromYmd(ymd);
	if (!d) return '';
	return new Intl.DateTimeFormat(locale || undefined, { weekday: 'short', day: 'numeric', month: 'short', timeZone: 'UTC' }).format(d);
}

/**
 * Month calendar.
 *
 * @param {string}   month            'YYYY-MM' currently displayed.
 * @param {Function} onMonthChange    (ym) => void
 * @param {string}   [value]          Selected 'YYYY-MM-DD'.
 * @param {Function} [onChange]       (ymd) => void
 * @param {Function} [getDayState]    (ymd) => 'available'|'unavailable'|'loading'|undefined
 * @param {string}   [minDate]        Earliest selectable date.
 * @param {string}   [maxDate]        Latest selectable date.
 * @param {string}   [today]          'YYYY-MM-DD' (site "today"); defaults to the browser's.
 * @param {number}   [weekStartsOn=1] 0 = Sunday, 1 = Monday.
 * @param {string}   [locale]
 * @param {boolean}  [showLegend]
 */
export default function Calendar({
	month,
	onMonthChange,
	value,
	onChange,
	getDayState,
	minDate,
	maxDate,
	today: todayProp,
	weekStartsOn = 1,
	locale,
	showLegend = false,
	labelledBy,
}) {
	const today = todayProp || localToday();
	const [focused, setFocused] = useState(value || (monthOf(today) === month ? today : `${month}-01`));
	const gridRef = useRef(null);
	const focusOnRender = useRef(false);

	useEffect(() => {
		if (monthOf(focused) !== month) {
			setFocused(value && monthOf(value) === month ? value : `${month}-01`);
		}
		// eslint-disable-next-line react-hooks/exhaustive-deps
	}, [month]);

	useEffect(() => {
		if (!focusOnRender.current || !gridRef.current) return;
		focusOnRender.current = false;
		const btn = gridRef.current.querySelector(`[data-date="${focused}"]`);
		if (btn) btn.focus();
	});

	const title = useMemo(() => {
		const d = fromYmd(`${month}-01`);
		return new Intl.DateTimeFormat(locale || undefined, { month: 'long', year: 'numeric', timeZone: 'UTC' }).format(d);
	}, [month, locale]);

	const labels = useMemo(() => weekdayLabels(locale, weekStartsOn), [locale, weekStartsOn]);

	const weeks = useMemo(() => {
		const first = fromYmd(`${month}-01`);
		const offset = (first.getUTCDay() - weekStartsOn + 7) % 7;
		const start = addDays(`${month}-01`, -offset);
		const cells = [];
		for (let i = 0; i < 42; i++) cells.push(addDays(start, i));
		const rows = [];
		for (let r = 0; r < 6; r++) rows.push(cells.slice(r * 7, r * 7 + 7));
		// Drop a trailing week that is entirely in the next month.
		return rows.filter((row) => row.some((d) => monthOf(d) === month));
	}, [month, weekStartsOn]);

	const isOutOfRange = (d) => (minDate && d < minDate) || (maxDate && d > maxDate);
	const isDisabled = (d) => d < today || isOutOfRange(d) || (getDayState ? getDayState(d) === 'unavailable' : false);

	// Exactly one day is tabbable: the focused day, or (when that one is
	// disabled) the selected day, else the first enabled day of the month.
	const tabStop = (() => {
		if (monthOf(focused) === month && !isDisabled(focused)) return focused;
		if (value && monthOf(value) === month && !isDisabled(value)) return value;
		for (const row of weeks) {
			for (const d of row) {
				if (monthOf(d) === month && !isDisabled(d)) return d;
			}
		}
		return focused;
	})();
	const prevDisabled = !!minDate && addMonths(month, -1) < monthOf(minDate);
	const nextDisabled = !!maxDate && addMonths(month, 1) > monthOf(maxDate);

	const moveFocus = (next) => {
		if (minDate && next < minDate) next = minDate;
		if (maxDate && next > maxDate) next = maxDate;
		setFocused(next);
		focusOnRender.current = true;
		if (monthOf(next) !== month && onMonthChange) onMonthChange(monthOf(next));
	};

	const onKeyDown = (e) => {
		const map = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 };
		const rtl = gridRef.current && getComputedStyle(gridRef.current).direction === 'rtl';
		if (map[e.key] !== undefined) {
			e.preventDefault();
			let delta = map[e.key];
			if (rtl && (e.key === 'ArrowLeft' || e.key === 'ArrowRight')) delta = -delta;
			moveFocus(addDays(focused, delta));
		} else if (e.key === 'Home' || e.key === 'End') {
			e.preventDefault();
			const dow = (fromYmd(focused).getUTCDay() - weekStartsOn + 7) % 7;
			moveFocus(addDays(focused, e.key === 'Home' ? -dow : 6 - dow));
		} else if (e.key === 'PageUp' || e.key === 'PageDown') {
			e.preventDefault();
			const target = addMonths(monthOf(focused), e.key === 'PageUp' ? -1 : 1);
			const day = Math.min(Number(focused.slice(8)), 28);
			moveFocus(`${target}-${pad(day)}`);
		}
	};

	return (
		<div className="bp-cal">
			<div className="bp-cal__header">
				<div className="bp-cal__title" aria-live="polite" id={labelledBy ? undefined : `bp-cal-title-${month}`}>
					{title}
				</div>
				<div className="bp-cal__nav">
					<IconButton
						icon="chevron-left"
						size="sm"
						variant="ghost"
						label={__('Previous month', 'pointly-booking')}
						disabled={prevDisabled}
						onClick={() => onMonthChange && onMonthChange(addMonths(month, -1))}
					/>
					<IconButton
						icon="chevron-right"
						size="sm"
						variant="ghost"
						label={__('Next month', 'pointly-booking')}
						disabled={nextDisabled}
						onClick={() => onMonthChange && onMonthChange(addMonths(month, 1))}
					/>
				</div>
			</div>

			<div role="grid" aria-labelledby={labelledBy || `bp-cal-title-${month}`} ref={gridRef} onKeyDown={onKeyDown}>
				<div role="row" className="bp-cal__grid">
					{labels.map((l) => (
						<div key={l.long} role="columnheader" className="bp-cal__weekday" aria-label={l.long}>
							{l.short}
						</div>
					))}
				</div>
				{weeks.map((row) => (
					<div role="row" className="bp-cal__grid" key={row[0]} style={{ marginTop: 4 }}>
						{row.map((d) => {
							const outside = monthOf(d) !== month;
							if (outside) {
								return <div role="gridcell" key={d} className="bp-cal__day is-outside" aria-hidden="true" />;
							}
							const past = d < today;
							const state = getDayState ? getDayState(d) : undefined;
							const disabled = past || isOutOfRange(d) || state === 'unavailable';
							const selected = value === d;
							const classes = [
								'bp-cal__day',
								d === today ? 'is-today' : '',
								selected ? 'is-selected' : '',
								past ? 'is-past' : '',
								!past && state === 'available' ? 'is-available' : '',
								!past && state === 'unavailable' ? 'is-unavailable' : '',
								state === 'loading' ? 'is-loading' : '',
							]
								.filter(Boolean)
								.join(' ');
							const dayLabel = formatDateLong(d, locale);
							const stateLabel = !past && state === 'available'
								? __('available', 'pointly-booking')
								: !past && state === 'unavailable'
									? __('no free times', 'pointly-booking')
									: '';
							return (
								<div role="gridcell" key={d} aria-selected={selected ? 'true' : undefined}>
									<button
										type="button"
										className={classes}
										data-date={d}
										tabIndex={d === tabStop ? 0 : -1}
										disabled={disabled}
										aria-pressed={selected ? 'true' : 'false'}
										aria-label={stateLabel ? `${dayLabel}, ${stateLabel}` : dayLabel}
										aria-current={d === today ? 'date' : undefined}
										onFocus={() => setFocused(d)}
										onClick={() => onChange && onChange(d)}
									>
										{Number(d.slice(8))}
									</button>
								</div>
							);
						})}
					</div>
				))}
			</div>

			{showLegend ? (
				<div className="bp-cal__legend" aria-hidden="true">
					<span className="bp-cal__legend-item">
						<span className="bp-cal__legend-swatch" />
						{__('Available', 'pointly-booking')}
					</span>
					<span className="bp-cal__legend-item">
						<span className="bp-cal__legend-swatch bp-cal__legend-swatch--off" />
						{__('Unavailable', 'pointly-booking')}
					</span>
				</div>
			) : null}
		</div>
	);
}

/**
 * Date input with a popover calendar.
 */
export function DatePicker({ value, onChange, placeholder = __('Select date', 'pointly-booking'), minDate, maxDate, locale, weekStartsOn = 1, id, disabled, 'aria-required': _ariaRequired, ...aria }) {
	const [open, setOpen] = useState(false);
	const [month, setMonth] = useState(monthOf(value || localToday()));
	const rootRef = useRef(null);
	const btnRef = useRef(null);
	useEscape(() => {
		setOpen(false);
		btnRef.current && btnRef.current.focus();
	}, open);
	useOnClickOutside(rootRef, () => setOpen(false), open);

	useEffect(() => {
		if (open) {
			setMonth(monthOf(value || localToday()));
			window.requestAnimationFrame(() => {
				const el = rootRef.current && rootRef.current.querySelector('.bp-cal__day[tabindex="0"]');
				if (el) el.focus();
			});
		}
		// eslint-disable-next-line react-hooks/exhaustive-deps
	}, [open]);

	return (
		<div ref={rootRef} className="bp-combobox">
			<button
				ref={btnRef}
				id={id}
				type="button"
				className="bp-combobox__control"
				aria-haspopup="dialog"
				aria-expanded={open ? 'true' : 'false'}
				disabled={disabled}
				onClick={() => setOpen((o) => !o)}
				{...aria}
			>
				<Icon name="calendar" className="bp-combobox__chevron" />
				<span className={['bp-combobox__value', value ? '' : 'bp-combobox__placeholder'].join(' ')}>
					{value ? formatDateShort(value, locale) : placeholder}
				</span>
			</button>
			{open ? (
				<div className="bp-popover" role="dialog" aria-label={__('Choose date', 'pointly-booking')} style={{ padding: 12, minWidth: 288, insetInlineEnd: 'auto' }}>
					<Calendar
						month={month}
						onMonthChange={setMonth}
						value={value}
						minDate={minDate}
						maxDate={maxDate}
						locale={locale}
						weekStartsOn={weekStartsOn}
						today="0000-00-00"
						onChange={(d) => {
							if (onChange) onChange(d);
							setOpen(false);
							btnRef.current && btnRef.current.focus();
						}}
					/>
				</div>
			) : null}
		</div>
	);
}
