import { format, parseISO } from "date-fns";
import { RRule, rrulestr } from "rrule";
import type { Frequency, Weekday } from "@/lib/rfc5545-types";

export type SupportedRecurrenceFrequency =
	| "NONE"
	| "DAILY"
	| "WEEKLY"
	| "MONTHLY";

export interface RecurrenceFormValue {
	freq: SupportedRecurrenceFrequency;
	interval: number;
	byDay: Weekday[];
	count?: number;
	until?: string;
}

export interface RecurrenceValidationResult {
	isValid: boolean;
	errors: Partial<Record<"count" | "until" | "rule", string>>;
}

const EMPTY_RECURRENCE: RecurrenceFormValue = {
	freq: "NONE",
	interval: 1,
	byDay: [],
};

const RULE_FREQUENCIES: Record<
	Exclude<SupportedRecurrenceFrequency, "NONE">,
	number
> = {
	DAILY: RRule.DAILY,
	WEEKLY: RRule.WEEKLY,
	MONTHLY: RRule.MONTHLY,
};

const WEEKDAY_ORDER: Weekday[] = ["MO", "TU", "WE", "TH", "FR", "SA", "SU"];

const RULE_WEEKDAYS = {
	MO: RRule.MO,
	TU: RRule.TU,
	WE: RRule.WE,
	TH: RRule.TH,
	FR: RRule.FR,
	SA: RRule.SA,
	SU: RRule.SU,
};

const FREQUENCY_LABELS: Record<number, SupportedRecurrenceFrequency> = {
	[RRule.DAILY]: "DAILY",
	[RRule.WEEKLY]: "WEEKLY",
	[RRule.MONTHLY]: "MONTHLY",
};

const SUPPORTED_FREQUENCIES = new Set<SupportedRecurrenceFrequency>([
	"NONE",
	"DAILY",
	"WEEKLY",
	"MONTHLY",
]);

const sortWeekdays = (days: string[] | undefined): Weekday[] => {
	if (!days?.length) return [];
	return [...days]
		.filter((day): day is Weekday => WEEKDAY_ORDER.includes(day as Weekday))
		.sort(
			(left, right) =>
				WEEKDAY_ORDER.indexOf(left) - WEEKDAY_ORDER.indexOf(right),
		);
};

const toUntilDate = (until: string): Date => {
	const parsed = parseISO(`${until}T23:59:59`);
	return parsed;
};

const toRRuleOptions = (value: RecurrenceFormValue, dtstart?: string) => {
	if (value.freq === "NONE") return null;

	const options: ConstructorParameters<typeof RRule>[0] = {
		freq: RULE_FREQUENCIES[value.freq],
		interval: value.interval,
	};

	if (dtstart) {
		options.dtstart = parseISO(dtstart);
	}

	if (value.freq === "WEEKLY" && value.byDay.length > 0) {
		options.byweekday = value.byDay.map((day) => RULE_WEEKDAYS[day]);
	}

	if (value.count) options.count = value.count;
	if (value.until) options.until = toUntilDate(value.until);

	return options;
};

export const getDefaultRecurrence = (): RecurrenceFormValue => ({
	...EMPTY_RECURRENCE,
});

export const validateRecurrence = (
	value: RecurrenceFormValue,
): RecurrenceValidationResult => {
	const errors: RecurrenceValidationResult["errors"] = {};

	if (!SUPPORTED_FREQUENCIES.has(value.freq)) {
		errors.rule = "Unsupported recurrence frequency.";
	}

	if (!Number.isInteger(value.interval) || value.interval < 1) {
		errors.rule = "Interval must be at least 1.";
	}

	if (
		value.count !== undefined &&
		(!Number.isInteger(value.count) || value.count < 1)
	) {
		errors.count = "Count must be a whole number greater than 0.";
	}

	if (value.until) {
		const parsed = parseISO(value.until);
		if (Number.isNaN(parsed.getTime())) {
			errors.until = "Until must be a valid date.";
		}
	}

	if (value.count !== undefined && value.until) {
		errors.rule = "Choose either count or until, not both.";
	}

	if (value.freq === "WEEKLY" && value.byDay.length === 0) {
		errors.rule = "Choose at least one weekday for weekly recurrence.";
	}

	return {
		isValid: Object.keys(errors).length === 0,
		errors,
	};
};

