import { join, resolve } from "node:path";
import { snakeCase } from "change-case";
import { HEADINGS } from "../content/constants.js";
import { getSettings } from "../settings.js";
import { InternalError } from "../util/internalError.js";
import { Renderer } from "./base.js";
import { Site } from "./base.js";
import { escapeText } from "./util.js";
export class MarkdownSite extends Site {
    #pages = new Map();
    #docsData;
    #pageMetadata = [];
    setDocsData(docsData) {
        this.#docsData = docsData;
    }
    buildPagePath(...[slug, { appendIndex = false } = {}]) {
        const settings = getSettings();
        if (appendIndex) {
            slug += "/index";
        }
        return resolve(join(settings.output.pageOutDir, `${slug}.md`));
    }
    hasPage(...[path]) {
        return this.#pages.has(path);
    }
    createPage(...[path, slug, frontMatter]) {
        if (!this.#docsData) {
            throw new InternalError("Docs data not set");
        }
        const renderer = this.getRenderer({
            currentPageSlug: slug,
            currentPagePath: path,
            site: this,
            docsData: this.#docsData,
            frontMatter,
        });
        this.#pages.set(path, renderer);
        return renderer;
    }
    render() {
        const pages = {};
        for (const [path, renderer] of this.#pages) {
            const { contents, metadata } = renderer.render();
            pages[path] = contents;
            if (metadata) {
                this.#pageMetadata.push(metadata);
            }
        }
        this.processPageMetadata(this.#pageMetadata);
        return pages;
    }
    processPageMetadata(_pageMetadata) {
        // Do nothing
    }
}
export class MarkdownRenderer extends Renderer {
    #isFinalized = false;
    #contextStack = [];
    #docsData;
    #site;
    #pageMetadata;
    #currentOperation;
    #currentSection;
    #rendererLines = [];
    constructor({ docsData, site, currentPageSlug, frontMatter, }) {
        super();
        this.#docsData = docsData;
        this.#site = site;
        if (currentPageSlug !== undefined && frontMatter) {
            this.#pageMetadata = {
                sidebarPosition: frontMatter.sidebarPosition,
                sidebarLabel: frontMatter.sidebarLabel,
                slug: currentPageSlug,
                operations: [],
            };
        }
    }
    getSite() {
        return this.#site;
    }
    createOperationSection(...[{ method, path, operationId, summary, description }, cb,]) {
        const { showDebugPlaceholders } = getSettings().display;
        const id = `operation-${snakeCase(operationId)}`;
        this.enterContext({ id, type: "operation" });
        if (this.#pageMetadata) {
            const currentOperation = {
                fragment: id,
                method,
                path,
            };
            this.#currentOperation = currentOperation;
            this.#pageMetadata.operations.push(currentOperation);
        }
        this.handleCreateOperationTitle(() => {
            path = escapeText(path, {
                escape: "markdown",
            });
            this.createHeading(HEADINGS.SECTION_TITLE_HEADING_LEVEL, `${this.createPill("primary", () => `<b>${method.toUpperCase()}</b>`)} ${path}`, { id, escape: "none" });
        });
        if (summary || showDebugPlaceholders) {
            this.handleCreateOperationSummary(() => {
                if (summary && description) {
                    this.createText(`_${summary}_`);
                }
                else if (summary) {
                    this.createText(summary);
                }
                else if (showDebugPlaceholders) {
                    this.createDebugPlaceholder(() => "No summary provided");
                }
            });
        }
        if (description || showDebugPlaceholders) {
            this.handleCreateOperationDescription(() => {
                if (description) {
                    this.createText(description);
                }
                else if (showDebugPlaceholders) {
                    this.createDebugPlaceholder(() => "No description provided");
                }
            });
        }
        cb();
        this.#currentOperation = undefined;
        this.exitContext();
    }
    createTopLevelSection({ title, annotations = [], }, cb) {
        for (const annotation of annotations) {
            title += ` ${this.createPill(annotation.variant, () => annotation.title)}`;
        }
        this.createSection(() => {
            this.createSectionTitle(() => this.createHeading(HEADINGS.SECTION_HEADING_LEVEL, title, {
                id: this.getCurrentId(),
            }));
            this.createSectionContent(cb);
        });
    }
    createSecuritySection(...[cb]) {
        this.enterContext({ id: "security", type: "section" });
        if (this.#currentOperation) {
            this.#currentSection = {
                fragment: this.getCurrentId(),
                properties: [],
            };
            this.#currentOperation.security = this.#currentSection;
        }
        this.createTopLevelSection({ title: "Security" }, () => this.handleCreateSecurity(cb));
        this.#currentSection = undefined;
        this.exitContext();
    }
    handleCreateOperationTitle(cb) {
        cb();
    }
    handleCreateOperationSummary(cb) {
        cb();
    }
    handleCreateOperationDescription(cb) {
        cb();
    }
    handleCreateSecurity(cb) {
        cb();
    }
    createParametersSection(...[cb]) {
        this.enterContext({ id: "parameters", type: "section" });
        if (this.#currentOperation) {
            this.#currentSection = {
                fragment: this.getCurrentId(),
                properties: [],
            };
            this.#currentOperation.parameters = this.#currentSection;
        }
        this.createTopLevelSection({ title: "Parameters" }, () => this.handleCreateParameters(cb));
        this.#currentSection = undefined;
        this.exitContext();
    }
    handleCreateParameters(cb) {
        cb();
    }
    createRequestSection(...[{ isOptional, createDisplayType, createDescription, createExamples, createDefaultValue, createBreakouts, },]) {
        this.enterContext({ id: "request", type: "section" });
        if (this.#currentOperation) {
            this.#currentSection = {
                fragment: this.getCurrentId(),
                properties: [],
            };
            this.#currentOperation.requestBody = this.#currentSection;
        }
        const annotations = [];
        if (isOptional) {
            annotations.push({
                title: "Optional",
                variant: "info",
            });
        }
        this.createTopLevelSection({
            title: "Request Body",
            annotations,
        }, () => {
            if (createDisplayType) {
                this.handleCreateRequestDisplayType(createDisplayType);
            }
            if (createDescription) {
                this.handleCreateRequestDescription(createDescription);
            }
            if (createExamples) {
                this.handleCreateRequestExamples(createExamples);
            }
            if (createDefaultValue) {
                this.handleCreateRequestDefaultValue(createDefaultValue);
            }
            this.handleCreateBreakouts(createBreakouts);
        });
        this.#currentSection = undefined;
        this.exitContext();
    }
    createResponsesSection(...[cb, { title = "Responses" } = {}]) {
        this.enterContext({ id: "responses", type: "section" });
        if (this.#currentOperation) {
            this.#currentOperation.responses = {};
        }
        this.createTabbedSection(() => {
            this.createSectionTitle(() => this.createHeading(HEADINGS.SECTION_HEADING_LEVEL, title, {
                id: this.getCurrentId(),
            }));
            cb(({ statusCode, contentType, createDisplayType, createDescription, createExamples, createDefaultValue, createBreakouts, }) => {
                this.enterContext({ id: statusCode, type: "section" });
                this.enterContext({
                    id: contentType.replace("/", "-"),
                    type: "section",
                });
                if (this.#currentOperation?.responses) {
                    this.#currentSection = {
                        fragment: this.getCurrentId(),
                        properties: [],
                    };
                    this.#currentOperation.responses[`${statusCode}-${contentType}`] =
                        this.#currentSection;
                }
                this.createTabbedSectionTab(() => this.createText(statusCode), {
                    id: this.getCurrentId(),
                });
                this.createSectionContent(() => {
                    if (createDisplayType) {
                        this.handleCreateResponseDisplayType(createDisplayType);
                    }
                    if (createDescription) {
                        this.handleCreateResponseDescription(createDescription);
                    }
                    if (createExamples) {
                        this.handleCreateResponseExamples(createExamples);
                    }
                    if (createDefaultValue) {
                        this.handleCreateResponseDefaultValue(createDefaultValue);
                    }
                    this.handleCreateBreakouts(createBreakouts);
                }, {
                    id: this.getCurrentId(),
                });
                this.#currentSection = undefined;
                this.exitContext();
                this.exitContext();
            });
        });
        this.exitContext();
    }
    handleCreateRequestDisplayType(cb) {
        cb();
    }
    handleCreateRequestDescription(cb) {
        cb();
    }
    handleCreateRequestExamples(cb) {
        cb();
    }
    handleCreateRequestDefaultValue(cb) {
        cb();
    }
    handleCreateResponseDisplayType(cb) {
        cb();
    }
    handleCreateResponseDescription(cb) {
        cb();
    }
    handleCreateResponseExamples(cb) {
        cb();
    }
    handleCreateResponseDefaultValue(cb) {
        cb();
    }
    handleCreateBreakouts(cb) {
        cb();
    }
    createExpandableBreakout(...[props]) {
        if (this.#currentSection && props.isTopLevel) {
            this.#currentSection.properties.push({
                fragment: this.getCurrentId(),
                name: props.rawTitle,
            });
        }
        this.handleCreateExpandableBreakout(props);
    }
    handleCreateExpandableBreakout(...[{ createTitle, createDescription, createExamples, createDefaultValue },]) {
        createTitle();
        createDescription?.();
        createExamples?.();
        createDefaultValue?.();
    }
    createExpandableProperty(...[props]) {
        if (this.#currentSection && props.isTopLevel) {
            this.#currentSection.properties.push({
                fragment: this.getCurrentId(),
                name: props.rawTitle,
            });
        }
        this.handleCreateExpandableProperty(props);
    }
    handleCreateExpandableProperty(...[{ rawTitle, typeInfo, annotations, createDescription, createExamples, createDefaultValue, },]) {
        let type;
        if (typeInfo) {
            type =
                " " +
                    this.createCode(this.#computeSingleLineDisplayType(typeInfo), {
                        variant: "raw",
                        style: "inline",
                        escape: "mdx",
                        append: false,
                    });
        }
        const renderedAnnotations = annotations.map((annotation) => {
            return this.createPill(annotation.variant, () => annotation.title);
        });
        this.createHeading(HEADINGS.PROPERTY_HEADING_LEVEL, `${rawTitle} ${renderedAnnotations.join(" ")}${type}`, { id: this.getCurrentId(), escape: "mdx" });
        createDescription?.();
        createExamples?.();
        createDefaultValue?.();
    }
    createFrontMatterDisplayType(...[{ typeInfo }]) {
        this.createCode(this.#computeSingleLineDisplayType(typeInfo), {
            variant: "raw",
            style: "inline",
            escape: "mdx",
        });
    }
    createHeading(...[level, text, { escape = "markdown", id, append = true } = {},]) {
        let line = `${`#`.repeat(level)} ${escapeText(text, { escape })}`;
        if (id) {
            line += ` {#${id}}`;
        }
        if (append) {
            this.appendLine(line);
        }
        return line;
    }
    createText(...[text, { escape = "mdx", append = true } = {}]) {
        const escapedText = escapeText(text, { escape });
        if (append) {
            this.appendLine(escapedText);
        }
        return escapedText;
    }
    createCode(...[text, options]) {
        let line;
        if (options?.variant === "raw") {
            if (options.style === "inline") {
                line = `<code>${text}</code>`;
            }
            else {
                line = `<pre>
<code>
${text}\n</code>\n</pre>`;
            }
        }
        else {
            if (options?.style === "inline") {
                line = `\`${text}\``;
            }
            else {
                line = `\`\`\`${options?.language ?? ""}\n${text}\n\`\`\``;
            }
        }
        if (options?.append) {
            this.appendLine(line);
        }
        return line;
    }
    createList(...[items, { escape = "markdown", append = true } = {},]) {
        const list = items
            .map((item) => "- " + escapeText(item, { escape }))
            .join("\n");
        if (append) {
            this.appendLine(list);
        }
        return list;
    }
    createPill(
    // We don't use variant in the basic Markdown version
    ...[_variant, cb, { append = false } = {}]) {
        const line = `(${cb()})`;
        if (append) {
            this.appendLine(line);
        }
        return line;
    }
    // TODO: need to make other overrides explicitly define return type like we do
    // here, since overridding doesn't infer type from parent
    createSection(...[cb]) {
        cb();
    }
    createSectionTitle(...[cb]) {
        cb();
    }
    createSectionContent(...[cb]) {
        cb();
    }
    createTabbedSection(...[cb]) {
        cb();
    }
    createTabbedSectionTab(...[cb]) {
        cb();
    }
    #computeSingleLineDisplayType = (typeInfo) => {
        switch (typeInfo.label) {
            case "array":
            case "map":
            case "set": {
                const children = typeInfo.children.map(this.#computeSingleLineDisplayType);
                return `${typeInfo.label}&lt;${children.join(",")}&gt;`;
            }
            case "union":
            case "enum": {
                const children = typeInfo.children.map(this.#computeSingleLineDisplayType);
                return children.join(" | ");
            }
            default: {
                return typeInfo.linkedLabel;
            }
        }
    };
    createDebugPlaceholder(...[cb]) {
        this.appendLine(cb());
    }
    enterContext(...[context]) {
        this.#contextStack.push(context);
    }
    exitContext() {
        this.#contextStack.pop();
    }
    getCurrentId(...[postFixId]) {
        const ids = this.#contextStack.map((context) => context.id);
        if (postFixId) {
            ids.push(postFixId);
        }
        return ids.join(this.getIdSeparator()).toLowerCase();
    }
    getIdSeparator() {
        return "+";
    }
    getContextStack() {
        return this.#contextStack;
    }
    getCurrentContextType() {
        const topLevelContext = this.#contextStack.at(-1);
        if (!topLevelContext) {
            throw new InternalError("No context found");
        }
        return topLevelContext.type;
    }
    getSchemaDepth() {
        return this.#contextStack.filter((context) => context.type === "schema")
            .length;
    }
    alreadyInContext(...[id]) {
        return this.#contextStack.some((context) => context.id === id);
    }
    getDocsData() {
        return this.#docsData;
    }
    appendLine(line) {
        // We check to make sure we have a value, because some renderers don't need
        // to do anything for certain operations. They signal this by returning null
        if (line) {
            this.#rendererLines.push(line);
        }
    }
    render() {
        if (this.#isFinalized) {
            throw new InternalError("Renderer has already been finalized");
        }
        const contents = this.#rendererLines.join("\n\n");
        this.#isFinalized = true;
        return { contents, metadata: this.#pageMetadata };
    }
}
//# sourceMappingURL=markdown.js.map