import { readdirSync, readFileSync } from "node:fs";
import { dirname, join, relative } from "node:path";
import { fileURLToPath } from "node:url";
import { assertNever } from "../../util/assertNever.js";
import { getSettings } from "../settings.js";
const ASSET_PATH = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "assets");
const SAVE_PAGE = Symbol();
function getEmbedPath(embedName) {
    return join(getSettings().output.componentOutDir, "embeds", embedName + ".mdx");
}
function getEmbedSymbol(embedName) {
    return `Embed${embedName}`;
}
export class Site {
    #pages = new Map();
    constructor() {
        // Prepopulate the list of static assets to be saved
        const assetFileList = readdirSync(ASSET_PATH, {
            recursive: true,
            withFileTypes: true,
        })
            .filter((f) => f.isFile() && f.name !== "tsconfig.json")
            .map((f) => join(f.parentPath, f.name).replace(ASSET_PATH + "/", ""));
        for (const assetFile of assetFileList) {
            this.#pages.set(join(getSettings().output.componentOutDir, assetFile), readFileSync(join(ASSET_PATH, assetFile), "utf-8"));
        }
    }
    createPage(path) {
        // Reserve the name, since we sometimes check to see if pages already exist
        this.#pages.set(path, "");
        return new Renderer({
            site: this,
            currentPagePath: path,
        });
    }
    createEmbedPage(embedName) {
        const embedPath = getEmbedPath(embedName);
        if (this.#pages.has(embedPath)) {
            return;
        }
        return this.createPage(embedPath);
    }
    createRawPage(path, contents) {
        this.#pages.set(path, contents);
    }
    getPages() {
        return this.#pages;
    }
    [SAVE_PAGE](path, content) {
        this.#pages.set(path, content);
    }
}
export class Renderer {
    #site;
    #currentPagePath;
    #frontMatter;
    #imports = new Map();
    #includeSidebar = false;
    #lines = [];
    constructor({ site, currentPagePath, }) {
        this.#site = site;
        this.#currentPagePath = currentPagePath;
    }
    escapeText(text, { mdxOnly = false } = {}) {
        if (mdxOnly) {
            return text.replace(/(?<!\\)\{/g, "\\{").replace(/(?<!\\)\}/g, "\\}");
        }
        return text
            .replace(/(?<!\\)\\/g, "\\\\")
            .replace(/(?<!\\)`/g, "\\`")
            .replace(/(?<!\\)\*/g, "\\*")
            .replace(/(?<!\\)_/g, "\\_")
            .replace(/(?<!\\)\{/g, "\\{")
            .replace(/(?<!\\)\}/g, "\\}")
            .replace(/(?<!\\)\[/g, "\\[")
            .replace(/(?<!\\)\]/g, "\\]")
            .replace(/(?<!\\)</g, "\\<")
            .replace(/(?<!\\)>/g, "\\>")
            .replace(/(?<!\\)\(/g, "\\(")
            .replace(/(?<!\\)\)/g, "\\)")
            .replace(/(?<!\\)#/g, "\\#")
            .replace(/(?<!\\)\+/g, "\\+")
            .replace(/(?<!\\)!/g, "\\!")
            .replace(/(?<!\\)\|/g, "\\|");
    }
    insertFrontMatter({ sidebarPosition, sidebarLabel, }) {
        const framework = getSettings().output.framework;
        switch (framework) {
            case "docusaurus": {
                this.#frontMatter = `---
sidebar_position: ${sidebarPosition}
sidebar_label: ${this.escapeText(sidebarLabel)}
---`;
                break;
            }
            case "nextra": {
                this.#frontMatter = `---
sidebarTitle: ${this.escapeText(sidebarLabel)}
---`;
                break;
            }
            default: {
                assertNever(framework);
            }
        }
    }
    appendHeading(level, text, { escape = true } = {}) {
        this.#lines.push(`#`.repeat(level) + " " + this.escapeText(text, { mdxOnly: !escape }));
    }
    appendParagraph(text, { escape = false } = {}) {
        this.#lines.push(this.escapeText(text, { mdxOnly: !escape }));
    }
    appendCode(text) {
        this.#lines.push(`\`\`\`\n${text}\n\`\`\``);
    }
    appendList(items, { escape = true } = {}) {
        this.#lines.push(items
            .map((item) => "- " + this.escapeText(item, { mdxOnly: !escape }))
            .join("\n"));
    }
    appendRaw(text) {
        this.#lines.push(text);
    }
    beginExpandableSection(title, { isOpenOnLoad = false, escape = true, }) {
        this.#lines.push(`<details ${isOpenOnLoad ? "open" : ""}>`);
        this.#lines.push(`<summary>${this.escapeText(title, { mdxOnly: !escape })}</summary>`);
    }
    endExpandableSection() {
        this.#lines.push("</details>");
    }
    appendSidebarLink({ title, embedName, }) {
        // If this is a circular import, skip processing sidebar
        if (!this.#insertEmbedImport(embedName)) {
            // TODO: add debug logging
            return;
        }
        this.#includeSidebar = true;
        this.#insertComponentImport("SideBarCta", "SideBar/index.tsx");
        this.#insertComponentImport("SideBar", "SideBar/index.tsx");
        this.#lines.push(`<p>
  <SideBarCta cta="${`View ${this.escapeText(title, { mdxOnly: true })}`}" title="${this.escapeText(title)}">
    <${getEmbedSymbol(embedName)} />
  </SideBarCta>
</p>`);
    }
    // TODO: need to type this properly, but we can't import types from assets
    // since they can't be built as part of this TS project
    appendTryItNow(props) {
        this.insertThirdPartyImport("TryItNow", "@speakeasy-api/docs-md");
        const escapedProps = Object.fromEntries(Object.entries(props).map(([key, value]) => [
            key,
            typeof value === "string"
                ? this.escapeText(value, { mdxOnly: true })
                : JSON.stringify(value),
        ]));
        this.#lines.push(`<TryItNow {...${JSON.stringify(escapedProps)}} externalDependencies={${JSON.stringify(props.externalDependencies)}} defaultValue={\`${props.defaultValue}\`} />`);
    }
    finalize() {
        let imports = "";
        for (const [importPath, symbols] of this.#imports) {
            if (symbols.defaultAlias && symbols.namedImports.size > 0) {
                imports += `import ${symbols.defaultAlias}, { ${Array.from(symbols.namedImports).join(", ")} } from "${importPath}";\n`;
            }
            else if (symbols.defaultAlias) {
                imports += `import ${symbols.defaultAlias} from "${importPath}";\n`;
            }
            else {
                imports += `import { ${Array.from(symbols.namedImports).join(", ")} } from "${importPath}";\n`;
            }
        }
        const data = (this.#frontMatter ? this.#frontMatter + "\n\n" : "") +
            (imports ? imports + "\n\n" : "") +
            (this.#includeSidebar ? "<SideBar />\n\n" : "") +
            this.#lines.join("\n\n");
        this.#lines = [];
        this.#site[SAVE_PAGE](this.#currentPagePath, data);
    }
    #insertDefaultImport(importPath, symbol) {
        if (!this.#imports.has(importPath)) {
            this.#imports.set(importPath, {
                defaultAlias: undefined,
                namedImports: new Set(),
            });
        }
        // Will never be undefined due to the above. I wish TypeScript could narrow
        // map/set has calls
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        this.#imports.get(importPath).defaultAlias = symbol;
    }
    #insertNamedImport(importPath, symbol) {
        if (!this.#imports.has(importPath)) {
            this.#imports.set(importPath, {
                defaultAlias: undefined,
                namedImports: new Set(),
            });
        }
        this.#imports.get(importPath)?.namedImports.add(symbol);
    }
    #insertEmbedImport(embedName) {
        const embedPath = getEmbedPath(embedName);
        // TODO: handle this more gracefully. This happens when we have a direct
        // circular dependency, and the page needs to import itself
        if (this.#currentPagePath === embedPath) {
            return false;
        }
        let importPath = relative(dirname(this.#currentPagePath), embedPath);
        // Check if this is an import to a file in the same directory, which
        // for some reason relative doesn't include the ./ in
        if (!importPath.startsWith("./") && !importPath.startsWith("../")) {
            importPath = `./${importPath}`;
        }
        this.#insertDefaultImport(importPath, getEmbedSymbol(embedName));
        return true;
    }
    #insertComponentImport(symbol, componentPath) {
        const importPath = relative(dirname(this.#currentPagePath), join(getSettings().output.componentOutDir, componentPath));
        this.#insertNamedImport(importPath, symbol);
    }
    insertThirdPartyImport(symbol, importPath) {
        this.#insertNamedImport(importPath, symbol);
    }
}
//# sourceMappingURL=renderer.js.map