import {beforeEach, afterEach, describe, it, expect, vi} from 'vitest'

import {parseTaskText, PrefixMode} from './parseTaskText'
import {getDateFromText, parseDate} from '../helpers/time/parseDate'
import {calculateDayInterval} from '../helpers/time/calculateDayInterval'
import {PRIORITIES} from '@/constants/priorities'
import {MILLISECONDS_A_DAY} from '@/constants/date'
import type {IRepeatAfter} from '@/types/IRepeatAfter'

describe('Parse Task Text', () => {
	beforeEach(() => {
		vi.useFakeTimers()
	})

	afterEach(() => {
		vi.useRealTimers()
	})

	it('should return text with no intents as is', () => {
		expect(parseTaskText('Lorem Ipsum').text).toBe('Lorem Ipsum')
	})

	it('should not parse text when disabled', () => {
		const text = 'Lorem Ipsum today *label +list !2 @user'
		const result = parseTaskText(text, PrefixMode.Disabled)

		expect(result.text).toBe(text)
	})

	it('should parse text in todoist mode when configured', () => {
		const result = parseTaskText('Lorem Ipsum today @label #list !2 +user', PrefixMode.Todoist)

		expect(result.text).toBe('Lorem Ipsum')
		const now = new Date()
		expect(result?.date?.getFullYear()).toBe(now.getFullYear())
		expect(result?.date?.getMonth()).toBe(now.getMonth())
		expect(result?.date?.getDate()).toBe(now.getDate())
		expect(result.labels).toHaveLength(1)
		expect(result.labels[0]).toBe('label')
		expect(result.list).toBe('list')
		expect(result.priority).toBe(2)
		expect(result.assignees).toHaveLength(1)
		expect(result.assignees[0]).toBe('user')
	})

	it('should ignore email addresses', () => {
		const text = 'Lorem Ipsum email@example.com'
		const result = parseTaskText(text)

		expect(result.text).toBe(text)
	})

	describe('Date Parsing', () => {
		it('should not return any date if none was provided', () => {
			const result = parseTaskText('Lorem Ipsum')

			expect(result.text).toBe('Lorem Ipsum')
			expect(result.date).toBeNull()
		})
		it('should ignore casing', () => {
			const result = parseTaskText('Lorem Ipsum ToDay')

			expect(result.text).toBe('Lorem Ipsum')
			const now = new Date()
			expect(result?.date?.getFullYear()).toBe(now.getFullYear())
			expect(result?.date?.getMonth()).toBe(now.getMonth())
			expect(result?.date?.getDate()).toBe(now.getDate())
		})
		it('should recognize today', () => {
			const result = parseTaskText('Lorem Ipsum today')

			expect(result.text).toBe('Lorem Ipsum')
			const now = new Date()
			expect(result?.date?.getFullYear()).toBe(now.getFullYear())
			expect(result?.date?.getMonth()).toBe(now.getMonth())
			expect(result?.date?.getDate()).toBe(now.getDate())
		})
		describe('should recognize today with a time', () => {
			const cases = {
				'at 15:00': '15:0',
				'@ 15:00': '15:0',
				'at 15:30': '15:30',
				'@ 3pm': '15:0',
				'at 3pm': '15:0',
				'at 3 pm': '15:0',
				'at 3am': '3:0',
				'at 3:12 am': '3:12',
				'at 3:12 pm': '15:12',
			} as const

			for (const c in cases) {
				it(`should recognize today with a time ${c}`, () => {
					const result = parseTaskText(`Lorem Ipsum today ${c}`)

					expect(result.text).toBe('Lorem Ipsum')
					const now = new Date()
					expect(result?.date?.getFullYear()).toBe(now.getFullYear())
					expect(result?.date?.getMonth()).toBe(now.getMonth())
					expect(result?.date?.getDate()).toBe(now.getDate())
					expect(`${result?.date?.getHours()}:${result?.date?.getMinutes()}`).toBe(cases[c as keyof typeof cases])
					expect(result?.date?.getSeconds()).toBe(0)
				})
			}
		})
		it('should recognize tomorrow', () => {
			const result = parseTaskText('Lorem Ipsum tomorrow')

			expect(result.text).toBe('Lorem Ipsum')
			const tomorrow = new Date()
			tomorrow.setDate(tomorrow.getDate() + 1)
			expect(result?.date?.getFullYear()).toBe(tomorrow.getFullYear())
			expect(result?.date?.getMonth()).toBe(tomorrow.getMonth())
			expect(result?.date?.getDate()).toBe(tomorrow.getDate())
		})
		it('should recognize next monday', () => {
			const result = parseTaskText('Lorem Ipsum next monday')

			const untilNextMonday = calculateDayInterval('nextMonday')

			expect(result.text).toBe('Lorem Ipsum')
			const nextMonday = new Date()
			nextMonday.setDate(nextMonday.getDate() + untilNextMonday)
			expect(result?.date?.getFullYear()).toBe(nextMonday.getFullYear())
			expect(result?.date?.getMonth()).toBe(nextMonday.getMonth())
			expect(result?.date?.getDate()).toBe(nextMonday.getDate())
		})
		it('should recognize next monday and ignore casing', () => {
			const result = parseTaskText('Lorem Ipsum nExt Monday')

			const untilNextMonday = calculateDayInterval('nextMonday')

			expect(result.text).toBe('Lorem Ipsum')
			const nextMonday = new Date()
			nextMonday.setDate(nextMonday.getDate() + untilNextMonday)
			expect(result?.date?.getFullYear()).toBe(nextMonday.getFullYear())
			expect(result?.date?.getMonth()).toBe(nextMonday.getMonth())
			expect(result?.date?.getDate()).toBe(nextMonday.getDate())
		})
		it('should recognize this weekend', () => {
			const result = parseTaskText('Lorem Ipsum this weekend')

			const untilThisWeekend = calculateDayInterval('thisWeekend')

			expect(result.text).toBe('Lorem Ipsum')
			const thisWeekend = new Date()
			thisWeekend.setDate(thisWeekend.getDate() + untilThisWeekend)
			expect(result?.date?.getFullYear()).toBe(thisWeekend.getFullYear())
			expect(result?.date?.getMonth()).toBe(thisWeekend.getMonth())
			expect(result?.date?.getDate()).toBe(thisWeekend.getDate())
		})
		it('should recognize later this week', () => {
			const result = parseTaskText('Lorem Ipsum later this week')

			const untilLaterThisWeek = calculateDayInterval('laterThisWeek')

			expect(result.text).toBe('Lorem Ipsum')
			const laterThisWeek = new Date()
			laterThisWeek.setDate(laterThisWeek.getDate() + untilLaterThisWeek)
			expect(result?.date?.getFullYear()).toBe(laterThisWeek.getFullYear())
			expect(result?.date?.getMonth()).toBe(laterThisWeek.getMonth())
			expect(result?.date?.getDate()).toBe(laterThisWeek.getDate())
		})
		it('should recognize later next week', () => {
			const result = parseTaskText('Lorem Ipsum later next week')

			const untilLaterNextWeek = calculateDayInterval('laterNextWeek')

			expect(result.text).toBe('Lorem Ipsum')
			const laterNextWeek = new Date()
			laterNextWeek.setDate(laterNextWeek.getDate() + untilLaterNextWeek)
			expect(result?.date?.getFullYear()).toBe(laterNextWeek.getFullYear())
			expect(result?.date?.getMonth()).toBe(laterNextWeek.getMonth())
			expect(result?.date?.getDate()).toBe(laterNextWeek.getDate())
		})
		it('should recognize next week', () => {
			const result = parseTaskText('Lorem Ipsum next week')

			const untilNextWeek = calculateDayInterval('nextWeek')

			expect(result.text).toBe('Lorem Ipsum')
			const nextWeek = new Date()
			nextWeek.setDate(nextWeek.getDate() + untilNextWeek)
			expect(result?.date?.getFullYear()).toBe(nextWeek.getFullYear())
			expect(result?.date?.getMonth()).toBe(nextWeek.getMonth())
			expect(result?.date?.getDate()).toBe(nextWeek.getDate())
		})
		it('should recognize next month', () => {
			const result = parseTaskText('Lorem Ipsum next month')

			expect(result.text).toBe('Lorem Ipsum')
			const nextMonth = new Date()
			nextMonth.setDate(1)
			nextMonth.setMonth(nextMonth.getMonth() + 1)
			expect(result?.date?.getFullYear()).toBe(nextMonth.getFullYear())
			expect(result?.date?.getMonth()).toBe(nextMonth.getMonth())
			expect(result?.date?.getDate()).toBe(nextMonth.getDate())
		})
		it('should recognize a date', () => {
			const result = parseTaskText('Lorem Ipsum 06/26/2021')

			expect(result.text).toBe('Lorem Ipsum')
			const date = new Date()
			date.setFullYear(2021, 5, 26)
			expect(result?.date?.getFullYear()).toBe(date.getFullYear())
			expect(result?.date?.getMonth()).toBe(date.getMonth())
			expect(result?.date?.getDate()).toBe(date.getDate())
		})
		it('should recognize end of month', () => {
			const result = parseTaskText('Lorem Ipsum end of month')

			expect(result.text).toBe('Lorem Ipsum')
			const curDate = new Date()
			const date = new Date(curDate.getFullYear(), curDate.getMonth() + 1, 0)
			expect(result?.date?.getFullYear()).toBe(date.getFullYear())
			expect(result?.date?.getMonth()).toBe(date.getMonth())
			expect(result?.date?.getDate()).toBe(date.getDate())
		})

		const cases = {
			'monday': 1,
			'Monday': 1,
			'mon': 1,
			'Mon': 1,
			'tuesday': 2,
			'Tuesday': 2,
			'tue': 2,
			'Tue': 2,
			'wednesday': 3,
			'Wednesday': 3,
			'wed': 3,
			'Wed': 3,
			'thursday': 4,
			'Thursday': 4,
			'thu': 4,
			'Thu': 4,
			'friday': 5,
			'Friday': 5,
			'fri': 5,
			'Fri': 5,
			'saturday': 6,
			'Saturday': 6,
			'sat': 6,
			'Sat': 6,
			'sunday': 7,
			'Sunday': 7,
			'sun': 7,
			'Sun': 7,
		} as Record<string, number>
		for (const c in cases) {
			it(`should recognize ${c} as weekday`, () => {
				const result = parseTaskText(`Lorem Ipsum ${c}`)

				expect(result.text).toBe('Lorem Ipsum')
				const nextDate = new Date()
				nextDate.setDate(nextDate.getDate() + ((cases[c] + 7 - nextDate.getDay()) % 7))
				expect(`${result?.date?.getFullYear()}-${result?.date?.getMonth()}-${result?.date?.getDate()}`).toBe(`${nextDate.getFullYear()}-${nextDate.getMonth()}-${nextDate.getDate()}`)
			})
		}
		it('should recognize weekdays with time', () => {
			const result = parseTaskText('Lorem Ipsum thu at 14:00')

			expect(result.text).toBe('Lorem Ipsum')
			const nextThursday = new Date()
			nextThursday.setDate(nextThursday.getDate() + ((4 + 7 - nextThursday.getDay()) % 7))
			expect(`${result?.date?.getFullYear()}-${result?.date?.getMonth()}-${result?.date?.getDate()}`).toBe(`${nextThursday.getFullYear()}-${nextThursday.getMonth()}-${nextThursday.getDate()}`)
			expect(`${result?.date?.getHours()}:${result?.date?.getMinutes()}`).toBe('14:0')
		})
		it('should recognize dates of the month in the past but next month', () => {
			const time = new Date(2022, 0, 15)
			vi.setSystemTime(time)

			const result = parseTaskText(`Lorem Ipsum ${time.getDate() - 1}th`)

			expect(result.text).toBe('Lorem Ipsum')
			expect(result?.date?.getDate()).toBe(time.getDate() - 1)
			expect(result?.date?.getMonth()).toBe(time.getMonth() + 1)
		})
		it('should recognize dates of the month in the past but next month when february is the next month', () => {
			const jan = new Date(2022, 0, 30)
			vi.setSystemTime(jan)

			const result = parseTaskText(`Lorem Ipsum ${jan.getDate() - 1}th`)

			const expectedDate = new Date(2022, 2, jan.getDate() - 1)
			expect(result.text).toBe('Lorem Ipsum')
			expect(result?.date?.getDate()).toBe(expectedDate.getDate())
			expect(result?.date?.getMonth()).toBe(expectedDate.getMonth())
		})
		it('should recognize dates of the month in the past but next month when the next month has less days than this one', () => {
			const mar = new Date(2022, 2, 32)
			vi.setSystemTime(mar)

			const result = parseTaskText(`Lorem Ipsum 31st`)

			const expectedDate = new Date(2022, 4, 31)
			expect(result.text).toBe('Lorem Ipsum')
			expect(result?.date?.getDate()).toBe(expectedDate.getDate())
			expect(result?.date?.getMonth()).toBe(expectedDate.getMonth())
		})
		it('should recognize dates of the month in the future', () => {
			const nextDay = new Date(+new Date() + MILLISECONDS_A_DAY)
			const result = parseTaskText(`Lorem Ipsum ${nextDay.getDate()}nd`)

			expect(result.text).toBe('Lorem Ipsum')
			expect(result?.date?.getDate()).toBe(nextDay.getDate())
		})
		it('should only recognize weekdays with a space before or after them 1', () => {
			const result = parseTaskText('Lorem Ipsum renewed')

			expect(result.text).toBe('Lorem Ipsum renewed')
			expect(result.date).toBeNull()
		})
		it('should only recognize weekdays with a space before or after them 2', () => {
			const result = parseTaskText('Lorem Ipsum github')

			expect(result.text).toBe('Lorem Ipsum github')
			expect(result.date).toBeNull()
		})
		describe('Should not recognize weekdays in words', () => {
			const cases = [
				'renewed',
				'github',
				'fix monitor stand',
				'order wedding cake',
				'investigate thumping noise',
				'iron frilly napkins',
				'take photo of saturn',
				'fix sunglasses',
				'monitor blood pressure',
				'Monitor blood pressure',
				'buy almonds',
			]

			cases.forEach(c => {
				it(`should not recognize text with ${c} at the beginning as weekday`, () => {
					const result = parseTaskText(`${c} dolor sit amet`)

					expect(result.text).toBe(`${c} dolor sit amet`)
					expect(result.date).toBeNull()
				})
				it(`should not recognize text with ${c} at the end as weekday`, () => {
					const result = parseTaskText(`Lorem Ipsum ${c}`)

					expect(result.text).toBe(`Lorem Ipsum ${c}`)
					expect(result.date).toBeNull()
				})
				it(`should not recognize text with ${c} as weekday`, () => {
					const result = parseTaskText(`Lorem Ipsum ${c} dolor`)

					expect(result.text).toBe(`Lorem Ipsum ${c} dolor`)
					expect(result.date).toBeNull()
				})
			})
		})
		it('should not recognize date number with no spacing around them', () => {
			const result = parseTaskText('Lorem Ispum v1.1.1')

			expect(result.text).toBe('Lorem Ispum v1.1.1')
			expect(result.date).toBeNull()
		})
		it('should not recognize dates in urls', () => {
			const text = 'https://some-url.org/blog/2019/1/233526-some-more-text'
			const result = parseTaskText(text)

			expect(result.text).toBe(text)
			expect(result.date).toBeNull()
		})

		describe('Parse weekdays', () => {

			const days = {
				'mon': 1,
				'monday': 1,
				'tue': 2,
				'tuesday': 2,
				'wed': 3,
				'wednesday': 3,
				'thu': 4,
				'thursday': 4,
				'fri': 5,
				'friday': 5,
				'sat': 6,
				'saturday': 6,
				'sun': 7,
				'sunday': 7,
			} as Record<string, number>

			const prefix = [
				'next ',
				'',
			]

			prefix.forEach(p => {
				for (const d in days) {
					it(`should recognize ${p}${d}`, () => {
						const result = parseTaskText(`Lorem Ipsum ${p}${d}`)

						const next = new Date()
						const distance = (days[d] + 7 - next.getDay()) % 7
						next.setDate(next.getDate() + distance)

						expect(result.text).toBe('Lorem Ipsum')
						expect(result?.date?.getFullYear()).toBe(next.getFullYear())
						expect(result?.date?.getMonth()).toBe(next.getMonth())
						expect(result?.date?.getDate()).toBe(next.getDate())
					})
				}
			})

			// This tests only standalone days are recognized and not things like "github", "monitor" or "renewed".
			// We're not using real words here to generate tests for all days on the fly.
			for (const d in days) {
				it(`should not recognize ${d} with a space before it but none after it`, () => {
					const text = `Lorem Ipsum ${d}ipsum`
					const result = parseTaskText(text)

					expect(result.text).toBe(text)
					expect(result.date).toBeNull()
				})
				it(`should not recognize ${d} with a space after it but none before it`, () => {
					const text = `Lorem ipsum${d} dolor`
					const result = parseTaskText(text)

					expect(result.text).toBe(text)
					expect(result.date).toBeNull()
				})
				it(`should not recognize ${d} with no space before or after it`, () => {
					const text = `Lorem Ipsum lorem${d}ipsum`
					const result = parseTaskText(text)

					expect(result.text).toBe(text)
					expect(result.date).toBeNull()
				})
			}
		})

		describe('Parse date from text', () => {
			const now = new Date()
			now.setFullYear(2021, 5, 24)

			const cases = {
				'Lorem Ipsum 06/08/2021 ad': '2021-6-8',
				'Lorem Ipsum 6/7/21 ad': '2021-6-7',
				'dolor sit amet 27/07/2021,': null,
				'dolor sit amet 2021/07/06,': '2021-7-6',
				'dolor sit amet 2021-07-06': '2021-7-6',
				'dolor sit amet 27 jan': '2022-1-27',
				'dolor sit amet 27/1': '2022-1-27',
				'dolor sit amet 27/01': '2022-1-27',
				'dolor sit amet 16/12': '2021-12-16',
				'dolor sit amet 01/27': '2022-1-27',
				'dolor sit amet 1/27': '2022-1-27',
				'dolor sit amet Jan 27': '2022-1-27',
				'dolor sit amet jan 27': '2022-1-27',
				'dolor sit amet feb 21': '2022-2-21',
				'dolor sit amet mar 21': '2022-3-21',
				'dolor sit amet apr 21': '2022-4-21',
				'dolor sit amet may 21': '2022-5-21',
				'dolor sit amet jun 21': '2022-6-21',
				'dolor sit amet jul 21': '2021-7-21',
				'dolor sit amet aug 21': '2021-8-21',
				'dolor sit amet sep 21': '2021-9-21',
				'dolor sit amet oct 21': '2021-10-21',
				'dolor sit amet nov 21': '2021-11-21',
				'dolor sit amet dec 21': '2021-12-21',
			} as Record<string, string | null>

			for (const c in cases) {
				it(`should parse '${c}' as '${cases[c]}'`, () => {
					const {date} = getDateFromText(c, now)
					if (date === null && cases[c] === null) {
						expect(date).toBeNull()
						return
					}

					expect(`${date?.getFullYear()}-${date.getMonth() + 1}-${date?.getDate()}`).toBe(cases[c])
				})
			}
		})

		describe('Parse date from text in', () => {
			const now = new Date()
			now.setFullYear(2021, 5, 24)
			now.setHours(12)
			now.setMinutes(0)
			now.setSeconds(0)

			beforeEach(() => {
				vi.useFakeTimers()
				vi.setSystemTime(now)
			})

			afterEach(() => {
				vi.useRealTimers()
			})

			const cases = {
				'Lorem Ipsum in 1 hour': '2021-6-24 13:0',
				'in 2 hours': '2021-6-24 14:0',
				'in 1 day': '2021-6-25 12:0',
				'in 2 days': '2021-6-26 12:0',
				'in 1 week': '2021-7-1 12:0',
				'in 2 weeks': '2021-7-8 12:0',
				'in 4 weeks': '2021-7-22 12:0',
				'in 1 month': '2021-7-24 12:0',
				'in 3 months': '2021-9-24 12:0',
				'Something in 5 days at 10:00': '2021-6-29 10:0',
				'Something 17th at 10:00': '2021-7-17 10:0',
				'Something sep 17 at 10:00': '2021-9-17 10:0',
				'Something sep 17th at 10:00': '2021-9-17 10:0',
				'Something at 10:00 in 5 days': '2021-6-29 10:0',
				'Something at 10:00 17th': '2021-7-17 10:0',
				'Something at 10:00 sep 17th': '2021-9-17 10:0',
			} as Record<string, string>

			for (const c in cases) {
				it(`should parse '${c}' as '${cases[c]}'`, () => {
					const {date} = parseDate(c, now)
					if (date === null && cases[c] === null) {
						expect(date).toBeNull()
						return
					}

					expect(`${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}`).toBe(cases[c])
				})
			}
		})
	})

	describe('Labels', () => {
		it('should parse labels', () => {
			const result = parseTaskText('Lorem Ipsum *label1 *label2')

			expect(result.text).toBe('Lorem Ipsum')
			expect(result.labels).toHaveLength(2)
			expect(result.labels[0]).toBe('label1')
			expect(result.labels[1]).toBe('label2')
		})
		it('should parse labels from the start', () => {
			const result = parseTaskText('*label1 Lorem Ipsum *label2')

			expect(result.text).toBe('Lorem Ipsum')
			expect(result.labels).toHaveLength(2)
			expect(result.labels[0]).toBe('label1')
			expect(result.labels[1]).toBe('label2')
		})
		it('should resolve duplicate labels', () => {
			const result = parseTaskText('Lorem Ipsum *label1 *label1 *label2')

			expect(result.text).toBe('Lorem Ipsum')
			expect(result.labels).toHaveLength(2)
			expect(result.labels[0]).toBe('label1')
			expect(result.labels[1]).toBe('label2')
		})
		it('should correctly parse labels with spaces in them', () => {
			const result = parseTaskText(`Lorem *'label with space' Ipsum`)

			expect(result.text).toBe('Lorem Ipsum')
			expect(result.labels).toHaveLength(1)
			expect(result.labels[0]).toBe('label with space')
		})
		it('should correctly parse labels with spaces in them and "', () => {
			const result = parseTaskText('Lorem *"label with space" Ipsum')

			expect(result.text).toBe('Lorem Ipsum')
			expect(result.labels).toHaveLength(1)
			expect(result.labels[0]).toBe('label with space')
		})
		it('should not parse labels called date expressions as dates', () => {
			const result = parseTaskText('Lorem Ipsum *today')

			expect(result.text).toBe('Lorem Ipsum')
			expect(result.labels).toHaveLength(1)
			expect(result.labels[0]).toBe('today')
		})
	})

	describe('List', () => {
		it('should parse a list', () => {
			const result = parseTaskText('Lorem Ipsum +list')

			expect(result.text).toBe('Lorem Ipsum')
			expect(result.list).toBe('list')
		})
		it('should parse a list with a space in it', () => {
			const result = parseTaskText(`Lorem Ipsum +'list with long name'`)

			expect(result.text).toBe('Lorem Ipsum')
			expect(result.list).toBe('list with long name')
		})
		it('should parse a list with a space in it and "', () => {
			const result = parseTaskText(`Lorem Ipsum +"list with long name"`)

			expect(result.text).toBe('Lorem Ipsum')
			expect(result.list).toBe('list with long name')
		})
		it('should parse only the first list', () => {
			const result = parseTaskText(`Lorem Ipsum +list1 +list2 +list3`)

			expect(result.text).toBe('Lorem Ipsum +list2 +list3')
			expect(result.list).toBe('list1')
		})
		it('should parse a list that\'s called like a date as list', () => {
			const result = parseTaskText(`Lorem Ipsum +today`)

			expect(result.text).toBe('Lorem Ipsum')
			expect(result.list).toBe('today')
		})
	})

	describe('Priority', () => {
		for (const p in PRIORITIES) {
			it(`should parse priority ${p}`, () => {
				const result = parseTaskText(`Lorem Ipsum !${PRIORITIES[p]}`)

				expect(result.text).toBe('Lorem Ipsum')
				expect(result.priority).toBe(PRIORITIES[p])
			})
		}
		it(`should not parse an invalid priority`, () => {
			const result = parseTaskText(`Lorem Ipsum !9999`)

			expect(result.text).toBe('Lorem Ipsum !9999')
			expect(result.priority).toBe(null)
		})
		it(`should not parse an invalid priority but use the first valid one it finds`, () => {
			const result = parseTaskText(`Lorem Ipsum !9999 !1`)

			expect(result.text).toBe('Lorem Ipsum !9999')
			expect(result.priority).toBe(1)
		})
	})

	describe('Assignee', () => {
		it('should parse an assignee', () => {
			const result = parseTaskText('Lorem Ipsum @user')

			expect(result.text).toBe('Lorem Ipsum')
			expect(result.assignees).toHaveLength(1)
			expect(result.assignees[0]).toBe('user')
		})
		it('should parse multiple assignees', () => {
			const result = parseTaskText('Lorem Ipsum @user1 @user2 @user3')

			expect(result.text).toBe('Lorem Ipsum')
			expect(result.assignees).toHaveLength(3)
			expect(result.assignees[0]).toBe('user1')
			expect(result.assignees[1]).toBe('user2')
			expect(result.assignees[2]).toBe('user3')
		})
		it('should parse avoid duplicate assignees', () => {
			const result = parseTaskText('Lorem Ipsum @user1 @user1 @user2')

			expect(result.text).toBe('Lorem Ipsum')
			expect(result.assignees).toHaveLength(2)
			expect(result.assignees[0]).toBe('user1')
			expect(result.assignees[1]).toBe('user2')
		})
		it('should parse an assignee with a space in it', () => {
			const result = parseTaskText(`Lorem Ipsum @'user with long name'`)

			expect(result.text).toBe('Lorem Ipsum')
			expect(result.assignees).toHaveLength(1)
			expect(result.assignees[0]).toBe('user with long name')
		})
		it('should parse an assignee with a space in it and "', () => {
			const result = parseTaskText(`Lorem Ipsum @"user with long name"`)

			expect(result.text).toBe('Lorem Ipsum')
			expect(result.assignees).toHaveLength(1)
			expect(result.assignees[0]).toBe('user with long name')
		})
		it('should parse an assignee who is called like a date as assignee', () => {
			const result = parseTaskText(`Lorem Ipsum @today`)

			expect(result.text).toBe('Lorem Ipsum')
			expect(result.assignees).toHaveLength(1)
			expect(result.assignees[0]).toBe('today')
		})
	})

	describe('Recurring Dates', () => {
		const cases = {
			'every 1 hour': {type: 'hours', amount: 1},
			'every hour': {type: 'hours', amount: 1},
			'every 5 hours': {type: 'hours', amount: 5},
			'every 12 hours': {type: 'hours', amount: 12},
			'every day': {type: 'days', amount: 1},
			'every 1 day': {type: 'days', amount: 1},
			'every 2 days': {type: 'days', amount: 2},
			'every week': {type: 'weeks', amount: 1},
			'every 1 week': {type: 'weeks', amount: 1},
			'every 3 weeks': {type: 'weeks', amount: 3},
			'every month': {type: 'months', amount: 1},
			'every 1 month': {type: 'months', amount: 1},
			'every 2 months': {type: 'months', amount: 2},
			'every year': {type: 'years', amount: 1},
			'every 1 year': {type: 'years', amount: 1},
			'every 4 years': {type: 'years', amount: 4},
			'anually': {type: 'years', amount: 1},
			'bianually': {type: 'months', amount: 6},
			'semiannually': {type: 'months', amount: 6},
			'biennially': {type: 'years', amount: 2},
			'daily': {type: 'days', amount: 1},
			'hourly': {type: 'hours', amount: 1},
			'monthly': {type: 'months', amount: 1},
			'weekly': {type: 'weeks', amount: 1},
			'yearly': {type: 'years', amount: 1},
			'every one hour': {type: 'hours', amount: 1}, // maybe unnesecary but better to include it for completeness sake
			'every two hours': {type: 'hours', amount: 2},
			'every three hours': {type: 'hours', amount: 3},
			'every four hours': {type: 'hours', amount: 4},
			'every five hours': {type: 'hours', amount: 5},
			'every six hours': {type: 'hours', amount: 6},
			'every seven hours': {type: 'hours', amount: 7},
			'every eight hours': {type: 'hours', amount: 8},
			'every nine hours': {type: 'hours', amount: 9},
			'every ten hours': {type: 'hours', amount: 10},
		} as Record<string, IRepeatAfter>

		for (const c in cases) {
			it(`should parse ${c} as recurring date every ${cases[c].amount} ${cases[c].type}`, () => {
				const result = parseTaskText(`Lorem Ipsum ${c}`)

				expect(result.text).toBe('Lorem Ipsum')
				expect(result?.repeats?.type).toBe(cases[c].type)
				expect(result?.repeats?.amount).toBe(cases[c].amount)
			})
		}
	})
})
