import type { DateLike } from '@vueuse/core'
import type { AvailableTimeLanguages, DateTimeAcceptedFormats, LanguageTranslations, TimeUnit } from '../../types/timeAgoT'

interface TimeDeltaOptions {
	Day?: number
	Hour?: number
	Minute?: number
	Second?: number
	Week?: number
	Month?: number
	Year?: number
}

export function timeDelta(date: string | Date, options: TimeDeltaOptions) {
	date = new Date(date)
	const { Day, Hour, Minute, Second, Week, Month, Year } = options
	if (Day) {
		date.setDate(date.getDate() + Day)
	}
	if (Hour) {
		date.setHours(date.getHours() + Hour)
	}
	if (Minute) {
		date.setMinutes(date.getMinutes() + Minute)
	}
	if (Second) {
		date.setSeconds(date.getSeconds() + Second)
	}
	if (Week) {
		date.setDate(date.getDate() + Week * 7)
	}
	if (Month) {
		date.setMonth(date.getMonth() + Month)
	}
	if (Year) {
		date.setFullYear(date.getFullYear() + Year)
	}
	return date
}

const translations: Record<AvailableTimeLanguages, LanguageTranslations> = {
	en: {
		year: 'year',
		month: 'month',
		week: 'week',
		day: 'day',
		hour: 'hour',
		minute: 'minute',
		second: 'second',
		ago: 'ago',
		in: 'in',
		justNow: 'Just now'
	},
	es: {
		year: 'año',
		month: 'mes',
		week: 'semana',
		day: 'día',
		hour: 'hora',
		minute: 'minuto',
		second: 'segundo',
		ago: 'hace',
		in: 'en',
		justNow: 'Justo ahora'
	},
	fr: {
		year: 'an',
		month: 'mois',
		week: 'semaine',
		day: 'jour',
		hour: 'heure',
		minute: 'minute',
		second: 'seconde',
		ago: 'il y a',
		in: 'dans',
		justNow: 'À l\'instant'
	},
	he: {
		year: { singular: 'שנה', plural: 'שנים' },
		month: { singular: 'חודש', plural: 'חודשים' },
		week: { singular: 'שבוע', plural: 'שבועות' },
		day: { singular: 'יום', plural: 'ימים' },
		hour: { singular: 'שעה', plural: 'שעות' },
		minute: { singular: 'דקה', plural: 'דקות' },
		second: { singular: 'שנייה', plural: 'שניות' },
		ago: 'לפני',
		in: 'בעוד',
		justNow: 'זה עתה'
	}
}

export function timeAgo(date: string | Date, lang: AvailableTimeLanguages = 'en') {
	if (!date) return ''
	if (typeof date === 'string') date = new Date(date)
	const now: Date = new Date()
	const nowNumber = now.getTime()
	const dateNumber = date.getTime()
	const seconds = Math.floor((dateNumber - nowNumber) / 1000)

	const intervals = [
		{ label: 'year', seconds: 31536000 },
		{ label: 'month', seconds: 2592000 },
		{ label: 'week', seconds: 604800 },
		{ label: 'day', seconds: 86400 },
		{ label: 'hour', seconds: 3600 },
		{ label: 'minute', seconds: 60 },
		{ label: 'second', seconds: 1 }
	]

	const selectedLang = translations[lang]

	for (const interval of intervals) {
		const count = Math.floor(Math.abs(seconds) / interval.seconds)
		if (count >= 1) {
			const suffix = seconds < 0 ? ` ${selectedLang.ago}` : ''
			const prefix = seconds > 0 && selectedLang.in !== 'in' ? `${selectedLang.in} ` : ''

			// Handle Hebrew plural forms
			if (lang === 'he') {
				const timeUnit = selectedLang[interval.label] as TimeUnit
				const form = count === 1 ? timeUnit.singular : timeUnit.plural

				// For Hebrew, we place the prefix/suffix before the number
				const hebrewPrefix = seconds < 0 ? `${selectedLang.ago} ` : (seconds > 0 ? `${selectedLang.in} ` : '')

				if (interval.label === 'day' && seconds > 0) {
					const hours = Math.floor((Math.abs(seconds) % 86400) / 3600)
					const hourUnit = selectedLang.hour as TimeUnit
					const hourForm = hours === 1 ? hourUnit.singular : hourUnit.plural
					return `${hebrewPrefix}${count} ${form}${hours > 0 ? ` ${hours} ${hourForm}` : ''}`
				}
				return `${hebrewPrefix}${count} ${form}`
			}

			// Handle other languages
			if (interval.label === 'day' && seconds > 0) {
				const hours = Math.floor((Math.abs(seconds) % 86400) / 3600)
				const hourLabel = hours > 1 ? `${selectedLang.hour}s` : selectedLang.hour
				return `${prefix}${count} ${selectedLang[interval.label]}${hours > 0 ? ` ${hours} ${hourLabel}` : ''}${suffix}`
			}
			return `${prefix}${count} ${selectedLang[interval.label]}${suffix}`
		}
	}

	return selectedLang.justNow as string
}

