/**
 * Notion ↔ rNotes converter.
 *
 * Import: Notion API block types → markdown → TipTap JSON
 * Export: TipTap JSON → Notion block format, creates pages via API
 */

import { markdownToTiptap, tiptapToMarkdown, extractPlainTextFromTiptap } from './markdown-tiptap';
import { registerConverter, hashContent } from './index';
import type { ConvertedNote, ImportInput, ImportResult, ExportOptions, ExportResult, NoteConverter } from './index';
import type { NoteItem } from '../schemas';
// Note: imports from './index' and './markdown-tiptap' resolve to rdocs-local copies

const NOTION_API_VERSION = '2022-06-28';
const NOTION_API_BASE = 'https://api.notion.com/v1';

/** Rate-limited fetch for Notion API (3 req/s). */
let lastRequestTime = 0;
async function notionFetch(url: string, opts: RequestInit & { token: string }): Promise<any> {
	const now = Date.now();
	const elapsed = now - lastRequestTime;
	if (elapsed < 334) { // ~3 req/s
		await new Promise(r => setTimeout(r, 334 - elapsed));
	}
	lastRequestTime = Date.now();

	const res = await fetch(url, {
		...opts,
		headers: {
			'Authorization': `Bearer ${opts.token}`,
			'Notion-Version': NOTION_API_VERSION,
			'Content-Type': 'application/json',
			...opts.headers,
		},
	});

	if (!res.ok) {
		const body = await res.text();
		throw new Error(`Notion API error ${res.status}: ${body}`);
	}
	return res.json();
}

/** Convert a Notion rich text array to markdown. */
function richTextToMarkdown(richText: any[]): string {
	if (!richText) return '';
	return richText.map((rt: any) => {
		let text = rt.plain_text || '';
		const ann = rt.annotations || {};
		if (ann.code) text = `\`${text}\``;
		if (ann.bold) text = `**${text}**`;
		if (ann.italic) text = `*${text}*`;
		if (ann.strikethrough) text = `~~${text}~~`;
		if (rt.href) text = `[${text}](${rt.href})`;
		return text;
	}).join('');
}

/** Convert a Notion block to markdown. */
function blockToMarkdown(block: any, indent = ''): string {
	const type = block.type;
	const data = block[type];
	if (!data) return '';

	switch (type) {
		case 'paragraph':
			return `${indent}${richTextToMarkdown(data.rich_text)}`;

		case 'heading_1':
			return `# ${richTextToMarkdown(data.rich_text)}`;

		case 'heading_2':
			return `## ${richTextToMarkdown(data.rich_text)}`;

		case 'heading_3':
			return `### ${richTextToMarkdown(data.rich_text)}`;

		case 'bulleted_list_item':
			return `${indent}- ${richTextToMarkdown(data.rich_text)}`;

		case 'numbered_list_item':
			return `${indent}1. ${richTextToMarkdown(data.rich_text)}`;

		case 'to_do': {
			const checked = data.checked ? 'x' : ' ';
			return `${indent}- [${checked}] ${richTextToMarkdown(data.rich_text)}`;
		}

		case 'toggle':
			return `${indent}- ${richTextToMarkdown(data.rich_text)}`;

		case 'code': {
			const lang = data.language || '';
			const code = richTextToMarkdown(data.rich_text);
			return `\`\`\`${lang}\n${code}\n\`\`\``;
		}

		case 'quote':
			return `> ${richTextToMarkdown(data.rich_text)}`;

		case 'callout': {
			const icon = data.icon?.emoji || '';
			return `> ${icon} ${richTextToMarkdown(data.rich_text)}`;
		}

		case 'divider':
			return '---';

		case 'image': {
			const url = data.file?.url || data.external?.url || '';
			const caption = data.caption ? richTextToMarkdown(data.caption) : '';
			return `![${caption}](${url})`;
		}

		case 'bookmark':
			return `[${data.url}](${data.url})`;

		case 'table': {
			// Tables are handled via children blocks
			return '';
		}

		case 'table_row': {
			const cells = (data.cells || []).map((cell: any[]) => richTextToMarkdown(cell));
			return `| ${cells.join(' | ')} |`;
		}

		case 'child_page':
			return `**${data.title}** (sub-page)`;

		case 'child_database':
			return `**${data.title}** (database)`;

		default:
			// Try to extract rich_text if available
			if (data.rich_text) {
				return richTextToMarkdown(data.rich_text);
			}
			return '';
	}
}

