import fs from "node:fs/promises";
import path from "node:path";
import articles from "./articles";
import jsdom from "jsdom";

const DUMPS_LOCATION = "article-dumps";
const PROCESSED_LOCATION = "processed-articles";

export async function scrapeArticlesMainContent() {
    const promises = articles.map((article) => {
        return new Promise<string>(async (resolve, reject) => {
            let text;
            try {
                text = await (await fetch(article.url)).text();
            } catch (e) {
                console.log("e:", article.url);
                reject(`error occurred with this one: ${e}`);
                return;
            }
            resolve(text);
        });
    });

    const results = await Promise.allSettled(promises);

    for (let i = 0; i < results.length; i++) {
        const result = results[i];
        if (result.status === "rejected") {
            continue;
        }
        try {
            const dom = new jsdom.JSDOM(result.value);
            fs.writeFile(
                path.resolve(".", DUMPS_LOCATION, articles[i].slug + ".html"),
                dom.window.document.body.getElementsByClassName("entries").item(0)?.innerHTML ?? "",
            );
        } catch (e) {
            console.log("d:", articles[i].url);
        }
    }
}

function deleteChildCommentsRecursive(node: Node) {
    for (const child of node.childNodes) {
        // comment node type === 8 === Node.COMMENT_NODE but not available here
        if (child.nodeType === 8) {
            child.remove();
        } else {
            deleteChildCommentsRecursive(child);
        }
    }
}

function setAsTitleIfContainsArticle(doc: Document, node: Node) {
    const el = node as HTMLSpanElement;
    if (el.innerHTML?.includes("A R T I C L E")) {
        el.innerHTML = el.innerHTML.replace("A R T I C L E", "");
        el.replaceWith(Object.assign(doc.createElement("h1"), { innerHTML: el.innerHTML }));
        let existingTitle = doc.head.querySelector("title");
        if (!existingTitle) {
            existingTitle = doc.createElement("title");
        }
        doc.head.appendChild(Object.assign(doc.createElement("title"), { innerHTML: el.innerHTML.trim() }));
        return true;
    }
    return false;
}

type ReplcementEntry = {
    tag?: string | null;
    attrs?: Partial<Record<string, any>>;
    extra?: (doc: Document, node: Node) => boolean;
};

const selectorReplacementMap: Record<string, ReplcementEntry | null> = {
    "title": null,
    "ul": {
        tag: null,
    },
    "i": {
        tag: "em",
    },
    "font": {
        tag: null,
    },
    "p": {
        tag: "p",
    },
    "div": {
        tag: null,
    },
    "img": null,
    "br": null,
    "wbr": null,
    "b": {
        tag: "strong",
        extra: setAsTitleIfContainsArticle,
    },
    "center": null,
    "hr": {
        tag: "hr",
    },
    "table": null,
    "span.title": {
        tag: "header",
        attrs: {
            className: "title",
        },
        extra: setAsTitleIfContainsArticle,
    },
    "span.posted": {
        tag: "article",
        attrs: {
            className: "posted",
        },
    },
} as const;

function forEachTextNode(doc: Document, root: Node, cb: (doc: Document, node: Text) => void) {
    for (const child of root.childNodes) {
        // text node type === 3 === Node.TEXT_NODE but not available here
        if (child.nodeType === 3) {
            cb(doc, child as Text);
        } else {
            forEachTextNode(doc, child, cb);
        }
    }
}

async function cleanupFile(fileName: string) {
    const filePath = path.resolve(".", DUMPS_LOCATION, fileName);
    const { window } = new jsdom.JSDOM((await fs.readFile(filePath)).toString());
    const document = window.document;
    for (const selector in selectorReplacementMap) {
        const replacement = selectorReplacementMap[selector];
        for (const node of document.querySelectorAll(selector).values()) {
            if (replacement) {
                if (replacement.extra?.(document, node)) {
                    continue;
                }
                const newNode = replacement.tag
                    ? document.createElement(replacement.tag)
                    : document.createDocumentFragment();
                newNode.replaceChildren(...node.childNodes);
                Object.assign(newNode, { ...replacement.attrs ?? {} });
                node.replaceWith(newNode);
            } else {
                node.remove();
            }
        }
    }
    forEachTextNode(document, document.documentElement, (doc: Document, node: Text) => {
        if (node.textContent?.match(/\s*=+\s*/)) {
            node.replaceWith(doc.createElement("hr"));
            return true;
        } else if (node.textContent?.includes("REFERENCES")) {
            node.replaceWith(Object.assign(doc.createElement("h3"), { innerHTML: node.textContent.trim() }));
            return true;
        }
        return false;
    });
    deleteChildCommentsRecursive(document.documentElement);
    fs.writeFile(
        path.resolve(".", PROCESSED_LOCATION, fileName),
        document.documentElement.outerHTML.replaceAll(/�/g, '"'),
    );
}

async function cleanup() {
    const promises: Promise<unknown>[] = [];
    for (const fileName of await fs.readdir(path.resolve(".", "article-dumps"))) {
        promises.push(cleanupFile(fileName));
    }
    await Promise.allSettled(promises);
}

cleanup();