function getBrowserNavigatorLocale(): string {
	if (typeof navigator !== 'object') return 'en-US'
	return navigator.languages && navigator.languages.length
		? navigator.languages[0]
		: navigator.language
}

/**
 * Adjust a date for a specific timezone
 * @param date The date to adjust
 * @param timeZone The timezone to use (e.g., 'UTC', 'America/New_York')
 * @returns Date parts with timezone adjustment applied
 */
export function handleTimezone(date: Date, intFmtOpt: Intl.DateTimeFormatOptions): Date {
	// If timeZone is UTC, convert to UTC directly
	if (intFmtOpt.timeZone === 'UTC') {
		const utcDate = new Date(date.getTime())
		utcDate.setMinutes(utcDate.getMinutes() + date.getTimezoneOffset())
		return utcDate
	}

	// For other timezones, use the Intl API
	try {
		// Always use en-US locale for parsing date parts to ensure Latin numerals
		const formatter = new Intl.DateTimeFormat('en-US', {
			...intFmtOpt,
			month: 'numeric' // Force numeric month format
		})

		// Format the date in the target timezone
		const formattedParts = formatter.formatToParts(date)

		// Extract date components from formatted parts
		const parts: Record<string, number> = {}

		formattedParts.forEach((part) => {
			if (part.type !== 'literal' && part.type !== 'timeZoneName') {
				parts[part.type] = Number.parseInt(part.value, 10)
			}
		})

		// Create a new date with these components
		const adjustedDate = new Date(
			parts.year,
			(parts.month || 1) - 1, // Month is 0-based in JS
			parts.day,
			parts.hour || 0,
			parts.minute || 0,
			parts.second || 0
		)

		return adjustedDate
	} catch (error) {
		console.warn(`Error handling timezone ${intFmtOpt.timeZone}:`, error)
		return date // Return original date on error
	}
}

export function getDatePartsMap(date: Date, locale: Intl.LocalesArgument, intFmtOpt?: Intl.DateTimeFormatOptions) {
	// Apply timezone adjustment if specified
	const d = intFmtOpt?.timeZone ? handleTimezone(date, intFmtOpt) : date

	// Use numeric formatting for year to ensure consistency across locales
	const year = d.getFullYear().toString()

	/// keep-sorted
	return {
		AmPm: d.toLocaleString(locale, { hour: 'numeric', hour12: true, minute: 'numeric' }).split(' ')[1],
		DD: String(d.getDate()).padStart(2, '0'),
		DDD: d.toLocaleString(locale, { weekday: 'short' }),
		DDDD: d.toLocaleString(locale, { weekday: 'long' }),
		HH: String(d.getHours()).padStart(2, '0'),
		mm: String(d.getMinutes()).padStart(2, '0'),
		MM: String(d.getMonth() + 1).padStart(2, '0'),
		MMM: d.toLocaleString(locale, { month: 'short' }),
		MMMM: d.toLocaleString(locale, { month: 'long' }),
		ss: String(d.getSeconds()).padStart(2, '0'),
		sss: String(d.getMilliseconds()).padStart(3, '0'),

		// Always use Latin numerals for year to ensure consistency
		YY: year.slice(-2),
		YYYY: year,
	}
}

