<!doctype html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>

        <style>
            textarea {
                width: 100%;
                height: 250px;
            }
        </style>
    </head>
    <body>
        <form onsubmit="convert(event)">
            <fieldset>
                <legend>Data</legend>

                <label for="input">Input</label>
                <br />
                <textarea name="input" id="input" required></textarea>
            </fieldset>

            <fieldset>
                <legend>Output</legend>

                <label for="output">Output</label>
                <br />
                <textarea name="output" id="output" disabled></textarea>
                <br />
                <button
                    type="button"
                    onclick="window.navigator.clipboard.writeText(document.getElementById('output').value)"
                >
                    Copy
                </button>
            </fieldset>

            <br />
            <button>Submit</button>
        </form>

        <script>
            const INDENT_SPACES = 4;
            function get_indent_str(indent) {
                return " ".repeat(indent > 0 ? indent * INDENT_SPACES : 0);
            }

            function gen_lisp(
                element,
                indent = 0,
                add_n_after_elements = false,
            ) {
                const indent_str = get_indent_str(indent);
                let output = "";

                for (const node of element.childNodes) {
                    if (node.nodeName === "#text") {
                        const data = node.data.trim();

                        if (!data) {
                            continue;
                        }

                        if (data.startsWith("{")) {
                            // templating
                            let text = "";

                            for (const line of data
                                .replaceAll('"', '\\"')
                                .split("\n")) {
                                text += ` ${line.trim()}`;
                            }

                            output += `${indent_str}(text "${text.trim().replaceAll('"', '\\"').replaceAll('\\\\"', '\\"').replaceAll("}} {{", "}}\n{{")}")\n`;
                        } else {
                            // normal text
                            output += `${indent_str}(text "${data.replaceAll('"', '\\"')}")\n`;
                        }
                    } else if (node.localName) {
                        output += `${indent_str}(${node.localName}\n`;

                        // add attributes
                        for (const attr of node.attributes || []) {
                            output += `${get_indent_str(indent + 1)}("${attr.name}" "${attr.value.replaceAll('"', '\\\\\\"')}")\n`;
                        }

                        // add children
                        output += gen_lisp(node, indent + 1);

                        // close block
                        output = output.slice(0, output.length - 1); // remove last character (new line)
                        output += add_n_after_elements ? ")\n\n" : ")\n";
                    } else if (node.nodeName === "#comment") {
                        let text = "";

                        for (const line of node.textContent.split("\n")) {
                            if (line.trim() === "prettier-ignore") {
                                continue;
                            }

                            text += `${indent_str};${line.trimEnd()}\n`;
                        }

                        output += text;
                    } else {
                        console.warn(
                            "skipped element with no local name:",
                            node,
                        );
                    }
                }

                return output;
            }

            function convert(e) {
                e.preventDefault();
                const element_document = new DOMParser().parseFromString(
                    e.target.input.value,
                    "text/html",
                );

                const start = performance.now();
                e.target.output.value = gen_lisp(
                    element_document.body,
                    0,
                    true,
                );

                console.log(
                    `processed ${e.target.input.value.length} characters in ${performance.now() - start}ms`,
                );
            }
        </script>
    </body>
</html>