/**
 * Utilities for loading site content and cleaning
 */
import path from "upath";
import * as fs from "fs";
import matter from "gray-matter";
import {
	getAllFiles,
	getMarkdownFiles,
	getRoot,
	guideSites,
	Markdown,
	MarkdownFrontmatter,
	MarkdownResources,
	parseFrontmatter,
} from "./file.utils";
import sizeOf from "image-size";

export function cleanCategories(fm: MarkdownFrontmatter): MarkdownFrontmatter {
	let topics: string[] = fm.topics ? fm.topics : [];
	if (fm.technologies) {
		topics = [...fm.technologies, ...topics];
		delete fm.technologies;
	}

	if (fm.products) {
		topics = [...fm.products, ...topics];
		delete fm.products;
	}

	// Now assign to topics, if it contains anything
	if (topics.length) {
		topics.sort();
		fm.topics = topics;
	}
	return fm;
}

export function writeTopicType(
	filePath: string,
	fm: MarkdownFrontmatter
): MarkdownFrontmatter {
	if (filePath.includes("/products/")) {
		fm.topicType = "product";
	} else if (filePath.includes("/technologies/")) {
		fm.topicType = "technology";
	}
	return fm;
}

export function cleanAllResources(
	resources: MarkdownResources
): Record<string, string> {
	/* For all Markdown resources, clean them up and return string for disk  */
	const results: Record<string, string> = {};
	Object.entries(resources).forEach((markdownRecord) => {
		results[markdownRecord[0]] = cleanResource(markdownRecord);
	});
	return results;
}

export const cleanResource = (
	document: Markdown | [string, MarkdownResources[string]]
) => {
	const [filePath, markdown] = Array.isArray(document)
		? document
		: [
				document.path,
				{ frontmatter: document.frontmatter, content: document.content },
		  ];
	let fm: MarkdownFrontmatter;
	fm = cleanCategories(markdown.frontmatter);
	fm = writeTopicType(filePath, fm);

	// Now make a string to later write to disk
	let cleanString1;
	cleanString1 = matter.stringify(markdown.content, fm);
	// js-yaml converts simple dates to date-times. It would be
	// better to https://github.com/jonschlinkert/gray-matter/issues/62
	// For now, just remove T00:00:00.000Z
	return cleanString1.replace("T00:00:00.000Z", "");
};

export const migrateFrontMatter = () => {
	const allMarkdownFiles = getMarkdownFiles();

	const markdowns = pipe(
		migrateLeadInAttribute,
		removeHasBodyAttribute,
		migrateVideoFrontmatter
	)(allMarkdownFiles);

	writeMarkdownDocuments(markdowns);
};

export const migrateCardThumbnail = () => {
	const allMarkdownFiles = getMarkdownFiles();

	const markdowns = pipe(migrateCardThumbnailFrontmatter)(allMarkdownFiles);

	writeMarkdownDocuments(markdowns);
};

export const writeMarkdownDocuments = (documents: Markdown[]) => {
	documents
		.filter((x) => x.isChanged)
		.forEach((document) => {
			const cleanedFileContent = cleanResource(document);
			document.onWrite?.();
			fs.writeFileSync(document.path, cleanedFileContent, { flag: "w+" });
		});
};

export const removeHasBodyAttribute = (documents: Markdown[]): Markdown[] => {
	return documents.map((document) => {
		if (typeof document.frontmatter.hasBody === "undefined") {
			return document;
		}
		delete document.frontmatter.hasBody;
		return { ...document, isChanged: true };
	});
};

export const migrateVideoFrontmatter = (documents: Markdown[]): Markdown[] => {
	return documents.map((document) => {
		if (!document.frontmatter.shortVideo && !document.frontmatter.longVideo) {
			return document;
		}
		const oldDocument = structuredClone(document);
		const videoRef =
			document.frontmatter.longVideo ?? document.frontmatter.shortVideo;
		const hasStart = !!videoRef?.start;
		const video =
			hasStart && videoRef
				? { url: videoRef.url, start: videoRef.start!, end: videoRef.end! }
				: videoRef?.url;
		delete document.frontmatter.shortVideo;
		delete document.frontmatter.longVideo;
		return {
			...document,
			isChanged: true,
			frontmatter: { ...document.frontmatter, video },
			onWrite: () => {
				const { longVideo, shortVideo } = oldDocument.frontmatter;
				[longVideo, shortVideo].forEach((video) => {
					if (
						video &&
						![
							oldDocument.frontmatter.thumbnail,
							oldDocument.frontmatter.cardThumbnail,
						].includes(video.poster)
					) {
						const resolvedPath = path.normalize(
							`${path.dirname(oldDocument.path)}/${video.poster}`
						);
						if (fs.existsSync(resolvedPath)) {
							fs.unlinkSync(resolvedPath);
						}
					}
				});
			},
		};
	});
};

