/**
 * It reads markdown files and write its content and metadata to json files that can be imported by React.
 * -   Files and directories names starting with a underscore (_) will be ignored
 * -   Symbolic links are ignored as of the moment
 * -   Filename-to-url encoder is not perfect. Some non-url-friendly filenames might cause problems
 */

import fs from "fs" // read and write files
import path from "path" // get relative path
import matter from "gray-matter" // parse markdown metadata
import toc from "markdown-toc" // table of contents generation

const dirPath = "./markdown" // where it will look for markdown documents
const outPath = "./src/data" // path to the json database

// data that will be converted to JSON string
const result = {
	date: {},
	tags: {},
	posts: {},
}

// creates directory/directories
// https://stackoverflow.com/a/40686946/12979111
function mkDirByPathSync(targetDir, { isRelativeToScript = false } = {}) {
	const sep = path.sep
	const initDir = path.isAbsolute(targetDir) ? sep : ""
	const baseDir = isRelativeToScript ? __dirname : "."

	return targetDir.split(sep).reduce((parentDir, childDir) => {
		const curDir = path.resolve(baseDir, parentDir, childDir)
		try {
			fs.mkdirSync(curDir)
		} catch (err) {
			if (err.code === "EEXIST") {
				// curDir already exists!
				return curDir
			}

			// To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on Windows.
			if (err.code === "ENOENT") {
				// Throw the original parentDir error on curDir `ENOENT` failure.
				throw new Error(
					`EACCES: permission denied, mkdir '${parentDir}'`
				)
			}

			const caughtErr =
				["EACCES", "EPERM", "EISDIR"].indexOf(err.code) > -1
			if (
				!caughtErr ||
				(caughtErr && curDir === path.resolve(targetDir))
			) {
				throw err // Throw if it's just the last created dir.
			}
		}

		return curDir
	}, initDir)
}

// only supports folders and files (no symbolic links)
// does not scale well for large number of folders
// it calls itself for every directory it finds
function recursiveParser(fileOrFolderPath: string) {
	// ignore if file/directory name starts with a underscore
	const fileOrFolderName = fileOrFolderPath.substring(
		fileOrFolderPath.lastIndexOf("/") + 1
	)
	if (fileOrFolderName.startsWith("_")) return

	// not perfect. Some filenames might cause problems.
	const stats = fs.lstatSync(fileOrFolderPath) // checks if the path leads to a directory or a file

	// don't use replaceAll
	const urlPath = `/${path.relative(dirPath, fileOrFolderPath)}` // path that will be used as site url
		.replace(/\.[^/.]+$/, "") // remove file extension
		.replace(/ /g, "-") // replace space with a dash "-"

	// if it's a directory, apply this function to every files/folders in it
	// if it's a file, parse and save it to file
	if (stats.isDirectory()) {
		fs.readdirSync(fileOrFolderPath).map((child) =>
			recursiveParser(`${fileOrFolderPath}/${child}`)
		)
	} else if (stats.isFile()) {
		// skip if file is not a markdown file
		if (!fileOrFolderName.endsWith(".md")) {
			console.log(`Ignoring non markdown file at: ${fileOrFolderPath}`)
			return
		}

		const parsedMarkdown = matter(fs.readFileSync(fileOrFolderPath, "utf8")) // parse markdown metadata
		const contentJSONFile = `${outPath}/posts${urlPath}.json`

		mkDirByPathSync(
			contentJSONFile.substring(0, contentJSONFile.lastIndexOf("/") + 1)
		)

		// write content to json file
		fs.writeFileSync(
			contentJSONFile,
			JSON.stringify({
				content: parsedMarkdown.content,
			})
		)

		result.posts[urlPath] = parsedMarkdown.data

		// preview
		// might cut mid html tag
		result.posts[urlPath].preview =
			parsedMarkdown.content.split(" ").slice(0, 20).join(" ") + " ..."

		// date
		if (!result.posts[urlPath].date) {
			throw Error(`Date does not exist in file: ${urlPath}`)
		}
		const postDate = new Date(parsedMarkdown.data.date)
		result.posts[urlPath].date = postDate.toLocaleString("default", {
			month: "short",
			day: "numeric",
			year: "numeric",
		})

		const YYYYMMDD = new Date(
			postDate.getTime() - postDate.getTimezoneOffset() * 60 * 1000
		)
			.toISOString()
			.split("T")[0]

		if (result.date[YYYYMMDD]) result.date[YYYYMMDD].push(urlPath)
		else result.date[YYYYMMDD] = [urlPath]

		//tags
		if (result.posts[urlPath].tags) {
			result.posts[urlPath].tags.forEach((tag) => {
				if (result.tags[tag]) result.tags[tag].push(urlPath)
				else result.tags[tag] = [urlPath]
			})
		}

		// toc
		result.posts[urlPath].toc = toc(result.posts[urlPath].content).content
	}
}

/** Step 1
 *  Deleting existing files
 */
try {
	fs.rmSync(`${outPath}/posts`, { recursive: true })
	// eslint-disable-next-line no-empty
} catch (err) {}

try {
	fs.unlinkSync(`${outPath}/posts.json`)
	// eslint-disable-next-line no-empty
} catch (err) {}

/** Step 2
 *  Populate result and write to src/data/posts/
 */

// check if it's a directory and start recursive function
if (fs.lstatSync(dirPath).isDirectory()) {
	recursiveParser(dirPath)
} else {
	throw Error("Initial path given does not lead to a directory")
}

let dateKeys: string[] = []
for (const dateKey in result.date) {
	dateKeys.push(dateKey)
}

dateKeys = dateKeys.sort()

const resultDate = result.date
result.date = {}

dateKeys.forEach(
	(sortedDateKey) => (result.date[sortedDateKey] = resultDate[sortedDateKey])
)

/** Step 3
 *  write to src/data/posts.json
 */

fs.writeFileSync(`${outPath}/posts.json`, JSON.stringify(result) + "\n")