/** Convert TipTap markdown content to Notion blocks. */
function markdownToNotionBlocks(md: string): any[] {
	const lines = md.split('\n');
	const blocks: any[] = [];

	let i = 0;
	while (i < lines.length) {
		const line = lines[i];

		// Empty line
		if (!line.trim()) {
			i++;
			continue;
		}

		// Headings
		const headingMatch = line.match(/^(#{1,3})\s+(.+)/);
		if (headingMatch) {
			const level = headingMatch[1].length;
			const text = headingMatch[2];
			const type = `heading_${level}` as string;
			blocks.push({
				type,
				[type]: {
					rich_text: [{ type: 'text', text: { content: text } }],
				},
			});
			i++;
			continue;
		}

		// Code blocks
		if (line.startsWith('```')) {
			const lang = line.slice(3).trim();
			const codeLines: string[] = [];
			i++;
			while (i < lines.length && !lines[i].startsWith('```')) {
				codeLines.push(lines[i]);
				i++;
			}
			blocks.push({
				type: 'code',
				code: {
					rich_text: [{ type: 'text', text: { content: codeLines.join('\n') } }],
					language: lang || 'plain text',
				},
			});
			i++; // skip closing ```
			continue;
		}

		// Blockquotes
		if (line.startsWith('> ')) {
			blocks.push({
				type: 'quote',
				quote: {
					rich_text: [{ type: 'text', text: { content: line.slice(2) } }],
				},
			});
			i++;
			continue;
		}

		// Task list items
		const taskMatch = line.match(/^- \[([ x])\]\s+(.+)/);
		if (taskMatch) {
			blocks.push({
				type: 'to_do',
				to_do: {
					rich_text: [{ type: 'text', text: { content: taskMatch[2] } }],
					checked: taskMatch[1] === 'x',
				},
			});
			i++;
			continue;
		}

		// Bullet list items
		if (line.match(/^[-*]\s+/)) {
			blocks.push({
				type: 'bulleted_list_item',
				bulleted_list_item: {
					rich_text: [{ type: 'text', text: { content: line.replace(/^[-*]\s+/, '') } }],
				},
			});
			i++;
			continue;
		}

		// Numbered list items
		if (line.match(/^\d+\.\s+/)) {
			blocks.push({
				type: 'numbered_list_item',
				numbered_list_item: {
					rich_text: [{ type: 'text', text: { content: line.replace(/^\d+\.\s+/, '') } }],
				},
			});
			i++;
			continue;
		}

		// Horizontal rule
		if (line.match(/^---+$/)) {
			blocks.push({ type: 'divider', divider: {} });
			i++;
			continue;
		}

		// Default: paragraph
		blocks.push({
			type: 'paragraph',
			paragraph: {
				rich_text: [{ type: 'text', text: { content: line } }],
			},
		});
		i++;
	}

	return blocks;
}

const notionConverter: NoteConverter = {
	id: 'notion',
	name: 'Notion',
	requiresAuth: true,

	async import(input: ImportInput): Promise<ImportResult> {
		const token = input.accessToken;
		if (!token) throw new Error('Notion import requires an access token. Connect your Notion account first.');
		if (!input.pageIds || input.pageIds.length === 0) {
			throw new Error('No Notion pages selected for import');
		}

		const notes: ConvertedNote[] = [];
		const warnings: string[] = [];

		for (const pageId of input.pageIds) {
			try {
				// Fetch page metadata
				const page = await notionFetch(`${NOTION_API_BASE}/pages/${pageId}`, {
					method: 'GET',
					token,
				});

				// Extract title
				const titleProp = page.properties?.title || page.properties?.Name;
				const title = titleProp?.title?.[0]?.plain_text || 'Untitled';

				// Fetch all blocks (paginated)
				const allBlocks: any[] = [];
				let cursor: string | undefined;
				do {
					const url = `${NOTION_API_BASE}/blocks/${pageId}/children?page_size=100${cursor ? `&start_cursor=${cursor}` : ''}`;
					const result = await notionFetch(url, { method: 'GET', token });
					allBlocks.push(...(result.results || []));
					cursor = result.has_more ? result.next_cursor : undefined;
				} while (cursor);

				// Handle table rows specially
				const mdParts: string[] = [];
				let inTable = false;
				let tableRowIndex = 0;

				for (const block of allBlocks) {
					if (block.type === 'table') {
						inTable = true;
						tableRowIndex = 0;
						// Fetch table children
						const tableChildren = await notionFetch(
							`${NOTION_API_BASE}/blocks/${block.id}/children?page_size=100`,
							{ method: 'GET', token }
						);
						for (const child of tableChildren.results || []) {
							const rowMd = blockToMarkdown(child);
							mdParts.push(rowMd);
							if (tableRowIndex === 0) {
								// Add separator after header
								const cellCount = (child.table_row?.cells || []).length;
								mdParts.push(`| ${Array(cellCount).fill('---').join(' | ')} |`);
							}
							tableRowIndex++;
						}
						inTable = false;
					} else {
						const md = blockToMarkdown(block);
						if (md) mdParts.push(md);
					}
				}

				const markdown = mdParts.join('\n\n');
				const tiptapJson = markdownToTiptap(markdown);
				const contentPlain = extractPlainTextFromTiptap(tiptapJson);

				// Extract tags from Notion properties
				const tags: string[] = [];
				if (page.properties) {
					for (const [key, value] of Object.entries(page.properties) as [string, any][]) {
						if (value.type === 'multi_select') {
							tags.push(...(value.multi_select || []).map((s: any) => s.name.toLowerCase()));
						} else if (value.type === 'select' && value.select) {
							tags.push(value.select.name.toLowerCase());
						}
					}
				}

				notes.push({
					title,
					content: tiptapJson,
					contentPlain,
					markdown,
					tags: [...new Set(tags)],
					sourceRef: {
						source: 'notion',
						externalId: pageId,
						lastSyncedAt: Date.now(),
						contentHash: hashContent(markdown),
					},
				});

				// Recursively import child pages if requested
				if (input.recursive) {
					for (const block of allBlocks) {
						if (block.type === 'child_page') {
							try {
								const childResult = await this.import({
									...input,
									pageIds: [block.id],
									recursive: true,
								});
								notes.push(...childResult.notes);
								warnings.push(...childResult.warnings);
							} catch (err) {
								warnings.push(`Failed to import child page "${block.child_page?.title}": ${(err as Error).message}`);
							}
						}
					}
				}
			} catch (err) {
				warnings.push(`Failed to import page ${pageId}: ${(err as Error).message}`);
			}
		}

		return { notes, notebookTitle: 'Notion Import', warnings };
	},

	async export(notes: NoteItem[], opts: ExportOptions): Promise<ExportResult> {
		const token = opts.accessToken;
		if (!token) throw new Error('Notion export requires an access token. Connect your Notion account first.');

		const warnings: string[] = [];
		const results: any[] = [];

		for (const note of notes) {
			try {
				// Convert to markdown first
				let md: string;
				if (note.contentFormat === 'tiptap-json' && note.content) {
					md = tiptapToMarkdown(note.content);
				} else {
					md = note.content?.replace(/<[^>]*>/g, '').trim() || '';
				}

				// Convert markdown to Notion blocks
				const blocks = markdownToNotionBlocks(md);

				// Create page in Notion
				// If parentId is provided, create as child page; otherwise create in workspace root
				const parent = opts.parentId
					? { page_id: opts.parentId }
					: { type: 'page_id' as const, page_id: opts.parentId || '' };

				// For workspace-level pages, we need a database or page parent
				// Default to creating standalone pages
				const createBody: any = {
					parent: opts.parentId
						? { page_id: opts.parentId }
						: { type: 'workspace', workspace: true },
					properties: {
						title: {
							title: [{ type: 'text', text: { content: note.title } }],
						},
					},
					children: blocks.slice(0, 100), // Notion limit: 100 blocks per request
				};

				const page = await notionFetch(`${NOTION_API_BASE}/pages`, {
					method: 'POST',
					token,
					body: JSON.stringify(createBody),
				});

				results.push({ noteId: note.id, notionPageId: page.id });

				// If more than 100 blocks, append in batches
				if (blocks.length > 100) {
					for (let i = 100; i < blocks.length; i += 100) {
						const batch = blocks.slice(i, i + 100);
						await notionFetch(`${NOTION_API_BASE}/blocks/${page.id}/children`, {
							method: 'PATCH',
							token,
							body: JSON.stringify({ children: batch }),
						});
					}
				}
			} catch (err) {
				warnings.push(`Failed to export "${note.title}": ${(err as Error).message}`);
			}
		}

		// Return results as JSON since we don't produce a file
		const data = new TextEncoder().encode(JSON.stringify({ exported: results, warnings }));
		return {
			data,
			filename: 'notion-export-results.json',
			mimeType: 'application/json',
		};
	},
};

registerConverter(notionConverter);
