
import { marked } from 'marked';
import { MarkdownOptions } from './render';

export interface DescriptionListToken extends marked.Tokens.Generic {
	type: 'description_list';
	items: (DescriptionTermToken | DescriptionDetailToken)[];
}

export type DescriptionElemToken = DescriptionTermToken | DescriptionDetailToken;

export interface DescriptionTermToken extends marked.Tokens.Generic {
	type: 'description_term';
	text: string;
}

export interface DescriptionDetailToken extends marked.Tokens.Generic {
	type: 'description_detail';
	text: string;
}

export function description_list_ext(renderer: marked.Renderer, opts: MarkdownOptions) : marked.TokenizerExtension & marked.RendererExtension {
	return {
		name: 'description_list',
		level: 'block',
		start: (src) => src.match(/^: /)?.index,
		tokenizer(src, tokens) {
			const start = src.match(/^: /)?.index;
			const lines = src.slice(start).split(/\n/g);
			const token: DescriptionListToken = {
				type: 'description_list',
				raw: '',
				items: [ ]
			};

			let current: DescriptionElemToken;
			const render_current = () => {
				if (current) {
					this.lexer.blockTokens(current.text, current.tokens);
					current = null;
				}
			};

			for (const line of lines) {
				// Skip empty lines
				if (! line.trim()) {
					token.raw += line + '\n';

					if (current) {
						current.raw += line + '\n';
						current.text += '\n';
					}

					continue;
				}

				// If the line starts immediately with a colon, it is a <dt>
				if (line.startsWith(': ')) {
					render_current();
					token.raw += line + '\n';
					token.items.push(
						current = {
							type: 'description_term',
							raw: line,
							text: line.slice(2),
							tokens: [ ],
						}
					);
					continue;
				}

				// If the line starts with a colon after an indent, it is a <dd>
				if (line.startsWith('  : ')) {
					render_current();
					token.raw += line + '\n';
					token.items.push(
						current = {
							type: 'description_detail',
							raw: line,
							text: line.slice(4),
							tokens: [ ],
						}
					);
					continue;
				}

				// If the line starts with (at least) two indents, it is a child
				// of the current element
				if (line.startsWith('    ')) {
					token.raw += line + '\n';
					current.raw += '\n' + line;
					current.text += '\n' + line.slice(current.type === 'description_term' ? 2 : 4);
					continue;
				}

				// If the line starts with one indent, it is a child of the current
				// <dt> (but is not allowed after a <dd>)
				if (line.startsWith('  ')) {
					if (current.type !== 'description_term') {
						render_current();
						break;
					}

					token.raw += line + '\n';
					current.raw += '\n' + line;
					current.text += '\n' + line.slice(2);
					continue;
				}

				// If the line starts any other way, it is the start of new content
				// and we are done parsing
				render_current();
				break;
			}
			
			render_current();

			if (token.items.length) {
				return token;
			}
		},
		renderer(token: DescriptionListToken) {
			const items = token.items.map((item) => {
				const tag = item.type === 'description_term' ? 'dt' : 'dd';
				return `<${tag}>${this.parser.parse(item.tokens)}</${tag}>`;
			});

			return `<dl>${items.join('')}</dl>`;
		}
	};
}