export function migrateLeadInAttribute(documents: Markdown[]): Markdown[] {
	return documents.map((document) => {
		if (!document.frontmatter.leadin) {
			return document;
		}
		const hasContent = document.content && document.content !== "\n";
		const oldLeadin = document.frontmatter.leadin;
		delete document.frontmatter.leadin;
		return {
			...document,
			content: !hasContent ? oldLeadin : document.content,
			isChanged: true,
		};
	});
}

export function writeCleanResources(): void {
	/* Crawl the tree and write files for all cleaned up resources */
	const root = getRoot();
	const resourceFiles = getAllFiles(root, []);
	const markdownResources = parseFrontmatter(resourceFiles);
	const cleanedResources = cleanAllResources(markdownResources);

	Object.entries(cleanedResources).forEach(([filePath, markdown]) => {
		const pattern = /\n+$/; // Matches one or more newline characters at the end of the string
		const replacement = "\n"; // Replace with a single newline character

		const m = markdown.replace(pattern, replacement);
		fs.writeFileSync(filePath, m, { flag: "w+" });
	});
}

export const migrateCardThumbnailFrontmatter = (
	documents: Markdown[]
): Markdown[] => {
	return documents.map((document) => {
		if (typeof document.frontmatter.cardThumbnail === "undefined") {
			return document;
		}

		// Keep track of old values
		const oldThumbnailRelativePath = path.normalize(
			`${path.dirname(document.path)}/${document.frontmatter.thumbnail}`
		);
		const oldCardThumbnailRelativePath = path.normalize(
			`${path.dirname(document.path)}/${document.frontmatter.cardThumbnail}`
		);
		const thumbnailPngRelativePath = path.normalize(
			`${path.dirname(document.path)}/thumbnail.png`
		);

		// Check aspect ratio
		const thumbnailImageSize = sizeOf(oldThumbnailRelativePath);
		const thumnailIsSquare =
			thumbnailImageSize.width == thumbnailImageSize.height;

		// If thumbnail is square, replace it with cardThumbnail
		if (thumnailIsSquare) {
			// Delete old thumbnail
			if (fs.existsSync(oldThumbnailRelativePath)) {
				fs.unlinkSync(oldThumbnailRelativePath);
			}

			// Migrate frontmatter
			document.frontmatter.thumbnail = document.frontmatter.cardThumbnail;
			if (document.frontmatter.thumbnail == "./card.png") {
				if (fs.existsSync(oldCardThumbnailRelativePath)) {
					document.frontmatter.thumbnail = "./thumbnail.png";
					fs.renameSync(oldCardThumbnailRelativePath, thumbnailPngRelativePath);
				}
			}
		} else {
			// Delete old cardThumbnail
			if (fs.existsSync(oldCardThumbnailRelativePath)) {
				fs.unlinkSync(oldCardThumbnailRelativePath);
			}
		}

		delete document.frontmatter.cardThumbnail;

		return { ...document, isChanged: true };
	});
};

type TopicTypes = {
	[key: string]: string[];
};
export type AllTopicTypes = {
	[key: string]: TopicTypes;
};

export function dumpTopics(): AllTopicTypes {
	/* Utility function to get a content migration spreadsheet of topics */
	const allTopicTypes: AllTopicTypes = {};
	guideSites.forEach((site) => {
		allTopicTypes[site] = {
			product: [],
			technology: [],
			topic: [],
		};
		const topics = path.normalize(`${__dirname}/../sites/${site}/topics`);
		const resourceFiles = getAllFiles(topics, []);
		const markdownResources = parseFrontmatter(resourceFiles);
		Object.entries(markdownResources).forEach(([filePath, markdown]) => {
			const label = filePath.split(path.sep)[10];
			if (label !== "index.md") {
				const topicType = markdown.frontmatter.topicType;
				const key = topicType ? topicType : "topic";
				allTopicTypes[site][key].push(label);
			}
		});
	});
	return allTopicTypes;
}

export function dumpAuthors(): AllTopicTypes {
	/* Utility function to get a content migration spreadsheet of authors */
	const allAuthorTypes: AllTopicTypes = {};
	guideSites.forEach((site) => {
		allAuthorTypes[site] = {
			author: [],
		};
		const authors = path.normalize(`${__dirname}/../sites/${site}/authors`);
		const resourceFiles = getAllFiles(authors, []);
		const markdownResources = parseFrontmatter(resourceFiles);
		Object.keys(markdownResources).forEach((filePath) => {
			const label = filePath.split(path.sep)[10];
			if (label !== "index.md") {
				allAuthorTypes[site]["author"].push(label);
			}
		});
	});
	return allAuthorTypes;
}

type MarkdownTransducer = (markdowns: Markdown[]) => Markdown[];
export const pipe = (...fns: MarkdownTransducer[]) => {
	return (markdowns: Markdown[]) =>
		fns.reduce((prev, fn) => fn(prev), markdowns);
};
