/*
 * Copyright (C) 2026 Fluxer Contributors
 *
 * This file is part of Fluxer.
 *
 * Fluxer is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Fluxer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
 */

import {describe, expect, test} from 'vitest';
import {Parser} from '../parser/parser';
import {EmojiKind, NodeType, ParserFlags, TimestampStyle} from '../types/enums';

describe('Fluxer Markdown Parser', () => {
	test('timestamp default', () => {
		const input = 'Current time: <t:1618953630>';
		const flags = 0;
		const parser = new Parser(input, flags);
		const {nodes: ast} = parser.parse();

		expect(ast).toEqual([
			{type: NodeType.Text, content: 'Current time: '},
			{
				type: NodeType.Timestamp,
				timestamp: 1618953630,
				style: TimestampStyle.ShortDateTime,
			},
		]);
	});

	test('timestamp with style', () => {
		const input = '<t:1618953630:d> is the date.';
		const flags = 0;
		const parser = new Parser(input, flags);
		const {nodes: ast} = parser.parse();

		expect(ast).toEqual([
			{
				type: NodeType.Timestamp,
				timestamp: 1618953630,
				style: TimestampStyle.ShortDate,
			},
			{type: NodeType.Text, content: ' is the date.'},
		]);
	});

	test('timestamp with short date & short time style', () => {
		const input = '<t:1618953630:s>';
		const flags = 0;
		const parser = new Parser(input, flags);
		const {nodes: ast} = parser.parse();

		expect(ast).toEqual([
			{
				type: NodeType.Timestamp,
				timestamp: 1618953630,
				style: TimestampStyle.ShortDateShortTime,
			},
		]);
	});

	test('timestamp with short date & medium time style', () => {
		const input = '<t:1618953630:S>';
		const flags = 0;
		const parser = new Parser(input, flags);
		const {nodes: ast} = parser.parse();

		expect(ast).toEqual([
			{
				type: NodeType.Timestamp,
				timestamp: 1618953630,
				style: TimestampStyle.ShortDateMediumTime,
			},
		]);
	});

	test('timestamp invalid style', () => {
		const input = 'Check this <t:1618953630:z> time.';
		const flags = 0;
		const parser = new Parser(input, flags);
		const {nodes: ast} = parser.parse();

		expect(ast).toEqual([{type: NodeType.Text, content: 'Check this <t:1618953630:z> time.'}]);
	});

	test('timestamp non numeric', () => {
		const input = 'Check <t:abc123> time.';
		const flags = 0;
		const parser = new Parser(input, flags);
		const {nodes: ast} = parser.parse();

		expect(ast).toEqual([{type: NodeType.Text, content: 'Check <t:abc123> time.'}]);
	});

	test('timestamp with milliseconds should not parse', () => {
		const input = 'Time: <t:1618953630.123>';
		const flags = 0;
		const parser = new Parser(input, flags);
		const {nodes: ast} = parser.parse();

		expect(ast).toEqual([{type: NodeType.Text, content: 'Time: <t:1618953630.123>'}]);
	});

	test('timestamp with partial milliseconds should not parse', () => {
		const input = '<t:1618953630.1>';
		const flags = 0;
		const parser = new Parser(input, flags);
		const {nodes: ast} = parser.parse();

		expect(ast).toEqual([{type: NodeType.Text, content: '<t:1618953630.1>'}]);
	});

	test('timestamp with excess millisecond precision should not parse', () => {
		const input = '<t:1618953630.123456>';
		const flags = 0;
		const parser = new Parser(input, flags);
		const {nodes: ast} = parser.parse();

		expect(ast).toEqual([{type: NodeType.Text, content: '<t:1618953630.123456>'}]);
	});

	test('timestamp with milliseconds and style should not parse', () => {
		const input = '<t:1618953630.123:R>';
		const flags = 0;
		const parser = new Parser(input, flags);
		const {nodes: ast} = parser.parse();

		expect(ast).toEqual([{type: NodeType.Text, content: '<t:1618953630.123:R>'}]);
	});

	test('timestamp in mixed content', () => {
		const input = 'Hello <a:wave:12345> 🦶 <t:1618953630:d> <:smile:9876>';
		const flags = 0;
		const parser = new Parser(input, flags);
		const {nodes: ast} = parser.parse();

		expect(ast).toEqual([
			{type: NodeType.Text, content: 'Hello '},
			{
				type: NodeType.Emoji,
				kind: {
					kind: EmojiKind.Custom,
					name: 'wave',
					id: '12345',
					animated: true,
				},
			},
			{type: NodeType.Text, content: ' '},
			{
				type: NodeType.Emoji,
				kind: {
					kind: EmojiKind.Standard,
					raw: '🦶',
					codepoints: '1f9b6',
					name: expect.any(String),
				},
			},
			{type: NodeType.Text, content: ' '},
			{
				type: NodeType.Timestamp,
				timestamp: 1618953630,
				style: TimestampStyle.ShortDate,
			},
			{type: NodeType.Text, content: ' '},
			{
				type: NodeType.Emoji,
				kind: {
					kind: EmojiKind.Custom,
					name: 'smile',
					id: '9876',
					animated: false,
				},
			},
		]);
	});

	test('timestamp edge cases', () => {
		const inputs = ['<t:>', '<t:1618953630:>', '<t::d>', '<t:1618953630::d>', '<t:1618953630:d:R>'];

		for (const input of inputs) {
			const parser = new Parser(input, 0);
			const {nodes: ast} = parser.parse();

			expect(ast).toEqual([{type: NodeType.Text, content: input}]);
		}
	});

	test('very large valid timestamp', () => {
		const input = '<t:9999999999>';
		const flags = 0;
		const parser = new Parser(input, flags);
		const {nodes: ast} = parser.parse();

		expect(ast).toEqual([
			{
				type: NodeType.Timestamp,
				timestamp: 9999999999,
				style: TimestampStyle.ShortDateTime,
			},
		]);
	});

	test('timestamp with leading zeros', () => {
		const input = '<t:0001618953630>';
		const flags = 0;
		const parser = new Parser(input, flags);
		const {nodes: ast} = parser.parse();

		expect(ast).toEqual([
			{
				type: NodeType.Timestamp,
				timestamp: 1618953630,
				style: TimestampStyle.ShortDateTime,
			},
		]);
	});

	test('timestamp in code block should not be parsed', () => {
		const input = '```\n<t:1618953630>\n```';
		const flags = ParserFlags.ALLOW_CODE_BLOCKS;
		const parser = new Parser(input, flags);
		const {nodes: ast} = parser.parse();

		expect(ast).toEqual([
			{
				type: NodeType.CodeBlock,
				language: undefined,
				content: '<t:1618953630>\n',
			},
		]);
	});

	test('timestamp in inline code should not be parsed', () => {
		const input = '`<t:1618953630>`';
		const flags = 0;
		const parser = new Parser(input, flags);
		const {nodes: ast} = parser.parse();

		expect(ast).toEqual([
			{
				type: NodeType.InlineCode,
				content: '<t:1618953630>',
			},
		]);
	});

	test('timestamp with non-digit characters should not parse', () => {
		const inputs = [
			'<t:12a34>',
			'<t:12.34>',
			'<t:12,34>',
			'<t:1234e5>',
			'<t:+1234>',
			'<t:-1234>',
			'<t:1234 >',
			'<t: 1234>',
			'<t:1_234>',
			'<t:0x1234>',
			'<t:0b1010>',
			'<t:123.456.789>',
		];

		for (const input of inputs) {
			const parser = new Parser(input, 0);
			const {nodes: ast} = parser.parse();

			expect(ast).toEqual([{type: NodeType.Text, content: input}]);
		}
	});

	test('timestamp with valid integer formats', () => {
		const inputs = ['<t:1234>', '<t:01234>', '<t:9999999999>'];

		for (const input of inputs) {
			const parser = new Parser(input, 0);
			const {nodes: ast} = parser.parse();

			expect(ast[0].type).toBe(NodeType.Timestamp);
		}
	});

	test('timestamp with zero value should not parse', () => {
		const parser = new Parser('<t:0>', 0);
		const {nodes: ast} = parser.parse();

		expect(ast).toEqual([{type: NodeType.Text, content: '<t:0>'}]);
	});
});