export const parseRecurrenceRule = (rule?: string): RecurrenceFormValue => {
	if (!rule) return getDefaultRecurrence();

	const parsed = rrulestr(`RRULE:${rule}`) as RRule;
	const options = parsed.origOptions;
	const frequency = FREQUENCY_LABELS[options.freq ?? -1] ?? "NONE";

	if (!SUPPORTED_FREQUENCIES.has(frequency)) {
		return getDefaultRecurrence();
	}

	const byweekday = Array.isArray(options.byweekday)
		? options.byweekday.map((weekday) => weekday.toString())
		: options.byweekday
			? [options.byweekday.toString()]
			: [];

	return {
		freq: frequency,
		interval: options.interval ?? 1,
		byDay: sortWeekdays(byweekday),
		count: options.count ?? undefined,
		until: options.until ? format(options.until, "yyyy-MM-dd") : undefined,
	};
};

export const serializeRecurrenceRule = (
	value: RecurrenceFormValue,
	dtstart?: string,
): string | undefined => {
	if (value.freq === "NONE") return undefined;

	const validation = validateRecurrence(value);
	if (!validation.isValid) {
		throw new Error(
			validation.errors.rule ||
				validation.errors.count ||
				validation.errors.until ||
				"Invalid recurrence.",
		);
	}

	const options = toRRuleOptions(value, dtstart);
	if (!options) return undefined;

	const rule = new RRule(options);
	const ruleLine = rule
		.toString()
		.split("\n")
		.find((line) => line.startsWith("RRULE:"));
	if (!ruleLine) return undefined;

	const entries = ruleLine.replace(/^RRULE:/, "").split(";");
	const orderedKeys = ["FREQ", "INTERVAL", "UNTIL", "COUNT", "BYDAY"];
	const sorted = [...entries].sort((left, right) => {
		const leftKey = left.split("=")[0] ?? "";
		const rightKey = right.split("=")[0] ?? "";
		const leftIndex = orderedKeys.indexOf(leftKey);
		const rightIndex = orderedKeys.indexOf(rightKey);
		const normalizedLeftIndex =
			leftIndex === -1 ? Number.MAX_SAFE_INTEGER : leftIndex;
		const normalizedRightIndex =
			rightIndex === -1 ? Number.MAX_SAFE_INTEGER : rightIndex;
		return (
			normalizedLeftIndex - normalizedRightIndex ||
			leftKey.localeCompare(rightKey)
		);
	});

	return sorted.join(";");
};

export const getRecurrencePreview = (
	value: RecurrenceFormValue,
	dtstart?: string,
	limit = 3,
): string[] => {
	if (!dtstart || value.freq === "NONE") return [];

	const options = toRRuleOptions(value, dtstart);
	if (!options) return [];

	const validation = validateRecurrence(value);
	if (!validation.isValid) return [];

	const rule = new RRule(options);
	return rule
		.all((_, index) => index < limit)
		.map((date) => date.toISOString());
};

export const formatRecurrenceText = (rule?: string): string | null => {
	if (!rule) return null;
	return rrulestr(`RRULE:${rule}`).toText();
};

export const getWeekdayOptions = (): Array<{
	value: Weekday;
	label: string;
}> => [
	{ value: "MO", label: "Mon" },
	{ value: "TU", label: "Tue" },
	{ value: "WE", label: "Wed" },
	{ value: "TH", label: "Thu" },
	{ value: "FR", label: "Fri" },
	{ value: "SA", label: "Sat" },
	{ value: "SU", label: "Sun" },
];

export const isWeekdayPreset = (value: RecurrenceFormValue): boolean =>
	value.freq === "WEEKLY" &&
	value.interval === 1 &&
	value.byDay.join(",") === ["MO", "TU", "WE", "TH", "FR"].join(",");

export const recurrenceFrequencyLabels: Record<
	SupportedRecurrenceFrequency,
	string
> = {
	NONE: "Does not repeat",
	DAILY: "Daily",
	WEEKLY: "Weekly",
	MONTHLY: "Monthly",
};

export type { Frequency, Weekday };