// ? Avoid creating the date parts map on every call
const _datePartsMapNow = getDatePartsMap(new Date(), getBrowserNavigatorLocale())

// Sort tokens by length (longest first) to avoid partial replacements
const _orderedDateTokens = (
	Object.keys(_datePartsMapNow).sort((a, b) => b.length - a.length)
) as (keyof typeof _datePartsMapNow)[]

// ? Avoid creating the token regex pattern on every call
// ? no longer creating the Regex Objs it in a loop
const _tokenRegExPattern = new RegExp(_orderedDateTokens.map(token => token).join('|'), 'g')

export interface FormatDateOptions extends Partial<Pick<Intl.DateTimeFormatOptions, 'hour12'>> {
	fmt?: DateTimeAcceptedFormats
	locale?: Intl.LocalesArgument
	tz?: string
}
/**
 * Formats a date based on the provided format string, locale, and timezone.
 * @param date The date to format (string or Date object).
 * @param opts Options for formatting the date.
 * @param opts.fmt The format string (default is 'DD.MM.YY').
 * @param opts.locale The locale to use for formatting (default is browser's locale).
 * @param opts.tz The timezone to use for formatting (default is local timezone).
 * @returns Formatted date string.
 */
export function formatDate(
	date?: DateLike,
	opts: FormatDateOptions = {},
): string {
	let { fmt: format, locale, tz: timeZone, ...rest } = opts

	if (!date) return ''
	format = format || 'DD.MM.YY'

	locale = locale || getBrowserNavigatorLocale()
	try {
		// Validate the date
		const d = typeof date === 'string' || typeof date === 'number' ? new Date(date) : date

		// Check if date is valid
		if (Number.isNaN(d.getTime())) {
			console.warn('Invalid date provided to formatDate:', date)
			return ''
		}

		// For more complex formats that need localization, use Intl.DateTimeFormat
		/// keep-sorted
		const intFmtOpt: Intl.DateTimeFormatOptions = {
			day: 'numeric',
			hour: '2-digit',

			// Set default hour12 to true if not explicitly set
			// hour12: true,
			hour12: rest.hour12 === undefined ? true : rest.hour12,
			minute: '2-digit',
			month: 'long',
			second: '2-digit',
			timeZone, // Add timeZone if provided
			weekday: 'long',
			year: 'numeric',
		}

		const datePartsMap = getDatePartsMap(d, locale, intFmtOpt)

		const formatter = new Intl.DateTimeFormat(locale, intFmtOpt)

		const formattedParts = formatter.formatToParts(d)
		const partsMap: Partial<Record<Intl.DateTimeFormatPartTypes, string>> = {}

		formattedParts.forEach((part) => {
			if (part.type !== 'literal') {
				partsMap[part.type] = part.value
			}
		})

		// Add localized formats to our map
		if (partsMap.month) {
			datePartsMap.MMM = partsMap.month.substring(0, 3)
			datePartsMap.MMMM = partsMap.month
		}

		if (partsMap.weekday) {
			datePartsMap.DDD = partsMap.weekday.substring(0, 3)
			datePartsMap.DDDD = partsMap.weekday
		}

		if (partsMap.dayPeriod) {
			datePartsMap.AmPm = partsMap.dayPeriod
		}

		// Process the format string by replacing each token with its value
		return format.replace(
			_tokenRegExPattern,
			match => datePartsMap[match as keyof typeof datePartsMap]
		)
	} catch (error) {
		console.warn(`Error formatting date: ${date} with format: ${format}`, error)
		return ''
	}
}
export const fmtDate = formatDate
