import "./markdown.css";

import { ReactNode, forwardRef, useMemo } from "react";
import { Token, Tokens, marked } from "marked";
import { Link } from "react-router-dom";

interface MarkdownProps {
    content: string;
}

const Markdown = forwardRef<HTMLDivElement, MarkdownProps>((props: MarkdownProps, ref) => {
    let ctr = 0;
    function renderToken(t: Token): ReactNode {
        try {
            switch (t.type) {
                case "paragraph": {
                    return <div key={ctr++}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</div>;
                }
                case "image": {
                    return <img key={ctr++} src={t.href} />;
                }
                case "heading": {
                    switch (t.depth) {
                        case 1:
                            return <h1 key={ctr++}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</h1>;
                        case 2:
                            return <h2 key={ctr++}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</h2>;
                        case 3:
                            return <h3 key={ctr++}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</h3>;
                        case 4:
                            return <h4 key={ctr++}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</h4>;
                        case 5:
                            return <h5 key={ctr++}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</h5>;
                        case 6:
                            return <h6 key={ctr++}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</h6>;
                    }
                    throw new Error("Invalid heading");
                }
                case "codespan": {
                    return <code key={ctr++}>{t.raw}</code>;
                }
                case "code": {
                    return <pre key={ctr++}>{t.raw}</pre>;
                }
                case "br": {
                    return <br key={ctr++} />;
                }
                case "hr": {
                    return <hr key={ctr++} />;
                }
                case "strong": {
                    return <b key={ctr++}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</b>;
                }
                case "blockquote": {
                    return <blockquote key={ctr++}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</blockquote>;
                }
                case "link": {
                    return (
                        <Link to={t.href} key={ctr++}>
                            {t.tokens ? t.tokens.map(renderToken) : t.raw}
                        </Link>
                    );
                }
                case "list": {
                    if (t.ordered) {
                        return <ol key={ctr++}>{t.items.map(renderToken)}</ol>;
                    } else {
                        return <ul key={ctr++}>{t.items.map(renderToken)}</ul>;
                    }
                }
                case "list_item": {
                    return <li key={ctr++}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</li>;
                }
                case "em": {
                    return <em key={ctr++}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</em>;
                }
                case "del": {
                    return <s key={ctr++}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</s>;
                }
                case "table": {
                    return (
                        <table className="table-auto border-collapse" key={ctr++}>
                            <thead>
                                <tr>
                                    {(t.header as Tokens.TableCell[]).map(v => (
                                        <th className="border" key={ctr++}>
                                            {v.tokens ? v.tokens.map(renderToken) : v.text}
                                        </th>
                                    ))}
                                </tr>
                            </thead>
                            <tbody>
                                {(t.rows as Tokens.TableCell[][]).map(v => (
                                    <tr key={ctr++}>
                                        {v.map((d, d_key) => (
                                            <td className="border px-2 py-1" key={d_key}>
                                                {d.tokens ? d.tokens.map(renderToken) : d.text}
                                            </td>
                                        ))}
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    );
                }
                case "text": {
                    if ("tokens" in t) {
                        return (t.tokens as Array<Token>).map(renderToken);
                    }
                    return t.raw;
                }
                case "space": {
                    return " ";
                }
                default: {
                    console.debug(`Unknown token ${t.type}`);
                }
            }
        } catch (e) {
            console.error(e);
        }
    }

    const parsed = useMemo(() => {
        return marked.lexer(props.content);
    }, [props.content]);
    return (
        <div className="markdown" ref={ref}>
            {parsed.filter(a => a.type !== "footnote" && a.type !== "footnotes").map(a => renderToken(a))}
        </div>
    );
});

export default Markdown;
