import { dirname, join, relative } from "node:path";
import { HEADINGS } from "../content/constants.js";
import { getOnPageComplete, getSettings } from "../settings.js";
import { InternalError } from "../util/internalError.js";
import { MarkdownRenderer, MarkdownSite } from "./markdown.js";
import { escapeText, getPrettyCodeSampleLanguage } from "./util.js";
export class MdxSite extends MarkdownSite {
    #embedsCreated = new Set();
    #embedStack = [];
    buildPagePath(...[slug, { appendIndex = false } = {}]) {
        return this.compilerConfig.buildPagePath(slug, { appendIndex });
    }
    getRenderer(...[options]) {
        return new MdxRenderer({ ...options }, this);
    }
    createEmbed(...[{ slug, createdEmbeddedContent }]) {
        if (!this.docsData) {
            throw new InternalError("Docs data not set");
        }
        // Check if this is a circular reference
        if (this.#embedStack.includes(slug)) {
            return undefined;
        }
        this.#embedStack.push(slug);
        const { output: { embedOutDir }, } = getSettings();
        if (!embedOutDir) {
            throw new InternalError("Embed output directory not set, but should have been caught by the settings parser");
        }
        const loaderPath = join(embedOutDir, "loaders", `${slug}.tsx`);
        const embedPath = join(embedOutDir, "contents", `${slug}.mdx`);
        if (this.#embedsCreated.has(slug)) {
            this.#embedStack.pop();
            return loaderPath;
        }
        this.#embedsCreated.add(slug);
        // Create the lazy loader
        const importPath = relative(dirname(loaderPath), embedPath);
        const loaderContents = `import { lazy, Suspense } from "react";

// This import needs to have a literal value so bundlers can statically
// analyze this component. This is why we don't use a generic component that
// takes in a 'src' property, or anything like that.
const Contents = lazy(() => import("${importPath}"));

export default function() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Contents />
    </Suspense>
  );
}
`;
        getOnPageComplete()(loaderPath, loaderContents);
        // Create the embed contents
        const renderer = this.getRenderer({
            currentPageSlug: slug,
            currentPagePath: embedPath,
            site: this,
            docsData: this.docsData,
            compilerConfig: this.compilerConfig,
            isEmbed: true,
        });
        renderer.enterContext({
            id: `embed-${slug}`,
            type: "embed",
        });
        renderer.enterContext({ id: slug, type: "schema" });
        renderer.createEmbedWrapper(() => {
            createdEmbeddedContent(renderer);
        });
        renderer.exitContext();
        renderer.exitContext();
        const { contents } = renderer.render();
        getOnPageComplete()(embedPath, contents);
        this.#embedStack.pop();
        return loaderPath;
    }
}
let slugCounter = 0;
class MdxRenderer extends MarkdownRenderer {
    // Mapping of import path to imported symbols to imported symbol alias
    #imports = new Map();
    #frontMatter;
    #site;
    #hasEmbed = false;
    constructor(options, site) {
        super(options);
        this.#site = site;
        this.#frontMatter = this.compilerConfig.buildPagePreamble(options.frontMatter);
    }
    #insertNamedImport(importPath, symbol) {
        if (!this.#imports.has(importPath)) {
            this.#imports.set(importPath, new Map());
        }
        this.#imports.get(importPath)?.set(symbol, symbol);
    }
    #insertDefaultImport(importPath, alias) {
        if (!this.#imports.has(importPath)) {
            this.#imports.set(importPath, new Map());
        }
        this.#imports.get(importPath)?.set("default", alias);
    }
    #insertComponentImport(symbol) {
        if (!("componentPackageName" in this.compilerConfig)) {
            throw new InternalError("Component package name not set, but should have been caught by the settings parser");
        }
        this.#insertNamedImport(this.compilerConfig.componentPackageName, symbol);
    }
    #createComponentOpeningTag({ symbol, props, selfClosing, noImport = false, }) {
        if (!noImport) {
            this.#insertComponentImport(symbol);
        }
        function serializeProps(separator) {
            if (!props) {
                return "";
            }
            return Object.entries(props)
                .map(([key, value]) => {
                if (typeof value === "string") {
                    if (value.includes("\n")) {
                        return `${separator}${key}={\`${value}\`}`;
                    }
                    return `${separator}${key}="${value}"`;
                }
                else if (value !== undefined) {
                    return `${separator}${key}={${JSON.stringify(value)}}`;
                }
                else {
                    return "";
                }
            })
                .join("");
        }
        let serializedProps = serializeProps(" ");
        const isMultiline = serializedProps.length > 80;
        if (isMultiline) {
            serializedProps = serializeProps("\n  ");
        }
        return `<${symbol}${serializedProps}${selfClosing ? (isMultiline ? "\n/" : " /") : isMultiline ? "\n" : ""}>`;
    }
    #createComponentClosingTag(symbol) {
        return `</${symbol}>`;
    }
    #appendComponent({ symbol, props, noImport, }, cb) {
        this.appendLine(this.#createComponentOpeningTag({
            symbol,
            props,
            selfClosing: !cb,
            noImport,
        }));
        if (cb) {
            cb();
            this.appendLine(this.#createComponentClosingTag(symbol));
        }
    }
    #createComponent(symbol, props, cb) {
        if (cb) {
            return (this.#createComponentOpeningTag({
                symbol,
                props,
                selfClosing: false,
            }) +
                cb() +
                this.#createComponentClosingTag(symbol));
        }
        else {
            return this.#createComponentOpeningTag({
                symbol,
                props,
                selfClosing: true,
            });
        }
    }
    getIdSeparator() {
        return this.compilerConfig.elementIdSeparator ?? "+";
    }
    createEmbedWrapper(cb) {
        this.#appendComponent({
            symbol: "ExpandableSection",
            props: {},
        }, cb);
    }
    createEmbed(...[args]) {
        if (!this.#hasEmbed && !this.hasParentContextType("embed")) {
            this.#hasEmbed = true;
            this.#insertComponentImport("EmbedProvider");
        }
        // If we don't have a slug, make one up
        // TODO: fix the reason we're not getting a name on the generator side.
        if (!args.slug) {
            args.slug = `EmbedNameMissing${slugCounter++}`;
        }
        args.slug = args.slug.toLowerCase();
        const absolutePath = this.#site.createEmbed(args);
        // If we get back undefined, it means we have a circular reference
        if (!absolutePath) {
            // TODO: handle this circular reference better
            return;
        }
        const { triggerText, embedTitle, slug } = args;
        this.#appendComponent({ symbol: "Embed", props: { slot: "embed" } }, () => {
            const componentName = `Embed${slug}`;
            this.#appendComponent({
                symbol: "EmbedTrigger",
                props: { slot: "trigger", embedTitle, triggerText },
            }, () => {
                this.#appendComponent({
                    symbol: componentName,
                    props: {},
                    noImport: true,
                });
            });
            let importPath = relative(dirname(this.getPagePath()), absolutePath);
            // When an embed imports another embed, we don't get the leading `./`
            if (!importPath.startsWith(".")) {
                importPath = `./${importPath}`;
            }
            this.#insertDefaultImport(importPath, componentName);
        });
    }
    render() {
        // Render the main content
        const { contents, metadata } = super.render();
        // If we have front matter, add it to the top of the file
        let frontMatter = this.#frontMatter ? this.#frontMatter + "\n" : "";
        // Add imports after front matter
        for (const [importPath, symbols] of this.#imports) {
            const defaultImportName = symbols.get("default");
            const nonDefaultImports = Array.from(symbols)
                .filter(([symbol]) => symbol !== "default")
                .map(([symbol, alias]) => alias !== symbol ? `${alias} as ${symbol}` : symbol)
                .sort()
                .join(",\n  ");
            if (defaultImportName && nonDefaultImports.length > 0) {
                frontMatter += `import ${defaultImportName}, {\n  ${nonDefaultImports}\n} from "${importPath}";\n`;
            }
            else if (defaultImportName) {
                frontMatter += `import ${defaultImportName} from "${importPath}";\n`;
            }
            else {
                frontMatter += `import {\n  ${nonDefaultImports}\n} from "${importPath}";\n`;
            }
        }
        // Add a blank line after front matter, if it exists
        if (frontMatter) {
            frontMatter += "\n";
        }
        // Add the embed provider, if we need one
        if (this.#hasEmbed && !this.hasParentContextType("embed")) {
            frontMatter += "<EmbedProvider />\n\n";
        }
        // Return the final result
        return {
            contents: frontMatter + contents,
            metadata,
        };
    }
    createHeading(...[level, text, { escape = "markdown", id, append = true } = {},]) {
        let line = `${`#`.repeat(level)} ${escapeText(text, { escape })}`;
        if (id) {
            if (this.compilerConfig.formatHeadingId) {
                line += ` ${this.compilerConfig.formatHeadingId(id)}`;
            }
            else {
                line += ` \\{#${id}\\}`;
            }
        }
        if (append) {
            this.appendLine(line);
        }
        return line;
    }
    createCode(...[text, options]) {
        let line;
        if (options?.variant === "raw") {
            if (options.style === "inline") {
                line = `<code>${escapeText(text, { escape: options?.escape ?? "html" })}</code>`;
            }
            else {
                const escapedText = escapeText(text, {
                    escape: options?.escape ?? "html",
                }).replaceAll("`", "\\`");
                this.#insertComponentImport("Code");
                line = `<Code text={\`${escapedText}\`} />`;
            }
        }
        else {
            line = super.createCode(text, options ? { ...options, append: false } : undefined);
        }
        if (options?.append ?? true) {
            this.appendLine(line);
        }
        return line;
    }
    createPill(...[variant, cb, { append = false } = {}]) {
        const pill = this.#createComponent("Pill", { variant }, cb);
        if (append) {
            this.appendLine(pill);
        }
        return pill;
    }
    createTagSection(...[{ title, description }]) {
        this.#appendComponent({
            symbol: "Tag",
            props: { slot: "tag" },
        }, () => {
            this.#appendComponent({
                symbol: "TagTitle",
                props: { slot: "title" },
            }, title);
            if (description) {
                this.#appendComponent({
                    symbol: "TagDescription",
                    props: { slot: "description" },
                }, description);
            }
        });
    }
    createOperationSection(...args) {
        this.#appendComponent({
            symbol: "Operation",
            props: {},
        }, () => super.createOperationSection(...args));
    }
    handleCreateOperationTitle(cb) {
        this.#appendComponent({
            symbol: "OperationTitleSection",
            props: { slot: "title" },
        }, cb);
    }
    handleCreateOperationSummary(cb) {
        this.#appendComponent({
            symbol: "OperationSummarySection",
            props: { slot: "summary" },
        }, cb);
    }
    handleCreateOperationDescription(cb) {
        this.#appendComponent({
            symbol: "OperationDescriptionSection",
            props: { slot: "description" },
        }, cb);
    }
    createCodeSamplesSection(...[cb]) {
        this.enterContext({ id: "code-samples", type: "section" });
        this.#appendComponent({
            symbol: "OperationCodeSamplesSection",
            props: { slot: "code-samples" },
        }, () => {
            this.createTabbedSection(() => {
                cb({
                    createTryItNowEntry: ({ externalDependencies, defaultValue, language, packageManagerUrl, }) => {
                        this.enterContext({ id: language, type: "section" });
                        this.createTabbedSectionTab(() => this.createText(getPrettyCodeSampleLanguage(language)), { id: this.getCurrentId() });
                        this.createSectionContent(() => {
                            this.#appendComponent({
                                symbol: "TryItNow",
                                props: {
                                    externalDependencies,
                                    defaultValue,
                                    packageManagerUrl,
                                },
                            });
                        }, {
                            id: this.getCurrentId(),
                        });
                        this.exitContext();
                    },
                    createCodeSampleEntry: ({ language, value }) => {
                        this.enterContext({ id: language, type: "section" });
                        this.createTabbedSectionTab(() => this.createText(getPrettyCodeSampleLanguage(language)), { id: this.getCurrentId() });
                        this.createSectionContent(() => {
                            this.#appendComponent({
                                symbol: "CodeSample",
                                props: {},
                            }, () => this.createCode(value, {
                                language,
                                variant: "default",
                                style: "block",
                            }));
                        }, {
                            id: this.getCurrentId(),
                        });
                        this.exitContext();
                    },
                });
            });
        });
        this.exitContext();
    }
    createSecuritySection(...args) {
        this.#appendComponent({
            symbol: "OperationSecuritySection",
            props: { slot: "security" },
        }, () => super.createSecuritySection(...args));
    }
    createParametersSection(...args) {
        this.#appendComponent({
            symbol: "OperationParametersSection",
            props: { slot: "parameters" },
        }, () => super.createParametersSection(...args));
    }
    createRequestExamplesSection(...[cb]) {
        this.#appendComponent({
            symbol: "OperationRequestBodyExamplesSection",
            props: { slot: "request-body-examples" },
        }, () => super.createRequestExamplesSection(cb));
    }
    createRequestSection(...args) {
        this.#appendComponent({
            symbol: "OperationRequestBodySection",
            props: { slot: "request-body" },
        }, () => super.createRequestSection(...args));
    }
    createResponsesExamplesSection(...args) {
        this.#appendComponent({
            symbol: "OperationResponseBodyExamplesSection",
            props: { slot: "response-body-examples" },
        }, () => super.createResponsesExamplesSection(...args));
    }
    createResponsesSection(...args) {
        this.#appendComponent({
            symbol: "OperationResponseBodySection",
            props: { slot: "response-body" },
        }, () => super.createResponsesSection(...args));
    }
    handleCreateRequestDisplayType(cb) {
        this.#appendComponent({
            symbol: "OperationRequestBodyDisplayTypeSection",
            props: { slot: "request-body-display-type" },
        }, cb);
    }
    handleCreateRequestDescription(cb) {
        this.#appendComponent({
            symbol: "OperationRequestBodyDescriptionSection",
            props: { slot: "request-body-description" },
        }, cb);
    }
    handleCreateResponseDisplayType(cb) {
        this.#appendComponent({
            symbol: "OperationResponseBodyDisplayTypeSection",
            props: { slot: "response-body-display-type" },
        }, cb);
    }
    handleCreateResponseDescription(cb) {
        this.#appendComponent({
            symbol: "OperationResponseBodyDescriptionSection",
            props: { slot: "response-body-description" },
        }, cb);
    }
    handleCreateSecurity(cb) {
        this.#appendComponent({
            symbol: "ExpandableSection",
            props: {},
        }, cb);
    }
    handleCreateParameters(cb) {
        this.#appendComponent({
            symbol: "ExpandableSection",
            props: {},
        }, cb);
    }
    handleCreateBreakouts(cb) {
        this.#appendComponent({
            symbol: "ExpandableSection",
            props: {},
        }, cb);
    }
    #getBreakoutIdInfo() {
        const stack = this.getContextStack()
            .filter((c) => c.type === "schema")
            .map((c) => c.id);
        const id = stack.join("_");
        const parentId = stack.slice(0, -1).join("_") || undefined;
        return { id, parentId };
    }
    handleCreateExpandableBreakout(...[{ hasExpandableContent, createTitle, createDescription, createExamples, createDefaultValue, createProperties, createEmbed, isTopLevel, },]) {
        const { id, parentId } = this.#getBreakoutIdInfo();
        const expandByDefault = getSettings().display.expandTopLevelPropertiesOnPageLoad && isTopLevel;
        this.#appendComponent({
            symbol: "ExpandableBreakout",
            props: {
                slot: "entry",
                id,
                headingId: this.getCurrentId(),
                parentId,
                hasExpandableContent,
                expandByDefault,
            },
        }, () => {
            this.#appendComponent({
                symbol: "ExpandableBreakoutTitle",
                props: { slot: "title" },
            }, createTitle);
            if (createDescription) {
                this.#appendComponent({
                    symbol: "ExpandableBreakoutDescription",
                    props: { slot: "description" },
                }, createDescription);
            }
            if (createExamples) {
                this.#appendComponent({
                    symbol: "ExpandableBreakoutExamples",
                    props: { slot: "examples" },
                }, createExamples);
            }
            if (createDefaultValue) {
                this.#appendComponent({
                    symbol: "ExpandableBreakoutDefaultValue",
                    props: { slot: "defaultValue" },
                }, createDefaultValue);
            }
            if (createProperties) {
                this.#appendComponent({
                    symbol: "ExpandableBreakoutProperties",
                    props: { slot: "properties" },
                }, createProperties);
            }
            // Embeds handle their own component imports
            if (createEmbed) {
                createEmbed();
            }
        });
    }
    handleCreateExpandableProperty(...[{ typeInfo, annotations, rawTitle, isTopLevel, hasExpandableContent, createDescription, createExamples, createDefaultValue, createBreakouts, createEmbed, },]) {
        const { id, parentId } = this.#getBreakoutIdInfo();
        const expandByDefault = getSettings().display.expandTopLevelPropertiesOnPageLoad && isTopLevel;
        this.#appendComponent({
            symbol: "ExpandableProperty",
            props: {
                slot: "entry",
                id,
                headingId: this.getCurrentId(),
                parentId,
                typeInfo,
                typeAnnotations: annotations,
                hasExpandableContent,
                expandByDefault,
            },
        }, () => {
            this.#appendComponent({
                symbol: "ExpandablePropertyTitle",
                props: { slot: "title" },
            }, () => {
                this.createHeading(HEADINGS.PROPERTY_HEADING_LEVEL, rawTitle, {
                    id: this.getCurrentId(),
                    escape: "mdx",
                });
            });
            if (createDescription) {
                this.#appendComponent({
                    symbol: "ExpandablePropertyDescription",
                    props: { slot: "description" },
                }, createDescription);
            }
            if (createExamples) {
                this.#appendComponent({
                    symbol: "ExpandablePropertyExamples",
                    props: { slot: "examples" },
                }, createExamples);
            }
            if (createDefaultValue) {
                this.#appendComponent({
                    symbol: "ExpandablePropertyDefaultValue",
                    props: { slot: "defaultValue" },
                }, createDefaultValue);
            }
            if (createBreakouts) {
                this.#appendComponent({
                    symbol: "ExpandablePropertyBreakouts",
                    props: { slot: "breakouts" },
                }, createBreakouts);
            }
            // Embeds handle their own component imports
            if (createEmbed) {
                createEmbed();
            }
        });
    }
    createFrontMatterDisplayType(...[{ typeInfo }]) {
        this.#appendComponent({
            symbol: "FrontMatterDisplayType",
            props: { typeInfo },
        });
    }
    createSection(...[cb]) {
        this.#appendComponent({
            symbol: "Section",
            props: {},
        }, cb);
    }
    createSectionTitle(...[cb]) {
        this.#appendComponent({
            symbol: "SectionTitle",
            props: { slot: "title" },
        }, cb);
    }
    createSectionContent(...[cb, { id } = {}]) {
        this.#appendComponent({
            symbol: "SectionContent",
            props: { slot: "content", id },
        }, cb);
    }
    createTabbedSection(...[cb]) {
        // We have to do a manual Omit here, since TabbedSectionProps.children is
        // specially defined and not a standard PropsWithChildren type
        this.#appendComponent({
            symbol: "TabbedSection",
            props: {},
        }, cb);
    }
    createTabbedSectionTab(...[cb, { id }]) {
        this.#appendComponent({
            symbol: "SectionTab",
            props: { slot: "tab", id },
        }, cb);
    }
    createDebugPlaceholder(...[{ createTitle, createExample }]) {
        this.#appendComponent({
            symbol: "DebugPlaceholder",
            props: {},
        }, () => {
            createTitle();
            this.createText("_OpenAPI example:_");
            createExample();
        });
    }
}
//# sourceMappingURL=mdx.js.map