import { printIdentifier, formatTypeSpec, paramMessage, createTypeSpecLibrary, getEncode, getDiscriminator, getDiscriminatedUnion, getNamespaceFullName, isNeverType, resolveEncodedName, isArrayModelType, explainStringTemplateNotSerializable, isNullType, compilerAssert, ignoreDiagnostics, getExamples, serializeValueAsJson, getMinValueExclusive, getMaxValueExclusive, isSecret, getKnownValues, getTypeName, getSummary, isTemplateDeclaration, getMinLength, getMaxLength, getMinValue, getMaxValue, getPattern, getMinItems, getMaxItems, getFormat, getDoc, getDeprecated, navigateTypesInNamespace, resolvePath, emitFile, listServices, projectProgram, getService, interpolatePath, getAllTags, isDeprecated, isVoidType, isGlobalNamespace, getAnyExtensionFromPath, createDiagnosticCollector, getOpExamples } from '@typespec/compiler';
import { TypeEmitter, ObjectBuilder, Placeholder, ArrayBuilder, createAssetEmitter } from '@typespec/compiler/emitter-framework';
import { Visibility, getVisibilitySuffix, getHttpOperation, resolveRequestVisibility, getHttpService, resolveAuthentication, reportIfNoRoutes, getStatusCodeDescription, isContentTypeHeader, createMetadataInfo, getServers, isOverloadSameEndpoint, isOrExtendsHttpFile } from '@typespec/http';
import { getOpenAPITypeName, getExternalDocs, isReadonlyProperty, shouldInline, getExtensions as getExtensions$1, checkDuplicateTypeName, resolveOperationId, getParameterKey, resolveInfo, isDefaultResponse } from '@typespec/openapi';
import { buildVersionProjections } from '@typespec/versioning';
import { TwoLevelMap } from '@typespec/compiler/utils';

const validLocations = ["header", "query", "path"];
const extensionDecoratorName = "extension";
function getExtensions(element) {
    const decorators = [];
    for (const key of Object.keys(element)) {
        if (isExtensionKey(key)) {
            decorators.push({
                name: extensionDecoratorName,
                args: [key, element[key]],
            });
        }
    }
    return decorators;
}
function isExtensionKey(key) {
    return key.startsWith("x-");
}
function getParameterDecorators(parameter) {
    const decorators = [];
    decorators.push(...getExtensions(parameter));
    decorators.push(...getDecoratorsForSchema(parameter.schema));
    const locationDecorator = getLocationDecorator(parameter);
    if (locationDecorator)
        decorators.push(locationDecorator);
    return decorators;
}
function getLocationDecorator(parameter) {
    if (!validLocations.includes(parameter.in))
        return;
    const decorator = {
        name: parameter.in,
        args: [],
    };
    let format;
    switch (parameter.in) {
        case "header":
            format = getHeaderFormat(parameter.style);
            break;
        case "query":
            format = getQueryFormat(parameter.explode, parameter.style);
            break;
    }
    if (format) {
        decorator.args.push({ format });
    }
    return decorator;
}
function getQueryFormat(explode, style) {
    if (explode) {
        return "form";
    }
    else if (style === "form") {
        return "simple";
    }
    else if (style === "spaceDelimited") {
        return "ssv";
    }
    else if (style === "pipeDelimited") {
        return "pipes";
    }
    return;
}
function getHeaderFormat(style) {
    return style === "simple" ? "simple" : undefined;
}
function getDecoratorsForSchema(schema) {
    const decorators = [];
    if ("$ref" in schema) {
        return decorators;
    }
    decorators.push(...getExtensions(schema));
    switch (schema.type) {
        case "array":
            decorators.push(...getArraySchemaDecorators(schema));
            break;
        case "object":
            decorators.push(...getObjectSchemaDecorators(schema));
            break;
        case "integer":
        case "number":
            decorators.push(...getNumberSchemaDecorators(schema));
            break;
        case "string":
            decorators.push(...getStringSchemaDecorators(schema));
            break;
    }
    if (schema.oneOf) {
        decorators.push(...getOneOfSchemaDecorators());
    }
    return decorators;
}
function getOneOfSchemaDecorators(schema) {
    return [{ name: "oneOf", args: [] }];
}
function getArraySchemaDecorators(schema) {
    const decorators = [];
    if (typeof schema.minItems === "number") {
        decorators.push({ name: "minItems", args: [schema.minItems] });
    }
    if (typeof schema.maxItems === "number") {
        decorators.push({ name: "maxItems", args: [schema.maxItems] });
    }
    return decorators;
}
function getObjectSchemaDecorators(schema) {
    const decorators = [];
    if (schema.discriminator) {
        decorators.push({ name: "discriminator", args: [schema.discriminator.propertyName] });
    }
    return decorators;
}
function getNumberSchemaDecorators(schema) {
    const decorators = [];
    if (typeof schema.minimum === "number") {
        if (schema.exclusiveMinimum) {
            decorators.push({ name: "minValueExclusive", args: [schema.minimum] });
        }
        else {
            decorators.push({ name: "minValue", args: [schema.minimum] });
        }
    }
    if (typeof schema.maximum === "number") {
        if (schema.exclusiveMaximum) {
            decorators.push({ name: "maxValueExclusive", args: [schema.maximum] });
        }
        else {
            decorators.push({ name: "maxValue", args: [schema.maximum] });
        }
    }
    return decorators;
}
const knownStringFormats = new Set([
    "binary",
    "byte",
    "date",
    "date-time",
    "time",
    "duration",
    "uri",
]);
function getStringSchemaDecorators(schema) {
    const decorators = [];
    if (typeof schema.minLength === "number") {
        decorators.push({ name: "minLength", args: [schema.minLength] });
    }
    if (typeof schema.maxLength === "number") {
        decorators.push({ name: "maxLength", args: [schema.maxLength] });
    }
    if (typeof schema.pattern === "string") {
        decorators.push({ name: "pattern", args: [escapeRegex(schema.pattern)] });
    }
    if (typeof schema.format === "string" && !knownStringFormats.has(schema.format)) {
        decorators.push({ name: "format", args: [schema.format] });
    }
    return decorators;
}
function escapeRegex(str) {
    return str.replace(/\\/g, "\\\\");
}

function generateDocs(doc) {
    if (isEmptyDoc(doc)) {
        return ``;
    }
    const split = splitNewlines(doc);
    for (let i = 0; i < split.length; i++) {
        if (split[i].includes("@") || split[i].includes("*/")) {
            if (split.length === 1) {
                return `@doc("${split[0].replace(/\\/g, "\\\\").replace(/"/g, '\\"')}")`;
            }
            return `@doc("""\n${split.join("\n").replace(/\\/g, "\\\\").replace(/"/g, '\\"')}\n""")`;
        }
    }
    return `/**\n* ${split.join("\n* ")}\n*/`;
}
function splitNewlines(doc) {
    let docString = Array.isArray(doc) ? doc.join("\n") : doc;
    docString = docString.replace(/\r\n/g, "\n");
    docString = docString.replace(/\r/g, "\n");
    if (!docString.includes("\n")) {
        return [docString];
    }
    return docString.split("\n");
}
function isEmptyDoc(doc) {
    if (!doc) {
        return true;
    }
    if (Array.isArray(doc) && !doc.length) {
        return true;
    }
    return false;
}

function generateDecorator({ name, args }) {
    const hasArgs = args.length;
    const stringifiedArguments = hasArgs ? `(${args.map((a) => JSON.stringify(a)).join(", ")})` : "";
    return `@${name}${stringifiedArguments}`;
}
function generateDecorators(decorators) {
    const uniqueDecorators = new Set(decorators.map(generateDecorator));
    return Array.from(uniqueDecorators);
}

function generateTypeFromSchema(schema) {
    return getTypeFromRefableSchema(schema);
}
function getTypeFromRefableSchema(schema) {
    const hasRef = "$ref" in schema;
    return hasRef ? getRefName(schema.$ref) : getTypeFromSchema(schema);
}
function getTypeFromSchema(schema) {
    let type = "unknown";
    if (schema.enum) {
        type = getEnum(schema.enum);
    }
    else if (schema.anyOf) {
        type = getAnyOfType(schema);
    }
    else if (schema.type === "array") {
        type = getArrayType(schema);
    }
    else if (schema.type === "boolean") {
        type = "boolean";
    }
    else if (schema.type === "integer") {
        type = getIntegerType(schema);
    }
    else if (schema.type === "number") {
        type = getNumberType(schema);
    }
    else if (schema.type === "object") {
        type = getObjectType(schema);
    }
    else if (schema.oneOf) {
        type = getOneOfType(schema);
    }
    else if (schema.type === "string") {
        type = getStringType(schema);
    }
    if (schema.nullable) {
        type += ` | null`;
    }
    if (schema.default) {
        type += ` = ${JSON.stringify(schema.default)}`;
    }
    return type;
}
function getRefName(ref) {
    const name = ref.split("/").pop() ?? "";
    return name.split(".").map(printIdentifier).join(".");
}
function getAnyOfType(schema) {
    const definitions = [];
    for (const item of schema.anyOf ?? []) {
        definitions.push(generateTypeFromSchema(item));
    }
    return definitions.join(" | ");
}
function getOneOfType(schema) {
    const definitions = [];
    for (const item of schema.oneOf ?? []) {
        definitions.push(generateTypeFromSchema(item));
    }
    return definitions.join(" | ");
}
function getObjectType(schema) {
    // If we have `additionalProperties`, treat that as an 'indexer' and convert to a record.
    const recordType = typeof schema.additionalProperties === "object"
        ? `Record<${getTypeFromRefableSchema(schema.additionalProperties)}>`
        : "";
    if (!schema.properties && recordType) {
        return recordType;
    }
    const requiredProps = schema.required ?? [];
    const props = [];
    if (schema.properties) {
        for (const name of Object.keys(schema.properties)) {
            const decorators = generateDecorators(getDecoratorsForSchema(schema.properties[name]))
                .map((d) => `${d}\n`)
                .join("");
            const isOptional = !requiredProps.includes(name) ? "?" : "";
            props.push(`${decorators}${name}${isOptional}: ${getTypeFromRefableSchema(schema.properties[name])}`);
        }
    }
    const propertyCount = Object.keys(props).length;
    if (recordType && !propertyCount) {
        return recordType;
    }
    else if (recordType && propertyCount) {
        props.push(`...${recordType}`);
    }
    return `{${props.join("; ")}}`;
}
function getArrayType(schema) {
    const items = schema.items;
    if (!items) {
        return "unknown[]";
    }
    if ("$ref" in items) {
        return `${getRefName(items.$ref)}[]`;
    }
    // Prettier will get rid of the extra parenthesis for us
    return `(${getTypeFromSchema(items)})[]`;
}
function getIntegerType(schema) {
    const format = schema.format ?? "";
    switch (format) {
        case "int8":
        case "int16":
        case "int32":
        case "int64":
        case "uint8":
        case "uint16":
        case "uint32":
        case "uint64":
            return format;
        case "double-int":
            return "safeint";
        default:
            return "integer";
    }
}
function getNumberType(schema) {
    const format = schema.format ?? "";
    switch (format) {
        case "decimal":
        case "decimal128":
            return format;
        case "double":
            return "float64";
        case "float":
            return "float32";
        default:
            // Could be either 'float' or 'numeric' - add FIXME?
            return "numeric";
    }
}
function getStringType(schema) {
    const format = schema.format ?? "";
    let type = "string";
    switch (format) {
        case "binary":
        case "byte":
            type = "bytes";
            break;
        case "date":
            type = "plainDate";
            break;
        case "date-time":
            // Can be 'offsetDateTime' or 'utcDateTime' - needs FIXME or union?
            type = "utcDateTime";
            break;
        case "time":
            type = "plainTime";
            break;
        case "duration":
            type = "duration";
            break;
        case "uri":
            type = "url";
            break;
    }
    return type;
}
function getEnum(schemaEnum) {
    return schemaEnum.map((e) => JSON.stringify(e)).join(" | ");
}

function generateModel(model) {
    const definitions = [];
    const modelDeclaration = generateModelDeclaration(model);
    if (model.doc) {
        definitions.push(generateDocs(model.doc));
    }
    definitions.push(...generateDecorators(model.decorators));
    definitions.push(modelDeclaration.open);
    definitions.push(...model.properties.map(generateModelProperty));
    if (model.additionalProperties) {
        definitions.push(`...${generateTypeFromSchema(model.additionalProperties)};`);
    }
    if (modelDeclaration.close)
        definitions.push(modelDeclaration.close);
    return definitions.join("\n");
}
function generateModelDeclaration(model) {
    const modelName = model.name;
    const modelType = model.type ?? "object";
    if (model.is) {
        return { open: `model ${modelName} is ${model.is};` };
    }
    if (!model.extends) {
        return { open: `model ${modelName} {`, close: "}" };
    }
    if (modelType === "object") {
        return { open: `model ${modelName} extends ${model.extends} {`, close: "}" };
    }
    switch (modelType) {
        case "boolean":
        case "integer":
        case "number":
        case "string":
            return { open: `scalar ${modelName} extends ${model.extends};` };
    }
    return { open: `model ${modelName} {`, close: "}" };
}
function generateModelProperty(property) {
    // Decorators will be a combination of top-level (parameters) and
    // schema-level decorators.
    const decorators = generateDecorators([
        ...property.decorators,
        ...getDecoratorsForSchema(property.schema),
    ]).join(" ");
    const doc = property.doc ? generateDocs(property.doc) : "";
    return `${doc}${decorators} ${property.name}${property.isOptional ? "?" : ""}: ${generateTypeFromSchema(property.schema)};`;
}

function generateOperation(operation) {
    const definitions = [];
    if (operation.doc) {
        definitions.push(generateDocs(operation.doc));
    }
    definitions.push(...operation.tags.map((t) => `@tag("${t}")`));
    definitions.push(generateDecorators(operation.decorators).join(" "));
    // generate parameters
    const parameters = [
        ...operation.parameters.map(generateOperationParameter),
        ...generateRequestBodyParameters(operation.requestBodies),
    ];
    const responseTypes = operation.responseTypes.length
        ? operation.responseTypes.join(" | ")
        : "void";
    definitions.push(`op ${operation.name}(${parameters.join(", ")}): ${responseTypes};`);
    return definitions.join(" ");
}
function generateOperationParameter(parameter) {
    if ("$ref" in parameter) {
        // check if referencing a model or a property
        const refName = getRefName(parameter.$ref);
        const paramName = refName.indexOf(".") >= 0 ? refName.split(".").pop() : refName;
        // when refName and paramName match, we're referencing a model and can spread
        // TODO: Handle optionality
        return refName === paramName ? `...${refName}` : `${paramName}: ${refName}`;
    }
    const definitions = [];
    if (parameter.doc) {
        definitions.push(generateDocs(parameter.doc));
    }
    definitions.push(...generateDecorators(parameter.decorators));
    definitions.push(`${parameter.name}${parameter.isOptional ? "?" : ""}: ${generateTypeFromSchema(parameter.schema)}`);
    return definitions.join(" ");
}
function generateRequestBodyParameters(requestBodies) {
    if (!requestBodies.length) {
        return [];
    }
    const definitions = [];
    // Generate the content-type header if defined content-types is not just 'application/json'
    const contentTypes = requestBodies.map((r) => r.contentType);
    if (!supportsOnlyJson(contentTypes)) {
        definitions.push(`@header contentType: ${contentTypes.map((c) => `"${c}"`).join(" | ")}`);
    }
    // Get the set of referenced types
    const body = Array.from(new Set(requestBodies.filter((r) => !!r.schema).map((r) => generateTypeFromSchema(r.schema)))).join(" | ");
    if (body) {
        definitions.push(`@bodyRoot body: ${body}`);
    }
    return definitions;
}
function supportsOnlyJson(contentTypes) {
    return contentTypes.length === 1 && contentTypes[0] === "application/json";
}

function generateNamespace(name, namespace) {
    const definitions = [];
    definitions.push(`namespace ${name} {`);
    definitions.push(...namespace.models.map(generateModel));
    definitions.push(...namespace.operations.map(generateOperation));
    for (const [namespaceName, nestedNamespace] of Object.entries(namespace.namespaces)) {
        definitions.push(generateNamespace(namespaceName, nestedNamespace));
    }
    definitions.push("}");
    return definitions.join("\n");
}

function generateServiceInformation(serviceInfo) {
    const definitions = [];
    const { name, doc, ...info } = serviceInfo;
    definitions.push(`
    @service({
      title: "${name}"
    })
    @info(${JSON.stringify(info)})
    `);
    if (doc) {
        definitions.push(generateDocs(doc));
    }
    definitions.push(`namespace ${generateNamespaceName(name)};`);
    return definitions.join("\n");
}
function generateNamespaceName(name) {
    return name.replaceAll(/[^\w^\d_]+/g, "");
}

function generateMain(program) {
    return `
  import "@typespec/http";
  import "@typespec/openapi";
  import "@typespec/openapi3";

  using Http;
  using OpenAPI;

  ${generateServiceInformation(program.serviceInfo)}

  ${program.models.map(generateModel).join("\n\n")}

  ${program.operations.map(generateOperation).join("\n\n")}

  ${Object.entries(program.namespaces)
        .map(([name, namespace]) => generateNamespace(name, namespace))
        .join("\n\n")}
  `;
}

function getScopeAndName(originalName) {
    const path = originalName.split(".");
    const name = path.pop();
    return { scope: path.map(printIdentifier), name: printIdentifier(name), rawName: name };
}
function scopesMatch(a, b) {
    if (a.length !== b.length)
        return false;
    return a.every((scope, i) => scope === b[i]);
}

/**
 * Transforms #/components/parameters into TypeSpec models.
 * Overwrites properties of existing models if an existing model already exists.
 * Populates the provided `models` array in-place.
 * @param models
 * @param parameters
 * @returns
 */
function transformComponentParameters(models, parameters) {
    if (!parameters)
        return;
    for (const name of Object.keys(parameters)) {
        const parameter = parameters[name];
        transformComponentParameter(models, name, parameter);
    }
}
function transformComponentParameter(models, key, parameter) {
    const { name, scope } = getScopeAndName(key);
    // Get the model name this parameter belongs to
    const modelName = scope.length > 0 ? scope.pop() : name;
    // find a matching model, or create one if it doesn't exist
    let model = models.find((m) => m.name === modelName && scopesMatch(m.scope, scope));
    if (!model) {
        model = {
            scope,
            name: modelName,
            decorators: [],
            properties: [],
        };
        models.push(model);
    }
    const modelProperty = getModelPropertyFromParameter(parameter);
    // Check if the model already has a property of the matching name
    const propIndex = model.properties.findIndex((p) => p.name === modelProperty.name);
    if (propIndex >= 0) {
        model.properties[propIndex] = modelProperty;
    }
    else {
        model.properties.push(modelProperty);
    }
}
function getModelPropertyFromParameter(parameter) {
    return {
        name: printIdentifier(parameter.name),
        isOptional: !parameter.required,
        doc: parameter.description ?? parameter.schema.description,
        decorators: getParameterDecorators(parameter),
        schema: parameter.schema,
    };
}

/**
 * Transforms #/components/schemas into TypeSpec models.
 * Populates the provided `models` array in-place.
 * @param models
 * @param schemas
 * @returns
 */
function transformComponentSchemas(models, schemas) {
    if (!schemas)
        return;
    for (const name of Object.keys(schemas)) {
        const schema = schemas[name];
        transformComponentSchema(models, name, schema);
    }
}
function transformComponentSchema(models, name, schema) {
    const extendsParent = getModelExtends(schema);
    const isParent = getModelIs(schema);
    models.push({
        ...getScopeAndName(name),
        decorators: [...getDecoratorsForSchema(schema)],
        doc: schema.description,
        properties: getModelPropertiesFromObjectSchema(schema),
        additionalProperties: typeof schema.additionalProperties === "object" ? schema.additionalProperties : undefined,
        extends: extendsParent,
        is: isParent,
        type: schema.type,
    });
}
function getModelExtends(schema) {
    switch (schema.type) {
        case "boolean":
            return "boolean";
        case "integer":
            return getIntegerType(schema);
        case "number":
            return getNumberType(schema);
        case "string":
            return getStringType(schema);
    }
    if (schema.type !== "object" || !schema.allOf) {
        return;
    }
    if (schema.allOf.length !== 1) {
        // TODO: Emit warning - can't extend more than 1 model
        return;
    }
    const parent = schema.allOf[0];
    if (!parent || !("$ref" in parent)) {
        // TODO: Error getting parent - must be a reference, not expression
        return;
    }
    return getRefName(parent.$ref);
}
function getModelIs(schema) {
    if (schema.type !== "array") {
        return;
    }
    return getArrayType(schema);
}
function getModelPropertiesFromObjectSchema({ properties, required = [], }) {
    if (!properties)
        return [];
    const modelProperties = [];
    for (const name of Object.keys(properties)) {
        const property = properties[name];
        modelProperties.push({
            name: printIdentifier(name),
            doc: property.description,
            schema: property,
            isOptional: !required.includes(name),
            decorators: [...getDecoratorsForSchema(property)],
        });
    }
    return modelProperties;
}

function transformNamespaces(models, operations) {
    // There can only be 1 file namespace - so if scopes is empty then entity belongs at root level
    const programDecs = {
        models: [],
        operations: [],
        namespaces: {},
    };
    expandModels(programDecs, models);
    expandOperations(programDecs, operations);
    return programDecs;
}
function expandModels(programDecs, models) {
    for (const model of models) {
        const { scope } = model;
        const namespace = getNamespace(programDecs, scope) ?? createNamespace(programDecs, scope);
        namespace.models.push(model);
    }
}
function expandOperations(programDecs, operations) {
    for (const operation of operations) {
        const { scope } = operation;
        const namespace = getNamespace(programDecs, scope) ?? createNamespace(programDecs, scope);
        namespace.operations.push(operation);
    }
}
function getNamespace(programDecs, scope) {
    if (!scope.length)
        return programDecs;
    let namespace = programDecs;
    for (const fragment of scope) {
        if (!namespace)
            return;
        namespace = namespace.namespaces[fragment];
    }
    return namespace;
}
function createNamespace(programDecs, scope) {
    let namespace = programDecs;
    for (const fragment of scope) {
        if (!namespace.namespaces[fragment]) {
            namespace.namespaces[fragment] = {
                namespaces: {},
                models: [],
                operations: [],
            };
        }
        namespace = namespace.namespaces[fragment];
    }
    return namespace;
}

const supportedHttpMethods = new Set([
    "delete",
    "get",
    "head",
    "patch",
    "post",
    "put",
]);

function convertHeaderName(s) {
    return s
        .split("-")
        .map((s, idx) => (idx === 0 ? s : s.substring(0, 1).toUpperCase() + s.substring(1)))
        .join("");
}

/**
 * Transforms #/paths/{route}/{httpMethod}/responses into TypeSpec models.
 * @param operationId - Used to generate model names with scopes.
 * @param operationResponses - The responses object for an operation.
 */
function collectOperationResponses(operationId, operationResponses) {
    const models = [];
    const rootDecorators = getExtensions(operationResponses);
    for (const statusCode of Object.keys(operationResponses)) {
        const response = operationResponses[statusCode];
        const decorators = [...rootDecorators];
        if ("$ref" in response) {
            //TODO: Support for referencing #/components/responseBodies
            continue;
        }
        // These headers will be applied to all of the models for this operation/statusCode
        const commonProperties = [];
        for (const name of Object.keys(response.headers ?? {})) {
            const property = convertHeaderToProperty(name, response.headers[name]);
            if (property)
                commonProperties.push(property);
        }
        decorators.push(...getExtensions(response));
        // `default` status code is treated as the fallback for any status codes returned that aren't defined.
        if (statusCode === "default") {
            decorators.push({ name: "defaultResponse", args: [] });
        }
        else {
            commonProperties.push(convertStatusCodeToProperty(statusCode));
        }
        if (isErrorStatusCode(statusCode)) {
            decorators.push({ name: "error", args: [] });
        }
        const scopeAndName = getScopeAndName(operationId);
        if (!response.content) {
            // This is common when there is no actual request body, just a statusCode, e.g. for errors
            models.push({
                scope: scopeAndName.scope,
                name: generateResponseModelName(scopeAndName.rawName, statusCode),
                decorators,
                properties: commonProperties,
                doc: response.description,
            });
        }
        else {
            // An operation may produce multiple content types, so need a model for each one.
            for (const contentType of Object.keys(response.content ?? {})) {
                const properties = [...commonProperties];
                const contentBody = response.content[contentType];
                // Wouldn't expect schema can be undefined since that implies contentType is not needed.
                if (contentBody.schema) {
                    properties.push({
                        name: "body",
                        isOptional: false, // TODO: use the real value
                        decorators: [{ name: "bodyRoot", args: [] }],
                        schema: contentBody.schema,
                    });
                }
                // Default is application/json, so it doesn't need to be specified
                if (contentType !== "application/json") {
                    properties.push({
                        name: "contentType",
                        decorators: [{ name: "header", args: [] }],
                        isOptional: false,
                        schema: { type: "string", enum: [contentType] },
                    });
                }
                models.push({
                    scope: scopeAndName.scope,
                    name: generateResponseModelName(scopeAndName.rawName, statusCode, contentType),
                    decorators,
                    properties,
                    doc: response.description,
                });
            }
        }
    }
    return models;
}
function convertHeaderToProperty(name, meta) {
    const normalizedName = convertHeaderName(name);
    // TODO: handle style
    const headerDecorator = { name: "header", args: [] };
    if (normalizedName !== name) {
        headerDecorator.args.push(name);
    }
    if ("$ref" in meta) {
        // Unhandled right now
        return;
    }
    return {
        name: normalizedName,
        decorators: [headerDecorator, ...getDecoratorsForSchema(meta.schema)],
        doc: meta.description ?? meta.schema.description,
        isOptional: !meta.required,
        schema: meta.schema,
    };
}
function convertStatusCodeToProperty(statusCode) {
    const schema = { type: "integer", format: "int32" };
    if (statusCode === "1XX") {
        schema.minimum = 100;
        schema.maximum = 199;
    }
    else if (statusCode === "2XX") {
        schema.minimum = 200;
        schema.maximum = 299;
    }
    else if (statusCode === "3XX") {
        schema.minimum = 300;
        schema.maximum = 399;
    }
    else if (statusCode === "4XX") {
        schema.minimum = 400;
        schema.maximum = 499;
    }
    else if (statusCode === "5XX") {
        schema.minimum = 500;
        schema.maximum = 599;
    }
    else {
        const literalStatusCode = parseInt(statusCode, 10);
        if (!isValidLiteralStatusCode(literalStatusCode)) ;
        else {
            schema.enum = [literalStatusCode];
        }
    }
    return {
        name: "statusCode",
        schema,
        decorators: [{ name: "statusCode", args: [] }],
        isOptional: false,
    };
}
function isValidLiteralStatusCode(statusCode) {
    return isFinite(statusCode) && statusCode >= 100 && statusCode <= 599;
}
function isErrorStatusCode(statusCode) {
    if (["1XX", "2XX", "3XX", "default"].includes(statusCode)) {
        return false;
    }
    else if (["4XX", "5XX"].includes(statusCode)) {
        return true;
    }
    const literalStatusCode = parseInt(statusCode, 10);
    return isFinite(literalStatusCode) && literalStatusCode >= 400;
}
function generateResponseModelName(operationId, statusCode, contentType) {
    if (statusCode === "default") {
        statusCode = "Default";
    }
    let modelName = `${operationId}${statusCode}`;
    if (contentType) {
        modelName += convertContentType(contentType);
    }
    return printIdentifier(modelName + "Response");
}
function convertContentType(contentType) {
    return contentType
        .replaceAll("*", "Star")
        .split("/")
        .map((s) => s.substring(0, 1).toUpperCase() + s.substring(1))
        .join("");
}

/**
 * Transforms each operation defined under #/paths/{route}/{httpMethod} into a TypeSpec operation.
 * @params models - The array of models to populate with any new models generated from the operation.
 * @param paths
 * @returns
 */
function transformPaths(models, paths) {
    const operations = [];
    for (const route of Object.keys(paths)) {
        const path = paths[route];
        for (const verb of supportedHttpMethods) {
            const operation = path[verb];
            if (!operation)
                continue;
            const parameters = operation.parameters?.map(transformOperationParameter) ?? [];
            const tags = operation.tags?.map((t) => t) ?? [];
            const operationResponses = operation.responses ?? {};
            const responseModels = collectOperationResponses(operation.operationId, operationResponses);
            models.push(...responseModels);
            operations.push({
                ...getScopeAndName(operation.operationId),
                decorators: [
                    ...getExtensions(operation),
                    { name: "route", args: [route] },
                    { name: verb, args: [] },
                ],
                parameters,
                doc: operation.description,
                operationId: operation.operationId,
                requestBodies: transformRequestBodies(operation.requestBody),
                responseTypes: responseModels.map((m) => m.name),
                tags: tags,
            });
        }
    }
    return operations;
}
function transformOperationParameter(parameter) {
    if ("$ref" in parameter) {
        return { $ref: parameter.$ref };
    }
    return {
        name: printIdentifier(parameter.name),
        doc: parameter.description,
        decorators: getParameterDecorators(parameter),
        isOptional: !parameter.required,
        schema: parameter.schema,
    };
}
function transformRequestBodies(requestBodies) {
    if (!requestBodies) {
        return [];
    }
    const typespecBodies = [];
    for (const contentType of Object.keys(requestBodies.content)) {
        const contentBody = requestBodies.content[contentType];
        typespecBodies.push({
            contentType,
            isOptional: !requestBodies.required,
            doc: requestBodies.description,
            encoding: contentBody.encoding,
            schema: contentBody.schema,
        });
    }
    return typespecBodies;
}

function transformServiceInfo(info) {
    return {
        name: info.title,
        doc: info.description,
        version: info.version,
        contact: info.contact,
        license: info.license,
        termsOfService: info.termsOfService,
        summary: info.summary,
    };
}

function transform(openapi) {
    const models = collectModels(openapi);
    const operations = transformPaths(models, openapi.paths);
    return {
        serviceInfo: transformServiceInfo(openapi.info),
        ...transformNamespaces(models, operations),
        augmentations: [],
    };
}
function collectModels(document) {
    const models = [];
    const components = document.components;
    // get models from `#/components/schema
    transformComponentSchemas(models, components?.schemas);
    // get models from `#/components/parameters
    transformComponentParameters(models, components?.parameters);
    return models;
}

async function convertOpenAPI3Document(document) {
    const program = transform(document);
    const content = generateMain(program);
    try {
        return await formatTypeSpec(content, {
            printWidth: 100,
            tabWidth: 2,
        });
    }
    catch {
        return content;
    }
}

const EmitterOptionsSchema = {
    type: "object",
    additionalProperties: false,
    properties: {
        "file-type": {
            type: "string",
            enum: ["yaml", "json"],
            nullable: true,
            description: "If the content should be serialized as YAML or JSON. Default 'yaml', it not specified infer from the `output-file` extension",
        },
        "output-file": {
            type: "string",
            nullable: true,
            description: [
                "Name of the output file.",
                " Output file will interpolate the following values:",
                "  - service-name: Name of the service if multiple",
                "  - version: Version of the service if multiple",
                "",
                ' Default: `{service-name}.{version}.openapi.yaml` or `.json` if `file-type` is `"json"`',
                "",
                " Example Single service no versioning",
                "  - `openapi.yaml`",
                "",
                " Example Multiple services no versioning",
                "  - `openapi.Org1.Service1.yaml`",
                "  - `openapi.Org1.Service2.yaml`",
                "",
                " Example Single service with versioning",
                "  - `openapi.v1.yaml`",
                "  - `openapi.v2.yaml`",
                "",
                " Example Multiple service with versioning",
                "  - `openapi.Org1.Service1.v1.yaml`",
                "  - `openapi.Org1.Service1.v2.yaml`",
                "  - `openapi.Org1.Service2.v1.0.yaml`",
                "  - `openapi.Org1.Service2.v1.1.yaml`    ",
            ].join("\n"),
        },
        "new-line": {
            type: "string",
            enum: ["crlf", "lf"],
            default: "lf",
            nullable: true,
            description: "Set the newline character for emitting files.",
        },
        "omit-unreachable-types": {
            type: "boolean",
            nullable: true,
            description: "Omit unreachable types.\nBy default all types declared under the service namespace will be included. With this flag on only types references in an operation will be emitted.",
        },
        "include-x-typespec-name": {
            type: "string",
            enum: ["inline-only", "never"],
            nullable: true,
            default: "never",
            description: "If the generated openapi types should have the `x-typespec-name` extension set with the name of the TypeSpec type that created it.\nThis extension is meant for debugging and should not be depended on.",
        },
        "safeint-strategy": {
            type: "string",
            enum: ["double-int", "int64"],
            nullable: true,
            default: "int64",
            description: [
                "How to handle safeint type. Options are:",
                " - `double-int`: Will produce `type: integer, format: double-int`",
                " - `int64`: Will produce `type: integer, format: int64`",
                "",
                "Default: `int64`",
            ].join("\n"),
        },
    },
    required: [],
};
const libDef = {
    name: "@typespec/openapi3",
    diagnostics: {
        "oneof-union": {
            severity: "error",
            messages: {
                default: "@oneOf decorator can only be used on a union or a model property which type is a union.",
            },
        },
        "inconsistent-shared-route-request-visibility": {
            severity: "error",
            messages: {
                default: "All operations with `@sharedRoutes` must have the same `@requestVisibility`.",
            },
        },
        "invalid-server-variable": {
            severity: "error",
            messages: {
                default: paramMessage `Server variable '${"propName"}' must be assignable to 'string'. It must either be a string, enum of string or union of strings.`,
            },
        },
        "invalid-format": {
            severity: "warning",
            messages: {
                default: paramMessage `Collection format '${"value"}' is not supported in OpenAPI3 ${"paramType"} parameters. Defaulting to type 'string'.`,
            },
        },
        "resource-namespace": {
            severity: "error",
            messages: {
                default: "Resource goes on namespace",
            },
        },
        "path-query": {
            severity: "error",
            messages: {
                default: `OpenAPI does not allow paths containing a query string.`,
            },
        },
        "duplicate-header": {
            severity: "error",
            messages: {
                default: paramMessage `The header ${"header"} is defined across multiple content types`,
            },
        },
        "status-code-in-default-response": {
            severity: "error",
            messages: {
                default: "a default response should not have an explicit status code",
            },
        },
        "invalid-schema": {
            severity: "error",
            messages: {
                default: paramMessage `Couldn't get schema for type ${"type"}`,
            },
        },
        "union-null": {
            severity: "error",
            messages: {
                default: "Cannot have a union containing only null types.",
            },
        },
        "empty-union": {
            severity: "error",
            messages: {
                default: "Empty unions are not supported for OpenAPI v3 - enums must have at least one value.",
            },
        },
        "empty-enum": {
            severity: "error",
            messages: {
                default: "Empty enums are not supported for OpenAPI v3 - enums must have at least one value.",
            },
        },
        "enum-unique-type": {
            severity: "error",
            messages: {
                default: "Enums are not supported unless all options are literals of the same type.",
            },
        },
        "invalid-default": {
            severity: "error",
            messages: {
                default: paramMessage `Invalid type '${"type"}' for a default value`,
            },
        },
        "inline-cycle": {
            severity: "error",
            messages: {
                default: paramMessage `Cycle detected in '${"type"}'. Use @friendlyName decorator to assign an OpenAPI definition name and make it non-inline.`,
            },
        },
        "unsupported-status-code-range": {
            severity: "error",
            messages: {
                default: paramMessage `Status code range '${"start"} to '${"end"}' is not supported. OpenAPI 3.0 can only represent range 1XX, 2XX, 3XX, 4XX and 5XX. Example: \`@minValue(400) @maxValue(499)\` for 4XX.`,
            },
        },
        "invalid-model-property": {
            severity: "error",
            messages: {
                default: paramMessage `'${"type"}' cannot be specified as a model property.`,
            },
        },
        "unsupported-auth": {
            severity: "warning",
            messages: {
                default: paramMessage `Authentication "${"authType"}" is not a known authentication by the openapi3 emitter, it will be ignored.`,
            },
        },
    },
    emitter: {
        options: EmitterOptionsSchema,
    },
};
const $lib = createTypeSpecLibrary(libDef);
const { createDiagnostic, reportDiagnostic, createStateSymbol } = $lib;

const refTargetsKey = createStateSymbol("refs");
const $useRef = (context, entity, refUrl) => {
    context.program.stateMap(refTargetsKey).set(entity, refUrl);
};
function getRef(program, entity) {
    return program.stateMap(refTargetsKey).get(entity);
}
const oneOfKey = createStateSymbol("oneOf");
const $oneOf = (context, entity) => {
    if (entity.kind === "ModelProperty" && entity.type.kind !== "Union") {
        reportDiagnostic(context.program, {
            code: "oneof-union",
            target: context.decoratorTarget,
        });
    }
    context.program.stateMap(oneOfKey).set(entity, true);
};
function getOneOf(program, entity) {
    return program.stateMap(oneOfKey).get(entity);
}

const ALIAS = Symbol.for('yaml.alias');
const DOC = Symbol.for('yaml.document');
const MAP = Symbol.for('yaml.map');
const PAIR = Symbol.for('yaml.pair');
const SCALAR = Symbol.for('yaml.scalar');
const SEQ = Symbol.for('yaml.seq');
const NODE_TYPE = Symbol.for('yaml.node.type');
const isAlias = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === ALIAS;
const isDocument = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === DOC;
const isMap = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === MAP;
const isPair = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === PAIR;
const isScalar = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === SCALAR;
const isSeq = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === SEQ;
function isCollection(node) {
    if (node && typeof node === 'object')
        switch (node[NODE_TYPE]) {
            case MAP:
            case SEQ:
                return true;
        }
    return false;
}
function isNode(node) {
    if (node && typeof node === 'object')
        switch (node[NODE_TYPE]) {
            case ALIAS:
            case MAP:
            case SCALAR:
            case SEQ:
                return true;
        }
    return false;
}
const hasAnchor = (node) => (isScalar(node) || isCollection(node)) && !!node.anchor;

const BREAK = Symbol('break visit');
const SKIP = Symbol('skip children');
const REMOVE = Symbol('remove node');
/**
 * Apply a visitor to an AST node or document.
 *
 * Walks through the tree (depth-first) starting from `node`, calling a
 * `visitor` function with three arguments:
 *   - `key`: For sequence values and map `Pair`, the node's index in the
 *     collection. Within a `Pair`, `'key'` or `'value'`, correspondingly.
 *     `null` for the root node.
 *   - `node`: The current node.
 *   - `path`: The ancestry of the current node.
 *
 * The return value of the visitor may be used to control the traversal:
 *   - `undefined` (default): Do nothing and continue
 *   - `visit.SKIP`: Do not visit the children of this node, continue with next
 *     sibling
 *   - `visit.BREAK`: Terminate traversal completely
 *   - `visit.REMOVE`: Remove the current node, then continue with the next one
 *   - `Node`: Replace the current node, then continue by visiting it
 *   - `number`: While iterating the items of a sequence or map, set the index
 *     of the next step. This is useful especially if the index of the current
 *     node has changed.
 *
 * If `visitor` is a single function, it will be called with all values
 * encountered in the tree, including e.g. `null` values. Alternatively,
 * separate visitor functions may be defined for each `Map`, `Pair`, `Seq`,
 * `Alias` and `Scalar` node. To define the same visitor function for more than
 * one node type, use the `Collection` (map and seq), `Value` (map, seq & scalar)
 * and `Node` (alias, map, seq & scalar) targets. Of all these, only the most
 * specific defined one will be used for each node.
 */
function visit(node, visitor) {
    const visitor_ = initVisitor(visitor);
    if (isDocument(node)) {
        const cd = visit_(null, node.contents, visitor_, Object.freeze([node]));
        if (cd === REMOVE)
            node.contents = null;
    }
    else
        visit_(null, node, visitor_, Object.freeze([]));
}
// Without the `as symbol` casts, TS declares these in the `visit`
// namespace using `var`, but then complains about that because
// `unique symbol` must be `const`.
/** Terminate visit traversal completely */
visit.BREAK = BREAK;
/** Do not visit the children of the current node */
visit.SKIP = SKIP;
/** Remove the current node */
visit.REMOVE = REMOVE;
function visit_(key, node, visitor, path) {
    const ctrl = callVisitor(key, node, visitor, path);
    if (isNode(ctrl) || isPair(ctrl)) {
        replaceNode(key, path, ctrl);
        return visit_(key, ctrl, visitor, path);
    }
    if (typeof ctrl !== 'symbol') {
        if (isCollection(node)) {
            path = Object.freeze(path.concat(node));
            for (let i = 0; i < node.items.length; ++i) {
                const ci = visit_(i, node.items[i], visitor, path);
                if (typeof ci === 'number')
                    i = ci - 1;
                else if (ci === BREAK)
                    return BREAK;
                else if (ci === REMOVE) {
                    node.items.splice(i, 1);
                    i -= 1;
                }
            }
        }
        else if (isPair(node)) {
            path = Object.freeze(path.concat(node));
            const ck = visit_('key', node.key, visitor, path);
            if (ck === BREAK)
                return BREAK;
            else if (ck === REMOVE)
                node.key = null;
            const cv = visit_('value', node.value, visitor, path);
            if (cv === BREAK)
                return BREAK;
            else if (cv === REMOVE)
                node.value = null;
        }
    }
    return ctrl;
}
function initVisitor(visitor) {
    if (typeof visitor === 'object' &&
        (visitor.Collection || visitor.Node || visitor.Value)) {
        return Object.assign({
            Alias: visitor.Node,
            Map: visitor.Node,
            Scalar: visitor.Node,
            Seq: visitor.Node
        }, visitor.Value && {
            Map: visitor.Value,
            Scalar: visitor.Value,
            Seq: visitor.Value
        }, visitor.Collection && {
            Map: visitor.Collection,
            Seq: visitor.Collection
        }, visitor);
    }
    return visitor;
}
function callVisitor(key, node, visitor, path) {
    if (typeof visitor === 'function')
        return visitor(key, node, path);
    if (isMap(node))
        return visitor.Map?.(key, node, path);
    if (isSeq(node))
        return visitor.Seq?.(key, node, path);
    if (isPair(node))
        return visitor.Pair?.(key, node, path);
    if (isScalar(node))
        return visitor.Scalar?.(key, node, path);
    if (isAlias(node))
        return visitor.Alias?.(key, node, path);
    return undefined;
}
function replaceNode(key, path, node) {
    const parent = path[path.length - 1];
    if (isCollection(parent)) {
        parent.items[key] = node;
    }
    else if (isPair(parent)) {
        if (key === 'key')
            parent.key = node;
        else
            parent.value = node;
    }
    else if (isDocument(parent)) {
        parent.contents = node;
    }
    else {
        const pt = isAlias(parent) ? 'alias' : 'scalar';
        throw new Error(`Cannot replace node with ${pt} parent`);
    }
}

const escapeChars = {
    '!': '%21',
    ',': '%2C',
    '[': '%5B',
    ']': '%5D',
    '{': '%7B',
    '}': '%7D'
};
const escapeTagName = (tn) => tn.replace(/[!,[\]{}]/g, ch => escapeChars[ch]);
class Directives {
    constructor(yaml, tags) {
        /**
         * The directives-end/doc-start marker `---`. If `null`, a marker may still be
         * included in the document's stringified representation.
         */
        this.docStart = null;
        /** The doc-end marker `...`.  */
        this.docEnd = false;
        this.yaml = Object.assign({}, Directives.defaultYaml, yaml);
        this.tags = Object.assign({}, Directives.defaultTags, tags);
    }
    clone() {
        const copy = new Directives(this.yaml, this.tags);
        copy.docStart = this.docStart;
        return copy;
    }
    /**
     * During parsing, get a Directives instance for the current document and
     * update the stream state according to the current version's spec.
     */
    atDocument() {
        const res = new Directives(this.yaml, this.tags);
        switch (this.yaml.version) {
            case '1.1':
                this.atNextDocument = true;
                break;
            case '1.2':
                this.atNextDocument = false;
                this.yaml = {
                    explicit: Directives.defaultYaml.explicit,
                    version: '1.2'
                };
                this.tags = Object.assign({}, Directives.defaultTags);
                break;
        }
        return res;
    }
    /**
     * @param onError - May be called even if the action was successful
     * @returns `true` on success
     */
    add(line, onError) {
        if (this.atNextDocument) {
            this.yaml = { explicit: Directives.defaultYaml.explicit, version: '1.1' };
            this.tags = Object.assign({}, Directives.defaultTags);
            this.atNextDocument = false;
        }
        const parts = line.trim().split(/[ \t]+/);
        const name = parts.shift();
        switch (name) {
            case '%TAG': {
                if (parts.length !== 2) {
                    onError(0, '%TAG directive should contain exactly two parts');
                    if (parts.length < 2)
                        return false;
                }
                const [handle, prefix] = parts;
                this.tags[handle] = prefix;
                return true;
            }
            case '%YAML': {
                this.yaml.explicit = true;
                if (parts.length !== 1) {
                    onError(0, '%YAML directive should contain exactly one part');
                    return false;
                }
                const [version] = parts;
                if (version === '1.1' || version === '1.2') {
                    this.yaml.version = version;
                    return true;
                }
                else {
                    const isValid = /^\d+\.\d+$/.test(version);
                    onError(6, `Unsupported YAML version ${version}`, isValid);
                    return false;
                }
            }
            default:
                onError(0, `Unknown directive ${name}`, true);
                return false;
        }
    }
    /**
     * Resolves a tag, matching handles to those defined in %TAG directives.
     *
     * @returns Resolved tag, which may also be the non-specific tag `'!'` or a
     *   `'!local'` tag, or `null` if unresolvable.
     */
    tagName(source, onError) {
        if (source === '!')
            return '!'; // non-specific tag
        if (source[0] !== '!') {
            onError(`Not a valid tag: ${source}`);
            return null;
        }
        if (source[1] === '<') {
            const verbatim = source.slice(2, -1);
            if (verbatim === '!' || verbatim === '!!') {
                onError(`Verbatim tags aren't resolved, so ${source} is invalid.`);
                return null;
            }
            if (source[source.length - 1] !== '>')
                onError('Verbatim tags must end with a >');
            return verbatim;
        }
        const [, handle, suffix] = source.match(/^(.*!)([^!]*)$/s);
        if (!suffix)
            onError(`The ${source} tag has no suffix`);
        const prefix = this.tags[handle];
        if (prefix) {
            try {
                return prefix + decodeURIComponent(suffix);
            }
            catch (error) {
                onError(String(error));
                return null;
            }
        }
        if (handle === '!')
            return source; // local tag
        onError(`Could not resolve tag: ${source}`);
        return null;
    }
    /**
     * Given a fully resolved tag, returns its printable string form,
     * taking into account current tag prefixes and defaults.
     */
    tagString(tag) {
        for (const [handle, prefix] of Object.entries(this.tags)) {
            if (tag.startsWith(prefix))
                return handle + escapeTagName(tag.substring(prefix.length));
        }
        return tag[0] === '!' ? tag : `!<${tag}>`;
    }
    toString(doc) {
        const lines = this.yaml.explicit
            ? [`%YAML ${this.yaml.version || '1.2'}`]
            : [];
        const tagEntries = Object.entries(this.tags);
        let tagNames;
        if (doc && tagEntries.length > 0 && isNode(doc.contents)) {
            const tags = {};
            visit(doc.contents, (_key, node) => {
                if (isNode(node) && node.tag)
                    tags[node.tag] = true;
            });
            tagNames = Object.keys(tags);
        }
        else
            tagNames = [];
        for (const [handle, prefix] of tagEntries) {
            if (handle === '!!' && prefix === 'tag:yaml.org,2002:')
                continue;
            if (!doc || tagNames.some(tn => tn.startsWith(prefix)))
                lines.push(`%TAG ${handle} ${prefix}`);
        }
        return lines.join('\n');
    }
}
Directives.defaultYaml = { explicit: false, version: '1.2' };
Directives.defaultTags = { '!!': 'tag:yaml.org,2002:' };

/**
 * Verify that the input string is a valid anchor.
 *
 * Will throw on errors.
 */
function anchorIsValid(anchor) {
    if (/[\x00-\x19\s,[\]{}]/.test(anchor)) {
        const sa = JSON.stringify(anchor);
        const msg = `Anchor must not contain whitespace or control characters: ${sa}`;
        throw new Error(msg);
    }
    return true;
}
function anchorNames(root) {
    const anchors = new Set();
    visit(root, {
        Value(_key, node) {
            if (node.anchor)
                anchors.add(node.anchor);
        }
    });
    return anchors;
}
/** Find a new anchor name with the given `prefix` and a one-indexed suffix. */
function findNewAnchor(prefix, exclude) {
    for (let i = 1; true; ++i) {
        const name = `${prefix}${i}`;
        if (!exclude.has(name))
            return name;
    }
}
function createNodeAnchors(doc, prefix) {
    const aliasObjects = [];
    const sourceObjects = new Map();
    let prevAnchors = null;
    return {
        onAnchor: (source) => {
            aliasObjects.push(source);
            if (!prevAnchors)
                prevAnchors = anchorNames(doc);
            const anchor = findNewAnchor(prefix, prevAnchors);
            prevAnchors.add(anchor);
            return anchor;
        },
        /**
         * With circular references, the source node is only resolved after all
         * of its child nodes are. This is why anchors are set only after all of
         * the nodes have been created.
         */
        setAnchors: () => {
            for (const source of aliasObjects) {
                const ref = sourceObjects.get(source);
                if (typeof ref === 'object' &&
                    ref.anchor &&
                    (isScalar(ref.node) || isCollection(ref.node))) {
                    ref.node.anchor = ref.anchor;
                }
                else {
                    const error = new Error('Failed to resolve repeated object (this should not happen)');
                    error.source = source;
                    throw error;
                }
            }
        },
        sourceObjects
    };
}

/**
 * Applies the JSON.parse reviver algorithm as defined in the ECMA-262 spec,
 * in section 24.5.1.1 "Runtime Semantics: InternalizeJSONProperty" of the
 * 2021 edition: https://tc39.es/ecma262/#sec-json.parse
 *
 * Includes extensions for handling Map and Set objects.
 */
function applyReviver(reviver, obj, key, val) {
    if (val && typeof val === 'object') {
        if (Array.isArray(val)) {
            for (let i = 0, len = val.length; i < len; ++i) {
                const v0 = val[i];
                const v1 = applyReviver(reviver, val, String(i), v0);
                if (v1 === undefined)
                    delete val[i];
                else if (v1 !== v0)
                    val[i] = v1;
            }
        }
        else if (val instanceof Map) {
            for (const k of Array.from(val.keys())) {
                const v0 = val.get(k);
                const v1 = applyReviver(reviver, val, k, v0);
                if (v1 === undefined)
                    val.delete(k);
                else if (v1 !== v0)
                    val.set(k, v1);
            }
        }
        else if (val instanceof Set) {
            for (const v0 of Array.from(val)) {
                const v1 = applyReviver(reviver, val, v0, v0);
                if (v1 === undefined)
                    val.delete(v0);
                else if (v1 !== v0) {
                    val.delete(v0);
                    val.add(v1);
                }
            }
        }
        else {
            for (const [k, v0] of Object.entries(val)) {
                const v1 = applyReviver(reviver, val, k, v0);
                if (v1 === undefined)
                    delete val[k];
                else if (v1 !== v0)
                    val[k] = v1;
            }
        }
    }
    return reviver.call(obj, key, val);
}

/**
 * Recursively convert any node or its contents to native JavaScript
 *
 * @param value - The input value
 * @param arg - If `value` defines a `toJSON()` method, use this
 *   as its first argument
 * @param ctx - Conversion context, originally set in Document#toJS(). If
 *   `{ keep: true }` is not set, output should be suitable for JSON
 *   stringification.
 */
function toJS(value, arg, ctx) {
    // eslint-disable-next-line @typescript-eslint/no-unsafe-return
    if (Array.isArray(value))
        return value.map((v, i) => toJS(v, String(i), ctx));
    if (value && typeof value.toJSON === 'function') {
        // eslint-disable-next-line @typescript-eslint/no-unsafe-call
        if (!ctx || !hasAnchor(value))
            return value.toJSON(arg, ctx);
        const data = { aliasCount: 0, count: 1, res: undefined };
        ctx.anchors.set(value, data);
        ctx.onCreate = res => {
            data.res = res;
            delete ctx.onCreate;
        };
        const res = value.toJSON(arg, ctx);
        if (ctx.onCreate)
            ctx.onCreate(res);
        return res;
    }
    if (typeof value === 'bigint' && !ctx?.keep)
        return Number(value);
    return value;
}

class NodeBase {
    constructor(type) {
        Object.defineProperty(this, NODE_TYPE, { value: type });
    }
    /** Create a copy of this node.  */
    clone() {
        const copy = Object.create(Object.getPrototypeOf(this), Object.getOwnPropertyDescriptors(this));
        if (this.range)
            copy.range = this.range.slice();
        return copy;
    }
    /** A plain JavaScript representation of this node. */
    toJS(doc, { mapAsMap, maxAliasCount, onAnchor, reviver } = {}) {
        if (!isDocument(doc))
            throw new TypeError('A document argument is required');
        const ctx = {
            anchors: new Map(),
            doc,
            keep: true,
            mapAsMap: mapAsMap === true,
            mapKeyWarned: false,
            maxAliasCount: typeof maxAliasCount === 'number' ? maxAliasCount : 100
        };
        const res = toJS(this, '', ctx);
        if (typeof onAnchor === 'function')
            for (const { count, res } of ctx.anchors.values())
                onAnchor(res, count);
        return typeof reviver === 'function'
            ? applyReviver(reviver, { '': res }, '', res)
            : res;
    }
}

class Alias extends NodeBase {
    constructor(source) {
        super(ALIAS);
        this.source = source;
        Object.defineProperty(this, 'tag', {
            set() {
                throw new Error('Alias nodes cannot have tags');
            }
        });
    }
    /**
     * Resolve the value of this alias within `doc`, finding the last
     * instance of the `source` anchor before this node.
     */
    resolve(doc) {
        let found = undefined;
        visit(doc, {
            Node: (_key, node) => {
                if (node === this)
                    return visit.BREAK;
                if (node.anchor === this.source)
                    found = node;
            }
        });
        return found;
    }
    toJSON(_arg, ctx) {
        if (!ctx)
            return { source: this.source };
        const { anchors, doc, maxAliasCount } = ctx;
        const source = this.resolve(doc);
        if (!source) {
            const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`;
            throw new ReferenceError(msg);
        }
        let data = anchors.get(source);
        if (!data) {
            // Resolve anchors for Node.prototype.toJS()
            toJS(source, null, ctx);
            data = anchors.get(source);
        }
        /* istanbul ignore if */
        if (!data || data.res === undefined) {
            const msg = 'This should not happen: Alias anchor was not resolved?';
            throw new ReferenceError(msg);
        }
        if (maxAliasCount >= 0) {
            data.count += 1;
            if (data.aliasCount === 0)
                data.aliasCount = getAliasCount(doc, source, anchors);
            if (data.count * data.aliasCount > maxAliasCount) {
                const msg = 'Excessive alias count indicates a resource exhaustion attack';
                throw new ReferenceError(msg);
            }
        }
        return data.res;
    }
    toString(ctx, _onComment, _onChompKeep) {
        const src = `*${this.source}`;
        if (ctx) {
            anchorIsValid(this.source);
            if (ctx.options.verifyAliasOrder && !ctx.anchors.has(this.source)) {
                const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`;
                throw new Error(msg);
            }
            if (ctx.implicitKey)
                return `${src} `;
        }
        return src;
    }
}
function getAliasCount(doc, node, anchors) {
    if (isAlias(node)) {
        const source = node.resolve(doc);
        const anchor = anchors && source && anchors.get(source);
        return anchor ? anchor.count * anchor.aliasCount : 0;
    }
    else if (isCollection(node)) {
        let count = 0;
        for (const item of node.items) {
            const c = getAliasCount(doc, item, anchors);
            if (c > count)
                count = c;
        }
        return count;
    }
    else if (isPair(node)) {
        const kc = getAliasCount(doc, node.key, anchors);
        const vc = getAliasCount(doc, node.value, anchors);
        return Math.max(kc, vc);
    }
    return 1;
}

const isScalarValue = (value) => !value || (typeof value !== 'function' && typeof value !== 'object');
class Scalar extends NodeBase {
    constructor(value) {
        super(SCALAR);
        this.value = value;
    }
    toJSON(arg, ctx) {
        return ctx?.keep ? this.value : toJS(this.value, arg, ctx);
    }
    toString() {
        return String(this.value);
    }
}
Scalar.BLOCK_FOLDED = 'BLOCK_FOLDED';
Scalar.BLOCK_LITERAL = 'BLOCK_LITERAL';
Scalar.PLAIN = 'PLAIN';
Scalar.QUOTE_DOUBLE = 'QUOTE_DOUBLE';
Scalar.QUOTE_SINGLE = 'QUOTE_SINGLE';

const defaultTagPrefix = 'tag:yaml.org,2002:';
function findTagObject(value, tagName, tags) {
    if (tagName) {
        const match = tags.filter(t => t.tag === tagName);
        const tagObj = match.find(t => !t.format) ?? match[0];
        if (!tagObj)
            throw new Error(`Tag ${tagName} not found`);
        return tagObj;
    }
    return tags.find(t => t.identify?.(value) && !t.format);
}
function createNode(value, tagName, ctx) {
    if (isDocument(value))
        value = value.contents;
    if (isNode(value))
        return value;
    if (isPair(value)) {
        const map = ctx.schema[MAP].createNode?.(ctx.schema, null, ctx);
        map.items.push(value);
        return map;
    }
    if (value instanceof String ||
        value instanceof Number ||
        value instanceof Boolean ||
        (typeof BigInt !== 'undefined' && value instanceof BigInt) // not supported everywhere
    ) {
        // https://tc39.es/ecma262/#sec-serializejsonproperty
        value = value.valueOf();
    }
    const { aliasDuplicateObjects, onAnchor, onTagObj, schema, sourceObjects } = ctx;
    // Detect duplicate references to the same object & use Alias nodes for all
    // after first. The `ref` wrapper allows for circular references to resolve.
    let ref = undefined;
    if (aliasDuplicateObjects && value && typeof value === 'object') {
        ref = sourceObjects.get(value);
        if (ref) {
            if (!ref.anchor)
                ref.anchor = onAnchor(value);
            return new Alias(ref.anchor);
        }
        else {
            ref = { anchor: null, node: null };
            sourceObjects.set(value, ref);
        }
    }
    if (tagName?.startsWith('!!'))
        tagName = defaultTagPrefix + tagName.slice(2);
    let tagObj = findTagObject(value, tagName, schema.tags);
    if (!tagObj) {
        if (value && typeof value.toJSON === 'function') {
            // eslint-disable-next-line @typescript-eslint/no-unsafe-call
            value = value.toJSON();
        }
        if (!value || typeof value !== 'object') {
            const node = new Scalar(value);
            if (ref)
                ref.node = node;
            return node;
        }
        tagObj =
            value instanceof Map
                ? schema[MAP]
                : Symbol.iterator in Object(value)
                    ? schema[SEQ]
                    : schema[MAP];
    }
    if (onTagObj) {
        onTagObj(tagObj);
        delete ctx.onTagObj;
    }
    const node = tagObj?.createNode
        ? tagObj.createNode(ctx.schema, value, ctx)
        : typeof tagObj?.nodeClass?.from === 'function'
            ? tagObj.nodeClass.from(ctx.schema, value, ctx)
            : new Scalar(value);
    if (tagName)
        node.tag = tagName;
    else if (!tagObj.default)
        node.tag = tagObj.tag;
    if (ref)
        ref.node = node;
    return node;
}

function collectionFromPath(schema, path, value) {
    let v = value;
    for (let i = path.length - 1; i >= 0; --i) {
        const k = path[i];
        if (typeof k === 'number' && Number.isInteger(k) && k >= 0) {
            const a = [];
            a[k] = v;
            v = a;
        }
        else {
            v = new Map([[k, v]]);
        }
    }
    return createNode(v, undefined, {
        aliasDuplicateObjects: false,
        keepUndefined: false,
        onAnchor: () => {
            throw new Error('This should not happen, please report a bug.');
        },
        schema,
        sourceObjects: new Map()
    });
}
// Type guard is intentionally a little wrong so as to be more useful,
// as it does not cover untypable empty non-string iterables (e.g. []).
const isEmptyPath = (path) => path == null ||
    (typeof path === 'object' && !!path[Symbol.iterator]().next().done);
class Collection extends NodeBase {
    constructor(type, schema) {
        super(type);
        Object.defineProperty(this, 'schema', {
            value: schema,
            configurable: true,
            enumerable: false,
            writable: true
        });
    }
    /**
     * Create a copy of this collection.
     *
     * @param schema - If defined, overwrites the original's schema
     */
    clone(schema) {
        const copy = Object.create(Object.getPrototypeOf(this), Object.getOwnPropertyDescriptors(this));
        if (schema)
            copy.schema = schema;
        copy.items = copy.items.map(it => isNode(it) || isPair(it) ? it.clone(schema) : it);
        if (this.range)
            copy.range = this.range.slice();
        return copy;
    }
    /**
     * Adds a value to the collection. For `!!map` and `!!omap` the value must
     * be a Pair instance or a `{ key, value }` object, which may not have a key
     * that already exists in the map.
     */
    addIn(path, value) {
        if (isEmptyPath(path))
            this.add(value);
        else {
            const [key, ...rest] = path;
            const node = this.get(key, true);
            if (isCollection(node))
                node.addIn(rest, value);
            else if (node === undefined && this.schema)
                this.set(key, collectionFromPath(this.schema, rest, value));
            else
                throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
        }
    }
    /**
     * Removes a value from the collection.
     * @returns `true` if the item was found and removed.
     */
    deleteIn(path) {
        const [key, ...rest] = path;
        if (rest.length === 0)
            return this.delete(key);
        const node = this.get(key, true);
        if (isCollection(node))
            return node.deleteIn(rest);
        else
            throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
    }
    /**
     * Returns item at `key`, or `undefined` if not found. By default unwraps
     * scalar values from their surrounding node; to disable set `keepScalar` to
     * `true` (collections are always returned intact).
     */
    getIn(path, keepScalar) {
        const [key, ...rest] = path;
        const node = this.get(key, true);
        if (rest.length === 0)
            return !keepScalar && isScalar(node) ? node.value : node;
        else
            return isCollection(node) ? node.getIn(rest, keepScalar) : undefined;
    }
    hasAllNullValues(allowScalar) {
        return this.items.every(node => {
            if (!isPair(node))
                return false;
            const n = node.value;
            return (n == null ||
                (allowScalar &&
                    isScalar(n) &&
                    n.value == null &&
                    !n.commentBefore &&
                    !n.comment &&
                    !n.tag));
        });
    }
    /**
     * Checks if the collection includes a value with the key `key`.
     */
    hasIn(path) {
        const [key, ...rest] = path;
        if (rest.length === 0)
            return this.has(key);
        const node = this.get(key, true);
        return isCollection(node) ? node.hasIn(rest) : false;
    }
    /**
     * Sets a value in this collection. For `!!set`, `value` needs to be a
     * boolean to add/remove the item from the set.
     */
    setIn(path, value) {
        const [key, ...rest] = path;
        if (rest.length === 0) {
            this.set(key, value);
        }
        else {
            const node = this.get(key, true);
            if (isCollection(node))
                node.setIn(rest, value);
            else if (node === undefined && this.schema)
                this.set(key, collectionFromPath(this.schema, rest, value));
            else
                throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
        }
    }
}
Collection.maxFlowStringSingleLineLength = 60;

/**
 * Stringifies a comment.
 *
 * Empty comment lines are left empty,
 * lines consisting of a single space are replaced by `#`,
 * and all other lines are prefixed with a `#`.
 */
const stringifyComment = (str) => str.replace(/^(?!$)(?: $)?/gm, '#');
function indentComment(comment, indent) {
    if (/^\n+$/.test(comment))
        return comment.substring(1);
    return indent ? comment.replace(/^(?! *$)/gm, indent) : comment;
}
const lineComment = (str, indent, comment) => str.endsWith('\n')
    ? indentComment(comment, indent)
    : comment.includes('\n')
        ? '\n' + indentComment(comment, indent)
        : (str.endsWith(' ') ? '' : ' ') + comment;

const FOLD_FLOW = 'flow';
const FOLD_BLOCK = 'block';
const FOLD_QUOTED = 'quoted';
/**
 * Tries to keep input at up to `lineWidth` characters, splitting only on spaces
 * not followed by newlines or spaces unless `mode` is `'quoted'`. Lines are
 * terminated with `\n` and started with `indent`.
 */
function foldFlowLines(text, indent, mode = 'flow', { indentAtStart, lineWidth = 80, minContentWidth = 20, onFold, onOverflow } = {}) {
    if (!lineWidth || lineWidth < 0)
        return text;
    const endStep = Math.max(1 + minContentWidth, 1 + lineWidth - indent.length);
    if (text.length <= endStep)
        return text;
    const folds = [];
    const escapedFolds = {};
    let end = lineWidth - indent.length;
    if (typeof indentAtStart === 'number') {
        if (indentAtStart > lineWidth - Math.max(2, minContentWidth))
            folds.push(0);
        else
            end = lineWidth - indentAtStart;
    }
    let split = undefined;
    let prev = undefined;
    let overflow = false;
    let i = -1;
    let escStart = -1;
    let escEnd = -1;
    if (mode === FOLD_BLOCK) {
        i = consumeMoreIndentedLines(text, i, indent.length);
        if (i !== -1)
            end = i + endStep;
    }
    for (let ch; (ch = text[(i += 1)]);) {
        if (mode === FOLD_QUOTED && ch === '\\') {
            escStart = i;
            switch (text[i + 1]) {
                case 'x':
                    i += 3;
                    break;
                case 'u':
                    i += 5;
                    break;
                case 'U':
                    i += 9;
                    break;
                default:
                    i += 1;
            }
            escEnd = i;
        }
        if (ch === '\n') {
            if (mode === FOLD_BLOCK)
                i = consumeMoreIndentedLines(text, i, indent.length);
            end = i + indent.length + endStep;
            split = undefined;
        }
        else {
            if (ch === ' ' &&
                prev &&
                prev !== ' ' &&
                prev !== '\n' &&
                prev !== '\t') {
                // space surrounded by non-space can be replaced with newline + indent
                const next = text[i + 1];
                if (next && next !== ' ' && next !== '\n' && next !== '\t')
                    split = i;
            }
            if (i >= end) {
                if (split) {
                    folds.push(split);
                    end = split + endStep;
                    split = undefined;
                }
                else if (mode === FOLD_QUOTED) {
                    // white-space collected at end may stretch past lineWidth
                    while (prev === ' ' || prev === '\t') {
                        prev = ch;
                        ch = text[(i += 1)];
                        overflow = true;
                    }
                    // Account for newline escape, but don't break preceding escape
                    const j = i > escEnd + 1 ? i - 2 : escStart - 1;
                    // Bail out if lineWidth & minContentWidth are shorter than an escape string
                    if (escapedFolds[j])
                        return text;
                    folds.push(j);
                    escapedFolds[j] = true;
                    end = j + endStep;
                    split = undefined;
                }
                else {
                    overflow = true;
                }
            }
        }
        prev = ch;
    }
    if (overflow && onOverflow)
        onOverflow();
    if (folds.length === 0)
        return text;
    if (onFold)
        onFold();
    let res = text.slice(0, folds[0]);
    for (let i = 0; i < folds.length; ++i) {
        const fold = folds[i];
        const end = folds[i + 1] || text.length;
        if (fold === 0)
            res = `\n${indent}${text.slice(0, end)}`;
        else {
            if (mode === FOLD_QUOTED && escapedFolds[fold])
                res += `${text[fold]}\\`;
            res += `\n${indent}${text.slice(fold + 1, end)}`;
        }
    }
    return res;
}
/**
 * Presumes `i + 1` is at the start of a line
 * @returns index of last newline in more-indented block
 */
function consumeMoreIndentedLines(text, i, indent) {
    let end = i;
    let start = i + 1;
    let ch = text[start];
    while (ch === ' ' || ch === '\t') {
        if (i < start + indent) {
            ch = text[++i];
        }
        else {
            do {
                ch = text[++i];
            } while (ch && ch !== '\n');
            end = i;
            start = i + 1;
            ch = text[start];
        }
    }
    return end;
}

const getFoldOptions = (ctx, isBlock) => ({
    indentAtStart: isBlock ? ctx.indent.length : ctx.indentAtStart,
    lineWidth: ctx.options.lineWidth,
    minContentWidth: ctx.options.minContentWidth
});
// Also checks for lines starting with %, as parsing the output as YAML 1.1 will
// presume that's starting a new document.
const containsDocumentMarker = (str) => /^(%|---|\.\.\.)/m.test(str);
function lineLengthOverLimit(str, lineWidth, indentLength) {
    if (!lineWidth || lineWidth < 0)
        return false;
    const limit = lineWidth - indentLength;
    const strLen = str.length;
    if (strLen <= limit)
        return false;
    for (let i = 0, start = 0; i < strLen; ++i) {
        if (str[i] === '\n') {
            if (i - start > limit)
                return true;
            start = i + 1;
            if (strLen - start <= limit)
                return false;
        }
    }
    return true;
}
function doubleQuotedString(value, ctx) {
    const json = JSON.stringify(value);
    if (ctx.options.doubleQuotedAsJSON)
        return json;
    const { implicitKey } = ctx;
    const minMultiLineLength = ctx.options.doubleQuotedMinMultiLineLength;
    const indent = ctx.indent || (containsDocumentMarker(value) ? '  ' : '');
    let str = '';
    let start = 0;
    for (let i = 0, ch = json[i]; ch; ch = json[++i]) {
        if (ch === ' ' && json[i + 1] === '\\' && json[i + 2] === 'n') {
            // space before newline needs to be escaped to not be folded
            str += json.slice(start, i) + '\\ ';
            i += 1;
            start = i;
            ch = '\\';
        }
        if (ch === '\\')
            switch (json[i + 1]) {
                case 'u':
                    {
                        str += json.slice(start, i);
                        const code = json.substr(i + 2, 4);
                        switch (code) {
                            case '0000':
                                str += '\\0';
                                break;
                            case '0007':
                                str += '\\a';
                                break;
                            case '000b':
                                str += '\\v';
                                break;
                            case '001b':
                                str += '\\e';
                                break;
                            case '0085':
                                str += '\\N';
                                break;
                            case '00a0':
                                str += '\\_';
                                break;
                            case '2028':
                                str += '\\L';
                                break;
                            case '2029':
                                str += '\\P';
                                break;
                            default:
                                if (code.substr(0, 2) === '00')
                                    str += '\\x' + code.substr(2);
                                else
                                    str += json.substr(i, 6);
                        }
                        i += 5;
                        start = i + 1;
                    }
                    break;
                case 'n':
                    if (implicitKey ||
                        json[i + 2] === '"' ||
                        json.length < minMultiLineLength) {
                        i += 1;
                    }
                    else {
                        // folding will eat first newline
                        str += json.slice(start, i) + '\n\n';
                        while (json[i + 2] === '\\' &&
                            json[i + 3] === 'n' &&
                            json[i + 4] !== '"') {
                            str += '\n';
                            i += 2;
                        }
                        str += indent;
                        // space after newline needs to be escaped to not be folded
                        if (json[i + 2] === ' ')
                            str += '\\';
                        i += 1;
                        start = i + 1;
                    }
                    break;
                default:
                    i += 1;
            }
    }
    str = start ? str + json.slice(start) : json;
    return implicitKey
        ? str
        : foldFlowLines(str, indent, FOLD_QUOTED, getFoldOptions(ctx, false));
}
function singleQuotedString(value, ctx) {
    if (ctx.options.singleQuote === false ||
        (ctx.implicitKey && value.includes('\n')) ||
        /[ \t]\n|\n[ \t]/.test(value) // single quoted string can't have leading or trailing whitespace around newline
    )
        return doubleQuotedString(value, ctx);
    const indent = ctx.indent || (containsDocumentMarker(value) ? '  ' : '');
    const res = "'" + value.replace(/'/g, "''").replace(/\n+/g, `$&\n${indent}`) + "'";
    return ctx.implicitKey
        ? res
        : foldFlowLines(res, indent, FOLD_FLOW, getFoldOptions(ctx, false));
}
function quotedString(value, ctx) {
    const { singleQuote } = ctx.options;
    let qs;
    if (singleQuote === false)
        qs = doubleQuotedString;
    else {
        const hasDouble = value.includes('"');
        const hasSingle = value.includes("'");
        if (hasDouble && !hasSingle)
            qs = singleQuotedString;
        else if (hasSingle && !hasDouble)
            qs = doubleQuotedString;
        else
            qs = singleQuote ? singleQuotedString : doubleQuotedString;
    }
    return qs(value, ctx);
}
// The negative lookbehind avoids a polynomial search,
// but isn't supported yet on Safari: https://caniuse.com/js-regexp-lookbehind
let blockEndNewlines;
try {
    blockEndNewlines = new RegExp('(^|(?<!\n))\n+(?!\n|$)', 'g');
}
catch {
    blockEndNewlines = /\n+(?!\n|$)/g;
}
function blockString({ comment, type, value }, ctx, onComment, onChompKeep) {
    const { blockQuote, commentString, lineWidth } = ctx.options;
    // 1. Block can't end in whitespace unless the last line is non-empty.
    // 2. Strings consisting of only whitespace are best rendered explicitly.
    if (!blockQuote || /\n[\t ]+$/.test(value) || /^\s*$/.test(value)) {
        return quotedString(value, ctx);
    }
    const indent = ctx.indent ||
        (ctx.forceBlockIndent || containsDocumentMarker(value) ? '  ' : '');
    const literal = blockQuote === 'literal'
        ? true
        : blockQuote === 'folded' || type === Scalar.BLOCK_FOLDED
            ? false
            : type === Scalar.BLOCK_LITERAL
                ? true
                : !lineLengthOverLimit(value, lineWidth, indent.length);
    if (!value)
        return literal ? '|\n' : '>\n';
    // determine chomping from whitespace at value end
    let chomp;
    let endStart;
    for (endStart = value.length; endStart > 0; --endStart) {
        const ch = value[endStart - 1];
        if (ch !== '\n' && ch !== '\t' && ch !== ' ')
            break;
    }
    let end = value.substring(endStart);
    const endNlPos = end.indexOf('\n');
    if (endNlPos === -1) {
        chomp = '-'; // strip
    }
    else if (value === end || endNlPos !== end.length - 1) {
        chomp = '+'; // keep
        if (onChompKeep)
            onChompKeep();
    }
    else {
        chomp = ''; // clip
    }
    if (end) {
        value = value.slice(0, -end.length);
        if (end[end.length - 1] === '\n')
            end = end.slice(0, -1);
        end = end.replace(blockEndNewlines, `$&${indent}`);
    }
    // determine indent indicator from whitespace at value start
    let startWithSpace = false;
    let startEnd;
    let startNlPos = -1;
    for (startEnd = 0; startEnd < value.length; ++startEnd) {
        const ch = value[startEnd];
        if (ch === ' ')
            startWithSpace = true;
        else if (ch === '\n')
            startNlPos = startEnd;
        else
            break;
    }
    let start = value.substring(0, startNlPos < startEnd ? startNlPos + 1 : startEnd);
    if (start) {
        value = value.substring(start.length);
        start = start.replace(/\n+/g, `$&${indent}`);
    }
    const indentSize = indent ? '2' : '1'; // root is at -1
    let header = (literal ? '|' : '>') + (startWithSpace ? indentSize : '') + chomp;
    if (comment) {
        header += ' ' + commentString(comment.replace(/ ?[\r\n]+/g, ' '));
        if (onComment)
            onComment();
    }
    if (literal) {
        value = value.replace(/\n+/g, `$&${indent}`);
        return `${header}\n${indent}${start}${value}${end}`;
    }
    value = value
        .replace(/\n+/g, '\n$&')
        .replace(/(?:^|\n)([\t ].*)(?:([\n\t ]*)\n(?![\n\t ]))?/g, '$1$2') // more-indented lines aren't folded
        //                ^ more-ind. ^ empty     ^ capture next empty lines only at end of indent
        .replace(/\n+/g, `$&${indent}`);
    const body = foldFlowLines(`${start}${value}${end}`, indent, FOLD_BLOCK, getFoldOptions(ctx, true));
    return `${header}\n${indent}${body}`;
}
function plainString(item, ctx, onComment, onChompKeep) {
    const { type, value } = item;
    const { actualString, implicitKey, indent, indentStep, inFlow } = ctx;
    if ((implicitKey && value.includes('\n')) ||
        (inFlow && /[[\]{},]/.test(value))) {
        return quotedString(value, ctx);
    }
    if (!value ||
        /^[\n\t ,[\]{}#&*!|>'"%@`]|^[?-]$|^[?-][ \t]|[\n:][ \t]|[ \t]\n|[\n\t ]#|[\n\t :]$/.test(value)) {
        // not allowed:
        // - empty string, '-' or '?'
        // - start with an indicator character (except [?:-]) or /[?-] /
        // - '\n ', ': ' or ' \n' anywhere
        // - '#' not preceded by a non-space char
        // - end with ' ' or ':'
        return implicitKey || inFlow || !value.includes('\n')
            ? quotedString(value, ctx)
            : blockString(item, ctx, onComment, onChompKeep);
    }
    if (!implicitKey &&
        !inFlow &&
        type !== Scalar.PLAIN &&
        value.includes('\n')) {
        // Where allowed & type not set explicitly, prefer block style for multiline strings
        return blockString(item, ctx, onComment, onChompKeep);
    }
    if (containsDocumentMarker(value)) {
        if (indent === '') {
            ctx.forceBlockIndent = true;
            return blockString(item, ctx, onComment, onChompKeep);
        }
        else if (implicitKey && indent === indentStep) {
            return quotedString(value, ctx);
        }
    }
    const str = value.replace(/\n+/g, `$&\n${indent}`);
    // Verify that output will be parsed as a string, as e.g. plain numbers and
    // booleans get parsed with those types in v1.2 (e.g. '42', 'true' & '0.9e-3'),
    // and others in v1.1.
    if (actualString) {
        const test = (tag) => tag.default && tag.tag !== 'tag:yaml.org,2002:str' && tag.test?.test(str);
        const { compat, tags } = ctx.doc.schema;
        if (tags.some(test) || compat?.some(test))
            return quotedString(value, ctx);
    }
    return implicitKey
        ? str
        : foldFlowLines(str, indent, FOLD_FLOW, getFoldOptions(ctx, false));
}
function stringifyString(item, ctx, onComment, onChompKeep) {
    const { implicitKey, inFlow } = ctx;
    const ss = typeof item.value === 'string'
        ? item
        : Object.assign({}, item, { value: String(item.value) });
    let { type } = item;
    if (type !== Scalar.QUOTE_DOUBLE) {
        // force double quotes on control characters & unpaired surrogates
        if (/[\x00-\x08\x0b-\x1f\x7f-\x9f\u{D800}-\u{DFFF}]/u.test(ss.value))
            type = Scalar.QUOTE_DOUBLE;
    }
    const _stringify = (_type) => {
        switch (_type) {
            case Scalar.BLOCK_FOLDED:
            case Scalar.BLOCK_LITERAL:
                return implicitKey || inFlow
                    ? quotedString(ss.value, ctx) // blocks are not valid inside flow containers
                    : blockString(ss, ctx, onComment, onChompKeep);
            case Scalar.QUOTE_DOUBLE:
                return doubleQuotedString(ss.value, ctx);
            case Scalar.QUOTE_SINGLE:
                return singleQuotedString(ss.value, ctx);
            case Scalar.PLAIN:
                return plainString(ss, ctx, onComment, onChompKeep);
            default:
                return null;
        }
    };
    let res = _stringify(type);
    if (res === null) {
        const { defaultKeyType, defaultStringType } = ctx.options;
        const t = (implicitKey && defaultKeyType) || defaultStringType;
        res = _stringify(t);
        if (res === null)
            throw new Error(`Unsupported default string type ${t}`);
    }
    return res;
}

function createStringifyContext(doc, options) {
    const opt = Object.assign({
        blockQuote: true,
        commentString: stringifyComment,
        defaultKeyType: null,
        defaultStringType: 'PLAIN',
        directives: null,
        doubleQuotedAsJSON: false,
        doubleQuotedMinMultiLineLength: 40,
        falseStr: 'false',
        flowCollectionPadding: true,
        indentSeq: true,
        lineWidth: 80,
        minContentWidth: 20,
        nullStr: 'null',
        simpleKeys: false,
        singleQuote: null,
        trueStr: 'true',
        verifyAliasOrder: true
    }, doc.schema.toStringOptions, options);
    let inFlow;
    switch (opt.collectionStyle) {
        case 'block':
            inFlow = false;
            break;
        case 'flow':
            inFlow = true;
            break;
        default:
            inFlow = null;
    }
    return {
        anchors: new Set(),
        doc,
        flowCollectionPadding: opt.flowCollectionPadding ? ' ' : '',
        indent: '',
        indentStep: typeof opt.indent === 'number' ? ' '.repeat(opt.indent) : '  ',
        inFlow,
        options: opt
    };
}
function getTagObject(tags, item) {
    if (item.tag) {
        const match = tags.filter(t => t.tag === item.tag);
        if (match.length > 0)
            return match.find(t => t.format === item.format) ?? match[0];
    }
    let tagObj = undefined;
    let obj;
    if (isScalar(item)) {
        obj = item.value;
        const match = tags.filter(t => t.identify?.(obj));
        tagObj =
            match.find(t => t.format === item.format) ?? match.find(t => !t.format);
    }
    else {
        obj = item;
        tagObj = tags.find(t => t.nodeClass && obj instanceof t.nodeClass);
    }
    if (!tagObj) {
        const name = obj?.constructor?.name ?? typeof obj;
        throw new Error(`Tag not resolved for ${name} value`);
    }
    return tagObj;
}
// needs to be called before value stringifier to allow for circular anchor refs
function stringifyProps(node, tagObj, { anchors, doc }) {
    if (!doc.directives)
        return '';
    const props = [];
    const anchor = (isScalar(node) || isCollection(node)) && node.anchor;
    if (anchor && anchorIsValid(anchor)) {
        anchors.add(anchor);
        props.push(`&${anchor}`);
    }
    const tag = node.tag ? node.tag : tagObj.default ? null : tagObj.tag;
    if (tag)
        props.push(doc.directives.tagString(tag));
    return props.join(' ');
}
function stringify$1(item, ctx, onComment, onChompKeep) {
    if (isPair(item))
        return item.toString(ctx, onComment, onChompKeep);
    if (isAlias(item)) {
        if (ctx.doc.directives)
            return item.toString(ctx);
        if (ctx.resolvedAliases?.has(item)) {
            throw new TypeError(`Cannot stringify circular structure without alias nodes`);
        }
        else {
            if (ctx.resolvedAliases)
                ctx.resolvedAliases.add(item);
            else
                ctx.resolvedAliases = new Set([item]);
            item = item.resolve(ctx.doc);
        }
    }
    let tagObj = undefined;
    const node = isNode(item)
        ? item
        : ctx.doc.createNode(item, { onTagObj: o => (tagObj = o) });
    if (!tagObj)
        tagObj = getTagObject(ctx.doc.schema.tags, node);
    const props = stringifyProps(node, tagObj, ctx);
    if (props.length > 0)
        ctx.indentAtStart = (ctx.indentAtStart ?? 0) + props.length + 1;
    const str = typeof tagObj.stringify === 'function'
        ? tagObj.stringify(node, ctx, onComment, onChompKeep)
        : isScalar(node)
            ? stringifyString(node, ctx, onComment, onChompKeep)
            : node.toString(ctx, onComment, onChompKeep);
    if (!props)
        return str;
    return isScalar(node) || str[0] === '{' || str[0] === '['
        ? `${props} ${str}`
        : `${props}\n${ctx.indent}${str}`;
}

function stringifyPair({ key, value }, ctx, onComment, onChompKeep) {
    const { allNullValues, doc, indent, indentStep, options: { commentString, indentSeq, simpleKeys } } = ctx;
    let keyComment = (isNode(key) && key.comment) || null;
    if (simpleKeys) {
        if (keyComment) {
            throw new Error('With simple keys, key nodes cannot have comments');
        }
        if (isCollection(key) || (!isNode(key) && typeof key === 'object')) {
            const msg = 'With simple keys, collection cannot be used as a key value';
            throw new Error(msg);
        }
    }
    let explicitKey = !simpleKeys &&
        (!key ||
            (keyComment && value == null && !ctx.inFlow) ||
            isCollection(key) ||
            (isScalar(key)
                ? key.type === Scalar.BLOCK_FOLDED || key.type === Scalar.BLOCK_LITERAL
                : typeof key === 'object'));
    ctx = Object.assign({}, ctx, {
        allNullValues: false,
        implicitKey: !explicitKey && (simpleKeys || !allNullValues),
        indent: indent + indentStep
    });
    let keyCommentDone = false;
    let chompKeep = false;
    let str = stringify$1(key, ctx, () => (keyCommentDone = true), () => (chompKeep = true));
    if (!explicitKey && !ctx.inFlow && str.length > 1024) {
        if (simpleKeys)
            throw new Error('With simple keys, single line scalar must not span more than 1024 characters');
        explicitKey = true;
    }
    if (ctx.inFlow) {
        if (allNullValues || value == null) {
            if (keyCommentDone && onComment)
                onComment();
            return str === '' ? '?' : explicitKey ? `? ${str}` : str;
        }
    }
    else if ((allNullValues && !simpleKeys) || (value == null && explicitKey)) {
        str = `? ${str}`;
        if (keyComment && !keyCommentDone) {
            str += lineComment(str, ctx.indent, commentString(keyComment));
        }
        else if (chompKeep && onChompKeep)
            onChompKeep();
        return str;
    }
    if (keyCommentDone)
        keyComment = null;
    if (explicitKey) {
        if (keyComment)
            str += lineComment(str, ctx.indent, commentString(keyComment));
        str = `? ${str}\n${indent}:`;
    }
    else {
        str = `${str}:`;
        if (keyComment)
            str += lineComment(str, ctx.indent, commentString(keyComment));
    }
    let vsb, vcb, valueComment;
    if (isNode(value)) {
        vsb = !!value.spaceBefore;
        vcb = value.commentBefore;
        valueComment = value.comment;
    }
    else {
        vsb = false;
        vcb = null;
        valueComment = null;
        if (value && typeof value === 'object')
            value = doc.createNode(value);
    }
    ctx.implicitKey = false;
    if (!explicitKey && !keyComment && isScalar(value))
        ctx.indentAtStart = str.length + 1;
    chompKeep = false;
    if (!indentSeq &&
        indentStep.length >= 2 &&
        !ctx.inFlow &&
        !explicitKey &&
        isSeq(value) &&
        !value.flow &&
        !value.tag &&
        !value.anchor) {
        // If indentSeq === false, consider '- ' as part of indentation where possible
        ctx.indent = ctx.indent.substring(2);
    }
    let valueCommentDone = false;
    const valueStr = stringify$1(value, ctx, () => (valueCommentDone = true), () => (chompKeep = true));
    let ws = ' ';
    if (keyComment || vsb || vcb) {
        ws = vsb ? '\n' : '';
        if (vcb) {
            const cs = commentString(vcb);
            ws += `\n${indentComment(cs, ctx.indent)}`;
        }
        if (valueStr === '' && !ctx.inFlow) {
            if (ws === '\n')
                ws = '\n\n';
        }
        else {
            ws += `\n${ctx.indent}`;
        }
    }
    else if (!explicitKey && isCollection(value)) {
        const vs0 = valueStr[0];
        const nl0 = valueStr.indexOf('\n');
        const hasNewline = nl0 !== -1;
        const flow = ctx.inFlow ?? value.flow ?? value.items.length === 0;
        if (hasNewline || !flow) {
            let hasPropsLine = false;
            if (hasNewline && (vs0 === '&' || vs0 === '!')) {
                let sp0 = valueStr.indexOf(' ');
                if (vs0 === '&' &&
                    sp0 !== -1 &&
                    sp0 < nl0 &&
                    valueStr[sp0 + 1] === '!') {
                    sp0 = valueStr.indexOf(' ', sp0 + 1);
                }
                if (sp0 === -1 || nl0 < sp0)
                    hasPropsLine = true;
            }
            if (!hasPropsLine)
                ws = `\n${ctx.indent}`;
        }
    }
    else if (valueStr === '' || valueStr[0] === '\n') {
        ws = '';
    }
    str += ws + valueStr;
    if (ctx.inFlow) {
        if (valueCommentDone && onComment)
            onComment();
    }
    else if (valueComment && !valueCommentDone) {
        str += lineComment(str, ctx.indent, commentString(valueComment));
    }
    else if (chompKeep && onChompKeep) {
        onChompKeep();
    }
    return str;
}

function warn(logLevel, warning) {
    if (logLevel === 'debug' || logLevel === 'warn') {
        // https://github.com/typescript-eslint/typescript-eslint/issues/7478
        // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
        if (typeof process !== 'undefined' && process.emitWarning)
            process.emitWarning(warning);
        else
            console.warn(warning);
    }
}

const MERGE_KEY = '<<';
function addPairToJSMap(ctx, map, { key, value }) {
    if (ctx?.doc.schema.merge && isMergeKey(key)) {
        value = isAlias(value) ? value.resolve(ctx.doc) : value;
        if (isSeq(value))
            for (const it of value.items)
                mergeToJSMap(ctx, map, it);
        else if (Array.isArray(value))
            for (const it of value)
                mergeToJSMap(ctx, map, it);
        else
            mergeToJSMap(ctx, map, value);
    }
    else {
        const jsKey = toJS(key, '', ctx);
        if (map instanceof Map) {
            map.set(jsKey, toJS(value, jsKey, ctx));
        }
        else if (map instanceof Set) {
            map.add(jsKey);
        }
        else {
            const stringKey = stringifyKey(key, jsKey, ctx);
            const jsValue = toJS(value, stringKey, ctx);
            if (stringKey in map)
                Object.defineProperty(map, stringKey, {
                    value: jsValue,
                    writable: true,
                    enumerable: true,
                    configurable: true
                });
            else
                map[stringKey] = jsValue;
        }
    }
    return map;
}
const isMergeKey = (key) => key === MERGE_KEY ||
    (isScalar(key) &&
        key.value === MERGE_KEY &&
        (!key.type || key.type === Scalar.PLAIN));
// If the value associated with a merge key is a single mapping node, each of
// its key/value pairs is inserted into the current mapping, unless the key
// already exists in it. If the value associated with the merge key is a
// sequence, then this sequence is expected to contain mapping nodes and each
// of these nodes is merged in turn according to its order in the sequence.
// Keys in mapping nodes earlier in the sequence override keys specified in
// later mapping nodes. -- http://yaml.org/type/merge.html
function mergeToJSMap(ctx, map, value) {
    const source = ctx && isAlias(value) ? value.resolve(ctx.doc) : value;
    if (!isMap(source))
        throw new Error('Merge sources must be maps or map aliases');
    const srcMap = source.toJSON(null, ctx, Map);
    for (const [key, value] of srcMap) {
        if (map instanceof Map) {
            if (!map.has(key))
                map.set(key, value);
        }
        else if (map instanceof Set) {
            map.add(key);
        }
        else if (!Object.prototype.hasOwnProperty.call(map, key)) {
            Object.defineProperty(map, key, {
                value,
                writable: true,
                enumerable: true,
                configurable: true
            });
        }
    }
    return map;
}
function stringifyKey(key, jsKey, ctx) {
    if (jsKey === null)
        return '';
    if (typeof jsKey !== 'object')
        return String(jsKey);
    if (isNode(key) && ctx?.doc) {
        const strCtx = createStringifyContext(ctx.doc, {});
        strCtx.anchors = new Set();
        for (const node of ctx.anchors.keys())
            strCtx.anchors.add(node.anchor);
        strCtx.inFlow = true;
        strCtx.inStringifyKey = true;
        const strKey = key.toString(strCtx);
        if (!ctx.mapKeyWarned) {
            let jsonStr = JSON.stringify(strKey);
            if (jsonStr.length > 40)
                jsonStr = jsonStr.substring(0, 36) + '..."';
            warn(ctx.doc.options.logLevel, `Keys with collection values will be stringified due to JS Object restrictions: ${jsonStr}. Set mapAsMap: true to use object keys.`);
            ctx.mapKeyWarned = true;
        }
        return strKey;
    }
    return JSON.stringify(jsKey);
}

function createPair(key, value, ctx) {
    const k = createNode(key, undefined, ctx);
    const v = createNode(value, undefined, ctx);
    return new Pair(k, v);
}
class Pair {
    constructor(key, value = null) {
        Object.defineProperty(this, NODE_TYPE, { value: PAIR });
        this.key = key;
        this.value = value;
    }
    clone(schema) {
        let { key, value } = this;
        if (isNode(key))
            key = key.clone(schema);
        if (isNode(value))
            value = value.clone(schema);
        return new Pair(key, value);
    }
    toJSON(_, ctx) {
        const pair = ctx?.mapAsMap ? new Map() : {};
        return addPairToJSMap(ctx, pair, this);
    }
    toString(ctx, onComment, onChompKeep) {
        return ctx?.doc
            ? stringifyPair(this, ctx, onComment, onChompKeep)
            : JSON.stringify(this);
    }
}

function stringifyCollection(collection, ctx, options) {
    const flow = ctx.inFlow ?? collection.flow;
    const stringify = flow ? stringifyFlowCollection : stringifyBlockCollection;
    return stringify(collection, ctx, options);
}
function stringifyBlockCollection({ comment, items }, ctx, { blockItemPrefix, flowChars, itemIndent, onChompKeep, onComment }) {
    const { indent, options: { commentString } } = ctx;
    const itemCtx = Object.assign({}, ctx, { indent: itemIndent, type: null });
    let chompKeep = false; // flag for the preceding node's status
    const lines = [];
    for (let i = 0; i < items.length; ++i) {
        const item = items[i];
        let comment = null;
        if (isNode(item)) {
            if (!chompKeep && item.spaceBefore)
                lines.push('');
            addCommentBefore(ctx, lines, item.commentBefore, chompKeep);
            if (item.comment)
                comment = item.comment;
        }
        else if (isPair(item)) {
            const ik = isNode(item.key) ? item.key : null;
            if (ik) {
                if (!chompKeep && ik.spaceBefore)
                    lines.push('');
                addCommentBefore(ctx, lines, ik.commentBefore, chompKeep);
            }
        }
        chompKeep = false;
        let str = stringify$1(item, itemCtx, () => (comment = null), () => (chompKeep = true));
        if (comment)
            str += lineComment(str, itemIndent, commentString(comment));
        if (chompKeep && comment)
            chompKeep = false;
        lines.push(blockItemPrefix + str);
    }
    let str;
    if (lines.length === 0) {
        str = flowChars.start + flowChars.end;
    }
    else {
        str = lines[0];
        for (let i = 1; i < lines.length; ++i) {
            const line = lines[i];
            str += line ? `\n${indent}${line}` : '\n';
        }
    }
    if (comment) {
        str += '\n' + indentComment(commentString(comment), indent);
        if (onComment)
            onComment();
    }
    else if (chompKeep && onChompKeep)
        onChompKeep();
    return str;
}
function stringifyFlowCollection({ items }, ctx, { flowChars, itemIndent }) {
    const { indent, indentStep, flowCollectionPadding: fcPadding, options: { commentString } } = ctx;
    itemIndent += indentStep;
    const itemCtx = Object.assign({}, ctx, {
        indent: itemIndent,
        inFlow: true,
        type: null
    });
    let reqNewline = false;
    let linesAtValue = 0;
    const lines = [];
    for (let i = 0; i < items.length; ++i) {
        const item = items[i];
        let comment = null;
        if (isNode(item)) {
            if (item.spaceBefore)
                lines.push('');
            addCommentBefore(ctx, lines, item.commentBefore, false);
            if (item.comment)
                comment = item.comment;
        }
        else if (isPair(item)) {
            const ik = isNode(item.key) ? item.key : null;
            if (ik) {
                if (ik.spaceBefore)
                    lines.push('');
                addCommentBefore(ctx, lines, ik.commentBefore, false);
                if (ik.comment)
                    reqNewline = true;
            }
            const iv = isNode(item.value) ? item.value : null;
            if (iv) {
                if (iv.comment)
                    comment = iv.comment;
                if (iv.commentBefore)
                    reqNewline = true;
            }
            else if (item.value == null && ik?.comment) {
                comment = ik.comment;
            }
        }
        if (comment)
            reqNewline = true;
        let str = stringify$1(item, itemCtx, () => (comment = null));
        if (i < items.length - 1)
            str += ',';
        if (comment)
            str += lineComment(str, itemIndent, commentString(comment));
        if (!reqNewline && (lines.length > linesAtValue || str.includes('\n')))
            reqNewline = true;
        lines.push(str);
        linesAtValue = lines.length;
    }
    const { start, end } = flowChars;
    if (lines.length === 0) {
        return start + end;
    }
    else {
        if (!reqNewline) {
            const len = lines.reduce((sum, line) => sum + line.length + 2, 2);
            reqNewline = ctx.options.lineWidth > 0 && len > ctx.options.lineWidth;
        }
        if (reqNewline) {
            let str = start;
            for (const line of lines)
                str += line ? `\n${indentStep}${indent}${line}` : '\n';
            return `${str}\n${indent}${end}`;
        }
        else {
            return `${start}${fcPadding}${lines.join(' ')}${fcPadding}${end}`;
        }
    }
}
function addCommentBefore({ indent, options: { commentString } }, lines, comment, chompKeep) {
    if (comment && chompKeep)
        comment = comment.replace(/^\n+/, '');
    if (comment) {
        const ic = indentComment(commentString(comment), indent);
        lines.push(ic.trimStart()); // Avoid double indent on first line
    }
}

function findPair(items, key) {
    const k = isScalar(key) ? key.value : key;
    for (const it of items) {
        if (isPair(it)) {
            if (it.key === key || it.key === k)
                return it;
            if (isScalar(it.key) && it.key.value === k)
                return it;
        }
    }
    return undefined;
}
class YAMLMap extends Collection {
    static get tagName() {
        return 'tag:yaml.org,2002:map';
    }
    constructor(schema) {
        super(MAP, schema);
        this.items = [];
    }
    /**
     * A generic collection parsing method that can be extended
     * to other node classes that inherit from YAMLMap
     */
    static from(schema, obj, ctx) {
        const { keepUndefined, replacer } = ctx;
        const map = new this(schema);
        const add = (key, value) => {
            if (typeof replacer === 'function')
                value = replacer.call(obj, key, value);
            else if (Array.isArray(replacer) && !replacer.includes(key))
                return;
            if (value !== undefined || keepUndefined)
                map.items.push(createPair(key, value, ctx));
        };
        if (obj instanceof Map) {
            for (const [key, value] of obj)
                add(key, value);
        }
        else if (obj && typeof obj === 'object') {
            for (const key of Object.keys(obj))
                add(key, obj[key]);
        }
        if (typeof schema.sortMapEntries === 'function') {
            map.items.sort(schema.sortMapEntries);
        }
        return map;
    }
    /**
     * Adds a value to the collection.
     *
     * @param overwrite - If not set `true`, using a key that is already in the
     *   collection will throw. Otherwise, overwrites the previous value.
     */
    add(pair, overwrite) {
        let _pair;
        if (isPair(pair))
            _pair = pair;
        else if (!pair || typeof pair !== 'object' || !('key' in pair)) {
            // In TypeScript, this never happens.
            _pair = new Pair(pair, pair?.value);
        }
        else
            _pair = new Pair(pair.key, pair.value);
        const prev = findPair(this.items, _pair.key);
        const sortEntries = this.schema?.sortMapEntries;
        if (prev) {
            if (!overwrite)
                throw new Error(`Key ${_pair.key} already set`);
            // For scalars, keep the old node & its comments and anchors
            if (isScalar(prev.value) && isScalarValue(_pair.value))
                prev.value.value = _pair.value;
            else
                prev.value = _pair.value;
        }
        else if (sortEntries) {
            const i = this.items.findIndex(item => sortEntries(_pair, item) < 0);
            if (i === -1)
                this.items.push(_pair);
            else
                this.items.splice(i, 0, _pair);
        }
        else {
            this.items.push(_pair);
        }
    }
    delete(key) {
        const it = findPair(this.items, key);
        if (!it)
            return false;
        const del = this.items.splice(this.items.indexOf(it), 1);
        return del.length > 0;
    }
    get(key, keepScalar) {
        const it = findPair(this.items, key);
        const node = it?.value;
        return (!keepScalar && isScalar(node) ? node.value : node) ?? undefined;
    }
    has(key) {
        return !!findPair(this.items, key);
    }
    set(key, value) {
        this.add(new Pair(key, value), true);
    }
    /**
     * @param ctx - Conversion context, originally set in Document#toJS()
     * @param {Class} Type - If set, forces the returned collection type
     * @returns Instance of Type, Map, or Object
     */
    toJSON(_, ctx, Type) {
        const map = Type ? new Type() : ctx?.mapAsMap ? new Map() : {};
        if (ctx?.onCreate)
            ctx.onCreate(map);
        for (const item of this.items)
            addPairToJSMap(ctx, map, item);
        return map;
    }
    toString(ctx, onComment, onChompKeep) {
        if (!ctx)
            return JSON.stringify(this);
        for (const item of this.items) {
            if (!isPair(item))
                throw new Error(`Map items must all be pairs; found ${JSON.stringify(item)} instead`);
        }
        if (!ctx.allNullValues && this.hasAllNullValues(false))
            ctx = Object.assign({}, ctx, { allNullValues: true });
        return stringifyCollection(this, ctx, {
            blockItemPrefix: '',
            flowChars: { start: '{', end: '}' },
            itemIndent: ctx.indent || '',
            onChompKeep,
            onComment
        });
    }
}

const map = {
    collection: 'map',
    default: true,
    nodeClass: YAMLMap,
    tag: 'tag:yaml.org,2002:map',
    resolve(map, onError) {
        if (!isMap(map))
            onError('Expected a mapping for this tag');
        return map;
    },
    createNode: (schema, obj, ctx) => YAMLMap.from(schema, obj, ctx)
};

class YAMLSeq extends Collection {
    static get tagName() {
        return 'tag:yaml.org,2002:seq';
    }
    constructor(schema) {
        super(SEQ, schema);
        this.items = [];
    }
    add(value) {
        this.items.push(value);
    }
    /**
     * Removes a value from the collection.
     *
     * `key` must contain a representation of an integer for this to succeed.
     * It may be wrapped in a `Scalar`.
     *
     * @returns `true` if the item was found and removed.
     */
    delete(key) {
        const idx = asItemIndex(key);
        if (typeof idx !== 'number')
            return false;
        const del = this.items.splice(idx, 1);
        return del.length > 0;
    }
    get(key, keepScalar) {
        const idx = asItemIndex(key);
        if (typeof idx !== 'number')
            return undefined;
        const it = this.items[idx];
        return !keepScalar && isScalar(it) ? it.value : it;
    }
    /**
     * Checks if the collection includes a value with the key `key`.
     *
     * `key` must contain a representation of an integer for this to succeed.
     * It may be wrapped in a `Scalar`.
     */
    has(key) {
        const idx = asItemIndex(key);
        return typeof idx === 'number' && idx < this.items.length;
    }
    /**
     * Sets a value in this collection. For `!!set`, `value` needs to be a
     * boolean to add/remove the item from the set.
     *
     * If `key` does not contain a representation of an integer, this will throw.
     * It may be wrapped in a `Scalar`.
     */
    set(key, value) {
        const idx = asItemIndex(key);
        if (typeof idx !== 'number')
            throw new Error(`Expected a valid index, not ${key}.`);
        const prev = this.items[idx];
        if (isScalar(prev) && isScalarValue(value))
            prev.value = value;
        else
            this.items[idx] = value;
    }
    toJSON(_, ctx) {
        const seq = [];
        if (ctx?.onCreate)
            ctx.onCreate(seq);
        let i = 0;
        for (const item of this.items)
            seq.push(toJS(item, String(i++), ctx));
        return seq;
    }
    toString(ctx, onComment, onChompKeep) {
        if (!ctx)
            return JSON.stringify(this);
        return stringifyCollection(this, ctx, {
            blockItemPrefix: '- ',
            flowChars: { start: '[', end: ']' },
            itemIndent: (ctx.indent || '') + '  ',
            onChompKeep,
            onComment
        });
    }
    static from(schema, obj, ctx) {
        const { replacer } = ctx;
        const seq = new this(schema);
        if (obj && Symbol.iterator in Object(obj)) {
            let i = 0;
            for (let it of obj) {
                if (typeof replacer === 'function') {
                    const key = obj instanceof Set ? it : String(i++);
                    it = replacer.call(obj, key, it);
                }
                seq.items.push(createNode(it, undefined, ctx));
            }
        }
        return seq;
    }
}
function asItemIndex(key) {
    let idx = isScalar(key) ? key.value : key;
    if (idx && typeof idx === 'string')
        idx = Number(idx);
    return typeof idx === 'number' && Number.isInteger(idx) && idx >= 0
        ? idx
        : null;
}

const seq = {
    collection: 'seq',
    default: true,
    nodeClass: YAMLSeq,
    tag: 'tag:yaml.org,2002:seq',
    resolve(seq, onError) {
        if (!isSeq(seq))
            onError('Expected a sequence for this tag');
        return seq;
    },
    createNode: (schema, obj, ctx) => YAMLSeq.from(schema, obj, ctx)
};

const string = {
    identify: value => typeof value === 'string',
    default: true,
    tag: 'tag:yaml.org,2002:str',
    resolve: str => str,
    stringify(item, ctx, onComment, onChompKeep) {
        ctx = Object.assign({ actualString: true }, ctx);
        return stringifyString(item, ctx, onComment, onChompKeep);
    }
};

const nullTag = {
    identify: value => value == null,
    createNode: () => new Scalar(null),
    default: true,
    tag: 'tag:yaml.org,2002:null',
    test: /^(?:~|[Nn]ull|NULL)?$/,
    resolve: () => new Scalar(null),
    stringify: ({ source }, ctx) => typeof source === 'string' && nullTag.test.test(source)
        ? source
        : ctx.options.nullStr
};

const boolTag = {
    identify: value => typeof value === 'boolean',
    default: true,
    tag: 'tag:yaml.org,2002:bool',
    test: /^(?:[Tt]rue|TRUE|[Ff]alse|FALSE)$/,
    resolve: str => new Scalar(str[0] === 't' || str[0] === 'T'),
    stringify({ source, value }, ctx) {
        if (source && boolTag.test.test(source)) {
            const sv = source[0] === 't' || source[0] === 'T';
            if (value === sv)
                return source;
        }
        return value ? ctx.options.trueStr : ctx.options.falseStr;
    }
};

function stringifyNumber({ format, minFractionDigits, tag, value }) {
    if (typeof value === 'bigint')
        return String(value);
    const num = typeof value === 'number' ? value : Number(value);
    if (!isFinite(num))
        return isNaN(num) ? '.nan' : num < 0 ? '-.inf' : '.inf';
    let n = JSON.stringify(value);
    if (!format &&
        minFractionDigits &&
        (!tag || tag === 'tag:yaml.org,2002:float') &&
        /^\d/.test(n)) {
        let i = n.indexOf('.');
        if (i < 0) {
            i = n.length;
            n += '.';
        }
        let d = minFractionDigits - (n.length - i - 1);
        while (d-- > 0)
            n += '0';
    }
    return n;
}

const floatNaN$1 = {
    identify: value => typeof value === 'number',
    default: true,
    tag: 'tag:yaml.org,2002:float',
    test: /^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/,
    resolve: str => str.slice(-3).toLowerCase() === 'nan'
        ? NaN
        : str[0] === '-'
            ? Number.NEGATIVE_INFINITY
            : Number.POSITIVE_INFINITY,
    stringify: stringifyNumber
};
const floatExp$1 = {
    identify: value => typeof value === 'number',
    default: true,
    tag: 'tag:yaml.org,2002:float',
    format: 'EXP',
    test: /^[-+]?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)[eE][-+]?[0-9]+$/,
    resolve: str => parseFloat(str),
    stringify(node) {
        const num = Number(node.value);
        return isFinite(num) ? num.toExponential() : stringifyNumber(node);
    }
};
const float$1 = {
    identify: value => typeof value === 'number',
    default: true,
    tag: 'tag:yaml.org,2002:float',
    test: /^[-+]?(?:\.[0-9]+|[0-9]+\.[0-9]*)$/,
    resolve(str) {
        const node = new Scalar(parseFloat(str));
        const dot = str.indexOf('.');
        if (dot !== -1 && str[str.length - 1] === '0')
            node.minFractionDigits = str.length - dot - 1;
        return node;
    },
    stringify: stringifyNumber
};

const intIdentify$2 = (value) => typeof value === 'bigint' || Number.isInteger(value);
const intResolve$1 = (str, offset, radix, { intAsBigInt }) => (intAsBigInt ? BigInt(str) : parseInt(str.substring(offset), radix));
function intStringify$1(node, radix, prefix) {
    const { value } = node;
    if (intIdentify$2(value) && value >= 0)
        return prefix + value.toString(radix);
    return stringifyNumber(node);
}
const intOct$1 = {
    identify: value => intIdentify$2(value) && value >= 0,
    default: true,
    tag: 'tag:yaml.org,2002:int',
    format: 'OCT',
    test: /^0o[0-7]+$/,
    resolve: (str, _onError, opt) => intResolve$1(str, 2, 8, opt),
    stringify: node => intStringify$1(node, 8, '0o')
};
const int$1 = {
    identify: intIdentify$2,
    default: true,
    tag: 'tag:yaml.org,2002:int',
    test: /^[-+]?[0-9]+$/,
    resolve: (str, _onError, opt) => intResolve$1(str, 0, 10, opt),
    stringify: stringifyNumber
};
const intHex$1 = {
    identify: value => intIdentify$2(value) && value >= 0,
    default: true,
    tag: 'tag:yaml.org,2002:int',
    format: 'HEX',
    test: /^0x[0-9a-fA-F]+$/,
    resolve: (str, _onError, opt) => intResolve$1(str, 2, 16, opt),
    stringify: node => intStringify$1(node, 16, '0x')
};

const schema$2 = [
    map,
    seq,
    string,
    nullTag,
    boolTag,
    intOct$1,
    int$1,
    intHex$1,
    floatNaN$1,
    floatExp$1,
    float$1
];

function intIdentify$1(value) {
    return typeof value === 'bigint' || Number.isInteger(value);
}
const stringifyJSON = ({ value }) => JSON.stringify(value);
const jsonScalars = [
    {
        identify: value => typeof value === 'string',
        default: true,
        tag: 'tag:yaml.org,2002:str',
        resolve: str => str,
        stringify: stringifyJSON
    },
    {
        identify: value => value == null,
        createNode: () => new Scalar(null),
        default: true,
        tag: 'tag:yaml.org,2002:null',
        test: /^null$/,
        resolve: () => null,
        stringify: stringifyJSON
    },
    {
        identify: value => typeof value === 'boolean',
        default: true,
        tag: 'tag:yaml.org,2002:bool',
        test: /^true|false$/,
        resolve: str => str === 'true',
        stringify: stringifyJSON
    },
    {
        identify: intIdentify$1,
        default: true,
        tag: 'tag:yaml.org,2002:int',
        test: /^-?(?:0|[1-9][0-9]*)$/,
        resolve: (str, _onError, { intAsBigInt }) => intAsBigInt ? BigInt(str) : parseInt(str, 10),
        stringify: ({ value }) => intIdentify$1(value) ? value.toString() : JSON.stringify(value)
    },
    {
        identify: value => typeof value === 'number',
        default: true,
        tag: 'tag:yaml.org,2002:float',
        test: /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]*)?(?:[eE][-+]?[0-9]+)?$/,
        resolve: str => parseFloat(str),
        stringify: stringifyJSON
    }
];
const jsonError = {
    default: true,
    tag: '',
    test: /^/,
    resolve(str, onError) {
        onError(`Unresolved plain scalar ${JSON.stringify(str)}`);
        return str;
    }
};
const schema$1 = [map, seq].concat(jsonScalars, jsonError);

const binary = {
    identify: value => value instanceof Uint8Array, // Buffer inherits from Uint8Array
    default: false,
    tag: 'tag:yaml.org,2002:binary',
    /**
     * Returns a Buffer in node and an Uint8Array in browsers
     *
     * To use the resulting buffer as an image, you'll want to do something like:
     *
     *   const blob = new Blob([buffer], { type: 'image/jpeg' })
     *   document.querySelector('#photo').src = URL.createObjectURL(blob)
     */
    resolve(src, onError) {
        if (typeof Buffer === 'function') {
            return Buffer.from(src, 'base64');
        }
        else if (typeof atob === 'function') {
            // On IE 11, atob() can't handle newlines
            const str = atob(src.replace(/[\n\r]/g, ''));
            const buffer = new Uint8Array(str.length);
            for (let i = 0; i < str.length; ++i)
                buffer[i] = str.charCodeAt(i);
            return buffer;
        }
        else {
            onError('This environment does not support reading binary tags; either Buffer or atob is required');
            return src;
        }
    },
    stringify({ comment, type, value }, ctx, onComment, onChompKeep) {
        const buf = value; // checked earlier by binary.identify()
        let str;
        if (typeof Buffer === 'function') {
            str =
                buf instanceof Buffer
                    ? buf.toString('base64')
                    : Buffer.from(buf.buffer).toString('base64');
        }
        else if (typeof btoa === 'function') {
            let s = '';
            for (let i = 0; i < buf.length; ++i)
                s += String.fromCharCode(buf[i]);
            str = btoa(s);
        }
        else {
            throw new Error('This environment does not support writing binary tags; either Buffer or btoa is required');
        }
        if (!type)
            type = Scalar.BLOCK_LITERAL;
        if (type !== Scalar.QUOTE_DOUBLE) {
            const lineWidth = Math.max(ctx.options.lineWidth - ctx.indent.length, ctx.options.minContentWidth);
            const n = Math.ceil(str.length / lineWidth);
            const lines = new Array(n);
            for (let i = 0, o = 0; i < n; ++i, o += lineWidth) {
                lines[i] = str.substr(o, lineWidth);
            }
            str = lines.join(type === Scalar.BLOCK_LITERAL ? '\n' : ' ');
        }
        return stringifyString({ comment, type, value: str }, ctx, onComment, onChompKeep);
    }
};

function resolvePairs(seq, onError) {
    if (isSeq(seq)) {
        for (let i = 0; i < seq.items.length; ++i) {
            let item = seq.items[i];
            if (isPair(item))
                continue;
            else if (isMap(item)) {
                if (item.items.length > 1)
                    onError('Each pair must have its own sequence indicator');
                const pair = item.items[0] || new Pair(new Scalar(null));
                if (item.commentBefore)
                    pair.key.commentBefore = pair.key.commentBefore
                        ? `${item.commentBefore}\n${pair.key.commentBefore}`
                        : item.commentBefore;
                if (item.comment) {
                    const cn = pair.value ?? pair.key;
                    cn.comment = cn.comment
                        ? `${item.comment}\n${cn.comment}`
                        : item.comment;
                }
                item = pair;
            }
            seq.items[i] = isPair(item) ? item : new Pair(item);
        }
    }
    else
        onError('Expected a sequence for this tag');
    return seq;
}
function createPairs(schema, iterable, ctx) {
    const { replacer } = ctx;
    const pairs = new YAMLSeq(schema);
    pairs.tag = 'tag:yaml.org,2002:pairs';
    let i = 0;
    if (iterable && Symbol.iterator in Object(iterable))
        for (let it of iterable) {
            if (typeof replacer === 'function')
                it = replacer.call(iterable, String(i++), it);
            let key, value;
            if (Array.isArray(it)) {
                if (it.length === 2) {
                    key = it[0];
                    value = it[1];
                }
                else
                    throw new TypeError(`Expected [key, value] tuple: ${it}`);
            }
            else if (it && it instanceof Object) {
                const keys = Object.keys(it);
                if (keys.length === 1) {
                    key = keys[0];
                    value = it[key];
                }
                else {
                    throw new TypeError(`Expected tuple with one key, not ${keys.length} keys`);
                }
            }
            else {
                key = it;
            }
            pairs.items.push(createPair(key, value, ctx));
        }
    return pairs;
}
const pairs = {
    collection: 'seq',
    default: false,
    tag: 'tag:yaml.org,2002:pairs',
    resolve: resolvePairs,
    createNode: createPairs
};

class YAMLOMap extends YAMLSeq {
    constructor() {
        super();
        this.add = YAMLMap.prototype.add.bind(this);
        this.delete = YAMLMap.prototype.delete.bind(this);
        this.get = YAMLMap.prototype.get.bind(this);
        this.has = YAMLMap.prototype.has.bind(this);
        this.set = YAMLMap.prototype.set.bind(this);
        this.tag = YAMLOMap.tag;
    }
    /**
     * If `ctx` is given, the return type is actually `Map<unknown, unknown>`,
     * but TypeScript won't allow widening the signature of a child method.
     */
    toJSON(_, ctx) {
        if (!ctx)
            return super.toJSON(_);
        const map = new Map();
        if (ctx?.onCreate)
            ctx.onCreate(map);
        for (const pair of this.items) {
            let key, value;
            if (isPair(pair)) {
                key = toJS(pair.key, '', ctx);
                value = toJS(pair.value, key, ctx);
            }
            else {
                key = toJS(pair, '', ctx);
            }
            if (map.has(key))
                throw new Error('Ordered maps must not include duplicate keys');
            map.set(key, value);
        }
        return map;
    }
    static from(schema, iterable, ctx) {
        const pairs = createPairs(schema, iterable, ctx);
        const omap = new this();
        omap.items = pairs.items;
        return omap;
    }
}
YAMLOMap.tag = 'tag:yaml.org,2002:omap';
const omap = {
    collection: 'seq',
    identify: value => value instanceof Map,
    nodeClass: YAMLOMap,
    default: false,
    tag: 'tag:yaml.org,2002:omap',
    resolve(seq, onError) {
        const pairs = resolvePairs(seq, onError);
        const seenKeys = [];
        for (const { key } of pairs.items) {
            if (isScalar(key)) {
                if (seenKeys.includes(key.value)) {
                    onError(`Ordered maps must not include duplicate keys: ${key.value}`);
                }
                else {
                    seenKeys.push(key.value);
                }
            }
        }
        return Object.assign(new YAMLOMap(), pairs);
    },
    createNode: (schema, iterable, ctx) => YAMLOMap.from(schema, iterable, ctx)
};

function boolStringify({ value, source }, ctx) {
    const boolObj = value ? trueTag : falseTag;
    if (source && boolObj.test.test(source))
        return source;
    return value ? ctx.options.trueStr : ctx.options.falseStr;
}
const trueTag = {
    identify: value => value === true,
    default: true,
    tag: 'tag:yaml.org,2002:bool',
    test: /^(?:Y|y|[Yy]es|YES|[Tt]rue|TRUE|[Oo]n|ON)$/,
    resolve: () => new Scalar(true),
    stringify: boolStringify
};
const falseTag = {
    identify: value => value === false,
    default: true,
    tag: 'tag:yaml.org,2002:bool',
    test: /^(?:N|n|[Nn]o|NO|[Ff]alse|FALSE|[Oo]ff|OFF)$/,
    resolve: () => new Scalar(false),
    stringify: boolStringify
};

const floatNaN = {
    identify: value => typeof value === 'number',
    default: true,
    tag: 'tag:yaml.org,2002:float',
    test: /^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/,
    resolve: (str) => str.slice(-3).toLowerCase() === 'nan'
        ? NaN
        : str[0] === '-'
            ? Number.NEGATIVE_INFINITY
            : Number.POSITIVE_INFINITY,
    stringify: stringifyNumber
};
const floatExp = {
    identify: value => typeof value === 'number',
    default: true,
    tag: 'tag:yaml.org,2002:float',
    format: 'EXP',
    test: /^[-+]?(?:[0-9][0-9_]*)?(?:\.[0-9_]*)?[eE][-+]?[0-9]+$/,
    resolve: (str) => parseFloat(str.replace(/_/g, '')),
    stringify(node) {
        const num = Number(node.value);
        return isFinite(num) ? num.toExponential() : stringifyNumber(node);
    }
};
const float = {
    identify: value => typeof value === 'number',
    default: true,
    tag: 'tag:yaml.org,2002:float',
    test: /^[-+]?(?:[0-9][0-9_]*)?\.[0-9_]*$/,
    resolve(str) {
        const node = new Scalar(parseFloat(str.replace(/_/g, '')));
        const dot = str.indexOf('.');
        if (dot !== -1) {
            const f = str.substring(dot + 1).replace(/_/g, '');
            if (f[f.length - 1] === '0')
                node.minFractionDigits = f.length;
        }
        return node;
    },
    stringify: stringifyNumber
};

const intIdentify = (value) => typeof value === 'bigint' || Number.isInteger(value);
function intResolve(str, offset, radix, { intAsBigInt }) {
    const sign = str[0];
    if (sign === '-' || sign === '+')
        offset += 1;
    str = str.substring(offset).replace(/_/g, '');
    if (intAsBigInt) {
        switch (radix) {
            case 2:
                str = `0b${str}`;
                break;
            case 8:
                str = `0o${str}`;
                break;
            case 16:
                str = `0x${str}`;
                break;
        }
        const n = BigInt(str);
        return sign === '-' ? BigInt(-1) * n : n;
    }
    const n = parseInt(str, radix);
    return sign === '-' ? -1 * n : n;
}
function intStringify(node, radix, prefix) {
    const { value } = node;
    if (intIdentify(value)) {
        const str = value.toString(radix);
        return value < 0 ? '-' + prefix + str.substr(1) : prefix + str;
    }
    return stringifyNumber(node);
}
const intBin = {
    identify: intIdentify,
    default: true,
    tag: 'tag:yaml.org,2002:int',
    format: 'BIN',
    test: /^[-+]?0b[0-1_]+$/,
    resolve: (str, _onError, opt) => intResolve(str, 2, 2, opt),
    stringify: node => intStringify(node, 2, '0b')
};
const intOct = {
    identify: intIdentify,
    default: true,
    tag: 'tag:yaml.org,2002:int',
    format: 'OCT',
    test: /^[-+]?0[0-7_]+$/,
    resolve: (str, _onError, opt) => intResolve(str, 1, 8, opt),
    stringify: node => intStringify(node, 8, '0')
};
const int = {
    identify: intIdentify,
    default: true,
    tag: 'tag:yaml.org,2002:int',
    test: /^[-+]?[0-9][0-9_]*$/,
    resolve: (str, _onError, opt) => intResolve(str, 0, 10, opt),
    stringify: stringifyNumber
};
const intHex = {
    identify: intIdentify,
    default: true,
    tag: 'tag:yaml.org,2002:int',
    format: 'HEX',
    test: /^[-+]?0x[0-9a-fA-F_]+$/,
    resolve: (str, _onError, opt) => intResolve(str, 2, 16, opt),
    stringify: node => intStringify(node, 16, '0x')
};

class YAMLSet extends YAMLMap {
    constructor(schema) {
        super(schema);
        this.tag = YAMLSet.tag;
    }
    add(key) {
        let pair;
        if (isPair(key))
            pair = key;
        else if (key &&
            typeof key === 'object' &&
            'key' in key &&
            'value' in key &&
            key.value === null)
            pair = new Pair(key.key, null);
        else
            pair = new Pair(key, null);
        const prev = findPair(this.items, pair.key);
        if (!prev)
            this.items.push(pair);
    }
    /**
     * If `keepPair` is `true`, returns the Pair matching `key`.
     * Otherwise, returns the value of that Pair's key.
     */
    get(key, keepPair) {
        const pair = findPair(this.items, key);
        return !keepPair && isPair(pair)
            ? isScalar(pair.key)
                ? pair.key.value
                : pair.key
            : pair;
    }
    set(key, value) {
        if (typeof value !== 'boolean')
            throw new Error(`Expected boolean value for set(key, value) in a YAML set, not ${typeof value}`);
        const prev = findPair(this.items, key);
        if (prev && !value) {
            this.items.splice(this.items.indexOf(prev), 1);
        }
        else if (!prev && value) {
            this.items.push(new Pair(key));
        }
    }
    toJSON(_, ctx) {
        return super.toJSON(_, ctx, Set);
    }
    toString(ctx, onComment, onChompKeep) {
        if (!ctx)
            return JSON.stringify(this);
        if (this.hasAllNullValues(true))
            return super.toString(Object.assign({}, ctx, { allNullValues: true }), onComment, onChompKeep);
        else
            throw new Error('Set items must all have null values');
    }
    static from(schema, iterable, ctx) {
        const { replacer } = ctx;
        const set = new this(schema);
        if (iterable && Symbol.iterator in Object(iterable))
            for (let value of iterable) {
                if (typeof replacer === 'function')
                    value = replacer.call(iterable, value, value);
                set.items.push(createPair(value, null, ctx));
            }
        return set;
    }
}
YAMLSet.tag = 'tag:yaml.org,2002:set';
const set = {
    collection: 'map',
    identify: value => value instanceof Set,
    nodeClass: YAMLSet,
    default: false,
    tag: 'tag:yaml.org,2002:set',
    createNode: (schema, iterable, ctx) => YAMLSet.from(schema, iterable, ctx),
    resolve(map, onError) {
        if (isMap(map)) {
            if (map.hasAllNullValues(true))
                return Object.assign(new YAMLSet(), map);
            else
                onError('Set items must all have null values');
        }
        else
            onError('Expected a mapping for this tag');
        return map;
    }
};

/** Internal types handle bigint as number, because TS can't figure it out. */
function parseSexagesimal(str, asBigInt) {
    const sign = str[0];
    const parts = sign === '-' || sign === '+' ? str.substring(1) : str;
    const num = (n) => asBigInt ? BigInt(n) : Number(n);
    const res = parts
        .replace(/_/g, '')
        .split(':')
        .reduce((res, p) => res * num(60) + num(p), num(0));
    return (sign === '-' ? num(-1) * res : res);
}
/**
 * hhhh:mm:ss.sss
 *
 * Internal types handle bigint as number, because TS can't figure it out.
 */
function stringifySexagesimal(node) {
    let { value } = node;
    let num = (n) => n;
    if (typeof value === 'bigint')
        num = n => BigInt(n);
    else if (isNaN(value) || !isFinite(value))
        return stringifyNumber(node);
    let sign = '';
    if (value < 0) {
        sign = '-';
        value *= num(-1);
    }
    const _60 = num(60);
    const parts = [value % _60]; // seconds, including ms
    if (value < 60) {
        parts.unshift(0); // at least one : is required
    }
    else {
        value = (value - parts[0]) / _60;
        parts.unshift(value % _60); // minutes
        if (value >= 60) {
            value = (value - parts[0]) / _60;
            parts.unshift(value); // hours
        }
    }
    return (sign +
        parts
            .map(n => String(n).padStart(2, '0'))
            .join(':')
            .replace(/000000\d*$/, '') // % 60 may introduce error
    );
}
const intTime = {
    identify: value => typeof value === 'bigint' || Number.isInteger(value),
    default: true,
    tag: 'tag:yaml.org,2002:int',
    format: 'TIME',
    test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+$/,
    resolve: (str, _onError, { intAsBigInt }) => parseSexagesimal(str, intAsBigInt),
    stringify: stringifySexagesimal
};
const floatTime = {
    identify: value => typeof value === 'number',
    default: true,
    tag: 'tag:yaml.org,2002:float',
    format: 'TIME',
    test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*$/,
    resolve: str => parseSexagesimal(str, false),
    stringify: stringifySexagesimal
};
const timestamp = {
    identify: value => value instanceof Date,
    default: true,
    tag: 'tag:yaml.org,2002:timestamp',
    // If the time zone is omitted, the timestamp is assumed to be specified in UTC. The time part
    // may be omitted altogether, resulting in a date format. In such a case, the time part is
    // assumed to be 00:00:00Z (start of day, UTC).
    test: RegExp('^([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})' + // YYYY-Mm-Dd
        '(?:' + // time is optional
        '(?:t|T|[ \\t]+)' + // t | T | whitespace
        '([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}(\\.[0-9]+)?)' + // Hh:Mm:Ss(.ss)?
        '(?:[ \\t]*(Z|[-+][012]?[0-9](?::[0-9]{2})?))?' + // Z | +5 | -03:30
        ')?$'),
    resolve(str) {
        const match = str.match(timestamp.test);
        if (!match)
            throw new Error('!!timestamp expects a date, starting with yyyy-mm-dd');
        const [, year, month, day, hour, minute, second] = match.map(Number);
        const millisec = match[7] ? Number((match[7] + '00').substr(1, 3)) : 0;
        let date = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec);
        const tz = match[8];
        if (tz && tz !== 'Z') {
            let d = parseSexagesimal(tz, false);
            if (Math.abs(d) < 30)
                d *= 60;
            date -= 60000 * d;
        }
        return new Date(date);
    },
    stringify: ({ value }) => value.toISOString().replace(/((T00:00)?:00)?\.000Z$/, '')
};

const schema = [
    map,
    seq,
    string,
    nullTag,
    trueTag,
    falseTag,
    intBin,
    intOct,
    int,
    intHex,
    floatNaN,
    floatExp,
    float,
    binary,
    omap,
    pairs,
    set,
    intTime,
    floatTime,
    timestamp
];

const schemas = new Map([
    ['core', schema$2],
    ['failsafe', [map, seq, string]],
    ['json', schema$1],
    ['yaml11', schema],
    ['yaml-1.1', schema]
]);
const tagsByName = {
    binary,
    bool: boolTag,
    float: float$1,
    floatExp: floatExp$1,
    floatNaN: floatNaN$1,
    floatTime,
    int: int$1,
    intHex: intHex$1,
    intOct: intOct$1,
    intTime,
    map,
    null: nullTag,
    omap,
    pairs,
    seq,
    set,
    timestamp
};
const coreKnownTags = {
    'tag:yaml.org,2002:binary': binary,
    'tag:yaml.org,2002:omap': omap,
    'tag:yaml.org,2002:pairs': pairs,
    'tag:yaml.org,2002:set': set,
    'tag:yaml.org,2002:timestamp': timestamp
};
function getTags(customTags, schemaName) {
    let tags = schemas.get(schemaName);
    if (!tags) {
        if (Array.isArray(customTags))
            tags = [];
        else {
            const keys = Array.from(schemas.keys())
                .filter(key => key !== 'yaml11')
                .map(key => JSON.stringify(key))
                .join(', ');
            throw new Error(`Unknown schema "${schemaName}"; use one of ${keys} or define customTags array`);
        }
    }
    if (Array.isArray(customTags)) {
        for (const tag of customTags)
            tags = tags.concat(tag);
    }
    else if (typeof customTags === 'function') {
        tags = customTags(tags.slice());
    }
    return tags.map(tag => {
        if (typeof tag !== 'string')
            return tag;
        const tagObj = tagsByName[tag];
        if (tagObj)
            return tagObj;
        const keys = Object.keys(tagsByName)
            .map(key => JSON.stringify(key))
            .join(', ');
        throw new Error(`Unknown custom tag "${tag}"; use one of ${keys}`);
    });
}

const sortMapEntriesByKey = (a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0;
class Schema {
    constructor({ compat, customTags, merge, resolveKnownTags, schema, sortMapEntries, toStringDefaults }) {
        this.compat = Array.isArray(compat)
            ? getTags(compat, 'compat')
            : compat
                ? getTags(null, compat)
                : null;
        this.merge = !!merge;
        this.name = (typeof schema === 'string' && schema) || 'core';
        this.knownTags = resolveKnownTags ? coreKnownTags : {};
        this.tags = getTags(customTags, this.name);
        this.toStringOptions = toStringDefaults ?? null;
        Object.defineProperty(this, MAP, { value: map });
        Object.defineProperty(this, SCALAR, { value: string });
        Object.defineProperty(this, SEQ, { value: seq });
        // Used by createMap()
        this.sortMapEntries =
            typeof sortMapEntries === 'function'
                ? sortMapEntries
                : sortMapEntries === true
                    ? sortMapEntriesByKey
                    : null;
    }
    clone() {
        const copy = Object.create(Schema.prototype, Object.getOwnPropertyDescriptors(this));
        copy.tags = this.tags.slice();
        return copy;
    }
}

function stringifyDocument(doc, options) {
    const lines = [];
    let hasDirectives = options.directives === true;
    if (options.directives !== false && doc.directives) {
        const dir = doc.directives.toString(doc);
        if (dir) {
            lines.push(dir);
            hasDirectives = true;
        }
        else if (doc.directives.docStart)
            hasDirectives = true;
    }
    if (hasDirectives)
        lines.push('---');
    const ctx = createStringifyContext(doc, options);
    const { commentString } = ctx.options;
    if (doc.commentBefore) {
        if (lines.length !== 1)
            lines.unshift('');
        const cs = commentString(doc.commentBefore);
        lines.unshift(indentComment(cs, ''));
    }
    let chompKeep = false;
    let contentComment = null;
    if (doc.contents) {
        if (isNode(doc.contents)) {
            if (doc.contents.spaceBefore && hasDirectives)
                lines.push('');
            if (doc.contents.commentBefore) {
                const cs = commentString(doc.contents.commentBefore);
                lines.push(indentComment(cs, ''));
            }
            // top-level block scalars need to be indented if followed by a comment
            ctx.forceBlockIndent = !!doc.comment;
            contentComment = doc.contents.comment;
        }
        const onChompKeep = contentComment ? undefined : () => (chompKeep = true);
        let body = stringify$1(doc.contents, ctx, () => (contentComment = null), onChompKeep);
        if (contentComment)
            body += lineComment(body, '', commentString(contentComment));
        if ((body[0] === '|' || body[0] === '>') &&
            lines[lines.length - 1] === '---') {
            // Top-level block scalars with a preceding doc marker ought to use the
            // same line for their header.
            lines[lines.length - 1] = `--- ${body}`;
        }
        else
            lines.push(body);
    }
    else {
        lines.push(stringify$1(doc.contents, ctx));
    }
    if (doc.directives?.docEnd) {
        if (doc.comment) {
            const cs = commentString(doc.comment);
            if (cs.includes('\n')) {
                lines.push('...');
                lines.push(indentComment(cs, ''));
            }
            else {
                lines.push(`... ${cs}`);
            }
        }
        else {
            lines.push('...');
        }
    }
    else {
        let dc = doc.comment;
        if (dc && chompKeep)
            dc = dc.replace(/^\n+/, '');
        if (dc) {
            if ((!chompKeep || contentComment) && lines[lines.length - 1] !== '')
                lines.push('');
            lines.push(indentComment(commentString(dc), ''));
        }
    }
    return lines.join('\n') + '\n';
}

class Document {
    constructor(value, replacer, options) {
        /** A comment before this Document */
        this.commentBefore = null;
        /** A comment immediately after this Document */
        this.comment = null;
        /** Errors encountered during parsing. */
        this.errors = [];
        /** Warnings encountered during parsing. */
        this.warnings = [];
        Object.defineProperty(this, NODE_TYPE, { value: DOC });
        let _replacer = null;
        if (typeof replacer === 'function' || Array.isArray(replacer)) {
            _replacer = replacer;
        }
        else if (options === undefined && replacer) {
            options = replacer;
            replacer = undefined;
        }
        const opt = Object.assign({
            intAsBigInt: false,
            keepSourceTokens: false,
            logLevel: 'warn',
            prettyErrors: true,
            strict: true,
            uniqueKeys: true,
            version: '1.2'
        }, options);
        this.options = opt;
        let { version } = opt;
        if (options?._directives) {
            this.directives = options._directives.atDocument();
            if (this.directives.yaml.explicit)
                version = this.directives.yaml.version;
        }
        else
            this.directives = new Directives({ version });
        this.setSchema(version, options);
        // @ts-expect-error We can't really know that this matches Contents.
        this.contents =
            value === undefined ? null : this.createNode(value, _replacer, options);
    }
    /**
     * Create a deep copy of this Document and its contents.
     *
     * Custom Node values that inherit from `Object` still refer to their original instances.
     */
    clone() {
        const copy = Object.create(Document.prototype, {
            [NODE_TYPE]: { value: DOC }
        });
        copy.commentBefore = this.commentBefore;
        copy.comment = this.comment;
        copy.errors = this.errors.slice();
        copy.warnings = this.warnings.slice();
        copy.options = Object.assign({}, this.options);
        if (this.directives)
            copy.directives = this.directives.clone();
        copy.schema = this.schema.clone();
        // @ts-expect-error We can't really know that this matches Contents.
        copy.contents = isNode(this.contents)
            ? this.contents.clone(copy.schema)
            : this.contents;
        if (this.range)
            copy.range = this.range.slice();
        return copy;
    }
    /** Adds a value to the document. */
    add(value) {
        if (assertCollection(this.contents))
            this.contents.add(value);
    }
    /** Adds a value to the document. */
    addIn(path, value) {
        if (assertCollection(this.contents))
            this.contents.addIn(path, value);
    }
    /**
     * Create a new `Alias` node, ensuring that the target `node` has the required anchor.
     *
     * If `node` already has an anchor, `name` is ignored.
     * Otherwise, the `node.anchor` value will be set to `name`,
     * or if an anchor with that name is already present in the document,
     * `name` will be used as a prefix for a new unique anchor.
     * If `name` is undefined, the generated anchor will use 'a' as a prefix.
     */
    createAlias(node, name) {
        if (!node.anchor) {
            const prev = anchorNames(this);
            node.anchor =
                // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
                !name || prev.has(name) ? findNewAnchor(name || 'a', prev) : name;
        }
        return new Alias(node.anchor);
    }
    createNode(value, replacer, options) {
        let _replacer = undefined;
        if (typeof replacer === 'function') {
            value = replacer.call({ '': value }, '', value);
            _replacer = replacer;
        }
        else if (Array.isArray(replacer)) {
            const keyToStr = (v) => typeof v === 'number' || v instanceof String || v instanceof Number;
            const asStr = replacer.filter(keyToStr).map(String);
            if (asStr.length > 0)
                replacer = replacer.concat(asStr);
            _replacer = replacer;
        }
        else if (options === undefined && replacer) {
            options = replacer;
            replacer = undefined;
        }
        const { aliasDuplicateObjects, anchorPrefix, flow, keepUndefined, onTagObj, tag } = options ?? {};
        const { onAnchor, setAnchors, sourceObjects } = createNodeAnchors(this, 
        // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
        anchorPrefix || 'a');
        const ctx = {
            aliasDuplicateObjects: aliasDuplicateObjects ?? true,
            keepUndefined: keepUndefined ?? false,
            onAnchor,
            onTagObj,
            replacer: _replacer,
            schema: this.schema,
            sourceObjects
        };
        const node = createNode(value, tag, ctx);
        if (flow && isCollection(node))
            node.flow = true;
        setAnchors();
        return node;
    }
    /**
     * Convert a key and a value into a `Pair` using the current schema,
     * recursively wrapping all values as `Scalar` or `Collection` nodes.
     */
    createPair(key, value, options = {}) {
        const k = this.createNode(key, null, options);
        const v = this.createNode(value, null, options);
        return new Pair(k, v);
    }
    /**
     * Removes a value from the document.
     * @returns `true` if the item was found and removed.
     */
    delete(key) {
        return assertCollection(this.contents) ? this.contents.delete(key) : false;
    }
    /**
     * Removes a value from the document.
     * @returns `true` if the item was found and removed.
     */
    deleteIn(path) {
        if (isEmptyPath(path)) {
            if (this.contents == null)
                return false;
            // @ts-expect-error Presumed impossible if Strict extends false
            this.contents = null;
            return true;
        }
        return assertCollection(this.contents)
            ? this.contents.deleteIn(path)
            : false;
    }
    /**
     * Returns item at `key`, or `undefined` if not found. By default unwraps
     * scalar values from their surrounding node; to disable set `keepScalar` to
     * `true` (collections are always returned intact).
     */
    get(key, keepScalar) {
        return isCollection(this.contents)
            ? this.contents.get(key, keepScalar)
            : undefined;
    }
    /**
     * Returns item at `path`, or `undefined` if not found. By default unwraps
     * scalar values from their surrounding node; to disable set `keepScalar` to
     * `true` (collections are always returned intact).
     */
    getIn(path, keepScalar) {
        if (isEmptyPath(path))
            return !keepScalar && isScalar(this.contents)
                ? this.contents.value
                : this.contents;
        return isCollection(this.contents)
            ? this.contents.getIn(path, keepScalar)
            : undefined;
    }
    /**
     * Checks if the document includes a value with the key `key`.
     */
    has(key) {
        return isCollection(this.contents) ? this.contents.has(key) : false;
    }
    /**
     * Checks if the document includes a value at `path`.
     */
    hasIn(path) {
        if (isEmptyPath(path))
            return this.contents !== undefined;
        return isCollection(this.contents) ? this.contents.hasIn(path) : false;
    }
    /**
     * Sets a value in this document. For `!!set`, `value` needs to be a
     * boolean to add/remove the item from the set.
     */
    set(key, value) {
        if (this.contents == null) {
            // @ts-expect-error We can't really know that this matches Contents.
            this.contents = collectionFromPath(this.schema, [key], value);
        }
        else if (assertCollection(this.contents)) {
            this.contents.set(key, value);
        }
    }
    /**
     * Sets a value in this document. For `!!set`, `value` needs to be a
     * boolean to add/remove the item from the set.
     */
    setIn(path, value) {
        if (isEmptyPath(path)) {
            // @ts-expect-error We can't really know that this matches Contents.
            this.contents = value;
        }
        else if (this.contents == null) {
            // @ts-expect-error We can't really know that this matches Contents.
            this.contents = collectionFromPath(this.schema, Array.from(path), value);
        }
        else if (assertCollection(this.contents)) {
            this.contents.setIn(path, value);
        }
    }
    /**
     * Change the YAML version and schema used by the document.
     * A `null` version disables support for directives, explicit tags, anchors, and aliases.
     * It also requires the `schema` option to be given as a `Schema` instance value.
     *
     * Overrides all previously set schema options.
     */
    setSchema(version, options = {}) {
        if (typeof version === 'number')
            version = String(version);
        let opt;
        switch (version) {
            case '1.1':
                if (this.directives)
                    this.directives.yaml.version = '1.1';
                else
                    this.directives = new Directives({ version: '1.1' });
                opt = { merge: true, resolveKnownTags: false, schema: 'yaml-1.1' };
                break;
            case '1.2':
            case 'next':
                if (this.directives)
                    this.directives.yaml.version = version;
                else
                    this.directives = new Directives({ version });
                opt = { merge: false, resolveKnownTags: true, schema: 'core' };
                break;
            case null:
                if (this.directives)
                    delete this.directives;
                opt = null;
                break;
            default: {
                const sv = JSON.stringify(version);
                throw new Error(`Expected '1.1', '1.2' or null as first argument, but found: ${sv}`);
            }
        }
        // Not using `instanceof Schema` to allow for duck typing
        if (options.schema instanceof Object)
            this.schema = options.schema;
        else if (opt)
            this.schema = new Schema(Object.assign(opt, options));
        else
            throw new Error(`With a null YAML version, the { schema: Schema } option is required`);
    }
    // json & jsonArg are only used from toJSON()
    toJS({ json, jsonArg, mapAsMap, maxAliasCount, onAnchor, reviver } = {}) {
        const ctx = {
            anchors: new Map(),
            doc: this,
            keep: !json,
            mapAsMap: mapAsMap === true,
            mapKeyWarned: false,
            maxAliasCount: typeof maxAliasCount === 'number' ? maxAliasCount : 100
        };
        const res = toJS(this.contents, jsonArg ?? '', ctx);
        if (typeof onAnchor === 'function')
            for (const { count, res } of ctx.anchors.values())
                onAnchor(res, count);
        return typeof reviver === 'function'
            ? applyReviver(reviver, { '': res }, '', res)
            : res;
    }
    /**
     * A JSON representation of the document `contents`.
     *
     * @param jsonArg Used by `JSON.stringify` to indicate the array index or
     *   property name.
     */
    toJSON(jsonArg, onAnchor) {
        return this.toJS({ json: true, jsonArg, mapAsMap: false, onAnchor });
    }
    /** A YAML representation of the document. */
    toString(options = {}) {
        if (this.errors.length > 0)
            throw new Error('Document with errors cannot be stringified');
        if ('indent' in options &&
            (!Number.isInteger(options.indent) || Number(options.indent) <= 0)) {
            const s = JSON.stringify(options.indent);
            throw new Error(`"indent" option must be a positive integer, not ${s}`);
        }
        return stringifyDocument(this, options);
    }
}
function assertCollection(contents) {
    if (isCollection(contents))
        return true;
    throw new Error('Expected a YAML collection as document contents');
}

new Set('0123456789ABCDEFabcdef');
new Set("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-#;/?:@&=+$_.!~*'()");
new Set(',[]{}');
new Set(' ,[]{}\n\r\t');

function stringify(value, replacer, options) {
    let _replacer = null;
    if (typeof replacer === 'function' || Array.isArray(replacer)) {
        _replacer = replacer;
    }
    else if (options === undefined && replacer) {
        options = replacer;
    }
    if (typeof options === 'string')
        options = options.length;
    if (typeof options === 'number') {
        const indent = Math.round(options);
        options = indent < 1 ? undefined : indent > 8 ? { indent: 8 } : { indent };
    }
    if (value === undefined) {
        const { keepUndefined } = options ?? replacer ?? {};
        if (!keepUndefined)
            return undefined;
    }
    return new Document(value, _replacer, options).toString(options);
}

/**
 * OpenAPI3 schema emitter. Deals with emitting content of `components/schemas` section.
 */
class OpenAPI3SchemaEmitter extends TypeEmitter {
    #metadataInfo;
    #visibilityUsage;
    #options;
    constructor(emitter, metadataInfo, visibilityUsage, options) {
        super(emitter);
        this.#metadataInfo = metadataInfo;
        this.#visibilityUsage = visibilityUsage;
        this.#options = options;
    }
    modelDeclarationReferenceContext(model, name) {
        return this.reduceContext(model);
    }
    modelLiteralReferenceContext(model) {
        return this.reduceContext(model);
    }
    scalarDeclarationReferenceContext(scalar, name) {
        return this.reduceContext(scalar);
    }
    enumDeclarationReferenceContext(en, name) {
        return this.reduceContext(en);
    }
    unionDeclarationReferenceContext(union) {
        return this.reduceContext(union);
    }
    reduceContext(type) {
        const visibility = this.#getVisibilityContext();
        const patch = {};
        if (visibility !== Visibility.Read && !this.#metadataInfo.isTransformed(type, visibility)) {
            patch.visibility = Visibility.Read;
        }
        const contentType = this.#getContentType();
        if (contentType === "application/json") {
            patch.contentType = undefined;
        }
        return patch;
    }
    modelDeclaration(model, _) {
        const program = this.emitter.getProgram();
        const visibility = this.#getVisibilityContext();
        const schema = new ObjectBuilder({
            type: "object",
            required: this.#requiredModelProperties(model, visibility),
            properties: this.emitter.emitModelProperties(model),
        });
        if (model.indexer) {
            schema.set("additionalProperties", this.emitter.emitTypeReference(model.indexer.value));
        }
        const derivedModels = model.derivedModels.filter(includeDerivedModel);
        // getSchemaOrRef on all children to push them into components.schemas
        for (const child of derivedModels) {
            this.emitter.emitTypeReference(child);
        }
        const discriminator = getDiscriminator(program, model);
        if (discriminator) {
            const [union] = getDiscriminatedUnion(model, discriminator);
            const openApiDiscriminator = { ...discriminator };
            if (union.variants.size > 0) {
                openApiDiscriminator.mapping = this.#getDiscriminatorMapping(union);
            }
            schema.discriminator = openApiDiscriminator;
        }
        this.#applyExternalDocs(model, schema);
        if (model.baseModel) {
            schema.set("allOf", B.array([this.emitter.emitTypeReference(model.baseModel)]));
        }
        const baseName = getOpenAPITypeName(program, model, this.#typeNameOptions());
        const isMultipart = this.#getContentType().startsWith("multipart/");
        const name = isMultipart ? baseName + "MultiPart" : baseName;
        return this.#createDeclaration(model, name, this.#applyConstraints(model, schema));
    }
    #applyExternalDocs(typespecType, target) {
        const externalDocs = getExternalDocs(this.emitter.getProgram(), typespecType);
        if (externalDocs) {
            target.externalDocs = externalDocs;
        }
    }
    #typeNameOptions() {
        const serviceNamespaceName = this.emitter.getContext().serviceNamespaceName;
        return {
            // shorten type names by removing TypeSpec and service namespace
            namespaceFilter(ns) {
                const name = getNamespaceFullName(ns);
                return name !== serviceNamespaceName;
            },
        };
    }
    #getVisibilityContext() {
        return this.emitter.getContext().visibility ?? Visibility.Read;
    }
    #ignoreMetadataAnnotations() {
        return this.emitter.getContext().ignoreMetadataAnnotations;
    }
    #getContentType() {
        return this.emitter.getContext().contentType ?? "application/json";
    }
    modelLiteral(model) {
        const schema = new ObjectBuilder({
            type: "object",
            properties: this.emitter.emitModelProperties(model),
            required: this.#requiredModelProperties(model, this.#getVisibilityContext()),
        });
        if (model.indexer) {
            schema.set("additionalProperties", this.emitter.emitTypeReference(model.indexer.value));
        }
        return schema;
    }
    modelInstantiation(model, name) {
        name = name ?? getOpenAPITypeName(this.emitter.getProgram(), model, this.#typeNameOptions());
        if (!name) {
            return this.modelLiteral(model);
        }
        return this.modelDeclaration(model, name);
    }
    arrayDeclaration(array, name, elementType) {
        const schema = new ObjectBuilder({
            type: "array",
            items: this.emitter.emitTypeReference(elementType),
        });
        return this.#createDeclaration(array, name, this.#applyConstraints(array, schema));
    }
    arrayDeclarationReferenceContext(array, name, elementType) {
        return {
            visibility: this.#getVisibilityContext() | Visibility.Item,
        };
    }
    arrayLiteral(array, elementType) {
        return this.#inlineType(array, new ObjectBuilder({
            type: "array",
            items: this.emitter.emitTypeReference(elementType),
        }));
    }
    arrayLiteralReferenceContext(array, elementType) {
        return {
            visibility: this.#getVisibilityContext() | Visibility.Item,
        };
    }
    #requiredModelProperties(model, visibility) {
        const requiredProps = [];
        for (const prop of model.properties.values()) {
            if (isNeverType(prop.type)) {
                // If the property has a type of 'never', don't include it in the schema
                continue;
            }
            if (!this.#metadataInfo.isPayloadProperty(prop, visibility, this.#ignoreMetadataAnnotations())) {
                continue;
            }
            if (!this.#metadataInfo.isOptional(prop, visibility)) {
                const encodedName = resolveEncodedName(this.emitter.getProgram(), prop, this.#getContentType());
                requiredProps.push(encodedName);
            }
        }
        return requiredProps.length > 0 ? requiredProps : undefined;
    }
    modelProperties(model) {
        const program = this.emitter.getProgram();
        const props = new ObjectBuilder();
        const visibility = this.emitter.getContext().visibility;
        const contentType = this.#getContentType();
        for (const prop of model.properties.values()) {
            if (isNeverType(prop.type)) {
                // If the property has a type of 'never', don't include it in the schema
                continue;
            }
            if (!this.#metadataInfo.isPayloadProperty(prop, visibility, this.#ignoreMetadataAnnotations())) {
                continue;
            }
            const result = this.emitter.emitModelProperty(prop);
            const encodedName = resolveEncodedName(program, prop, contentType);
            props.set(encodedName, result);
        }
        const discriminator = getDiscriminator(program, model);
        if (discriminator && !(discriminator.propertyName in props)) {
            props.set(discriminator.propertyName, {
                type: "string",
                description: `Discriminator property for ${model.name}.`,
            });
        }
        if (Object.keys(props).length === 0) {
            return this.emitter.result.none();
        }
        return props;
    }
    modelPropertyLiteral(prop) {
        const program = this.emitter.getProgram();
        const isMultipart = this.#getContentType().startsWith("multipart/");
        if (isMultipart) {
            if (isBytesKeptRaw(program, prop.type) && getEncode(program, prop) === undefined) {
                return { type: "string", format: "binary" };
            }
            if (prop.type.kind === "Model" &&
                isArrayModelType(program, prop.type) &&
                isBytesKeptRaw(program, prop.type.indexer.value)) {
                return { type: "array", items: { type: "string", format: "binary" } };
            }
        }
        const refSchema = this.emitter.emitTypeReference(prop.type, {
            referenceContext: isMultipart &&
                (prop.type.kind !== "Union" ||
                    ![...prop.type.variants.values()].some((x) => isBytesKeptRaw(program, x.type)))
                ? { contentType: "application/json" }
                : {},
        });
        if (refSchema.kind !== "code") {
            reportDiagnostic(program, {
                code: "invalid-model-property",
                format: {
                    type: prop.type.kind,
                },
                target: prop,
            });
            return {};
        }
        const isRef = refSchema.value instanceof Placeholder || "$ref" in refSchema.value;
        const schema = this.#applyEncoding(prop, refSchema.value);
        // Apply decorators on the property to the type's schema
        const additionalProps = this.#applyConstraints(prop, {});
        if (prop.defaultValue) {
            additionalProps.default = getDefaultValue(program, prop.defaultValue);
        }
        if (isReadonlyProperty(program, prop)) {
            additionalProps.readOnly = true;
        }
        // Attach any additional OpenAPI extensions
        this.#attachExtensions(program, prop, additionalProps);
        if (schema && isRef && !(prop.type.kind === "Model" && isArrayModelType(program, prop.type))) {
            if (Object.keys(additionalProps).length === 0) {
                return schema;
            }
            else {
                return {
                    allOf: [schema],
                    ...additionalProps,
                };
            }
        }
        else {
            if (getOneOf(program, prop) && schema.anyOf) {
                schema.oneOf = schema.anyOf;
                delete schema.anyOf;
            }
            const merged = new ObjectBuilder(schema);
            for (const [key, value] of Object.entries(additionalProps)) {
                merged.set(key, value);
            }
            return merged;
        }
    }
    booleanLiteral(boolean) {
        return { type: "boolean", enum: [boolean.value] };
    }
    stringLiteral(string) {
        return { type: "string", enum: [string.value] };
    }
    stringTemplate(string) {
        if (string.stringValue !== undefined) {
            return { type: "string", enum: [string.stringValue] };
        }
        const diagnostics = explainStringTemplateNotSerializable(string);
        this.emitter
            .getProgram()
            .reportDiagnostics(diagnostics.map((x) => ({ ...x, severity: "warning" })));
        return { type: "string" };
    }
    numericLiteral(number) {
        return { type: "number", enum: [number.value] };
    }
    enumDeclaration(en, name) {
        const baseName = getOpenAPITypeName(this.emitter.getProgram(), en, this.#typeNameOptions());
        return this.#createDeclaration(en, baseName, new ObjectBuilder(this.#enumSchema(en)));
    }
    #enumSchema(en) {
        const program = this.emitter.getProgram();
        if (en.members.size === 0) {
            reportDiagnostic(program, { code: "empty-enum", target: en });
            return {};
        }
        const enumTypes = new Set();
        const enumValues = new Set();
        for (const member of en.members.values()) {
            enumTypes.add(typeof member.value === "number" ? "number" : "string");
            enumValues.add(member.value ?? member.name);
        }
        if (enumTypes.size > 1) {
            reportDiagnostic(program, { code: "enum-unique-type", target: en });
        }
        const schema = { type: enumTypes.values().next().value, enum: [...enumValues] };
        return this.#applyConstraints(en, schema);
    }
    enumMember(member) {
        return this.enumMemberReference(member);
    }
    enumMemberReference(member) {
        // would like to dispatch to the same `literal` codepaths but enum members aren't literal types
        switch (typeof member.value) {
            case "undefined":
                return { type: "string", enum: [member.name] };
            case "string":
                return { type: "string", enum: [member.value] };
            case "number":
                return { type: "number", enum: [member.value] };
        }
    }
    unionDeclaration(union, name) {
        const schema = this.#unionSchema(union);
        return this.#createDeclaration(union, name, schema);
    }
    #unionSchema(union) {
        const program = this.emitter.getProgram();
        if (union.variants.size === 0) {
            reportDiagnostic(program, { code: "empty-union", target: union });
            return new ObjectBuilder({});
        }
        const variants = Array.from(union.variants.values());
        const literalVariantEnumByType = {};
        const ofType = getOneOf(program, union) ? "oneOf" : "anyOf";
        const schemaMembers = [];
        let nullable = false;
        const discriminator = getDiscriminator(program, union);
        const isMultipart = this.#getContentType().startsWith("multipart/");
        for (const variant of variants) {
            if (isNullType(variant.type)) {
                nullable = true;
                continue;
            }
            if (isMultipart && isBytesKeptRaw(program, variant.type)) {
                schemaMembers.push({ schema: { type: "string", format: "binary" }, type: variant.type });
                continue;
            }
            if (isLiteralType(variant.type)) {
                if (!literalVariantEnumByType[variant.type.kind]) {
                    const enumValue = [variant.type.value];
                    literalVariantEnumByType[variant.type.kind] = enumValue;
                    schemaMembers.push({
                        schema: { type: literalType(variant.type), enum: enumValue },
                        type: null,
                    });
                }
                else {
                    literalVariantEnumByType[variant.type.kind].push(variant.type.value);
                }
            }
            else {
                const enumSchema = this.emitter.emitTypeReference(variant.type, {
                    referenceContext: isMultipart ? { contentType: "application/json" } : {},
                });
                compilerAssert(enumSchema.kind === "code", "Unexpected enum schema. Should be kind: code");
                schemaMembers.push({ schema: enumSchema.value, type: variant.type });
            }
        }
        if (schemaMembers.length === 0) {
            if (nullable) {
                // This union is equivalent to just `null` but OA3 has no way to specify
                // null as a value, so we throw an error.
                reportDiagnostic(program, { code: "union-null", target: union });
                return new ObjectBuilder({});
            }
            else {
                // completely empty union can maybe only happen with bugs?
                compilerAssert(false, "Attempting to emit an empty union");
            }
        }
        if (schemaMembers.length === 1) {
            // we can just return the single schema member after applying nullable
            const schema = schemaMembers[0].schema;
            const type = schemaMembers[0].type;
            const additionalProps = this.#applyConstraints(union, {});
            if (nullable) {
                additionalProps.nullable = true;
            }
            if (Object.keys(additionalProps).length === 0) {
                return new ObjectBuilder(schema);
            }
            else {
                if ((schema instanceof Placeholder || "$ref" in schema) &&
                    !(type && shouldInline(program, type))) {
                    if (type && type.kind === "Model") {
                        return new ObjectBuilder({
                            type: "object",
                            allOf: B.array([schema]),
                            ...additionalProps,
                        });
                    }
                    else {
                        return new ObjectBuilder({ oneOf: B.array([schema]), ...additionalProps });
                    }
                }
                else {
                    const merged = new ObjectBuilder(schema);
                    for (const [key, value] of Object.entries(additionalProps)) {
                        merged.set(key, value);
                    }
                    return merged;
                }
            }
        }
        const schema = {
            [ofType]: schemaMembers.map((m) => m.schema),
        };
        if (nullable) {
            schema.nullable = true;
        }
        if (discriminator) {
            // the decorator validates that all the variants will be a model type
            // with the discriminator field present.
            schema.discriminator = { ...discriminator };
            // Diagnostic already reported in compiler for unions
            const discriminatedUnion = ignoreDiagnostics(getDiscriminatedUnion(union, discriminator));
            if (discriminatedUnion.variants.size > 0) {
                schema.discriminator.mapping = this.#getDiscriminatorMapping(discriminatedUnion);
            }
        }
        return this.#applyConstraints(union, schema);
    }
    #getDiscriminatorMapping(union) {
        const mapping = {};
        for (const [key, model] of union.variants.entries()) {
            const ref = this.emitter.emitTypeReference(model);
            compilerAssert(ref.kind === "code", "Unexpected ref schema. Should be kind: code");
            mapping[key] = ref.value.$ref;
        }
        return mapping;
    }
    unionLiteral(union) {
        return this.#unionSchema(union);
    }
    unionVariants(union) {
        const variants = new ArrayBuilder();
        for (const variant of union.variants.values()) {
            variants.push(this.emitter.emitType(variant));
        }
        return variants;
    }
    unionVariant(variant) {
        return this.emitter.emitTypeReference(variant.type);
    }
    modelPropertyReference(prop) {
        return this.modelPropertyLiteral(prop);
    }
    #attachExtensions(program, type, emitObject) {
        // Attach any OpenAPI extensions
        const extensions = getExtensions$1(program, type);
        if (extensions) {
            for (const key of extensions.keys()) {
                emitObject[key] = extensions.get(key);
            }
        }
    }
    reference(targetDeclaration, pathUp, pathDown, commonScope) {
        if (targetDeclaration.value instanceof Placeholder) {
            // I don't think this is possible, confirm.
            throw new Error("Can't form reference to declaration that hasn't been created yet");
        }
        // these will be undefined when creating a self-reference
        const currentSfScope = pathUp[pathUp.length - 1];
        const targetSfScope = pathDown[0];
        if (targetSfScope && currentSfScope && !targetSfScope.sourceFile.meta.shouldEmit) {
            currentSfScope.sourceFile.meta.bundledRefs.push(targetDeclaration);
        }
        return { $ref: `#/components/schemas/${targetDeclaration.name}` };
    }
    circularReference(target, scope, cycle) {
        if (!cycle.containsDeclaration) {
            reportDiagnostic(this.emitter.getProgram(), {
                code: "inline-cycle",
                format: {
                    type: cycle.toString(),
                },
                target: cycle.first.type,
            });
            return {};
        }
        return super.circularReference(target, scope, cycle);
    }
    scalarDeclaration(scalar, name) {
        const isStd = this.#isStdType(scalar);
        const schema = this.#getSchemaForScalar(scalar);
        const baseName = getOpenAPITypeName(this.emitter.getProgram(), scalar, this.#typeNameOptions());
        // Don't create a declaration for std types
        return isStd ? schema : this.#createDeclaration(scalar, baseName, new ObjectBuilder(schema));
    }
    scalarInstantiation(scalar, name) {
        return this.#getSchemaForScalar(scalar);
    }
    tupleLiteral(tuple) {
        return { type: "array", items: {} };
    }
    #getSchemaForScalar(scalar) {
        let result = {};
        const isStd = this.#isStdType(scalar);
        if (isStd) {
            result = this.#getSchemaForStdScalars(scalar);
        }
        else if (scalar.baseScalar) {
            result = this.#getSchemaForScalar(scalar.baseScalar);
        }
        const withDecorators = this.#applyEncoding(scalar, this.#applyConstraints(scalar, result));
        if (isStd) {
            // Standard types are going to be inlined in the spec and we don't want the description of the scalar to show up
            delete withDecorators.description;
        }
        return withDecorators;
    }
    #getSchemaForStdScalars(scalar) {
        switch (scalar.name) {
            case "bytes":
                return { type: "string", format: "byte" };
            case "numeric":
                return { type: "number" };
            case "integer":
                return { type: "integer" };
            case "int8":
                return { type: "integer", format: "int8" };
            case "int16":
                return { type: "integer", format: "int16" };
            case "int32":
                return { type: "integer", format: "int32" };
            case "int64":
                return { type: "integer", format: "int64" };
            case "safeint":
                switch (this.#options.safeintStrategy) {
                    case "double-int":
                        return { type: "integer", format: "double-int" };
                    case "int64":
                    default:
                        return { type: "integer", format: "int64" };
                }
            case "uint8":
                return { type: "integer", format: "uint8" };
            case "uint16":
                return { type: "integer", format: "uint16" };
            case "uint32":
                return { type: "integer", format: "uint32" };
            case "uint64":
                return { type: "integer", format: "uint64" };
            case "float":
                return { type: "number" };
            case "float64":
                return { type: "number", format: "double" };
            case "float32":
                return { type: "number", format: "float" };
            case "decimal":
                return { type: "number", format: "decimal" };
            case "decimal128":
                return { type: "number", format: "decimal128" };
            case "string":
                return { type: "string" };
            case "boolean":
                return { type: "boolean" };
            case "plainDate":
                return { type: "string", format: "date" };
            case "utcDateTime":
            case "offsetDateTime":
                return { type: "string", format: "date-time" };
            case "plainTime":
                return { type: "string", format: "time" };
            case "duration":
                return { type: "string", format: "duration" };
            case "url":
                return { type: "string", format: "uri" };
            default:
                scalar.name;
                return {};
        }
    }
    #applySchemaExamples(type, target) {
        const program = this.emitter.getProgram();
        const examples = getExamples(program, type);
        if (examples.length > 0) {
            target.set("example", serializeValueAsJson(program, examples[0].value, type));
        }
    }
    #applyConstraints(type, original) {
        const schema = new ObjectBuilder(original);
        const program = this.emitter.getProgram();
        const applyConstraint = (fn, key) => {
            const value = fn(program, type);
            if (value !== undefined) {
                schema[key] = value;
            }
        };
        this.#applySchemaExamples(type, schema);
        applyConstraint(getMinLength, "minLength");
        applyConstraint(getMaxLength, "maxLength");
        applyConstraint(getMinValue, "minimum");
        applyConstraint(getMaxValue, "maximum");
        applyConstraint(getPattern, "pattern");
        applyConstraint(getMinItems, "minItems");
        applyConstraint(getMaxItems, "maxItems");
        const minValueExclusive = getMinValueExclusive(program, type);
        if (minValueExclusive !== undefined) {
            schema.minimum = minValueExclusive;
            schema.exclusiveMinimum = true;
        }
        const maxValueExclusive = getMaxValueExclusive(program, type);
        if (maxValueExclusive !== undefined) {
            schema.maximum = maxValueExclusive;
            schema.exclusiveMaximum = true;
        }
        if (isSecret(program, type)) {
            schema.format = "password";
        }
        // the stdlib applies a format of "url" but json schema wants "uri",
        // so ignore this format if it's the built-in type.
        if (!this.#isStdType(type) || type.name !== "url") {
            applyConstraint(getFormat, "format");
        }
        applyConstraint(getDoc, "description");
        applyConstraint(getSummary, "title");
        applyConstraint((p, t) => (getDeprecated(p, t) !== undefined ? true : undefined), "deprecated");
        this.#attachExtensions(program, type, schema);
        const values = getKnownValues(program, type);
        if (values) {
            return new ObjectBuilder({
                oneOf: [schema, this.#enumSchema(values)],
            });
        }
        return new ObjectBuilder(schema);
    }
    #inlineType(type, schema) {
        if (this.#options.includeXTypeSpecName !== "never") {
            schema.set("x-typespec-name", getTypeName(type, this.#typeNameOptions()));
        }
        return schema;
    }
    #createDeclaration(type, name, schema) {
        const refUrl = getRef(this.emitter.getProgram(), type);
        if (refUrl) {
            return {
                $ref: refUrl,
            };
        }
        if (shouldInline(this.emitter.getProgram(), type)) {
            return this.#inlineType(type, schema);
        }
        const title = getSummary(this.emitter.getProgram(), type);
        if (title) {
            schema.set("title", title);
        }
        const usage = this.#visibilityUsage.getUsage(type);
        const shouldAddSuffix = usage !== undefined && usage.size > 1;
        const visibility = this.#getVisibilityContext();
        const fullName = name + (shouldAddSuffix ? getVisibilitySuffix(visibility, Visibility.Read) : "");
        const decl = this.emitter.result.declaration(fullName, schema);
        checkDuplicateTypeName(this.emitter.getProgram(), type, fullName, Object.fromEntries(decl.scope.declarations.map((x) => [x.name, true])));
        return decl;
    }
    #isStdType(type) {
        return this.emitter.getProgram().checker.isStdType(type);
    }
    #applyEncoding(typespecType, target) {
        const encodeData = getEncode(this.emitter.getProgram(), typespecType);
        if (encodeData) {
            const newTarget = new ObjectBuilder(target);
            const newType = this.#getSchemaForStdScalars(encodeData.type);
            newTarget.type = newType.type;
            // If the target already has a format it takes priority. (e.g. int32)
            newTarget.format = this.#mergeFormatAndEncoding(newTarget.format, encodeData.encoding, newType.format);
            return newTarget;
        }
        const result = new ObjectBuilder(target);
        return result;
    }
    #mergeFormatAndEncoding(format, encoding, encodeAsFormat) {
        switch (format) {
            case undefined:
                return encodeAsFormat ?? encoding;
            case "date-time":
                switch (encoding) {
                    case "rfc3339":
                        return "date-time";
                    case "unixTimestamp":
                        return "unixtime";
                    case "rfc7231":
                        return "http-date";
                    default:
                        return encoding;
                }
            case "duration":
                switch (encoding) {
                    case "ISO8601":
                        return "duration";
                    default:
                        return encodeAsFormat ?? encoding;
                }
            default:
                return encodeAsFormat ?? encoding;
        }
    }
    intrinsic(intrinsic, name) {
        switch (name) {
            case "unknown":
                return {};
            case "null":
                return { nullable: true };
        }
        reportDiagnostic(this.emitter.getProgram(), {
            code: "invalid-schema",
            format: { type: name },
            target: intrinsic,
        });
        return {};
    }
    programContext(program) {
        const sourceFile = this.emitter.createSourceFile("openapi");
        return { scope: sourceFile.globalScope };
    }
}
function isLiteralType(type) {
    return type.kind === "Boolean" || type.kind === "String" || type.kind === "Number";
}
function literalType(type) {
    switch (type.kind) {
        case "String":
            return "string";
        case "Number":
            return "number";
        case "Boolean":
            return "boolean";
    }
}
function includeDerivedModel(model) {
    return (!isTemplateDeclaration(model) &&
        (model.templateMapper?.args === undefined ||
            model.templateMapper.args?.length === 0 ||
            model.derivedModels.length > 0));
}
const B = {
    array: (items) => {
        const builder = new ArrayBuilder();
        for (const item of items) {
            builder.push(item);
        }
        return builder;
    },
    object: (obj) => {
        const builder = new ObjectBuilder();
        for (const [key, value] of Object.entries(obj)) {
            builder.set(key, value);
        }
        return builder;
    },
};
function getDefaultValue(program, defaultType) {
    switch (defaultType.valueKind) {
        case "StringValue":
            return defaultType.value;
        case "NumericValue":
            return defaultType.value.asNumber() ?? undefined;
        case "BooleanValue":
            return defaultType.value;
        case "ArrayValue":
            return defaultType.values.map((x) => getDefaultValue(program, x));
        case "NullValue":
            return null;
        case "EnumValue":
            return defaultType.value.value ?? defaultType.value.name;
        default:
            reportDiagnostic(program, {
                code: "invalid-default",
                format: { type: defaultType.valueKind },
                target: defaultType,
            });
    }
}
function isBytesKeptRaw(program, type) {
    return type.kind === "Scalar" && type.name === "bytes" && getEncode(program, type) === undefined;
}

/**
 * Checks if two objects are deeply equal.
 *
 * Does not support cycles. Intended to be used only on plain data that can
 * be directly represented in JSON.
 */
function deepEquals(left, right) {
    if (left === right) {
        return true;
    }
    if (left === null || right === null || typeof left !== "object" || typeof right !== "object") {
        return false;
    }
    if (Array.isArray(left)) {
        return Array.isArray(right) ? arrayEquals(left, right, deepEquals) : false;
    }
    return mapEquals(new Map(Object.entries(left)), new Map(Object.entries(right)), deepEquals);
}
/**
 * Check if two arrays have the same elements.
 *
 * @param equals Optional callback for element equality comparison.
 *               Default is to compare by identity using `===`.
 */
function arrayEquals(left, right, equals = (x, y) => x === y) {
    if (left === right) {
        return true;
    }
    if (left.length !== right.length) {
        return false;
    }
    for (let i = 0; i < left.length; i++) {
        if (!equals(left[i], right[i])) {
            return false;
        }
    }
    return true;
}
/**
 * Check if two maps have the same entries.
 *
 * @param equals Optional callback for value equality comparison.
 *               Default is to compare by identity using `===`.
 */
function mapEquals(left, right, equals = (x, y) => x === y) {
    if (left === right) {
        return true;
    }
    if (left.size !== right.size) {
        return false;
    }
    for (const [key, value] of left) {
        if (!right.has(key) || !equals(value, right.get(key))) {
            return false;
        }
    }
    return true;
}
/**
 * Check if argument is not undefined.
 */
function isDefined(arg) {
    return arg !== undefined;
}

function resolveVisibilityUsage(program, metadataInfo, root, omitUnreachableTypes) {
    const usages = new Map();
    addUsagesInContainer(program, metadataInfo, root, usages);
    const reachableTypes = new Set(usages.keys());
    if (!omitUnreachableTypes) {
        // Evaluate all unreferenced types and the types they reference with Visibility.All
        const trackType = (type) => {
            if (!usages.has(type)) {
                navigateReferencedTypes(type, Visibility.All, (type, vis) => trackUsageExact(usages, type, vis));
            }
        };
        navigateTypesInNamespace(root, {
            model: trackType,
            scalar: trackType,
            enum: trackType,
            union: trackType,
        });
    }
    return {
        getUsage: (type) => {
            const used = usages.get(type);
            if (used === undefined) {
                return undefined;
            }
            return usages.get(type);
        },
        isUnreachable: (type) => {
            return !reachableTypes.has(type);
        },
    };
}
function addUsagesInContainer(program, metadataInfo, type, usages) {
    switch (type.kind) {
        case "Namespace":
            addUsagesInNamespace(program, metadataInfo, type, usages);
            break;
        case "Interface":
            addUsagesInInterface(program, metadataInfo, type, usages);
            break;
        case "Operation":
            addUsagesInOperation(program, metadataInfo, type, usages);
            break;
    }
}
function trackUsage(metadataInfo, usages, type, usage) {
    const effective = metadataInfo.getEffectivePayloadType(type, usage);
    trackUsageExact(usages, type, usage);
    if (effective !== type) {
        trackUsageExact(usages, effective, usage);
    }
}
function trackUsageExact(usages, type, usage) {
    const existingFlag = usages.get(type) ?? new Set();
    existingFlag.add(usage);
    usages.set(type, existingFlag);
}
function addUsagesInNamespace(program, metadataInfo, namespace, usages) {
    for (const subNamespace of namespace.namespaces.values()) {
        addUsagesInNamespace(program, metadataInfo, subNamespace, usages);
    }
    for (const Interface of namespace.interfaces.values()) {
        addUsagesInInterface(program, metadataInfo, Interface, usages);
    }
    for (const operation of namespace.operations.values()) {
        addUsagesInOperation(program, metadataInfo, operation, usages);
    }
}
function addUsagesInInterface(program, metadataInfo, Interface, usages) {
    for (const operation of Interface.operations.values()) {
        addUsagesInOperation(program, metadataInfo, operation, usages);
    }
}
function addUsagesInOperation(program, metadataInfo, operation, usages) {
    const httpOperation = ignoreDiagnostics(getHttpOperation(program, operation));
    const visibility = resolveRequestVisibility(program, operation, httpOperation.verb);
    if (httpOperation.parameters.body) {
        navigateReferencedTypes(httpOperation.parameters.body.type, visibility, (type, vis) => trackUsage(metadataInfo, usages, type, vis));
    }
    for (const param of httpOperation.parameters.parameters) {
        navigateReferencedTypes(param.param, visibility, (type, vis) => trackUsage(metadataInfo, usages, type, vis));
    }
    navigateReferencedTypes(operation.returnType, Visibility.Read, (type, vis) => trackUsage(metadataInfo, usages, type, vis));
}
function navigateReferencedTypes(type, usage, callback, visited = new TwoLevelMap()) {
    if (visited.get(type)?.get(usage)) {
        return;
    }
    visited.getOrAdd(type, usage, () => true);
    switch (type.kind) {
        case "Model":
            callback(type, usage);
            navigateIterable(type.properties, usage, callback, visited);
            if (type.baseModel) {
                navigateReferencedTypes(type.baseModel, usage, callback, visited);
            }
            navigateIterable(type.derivedModels, usage, callback, visited);
            if (type.baseModel) {
                navigateReferencedTypes(type.baseModel, usage, callback, visited);
            }
            if (type.indexer) {
                if (type.indexer.key.name === "integer") {
                    navigateReferencedTypes(type.indexer.value, usage | Visibility.Item, callback, visited);
                }
                else {
                    navigateReferencedTypes(type.indexer.value, usage, callback, visited);
                }
            }
            break;
        case "ModelProperty":
            navigateReferencedTypes(type.type, usage, callback, visited);
            break;
        case "Union":
            callback(type, usage);
            navigateIterable(type.variants, usage, callback, visited);
            break;
        case "UnionVariant":
            navigateReferencedTypes(type.type, usage, callback, visited);
            break;
        default:
            callback(type, usage);
            break;
    }
}
function navigateIterable(map, usage, callback, visited) {
    for (const type of map.values()) {
        navigateReferencedTypes(type, usage, callback, visited);
    }
}

const defaultFileType = "yaml";
const defaultOptions = {
    "new-line": "lf",
    "omit-unreachable-types": false,
    "include-x-typespec-name": "never",
    "safeint-strategy": "int64",
};
async function $onEmit(context) {
    const options = resolveOptions(context);
    const emitter = createOAPIEmitter(context, options);
    await emitter.emitOpenAPI();
}
/**
 * Get the OpenAPI 3 document records from the given program. The documents are
 * returned as a JS object.
 *
 * @param program The program to emit to OpenAPI 3
 * @param options OpenAPI 3 emit options
 * @returns An array of OpenAPI 3 document records.
 */
async function getOpenAPI3(program, options = {}) {
    const context = {
        program,
        // this value doesn't matter for getting the OpenAPI3 objects
        emitterOutputDir: "tsp-output",
        options: options,
        getAssetEmitter(TypeEmitterClass) {
            return createAssetEmitter(program, TypeEmitterClass, this);
        },
    };
    const resolvedOptions = resolveOptions(context);
    const emitter = createOAPIEmitter(context, resolvedOptions);
    return emitter.getOpenAPI();
}
function findFileTypeFromFilename(filename) {
    if (filename === undefined) {
        return defaultFileType;
    }
    switch (getAnyExtensionFromPath(filename)) {
        case ".yaml":
        case ".yml":
            return "yaml";
        case ".json":
            return "json";
        default:
            return defaultFileType;
    }
}
function resolveOptions(context) {
    const resolvedOptions = { ...defaultOptions, ...context.options };
    const fileType = resolvedOptions["file-type"] ?? findFileTypeFromFilename(resolvedOptions["output-file"]);
    const outputFile = resolvedOptions["output-file"] ?? `openapi.{service-name}.{version}.${fileType}`;
    return {
        fileType,
        newLine: resolvedOptions["new-line"],
        omitUnreachableTypes: resolvedOptions["omit-unreachable-types"],
        includeXTypeSpecName: resolvedOptions["include-x-typespec-name"],
        safeintStrategy: resolvedOptions["safeint-strategy"],
        outputFile: resolvePath(context.emitterOutputDir, outputFile),
    };
}
function createOAPIEmitter(context, options) {
    let program = context.program;
    let schemaEmitter;
    let root;
    let diagnostics;
    let currentService;
    // Get the service namespace string for use in name shortening
    let serviceNamespaceName;
    let currentPath;
    let currentEndpoint;
    let metadataInfo;
    let visibilityUsage;
    // Map model properties that represent shared parameters to their parameter
    // definition that will go in #/components/parameters. Inlined parameters do not go in
    // this map.
    let params;
    // Keep track of models that have had properties spread into parameters. We won't
    // consider these unreferenced when emitting unreferenced types.
    let paramModels;
    // De-dupe the per-endpoint tags that will be added into the #/tags
    let tags;
    const typeNameOptions = {
        // shorten type names by removing TypeSpec and service namespace
        namespaceFilter(ns) {
            const name = getNamespaceFullName(ns);
            return name !== serviceNamespaceName;
        },
    };
    return { emitOpenAPI, getOpenAPI };
    async function emitOpenAPI() {
        const services = await getOpenAPI();
        // first, emit diagnostics
        for (const serviceRecord of services) {
            if (serviceRecord.versioned) {
                for (const documentRecord of serviceRecord.versions) {
                    program.reportDiagnostics(documentRecord.diagnostics);
                }
            }
            else {
                program.reportDiagnostics(serviceRecord.diagnostics);
            }
        }
        if (program.compilerOptions.noEmit || program.hasError()) {
            return;
        }
        const multipleService = services.length > 1;
        for (const serviceRecord of services) {
            if (serviceRecord.versioned) {
                for (const documentRecord of serviceRecord.versions) {
                    await emitFile(program, {
                        path: resolveOutputFile(serviceRecord.service, multipleService, documentRecord.version),
                        content: serializeDocument(documentRecord.document, options.fileType),
                        newLine: options.newLine,
                    });
                }
            }
            else {
                await emitFile(program, {
                    path: resolveOutputFile(serviceRecord.service, multipleService),
                    content: serializeDocument(serviceRecord.document, options.fileType),
                    newLine: options.newLine,
                });
            }
        }
    }
    function initializeEmitter(service, allHttpAuthentications, defaultAuth, version) {
        currentService = service;
        metadataInfo = createMetadataInfo(program, {
            canonicalVisibility: Visibility.Read,
            canShareProperty: (p) => isReadonlyProperty(program, p),
        });
        visibilityUsage = resolveVisibilityUsage(program, metadataInfo, service.type, options.omitUnreachableTypes);
        schemaEmitter = createAssetEmitter(program, class extends OpenAPI3SchemaEmitter {
            constructor(emitter) {
                super(emitter, metadataInfo, visibilityUsage, options);
            }
        }, context);
        const securitySchemes = getOpenAPISecuritySchemes(allHttpAuthentications);
        const security = getOpenAPISecurity(defaultAuth);
        const info = resolveInfo(program, service.type);
        root = {
            openapi: "3.0.0",
            info: {
                title: "(title)",
                ...info,
                version: version ?? info?.version ?? "0.0.0",
            },
            externalDocs: getExternalDocs(program, service.type),
            tags: [],
            paths: {},
            security: security.length > 0 ? security : undefined,
            components: {
                parameters: {},
                requestBodies: {},
                responses: {},
                schemas: {},
                examples: {},
                securitySchemes: securitySchemes,
            },
        };
        diagnostics = createDiagnosticCollector();
        const servers = getServers(program, service.type);
        if (servers) {
            root.servers = resolveServers(servers);
        }
        serviceNamespaceName = getNamespaceFullName(service.type);
        currentPath = root.paths;
        params = new Map();
        paramModels = new Set();
        tags = new Set();
    }
    function isValidServerVariableType(program, type) {
        switch (type.kind) {
            case "String":
            case "Union":
            case "Scalar":
                return ignoreDiagnostics(program.checker.isTypeAssignableTo(type.projectionBase ?? type, program.checker.getStdType("string"), type));
            case "Enum":
                for (const member of type.members.values()) {
                    if (member.value && typeof member.value !== "string") {
                        return false;
                    }
                }
                return true;
            default:
                return false;
        }
    }
    function validateValidServerVariable(program, prop) {
        const isValid = isValidServerVariableType(program, prop.type);
        if (!isValid) {
            diagnostics.add(createDiagnostic({
                code: "invalid-server-variable",
                format: { propName: prop.name },
                target: prop,
            }));
        }
        return isValid;
    }
    function resolveServers(servers) {
        return servers.map((server) => {
            const variables = {};
            for (const [name, prop] of server.parameters) {
                if (!validateValidServerVariable(program, prop)) {
                    continue;
                }
                const variable = {
                    default: prop.defaultValue ? getDefaultValue(program, prop.defaultValue) : "",
                    description: getDoc(program, prop),
                };
                if (prop.type.kind === "Enum") {
                    variable.enum = getSchemaValue(prop.type, Visibility.Read, "application/json")
                        .enum;
                }
                else if (prop.type.kind === "Union") {
                    variable.enum = getSchemaValue(prop.type, Visibility.Read, "application/json")
                        .enum;
                }
                else if (prop.type.kind === "String") {
                    variable.enum = [prop.type.value];
                }
                attachExtensions(program, prop, variable);
                variables[name] = variable;
            }
            return {
                url: server.url,
                description: server.description,
                variables,
            };
        });
    }
    async function getOpenAPI() {
        const serviceRecords = [];
        const services = listServices(program);
        if (services.length === 0) {
            services.push({ type: program.getGlobalNamespaceType() });
        }
        for (const service of services) {
            const versions = buildVersionProjections(program, service.type);
            if (versions.length === 1 && versions[0].version === undefined) {
                // non-versioned spec
                const document = await getProjectedOpenAPIDocument(service, versions[0]);
                if (document === undefined) {
                    // an error occurred producing this document, so don't return it
                    return serviceRecords;
                }
                serviceRecords.push({
                    service,
                    versioned: false,
                    document: document[0],
                    diagnostics: document[1],
                });
            }
            else {
                // versioned spec
                const serviceRecord = {
                    service,
                    versioned: true,
                    versions: [],
                };
                serviceRecords.push(serviceRecord);
                for (const record of versions) {
                    const document = await getProjectedOpenAPIDocument(service, record);
                    if (document === undefined) {
                        // an error occurred producing this document
                        continue;
                    }
                    serviceRecord.versions.push({
                        service,
                        version: record.version,
                        document: document[0],
                        diagnostics: document[1],
                    });
                }
            }
        }
        return serviceRecords;
        async function getProjectedOpenAPIDocument(service, record) {
            const commonProjections = [
                {
                    projectionName: "target",
                    arguments: ["json"],
                },
            ];
            const originalProgram = program;
            const projectedProgram = (program = projectProgram(originalProgram, [
                ...commonProjections,
                ...record.projections,
            ]));
            const projectedServiceNs = projectedProgram.projector.projectedTypes.get(service.type);
            const document = await getOpenApiFromVersion(projectedServiceNs === projectedProgram.getGlobalNamespaceType()
                ? { type: projectedProgram.getGlobalNamespaceType() }
                : getService(program, projectedServiceNs), record.version);
            return document;
        }
    }
    function resolveOutputFile(service, multipleService, version) {
        return interpolatePath(options.outputFile, {
            "service-name": multipleService ? getNamespaceFullName(service.type) : undefined,
            version,
        });
    }
    /**
     * Validates that common responses are consistent and returns the minimal set that describes the differences.
     */
    function validateCommonResponses(statusCode, ops) {
        const statusCodeResponses = [];
        for (const op of ops) {
            for (const response of op.responses) {
                if (getOpenAPI3StatusCodes(response.statusCodes, response.type).includes(statusCode)) {
                    statusCodeResponses.push(response);
                }
            }
        }
        const ref = statusCodeResponses[0];
        const sameTypeKind = statusCodeResponses.every((r) => r.type.kind === ref.type.kind);
        const sameTypeValue = statusCodeResponses.every((r) => r.type === ref.type);
        if (sameTypeKind && sameTypeValue) {
            // response is consistent and in all shared operations. Only need one copy.
            return [ref];
        }
        else {
            return statusCodeResponses;
        }
    }
    /**
     * Validates that common bodies are consistent and returns the minimal set that describes the differences.
     */
    function validateCommonBodies(ops) {
        const allBodies = ops.map((op) => op.parameters.body);
        return [...new Set(allBodies)];
    }
    /**
     * Validates that common parameters are consistent and returns the minimal set that describes the differences.
     */
    function validateCommonParameters(ops, name, totalOps) {
        const finalParams = [];
        const commonParams = [];
        for (const op of ops) {
            const param = op.parameters.parameters.find((p) => p.name === name);
            if (param) {
                commonParams.push(param);
            }
        }
        const reference = commonParams[0];
        if (!reference) {
            return [];
        }
        const inAllOps = ops.length === totalOps;
        const sameLocations = commonParams.every((p) => p.type === reference.type);
        const sameOptionality = commonParams.every((p) => p.param.optional === reference.param.optional);
        const sameTypeKind = commonParams.every((p) => p.param.type.kind === reference.param.type.kind);
        const sameTypeValue = commonParams.every((p) => p.param.type === reference.param.type);
        if (inAllOps && sameLocations && sameOptionality && sameTypeKind && sameTypeValue) {
            // param is consistent and in all shared operations. Only need one copy.
            finalParams.push(reference);
        }
        else if (!inAllOps && sameLocations && sameOptionality && sameTypeKind && sameTypeValue) {
            // param is consistent when used, but does not appear in all shared operations. Only need one copy, but it must be optional.
            reference.param.optional = true;
            finalParams.push(reference);
        }
        else if (inAllOps && !(sameLocations && sameOptionality && sameTypeKind)) {
            // param is in all shared operations, but is not consistent. Need multiple copies, which must be optional.
            // exception allowed when the params only differ by their value (e.g. string enum values)
            commonParams.forEach((p) => {
                p.param.optional = true;
            });
            finalParams.push(...commonParams);
        }
        else {
            finalParams.push(...commonParams);
        }
        return finalParams;
    }
    function getOpenAPI3StatusCodes(statusCodes, response) {
        if (isDefaultResponse(program, response) || statusCodes === "*") {
            return ["default"];
        }
        else if (typeof statusCodes === "number") {
            return [String(statusCodes)];
        }
        else {
            return rangeToOpenAPI(statusCodes, response);
        }
    }
    function rangeToOpenAPI(range, diagnosticTarget) {
        const reportInvalid = () => diagnostics.add(createDiagnostic({
            code: "unsupported-status-code-range",
            format: { start: String(range.start), end: String(range.end) },
            target: diagnosticTarget,
        }));
        const codes = [];
        let start = range.start;
        let end = range.end;
        if (range.start < 100) {
            reportInvalid();
            start = 100;
            codes.push("default");
        }
        else if (range.end > 599) {
            reportInvalid();
            codes.push("default");
            end = 599;
        }
        const groups = [1, 2, 3, 4, 5];
        for (const group of groups) {
            if (start > end) {
                break;
            }
            const groupStart = group * 100;
            const groupEnd = groupStart + 99;
            if (start >= groupStart && start <= groupEnd) {
                codes.push(`${group}XX`);
                if (start !== groupStart || end < groupEnd) {
                    reportInvalid();
                }
                start = groupStart + 100;
            }
        }
        return codes;
    }
    function buildSharedOperations(operations) {
        const results = [];
        const paramMap = new Map();
        const responseMap = new Map();
        for (const op of operations) {
            // determine which parameters are shared by shared route operations
            for (const param of op.parameters.parameters) {
                if (paramMap.has(param.name)) {
                    paramMap.get(param.name).push(op);
                }
                else {
                    paramMap.set(param.name, [op]);
                }
            }
            // determine which responses are shared by shared route operations
            for (const response of op.responses) {
                const statusCodes = getOpenAPI3StatusCodes(response.statusCodes, op.operation);
                for (const statusCode of statusCodes) {
                    if (responseMap.has(statusCode)) {
                        responseMap.get(statusCode).push(op);
                    }
                    else {
                        responseMap.set(statusCode, [op]);
                    }
                }
            }
        }
        const totalOps = operations.length;
        const shared = {
            kind: "shared",
            operationId: operations.map((op) => resolveOperationId(program, op.operation)).join("_"),
            description: joinOps(operations, getDoc, " "),
            summary: joinOps(operations, getSummary, " "),
            path: operations[0].path,
            verb: operations[0].verb,
            operations: operations.map((op) => op.operation),
            parameters: {
                parameters: [],
            },
            bodies: undefined,
            authentication: operations[0].authentication,
            responses: new Map(),
        };
        for (const [paramName, ops] of paramMap) {
            const commonParams = validateCommonParameters(ops, paramName, totalOps);
            shared.parameters.parameters.push(...commonParams);
        }
        shared.bodies = validateCommonBodies(operations);
        for (const [statusCode, ops] of responseMap) {
            shared.responses.set(statusCode, validateCommonResponses(statusCode, ops));
        }
        results.push(shared);
        return results;
    }
    /**
     * Groups HttpOperations together if they share the same route.
     */
    function resolveOperations(operations) {
        const result = [];
        const pathMap = new Map();
        operations.forEach((op) => {
            // we don't emit overloads anyhow so emit them from grouping
            if (op.overloading !== undefined && isOverloadSameEndpoint(op)) {
                return;
            }
            const opKey = `${op.verb}|${op.path}`;
            pathMap.has(opKey) ? pathMap.get(opKey).push(op) : pathMap.set(opKey, [op]);
        });
        // now push either the singular HttpOperations or the constructed SharedHttpOperations
        for (const [_, ops] of pathMap) {
            if (ops.length === 1) {
                result.push(ops[0]);
            }
            else {
                const sharedOps = buildSharedOperations(ops);
                for (const op of sharedOps) {
                    result.push(op);
                }
            }
        }
        return result;
    }
    async function getOpenApiFromVersion(service, version) {
        try {
            const httpService = ignoreDiagnostics(getHttpService(program, service.type));
            const auth = resolveAuthentication(httpService);
            initializeEmitter(service, auth.schemes, auth.defaultAuth, version);
            reportIfNoRoutes(program, httpService.operations);
            for (const op of resolveOperations(httpService.operations)) {
                if (op.kind === "shared") {
                    const opAuth = auth.operationsAuth.get(op.operations[0]);
                    emitSharedOperation(op, opAuth);
                }
                else {
                    const opAuth = auth.operationsAuth.get(op.operation);
                    emitOperation(op, opAuth);
                }
            }
            emitParameters();
            emitSchemas(service.type);
            emitTags();
            // Clean up empty entries
            if (root.components) {
                for (const elem of Object.keys(root.components)) {
                    if (Object.keys(root.components[elem]).length === 0) {
                        delete root.components[elem];
                    }
                }
            }
            return [root, diagnostics.diagnostics];
        }
        catch (err) {
            if (err instanceof ErrorTypeFoundError) {
                // Return early, there must be a parse error if an ErrorType was
                // inserted into the TypeSpec output
                return;
            }
            else {
                throw err;
            }
        }
    }
    function joinOps(operations, func, joinChar) {
        const values = operations
            .map((op) => func(program, op.operation))
            .filter((op) => op !== undefined);
        if (values.length) {
            return values.join(joinChar);
        }
        else {
            return undefined;
        }
    }
    function emitSharedOperation(shared, authReference) {
        const { path: fullPath, verb: verb, operations: ops } = shared;
        if (!root.paths[fullPath]) {
            root.paths[fullPath] = {};
        }
        currentPath = root.paths[fullPath];
        if (!currentPath[verb]) {
            currentPath[verb] = {};
        }
        currentEndpoint = currentPath[verb];
        for (const op of ops) {
            const opTags = getAllTags(program, op);
            if (opTags) {
                const currentTags = currentEndpoint.tags;
                if (currentTags) {
                    // combine tags but eliminate duplicates
                    currentEndpoint.tags = [...new Set([...currentTags, ...opTags])];
                }
                else {
                    currentEndpoint.tags = opTags;
                }
                for (const tag of opTags) {
                    // Add to root tags if not already there
                    tags.add(tag);
                }
            }
        }
        // Set up basic endpoint fields
        currentEndpoint.operationId = shared.operationId;
        for (const op of ops) {
            applyExternalDocs(op, currentEndpoint);
        }
        currentEndpoint.summary = shared.summary;
        currentEndpoint.description = shared.description;
        currentEndpoint.parameters = [];
        currentEndpoint.responses = {};
        // Error out if shared routes do not have consistent `@parameterVisibility`. We can
        // lift this restriction in the future if a use case develops.
        const visibilities = shared.operations.map((op) => {
            return resolveRequestVisibility(program, op, verb);
        });
        if (visibilities.some((v) => v !== visibilities[0])) {
            diagnostics.add(createDiagnostic({
                code: "inconsistent-shared-route-request-visibility",
                target: ops[0],
            }));
        }
        const visibility = resolveRequestVisibility(program, shared.operations[0], verb);
        emitEndpointParameters(shared.parameters.parameters, visibility);
        if (shared.bodies) {
            if (shared.bodies.length === 1) {
                emitRequestBody(shared, shared.bodies[0], visibility);
            }
            else if (shared.bodies.length > 1) {
                emitMergedRequestBody(shared, shared.bodies, visibility);
            }
        }
        emitSharedResponses(shared, shared.responses);
        for (const op of ops) {
            if (isDeprecated(program, op)) {
                currentEndpoint.deprecated = true;
            }
            attachExtensions(program, op, currentEndpoint);
        }
        if (authReference) {
            emitEndpointSecurity(authReference);
        }
    }
    function emitOperation(operation, authReference) {
        const { path: fullPath, operation: op, verb, parameters } = operation;
        // If path contains a query string, issue msg and don't emit this endpoint
        if (fullPath.indexOf("?") > 0) {
            diagnostics.add(createDiagnostic({ code: "path-query", target: op }));
            return;
        }
        if (!root.paths[fullPath]) {
            root.paths[fullPath] = {};
        }
        currentPath = root.paths[fullPath];
        if (!currentPath[verb]) {
            currentPath[verb] = {};
        }
        currentEndpoint = currentPath[verb];
        const currentTags = getAllTags(program, op);
        if (currentTags) {
            currentEndpoint.tags = currentTags;
            for (const tag of currentTags) {
                // Add to root tags if not already there
                tags.add(tag);
            }
        }
        currentEndpoint.operationId = resolveOperationId(program, operation.operation);
        applyExternalDocs(op, currentEndpoint);
        // Set up basic endpoint fields
        currentEndpoint.summary = getSummary(program, operation.operation);
        currentEndpoint.description = getDoc(program, operation.operation);
        currentEndpoint.parameters = [];
        currentEndpoint.responses = {};
        const visibility = resolveRequestVisibility(program, operation.operation, verb);
        emitEndpointParameters(parameters.parameters, visibility);
        emitRequestBody(operation, parameters.body, visibility);
        emitResponses(operation, operation.responses);
        if (authReference) {
            emitEndpointSecurity(authReference);
        }
        if (isDeprecated(program, op)) {
            currentEndpoint.deprecated = true;
        }
        attachExtensions(program, op, currentEndpoint);
    }
    function emitSharedResponses(operation, responses) {
        for (const [statusCode, statusCodeResponses] of responses) {
            if (statusCodeResponses.length === 1) {
                emitResponseObject(operation, statusCode, statusCodeResponses[0]);
            }
            else {
                emitMergedResponseObject(operation, statusCode, statusCodeResponses);
            }
        }
    }
    function emitResponses(operation, responses) {
        for (const response of responses) {
            for (const statusCode of getOpenAPI3StatusCodes(response.statusCodes, response.type)) {
                emitResponseObject(operation, statusCode, response);
            }
        }
    }
    function isBinaryPayload(body, contentType) {
        return (body.kind === "Scalar" &&
            body.name === "bytes" &&
            contentType !== "application/json" &&
            contentType !== "text/plain");
    }
    function emitMergedResponseObject(operation, statusCode, responses) {
        const openApiResponse = {
            description: undefined,
            content: {},
        };
        const schemaMap = new Map();
        for (const response of responses) {
            if (response.description && response.description !== openApiResponse.description) {
                openApiResponse.description = openApiResponse.description
                    ? `${openApiResponse.description} ${response.description}`
                    : response.description;
            }
            emitResponseHeaders(openApiResponse, response.responses, response.type);
            emitResponseContent(operation, openApiResponse, response.responses, schemaMap);
            if (!openApiResponse.description) {
                openApiResponse.description = getResponseDescriptionForStatusCode(statusCode);
            }
            currentEndpoint.responses[statusCode] = openApiResponse;
        }
    }
    function emitResponseObject(operation, statusCode, response) {
        const openApiResponse = currentEndpoint.responses[statusCode] ?? {
            description: response.description ?? getResponseDescriptionForStatusCode(statusCode),
        };
        emitResponseHeaders(openApiResponse, response.responses, response.type);
        emitResponseContent(operation, openApiResponse, response.responses);
        currentEndpoint.responses[statusCode] = openApiResponse;
    }
    function emitResponseHeaders(obj, responses, target) {
        for (const data of responses) {
            if (data.headers && Object.keys(data.headers).length > 0) {
                obj.headers ??= {};
                // OpenAPI can't represent different headers per content type.
                // So we merge headers here, and report any duplicates unless they are identical
                for (const [key, value] of Object.entries(data.headers)) {
                    const headerVal = getResponseHeader(value);
                    const existing = obj.headers[key];
                    if (existing) {
                        if (!deepEquals(existing, headerVal)) {
                            diagnostics.add(createDiagnostic({
                                code: "duplicate-header",
                                format: { header: key },
                                target: target,
                            }));
                        }
                        continue;
                    }
                    obj.headers[key] = headerVal;
                }
            }
        }
    }
    function emitResponseContent(operation, obj, responses, schemaMap = undefined) {
        schemaMap ??= new Map();
        for (const data of responses) {
            if (data.body === undefined) {
                continue;
            }
            obj.content ??= {};
            for (const contentType of data.body.contentTypes) {
                const contents = getBodyContentEntry(operation, "response", data.body, Visibility.Read, contentType);
                if (schemaMap.has(contentType)) {
                    schemaMap.get(contentType).push(contents);
                }
                else {
                    schemaMap.set(contentType, [contents]);
                }
            }
            for (const [contentType, contents] of schemaMap) {
                if (contents.length === 1) {
                    obj.content[contentType] = contents[0];
                }
                else {
                    obj.content[contentType] = {
                        schema: { anyOf: contents.map((x) => x.schema) },
                    };
                }
            }
        }
    }
    function getResponseDescriptionForStatusCode(statusCode) {
        if (statusCode === "default") {
            return "An unexpected error response.";
        }
        return getStatusCodeDescription(statusCode) ?? "unknown";
    }
    function getResponseHeader(prop) {
        return getOpenAPIParameterBase(prop, Visibility.Read);
    }
    function callSchemaEmitter(type, visibility, ignoreMetadataAnnotations, contentType) {
        const result = emitTypeWithSchemaEmitter(type, visibility, ignoreMetadataAnnotations, contentType);
        switch (result.kind) {
            case "code":
                return result.value;
            case "declaration":
                return { $ref: `#/components/schemas/${result.name}` };
            case "circular":
                diagnostics.add(createDiagnostic({
                    code: "inline-cycle",
                    format: { type: getOpenAPITypeName(program, type, typeNameOptions) },
                    target: type,
                }));
                return {};
            case "none":
                return {};
        }
    }
    function getSchemaValue(type, visibility, contentType) {
        const result = emitTypeWithSchemaEmitter(type, visibility, false, contentType);
        switch (result.kind) {
            case "code":
            case "declaration":
                return result.value;
            case "circular":
                diagnostics.add(createDiagnostic({
                    code: "inline-cycle",
                    format: { type: getOpenAPITypeName(program, type, typeNameOptions) },
                    target: type,
                }));
                return {};
            case "none":
                return {};
        }
    }
    function emitTypeWithSchemaEmitter(type, visibility, ignoreMetadataAnnotations, contentType) {
        if (!metadataInfo.isTransformed(type, visibility)) {
            visibility = Visibility.Read;
        }
        contentType = contentType === "application/json" ? undefined : contentType;
        return schemaEmitter.emitType(type, {
            referenceContext: {
                visibility,
                serviceNamespaceName: serviceNamespaceName,
                ignoreMetadataAnnotations: ignoreMetadataAnnotations ?? false,
                contentType,
            },
        });
    }
    function isSharedHttpOperation(operation) {
        return operation.kind === "shared";
    }
    function findOperationExamples(operation) {
        if (isSharedHttpOperation(operation)) {
            return operation.operations.flatMap((op) => getOpExamples(program, op).map((x) => [op, x]));
        }
        else {
            return getOpExamples(program, operation.operation).map((x) => [operation.operation, x]);
        }
    }
    function getExamplesForBodyContentEntry(operation, target) {
        const examples = findOperationExamples(operation);
        if (examples.length === 0) {
            return {};
        }
        const flattenedExamples = examples
            .map(([op, example]) => {
            const value = target === "request" ? example.parameters : example.returnType;
            const type = target === "request" ? op.parameters : op.returnType;
            return value
                ? [{ value, title: example.title, description: example.description }, type]
                : undefined;
        })
            .filter(isDefined);
        return getExampleOrExamples(flattenedExamples);
    }
    function getExampleOrExamples(examples) {
        if (examples.length === 0) {
            return {};
        }
        if (examples.length === 1 &&
            examples[0][0].title === undefined &&
            examples[0][0].description === undefined) {
            const [example, type] = examples[0];
            return { example: serializeValueAsJson(program, example.value, type) };
        }
        else {
            const exampleObj = {};
            for (const [index, [example, type]] of examples.entries()) {
                exampleObj[example.title ?? `example${index}`] = {
                    summary: example.title,
                    description: example.description,
                    value: serializeValueAsJson(program, example.value, type),
                };
            }
            return { examples: exampleObj };
        }
    }
    function getBodyContentEntry(operation, target, body, visibility, contentType) {
        const isBinary = isBinaryPayload(body.type, contentType);
        if (isBinary) {
            return { schema: { type: "string", format: "binary" } };
        }
        switch (body.bodyKind) {
            case "single":
                return {
                    schema: getSchemaForSingleBody(body.type, visibility, body.isExplicit && body.containsMetadataAnnotations, contentType.startsWith("multipart/") ? contentType : undefined),
                    ...getExamplesForBodyContentEntry(operation, target),
                };
            case "multipart":
                return {
                    ...getBodyContentForMultipartBody(body, visibility, contentType),
                    ...getExamplesForBodyContentEntry(operation, target),
                };
        }
    }
    function getSchemaForSingleBody(type, visibility, ignoreMetadataAnnotations, multipart) {
        const effectiveType = metadataInfo.getEffectivePayloadType(type, visibility);
        return callSchemaEmitter(effectiveType, visibility, ignoreMetadataAnnotations, multipart ?? "application/json");
    }
    function getBodyContentForMultipartBody(body, visibility, contentType) {
        const properties = {};
        const requiredProperties = [];
        const encodings = {};
        for (const [partIndex, part] of body.parts.entries()) {
            const partName = part.name ?? `part${partIndex}`;
            let schema = isBytesKeptRaw(program, part.body.type)
                ? { type: "string", format: "binary" }
                : getSchemaForSingleBody(part.body.type, visibility, part.body.isExplicit && part.body.containsMetadataAnnotations, part.body.type.kind === "Union" ? contentType : undefined);
            if (part.multi) {
                schema = {
                    type: "array",
                    items: schema,
                };
            }
            properties[partName] = schema;
            const encoding = resolveEncodingForMultipartPart(part, visibility, schema);
            if (encoding) {
                encodings[partName] = encoding;
            }
            if (!part.optional) {
                requiredProperties.push(partName);
            }
        }
        const schema = {
            type: "object",
            properties,
            required: requiredProperties,
        };
        const name = "name" in body.type && body.type.name !== ""
            ? getOpenAPITypeName(program, body.type, typeNameOptions)
            : undefined;
        if (name) {
            root.components.schemas[name] = schema;
        }
        const result = {
            schema: name ? { $ref: "#/components/schemas/" + name } : schema,
        };
        if (Object.keys(encodings).length > 0) {
            result.encoding = encodings;
        }
        return result;
    }
    function resolveEncodingForMultipartPart(part, visibility, schema) {
        const encoding = {};
        if (!isDefaultContentTypeForOpenAPI3(part.body.contentTypes, schema)) {
            encoding.contentType = part.body.contentTypes.join(", ");
        }
        const headers = part.headers;
        if (headers.length > 0) {
            encoding.headers = {};
            for (const header of headers) {
                const schema = getOpenAPIParameterBase(header.property, visibility);
                if (schema !== undefined) {
                    encoding.headers[header.options.name] = schema;
                }
            }
        }
        if (Object.keys(encoding).length === 0) {
            return undefined;
        }
        return encoding;
    }
    function isDefaultContentTypeForOpenAPI3(contentTypes, schema) {
        if (contentTypes.length === 0) {
            return false;
        }
        if (contentTypes.length > 1) {
            return false;
        }
        const contentType = contentTypes[0];
        switch (contentType) {
            case "text/plain":
                return schema.type === "string" || schema.type === "number";
            case "application/octet-stream":
                return ((schema.type === "string" && schema.format === "binary") ||
                    (schema.type === "array" &&
                        schema.items?.type === "string" &&
                        schema.items?.format === "binary"));
            case "application/json":
                return schema.type === "object";
        }
        return false;
    }
    function getParamPlaceholder(property) {
        let spreadParam = false;
        if (property.sourceProperty) {
            // chase our sources all the way back to the first place this property
            // was defined.
            spreadParam = true;
            property = property.sourceProperty;
            while (property.sourceProperty) {
                property = property.sourceProperty;
            }
        }
        const refUrl = getRef(program, property);
        if (refUrl) {
            return {
                $ref: refUrl,
            };
        }
        if (params.has(property)) {
            return params.get(property);
        }
        const placeholder = {};
        // only parameters inherited by spreading from non-inlined type are shared in #/components/parameters
        if (spreadParam && property.model && !shouldInline(program, property.model)) {
            params.set(property, placeholder);
            paramModels.add(property.model);
        }
        return placeholder;
    }
    function emitEndpointParameters(parameters, visibility) {
        for (const httpOpParam of parameters) {
            if (params.has(httpOpParam.param)) {
                currentEndpoint.parameters.push(params.get(httpOpParam.param));
                continue;
            }
            if (httpOpParam.type === "header" && isContentTypeHeader(program, httpOpParam.param)) {
                continue;
            }
            emitParameter(httpOpParam, visibility);
        }
    }
    function emitMergedRequestBody(operation, bodies, visibility) {
        if (bodies === undefined) {
            return;
        }
        const requestBody = {
            description: undefined,
            content: {},
        };
        const schemaMap = new Map();
        for (const body of bodies) {
            const desc = body.property ? getDoc(program, body.property) : undefined;
            if (desc) {
                requestBody.description = requestBody.description
                    ? `${requestBody.description} ${desc}`
                    : desc;
            }
            const contentTypes = body.contentTypes.length > 0 ? body.contentTypes : ["application/json"];
            for (const contentType of contentTypes) {
                const { schema: bodySchema } = getBodyContentEntry(operation, "request", body, visibility, contentType);
                if (schemaMap.has(contentType)) {
                    schemaMap.get(contentType).push(bodySchema);
                }
                else {
                    schemaMap.set(contentType, [bodySchema]);
                }
            }
        }
        const content = {};
        for (const [contentType, schemaArray] of schemaMap) {
            if (schemaArray.length === 1) {
                content[contentType] = { schema: schemaArray[0] };
            }
            else {
                content[contentType] = {
                    schema: { anyOf: schemaArray },
                };
            }
        }
        requestBody.content = content;
        currentEndpoint.requestBody = requestBody;
    }
    function emitRequestBody(operation, body, visibility) {
        if (body === undefined || isVoidType(body.type)) {
            return;
        }
        const requestBody = {
            description: body.property ? getDoc(program, body.property) : undefined,
            required: body.property ? !body.property.optional : true,
            content: {},
        };
        const contentTypes = body.contentTypes.length > 0 ? body.contentTypes : ["application/json"];
        for (const contentType of contentTypes) {
            requestBody.content[contentType] = getBodyContentEntry(operation, "request", body, visibility, contentType);
        }
        currentEndpoint.requestBody = requestBody;
    }
    function emitParameter(parameter, visibility) {
        if (isNeverType(parameter.param.type)) {
            return;
        }
        const existing = currentEndpoint.parameters.find((p) => p.name === parameter.name && p.in === parameter.type);
        if (existing) {
            populateParameter(existing, parameter, visibility);
        }
        else {
            const ph = getParamPlaceholder(parameter.param);
            currentEndpoint.parameters.push(ph);
            // If the parameter already has a $ref, don't bother populating it
            if (!("$ref" in ph)) {
                populateParameter(ph, parameter, visibility);
            }
        }
    }
    function getOpenAPIParameterBase(param, visibility) {
        const typeSchema = getSchemaForType(param.type, visibility);
        if (!typeSchema) {
            return undefined;
        }
        const schema = applyEncoding(param, applyIntrinsicDecorators(param, typeSchema));
        if (param.defaultValue) {
            schema.default = getDefaultValue(program, param.defaultValue);
        }
        // Description is already provided in the parameter itself.
        delete schema.description;
        const oaiParam = {
            required: !param.optional,
            description: getDoc(program, param),
            schema,
        };
        attachExtensions(program, param, oaiParam);
        return oaiParam;
    }
    function mergeOpenApiParameters(param, base) {
        if (param.schema) {
            const schema = param.schema;
            if (schema.enum && base.schema.enum) {
                schema.enum = [...new Set([...schema.enum, ...base.schema.enum])];
            }
            param.schema = schema;
        }
        else {
            Object.assign(param, base);
        }
        return param;
    }
    function populateParameter(ph, parameter, visibility) {
        ph.name = parameter.name;
        ph.in = parameter.type;
        const paramBase = getOpenAPIParameterBase(parameter.param, visibility);
        if (paramBase) {
            ph = mergeOpenApiParameters(ph, paramBase);
        }
        const format = mapParameterFormat(parameter);
        if (format === undefined) {
            ph.schema = {
                type: "string",
            };
        }
        else {
            Object.assign(ph, format);
        }
    }
    function mapParameterFormat(parameter) {
        switch (parameter.type) {
            case "header":
                return mapHeaderParameterFormat(parameter);
            case "query":
                return mapQueryParameterFormat(parameter);
            case "path":
                return {};
        }
    }
    function mapHeaderParameterFormat(parameter) {
        switch (parameter.format) {
            case undefined:
                return {};
            case "csv":
            case "simple":
                return { style: "simple" };
            default:
                diagnostics.add(createDiagnostic({
                    code: "invalid-format",
                    format: {
                        paramType: "header",
                        value: parameter.format,
                    },
                    target: parameter.param,
                }));
                return undefined;
        }
    }
    function mapQueryParameterFormat(parameter) {
        switch (parameter.format) {
            case undefined:
                return {};
            case "csv":
            case "simple":
                return { style: "form", explode: false };
            case "multi":
            case "form":
                return { style: "form", explode: true };
            case "ssv":
                return { style: "spaceDelimited", explode: false };
            case "pipes":
                return { style: "pipeDelimited", explode: false };
            default:
                diagnostics.add(createDiagnostic({
                    code: "invalid-format",
                    format: {
                        paramType: "query",
                        value: parameter.format,
                    },
                    target: parameter.param,
                }));
                return undefined;
        }
    }
    function emitParameters() {
        for (const [property, param] of params) {
            const key = getParameterKey(program, property, param, root.components.parameters, typeNameOptions);
            root.components.parameters[key] = { ...param };
            for (const key of Object.keys(param)) {
                delete param[key];
            }
            param.$ref = "#/components/parameters/" + encodeURIComponent(key);
        }
    }
    function emitSchemas(serviceNamespace) {
        if (!options.omitUnreachableTypes) {
            processUnreferencedSchemas();
        }
        const files = schemaEmitter.getSourceFiles();
        if (files.length > 0) {
            compilerAssert(files.length === 1, `Should only have a single file for now but got ${files.length}`);
            const schemas = root.components.schemas;
            const declarations = files[0].globalScope.declarations;
            for (const declaration of declarations) {
                schemas[declaration.name] = declaration.value;
            }
        }
        function processUnreferencedSchemas() {
            const addSchema = (type) => {
                if (isOrExtendsHttpFile(program, type)) {
                    return;
                }
                if (visibilityUsage.isUnreachable(type) &&
                    !paramModels.has(type) &&
                    !shouldInline(program, type)) {
                    callSchemaEmitter(type, Visibility.All);
                }
            };
            const skipSubNamespaces = isGlobalNamespace(program, serviceNamespace);
            navigateTypesInNamespace(serviceNamespace, {
                model: addSchema,
                scalar: addSchema,
                enum: addSchema,
                union: addSchema,
            }, { skipSubNamespaces });
        }
    }
    function emitTags() {
        for (const tag of tags) {
            root.tags.push({ name: tag });
        }
    }
    function getSchemaForType(type, visibility) {
        return callSchemaEmitter(type, visibility);
    }
    function attachExtensions(program, type, emitObject) {
        // Attach any OpenAPI extensions
        const extensions = getExtensions$1(program, type);
        if (extensions) {
            for (const key of extensions.keys()) {
                emitObject[key] = extensions.get(key);
            }
        }
    }
    function applyIntrinsicDecorators(typespecType, target) {
        const newTarget = { ...target };
        const docStr = getDoc(program, typespecType);
        if (docStr) {
            newTarget.description = docStr;
        }
        const formatStr = getFormat(program, typespecType);
        if (formatStr) {
            newTarget.format = formatStr;
        }
        const pattern = getPattern(program, typespecType);
        if (pattern) {
            newTarget.pattern = pattern;
        }
        const minLength = getMinLength(program, typespecType);
        if (minLength !== undefined) {
            newTarget.minLength = minLength;
        }
        const maxLength = getMaxLength(program, typespecType);
        if (maxLength !== undefined) {
            newTarget.maxLength = maxLength;
        }
        const minValue = getMinValue(program, typespecType);
        if (minValue !== undefined) {
            newTarget.minimum = minValue;
        }
        const minValueExclusive = getMinValueExclusive(program, typespecType);
        if (minValueExclusive !== undefined) {
            newTarget.minimum = minValueExclusive;
            newTarget.exclusiveMinimum = true;
        }
        const maxValue = getMaxValue(program, typespecType);
        if (maxValue !== undefined) {
            newTarget.maximum = maxValue;
        }
        const maxValueExclusive = getMaxValueExclusive(program, typespecType);
        if (maxValueExclusive !== undefined) {
            newTarget.maximum = maxValueExclusive;
            newTarget.exclusiveMaximum = true;
        }
        const minItems = getMinItems(program, typespecType);
        if (!target.minItems && minItems !== undefined) {
            newTarget.minItems = minItems;
        }
        const maxItems = getMaxItems(program, typespecType);
        if (!target.maxItems && maxItems !== undefined) {
            newTarget.maxItems = maxItems;
        }
        if (isSecret(program, typespecType)) {
            newTarget.format = "password";
        }
        const title = getSummary(program, typespecType);
        if (title) {
            newTarget.title = title;
        }
        const values = getKnownValues(program, typespecType);
        if (values) {
            return {
                oneOf: [newTarget, callSchemaEmitter(values, Visibility.Read, false, "application/json")],
            };
        }
        attachExtensions(program, typespecType, newTarget);
        return newTarget;
    }
    function applyEncoding(typespecType, target) {
        const encodeData = getEncode(program, typespecType);
        if (encodeData) {
            const newTarget = { ...target };
            const newType = callSchemaEmitter(encodeData.type, Visibility.Read, false, "application/json");
            newTarget.type = newType.type;
            // If the target already has a format it takes priority. (e.g. int32)
            newTarget.format = mergeFormatAndEncoding(newTarget.format, encodeData.encoding, newType.format);
            return newTarget;
        }
        return target;
    }
    function mergeFormatAndEncoding(format, encoding, encodeAsFormat) {
        switch (format) {
            case undefined:
                return encodeAsFormat ?? encoding;
            case "date-time":
                switch (encoding) {
                    case "rfc3339":
                        return "date-time";
                    case "unixTimestamp":
                        return "unixtime";
                    case "rfc7231":
                        return "http-date";
                    default:
                        return encoding;
                }
            case "duration":
                switch (encoding) {
                    case "ISO8601":
                        return "duration";
                    default:
                        return encodeAsFormat ?? encoding;
                }
            default:
                return encodeAsFormat ?? encoding;
        }
    }
    function applyExternalDocs(typespecType, target) {
        const externalDocs = getExternalDocs(program, typespecType);
        if (externalDocs) {
            target.externalDocs = externalDocs;
        }
    }
    function getOpenAPISecuritySchemes(httpAuthentications) {
        const schemes = {};
        for (const httpAuth of httpAuthentications) {
            const scheme = getOpenAPI3Scheme(httpAuth);
            if (scheme) {
                schemes[httpAuth.id] = scheme;
            }
        }
        return schemes;
    }
    function getOpenAPISecurity(authReference) {
        const security = authReference.options.map((authOption) => {
            const securityOption = {};
            for (const httpAuthRef of authOption.all) {
                switch (httpAuthRef.kind) {
                    case "noAuth":
                        // should emit "{}" as a security option https://github.com/OAI/OpenAPI-Specification/issues/14#issuecomment-297457320
                        continue;
                    case "oauth2":
                        securityOption[httpAuthRef.auth.id] = httpAuthRef.scopes;
                        continue;
                    default:
                        securityOption[httpAuthRef.auth.id] = [];
                }
            }
            return securityOption;
        });
        return security;
    }
    function emitEndpointSecurity(authReference) {
        const security = getOpenAPISecurity(authReference);
        if (deepEquals(security, root.security)) {
            return;
        }
        if (security.length > 0) {
            currentEndpoint.security = security;
        }
    }
    function getOpenAPI3Scheme(auth) {
        const scheme = getOpenAPI3SchemeInternal(auth);
        if (scheme) {
            attachExtensions(program, auth.model, scheme);
        }
        return scheme;
    }
    function getOpenAPI3SchemeInternal(auth) {
        switch (auth.type) {
            case "http":
                return { type: "http", scheme: auth.scheme, description: auth.description };
            case "apiKey":
                return { type: "apiKey", in: auth.in, name: auth.name, description: auth.description };
            case "oauth2":
                const flows = {};
                const scopes = [];
                for (const flow of auth.flows) {
                    scopes.push(...flow.scopes.map((x) => x.value));
                    flows[flow.type] = {
                        authorizationUrl: flow.authorizationUrl,
                        tokenUrl: flow.tokenUrl,
                        refreshUrl: flow.refreshUrl,
                        scopes: Object.fromEntries(flow.scopes.map((x) => [x.value, x.description ?? ""])),
                    };
                }
                return { type: "oauth2", flows, description: auth.description };
            case "openIdConnect":
                return {
                    type: "openIdConnect",
                    openIdConnectUrl: auth.openIdConnectUrl,
                    description: auth.description,
                };
            case "noAuth":
                return undefined;
            default:
                diagnostics.add(createDiagnostic({
                    code: "unsupported-auth",
                    format: { authType: auth.type },
                    target: currentService.type,
                }));
                return undefined;
        }
    }
}
function serializeDocument(root, fileType) {
    sortOpenAPIDocument(root);
    switch (fileType) {
        case "json":
            return prettierOutput(JSON.stringify(root, null, 2));
        case "yaml":
            return stringify(root, {
                singleQuote: true,
                aliasDuplicateObjects: false,
                lineWidth: 0,
            });
    }
}
function prettierOutput(output) {
    return output + "\n";
}
class ErrorTypeFoundError extends Error {
    constructor() {
        super("Error type found in evaluated TypeSpec output");
    }
}
function sortObjectByKeys(obj) {
    return Object.keys(obj)
        .sort()
        .reduce((sortedObj, key) => {
        sortedObj[key] = obj[key];
        return sortedObj;
    }, {});
}
function sortOpenAPIDocument(doc) {
    doc.paths = sortObjectByKeys(doc.paths);
    if (doc.components?.schemas) {
        doc.components.schemas = sortObjectByKeys(doc.components.schemas);
    }
    if (doc.components?.parameters) {
        doc.components.parameters = sortObjectByKeys(doc.components.parameters);
    }
}

const namespace = "TypeSpec.OpenAPI";

var f0 = /*#__PURE__*/Object.freeze({
    __proto__: null,
    $lib: $lib,
    $onEmit: $onEmit,
    $oneOf: $oneOf,
    $useRef: $useRef,
    convertOpenAPI3Document: convertOpenAPI3Document,
    getOneOf: getOneOf,
    getOpenAPI3: getOpenAPI3,
    getRef: getRef,
    namespace: namespace,
    resolveOptions: resolveOptions
});

const TypeSpecJSSources = {
"dist/src/index.js": f0,
};
const TypeSpecSources = {
  "package.json": "{\"name\":\"@typespec/openapi3\",\"version\":\"0.58.0\",\"author\":\"Microsoft Corporation\",\"description\":\"TypeSpec library for emitting OpenAPI 3.0 from the TypeSpec REST protocol binding and converting OpenAPI3 to TypeSpec\",\"homepage\":\"https://typespec.io\",\"readme\":\"https://github.com/microsoft/typespec/blob/main/README.md\",\"license\":\"MIT\",\"repository\":{\"type\":\"git\",\"url\":\"git+https://github.com/microsoft/typespec.git\"},\"bugs\":{\"url\":\"https://github.com/microsoft/typespec/issues\"},\"keywords\":[\"typespec\"],\"bin\":{\"tsp-openapi3\":\"cmd/tsp-openapi3.js\"},\"type\":\"module\",\"main\":\"dist/src/index.js\",\"tspMain\":\"lib/main.tsp\",\"exports\":{\".\":{\"types\":\"./dist/src/index.d.ts\",\"default\":\"./dist/src/index.js\"},\"./testing\":{\"types\":\"./dist/src/testing/index.d.ts\",\"default\":\"./dist/src/testing/index.js\"}},\"engines\":{\"node\":\">=18.0.0\"},\"scripts\":{\"clean\":\"rimraf ./dist ./temp\",\"build\":\"npm run gen-version && npm run gen-extern-signature && tsc -p . && npm run lint-typespec-library\",\"watch\":\"tsc -p . --watch\",\"gen-extern-signature\":\"tspd --enable-experimental gen-extern-signature .\",\"lint-typespec-library\":\"tsp compile . --warn-as-error --import @typespec/library-linter --no-emit\",\"test\":\"vitest run\",\"test:watch\":\"vitest -w\",\"test:ui\":\"vitest --ui\",\"test:ci\":\"vitest run --coverage --reporter=junit --reporter=default\",\"lint\":\"eslint . --max-warnings=0\",\"lint:fix\":\"eslint . --fix\",\"regen-docs\":\"tspd doc .  --enable-experimental  --output-dir ../../docs/emitters/openapi3/reference\",\"regen-specs\":\"cross-env RECORD=true vitest run\",\"gen-version\":\"node scripts/generate-version.js\"},\"files\":[\"lib/*.tsp\",\"dist/**\",\"!dist/test/**\"],\"dependencies\":{\"@readme/openapi-parser\":\"~2.6.0\",\"yaml\":\"~2.4.5\"},\"peerDependencies\":{\"@typespec/compiler\":\"workspace:~\",\"@typespec/http\":\"workspace:~\",\"@typespec/openapi\":\"workspace:~\",\"@typespec/versioning\":\"workspace:~\"},\"devDependencies\":{\"@types/node\":\"~18.11.19\",\"@types/yargs\":\"~17.0.32\",\"@typespec/compiler\":\"workspace:~\",\"@typespec/http\":\"workspace:~\",\"@typespec/library-linter\":\"workspace:~\",\"@typespec/openapi\":\"workspace:~\",\"@typespec/rest\":\"workspace:~\",\"@typespec/tspd\":\"workspace:~\",\"@typespec/versioning\":\"workspace:~\",\"@vitest/coverage-v8\":\"^1.6.0\",\"@vitest/ui\":\"^1.6.0\",\"c8\":\"^10.1.2\",\"cross-env\":\"~7.0.3\",\"rimraf\":\"~5.0.7\",\"typescript\":\"~5.5.3\",\"vitest\":\"^1.6.0\"}}",
  "../compiler/lib/intrinsics.tsp": "import \"../dist/src/lib/intrinsic-decorators.js\";\n\n// This file contains all the intrinsic types of typespec. Everything here will always be loaded\nnamespace TypeSpec;\n\n/**\n * Represent a byte array\n */\nscalar bytes;\n\n/**\n * A numeric type\n */\nscalar numeric;\n\n/**\n * A whole number. This represent any `integer` value possible.\n * It is commonly represented as `BigInteger` in some languages.\n */\nscalar integer extends numeric;\n\n/**\n * A number with decimal value\n */\nscalar float extends numeric;\n\n/**\n * A 64-bit integer. (`-9,223,372,036,854,775,808` to `9,223,372,036,854,775,807`)\n */\nscalar int64 extends integer;\n\n/**\n * A 32-bit integer. (`-2,147,483,648` to `2,147,483,647`)\n */\nscalar int32 extends int64;\n\n/**\n * A 16-bit integer. (`-32,768` to `32,767`)\n */\nscalar int16 extends int32;\n\n/**\n * A 8-bit integer. (`-128` to `127`)\n */\nscalar int8 extends int16;\n\n/**\n * A 64-bit unsigned integer (`0` to `18,446,744,073,709,551,615`)\n */\nscalar uint64 extends integer;\n\n/**\n * A 32-bit unsigned integer (`0` to `4,294,967,295`)\n */\nscalar uint32 extends uint64;\n\n/**\n * A 16-bit unsigned integer (`0` to `65,535`)\n */\nscalar uint16 extends uint32;\n\n/**\n * A 8-bit unsigned integer (`0` to `255`)\n */\nscalar uint8 extends uint16;\n\n/**\n * An integer that can be serialized to JSON (`−9007199254740991 (−(2^53 − 1))` to `9007199254740991 (2^53 − 1)` )\n */\nscalar safeint extends int64;\n\n/**\n * A 32 bit floating point number. (`±1.5 x 10^−45` to `±3.4 x 10^38`)\n */\nscalar float64 extends float;\n\n/**\n * A 32 bit floating point number. (`±5.0 × 10^−324` to `±1.7 × 10^308`)\n */\nscalar float32 extends float64;\n\n/**\n * A decimal number with any length and precision. This represent any `decimal` value possible.\n * It is commonly represented as `BigDecimal` in some languages.\n */\nscalar decimal extends numeric;\n\n/**\n * A 128-bit decimal number.\n */\nscalar decimal128 extends decimal;\n\n/**\n * A sequence of textual characters.\n */\nscalar string;\n\n/**\n * A date on a calendar without a time zone, e.g. \"April 10th\"\n */\nscalar plainDate {\n  /**\n   * Create a plain date from an ISO 8601 string.\n   * @example\n   *\n   * ```tsp\n   * const time = plainTime.fromISO(\"2024-05-06\");\n   * ```\n   */\n  init fromISO(value: string);\n}\n\n/**\n * A time on a clock without a time zone, e.g. \"3:00 am\"\n */\nscalar plainTime {\n  /**\n   * Create a plain time from an ISO 8601 string.\n   * @example\n   *\n   * ```tsp\n   * const time = plainTime.fromISO(\"12:34\");\n   * ```\n   */\n  init fromISO(value: string);\n}\n\n/**\n * An instant in coordinated universal time (UTC)\"\n */\nscalar utcDateTime {\n  /**\n   * Create a date from an ISO 8601 string.\n   * @example\n   *\n   * ```tsp\n   * const time = utcDateTime.fromISO(\"2024-05-06T12:20-12Z\");\n   * ```\n   */\n  init fromISO(value: string);\n}\n\n/**\n * A date and time in a particular time zone, e.g. \"April 10th at 3:00am in PST\"\n */\nscalar offsetDateTime {\n  /**\n   * Create a date from an ISO 8601 string.\n   * @example\n   *\n   * ```tsp\n   * const time = offsetDateTime.fromISO(\"2024-05-06T12:20-12-0700\");\n   * ```\n   */\n  init fromISO(value: string);\n}\n\n/**\n * A duration/time period. e.g 5s, 10h\n */\nscalar duration {\n  /**\n   * Create a duration from an ISO 8601 string.\n   * @example\n   *\n   * ```tsp\n   * const time = duration.fromISO(\"P1Y1D\");\n   * ```\n   */\n  init fromISO(value: string);\n}\n\n/**\n * Boolean with `true` and `false` values.\n */\nscalar boolean;\n\n/**\n * @dev Array model type, equivalent to `Element[]`\n * @template Element The type of the array elements\n */\n@indexer(integer, Element)\nmodel Array<Element> {}\n\n/**\n * @dev Model with string properties where all the properties have type `Property`\n * @template Element The type of the properties\n */\n@indexer(string, Element)\nmodel Record<Element> {}\n",
  "../compiler/lib/std/main.tsp": "// TypeSpec standard library. Everything in here can be omitted by using `--nostdlib` cli flag or `nostdlib` in the config.\nimport \"./types.tsp\";\nimport \"./decorators.tsp\";\nimport \"./reflection.tsp\";\nimport \"./projected-names.tsp\";\n",
  "../compiler/lib/std/types.tsp": "namespace TypeSpec;\n\n/**\n * Represent a 32-bit unix timestamp datetime with 1s of granularity.\n * It measures time by the number of seconds that have elapsed since 00:00:00 UTC on 1 January 1970.\n */\n@encode(\"unixTimestamp\", int32)\nscalar unixTimestamp32 extends utcDateTime;\n\n/**\n * Represent a model\n */\n// Deprecated June 2023 sprint\n#deprecated \"object is deprecated. Please use {} for an empty model, `Record<unknown>` for a record with unknown property types, `unknown[]` for an array.\"\nmodel object {}\n\n/**\n * Represent a URL string as described by https://url.spec.whatwg.org/\n */\nscalar url extends string;\n\n/**\n * Represents a collection of optional properties.\n *\n * @template Source An object whose spread properties are all optional.\n */\n@doc(\"The template for adding optional properties.\")\n@withOptionalProperties\nmodel OptionalProperties<Source> {\n  ...Source;\n}\n\n/**\n * Represents a collection of updateable properties.\n *\n * @template Source An object whose spread properties are all updateable.\n */\n@doc(\"The template for adding updateable properties.\")\n@withUpdateableProperties\nmodel UpdateableProperties<Source> {\n  ...Source;\n}\n\n/**\n * Represents a collection of omitted properties.\n *\n * @template Source An object whose properties are spread.\n * @template Keys The property keys to omit.\n */\n@doc(\"The template for omitting properties.\")\n@withoutOmittedProperties(Keys)\nmodel OmitProperties<Source, Keys extends string> {\n  ...Source;\n}\n\n/**\n * Represents a collection of properties with only the specified keys included.\n *\n * @template Source An object whose properties are spread.\n * @template Keys The property keys to include.\n */\n@doc(\"The template for picking properties.\")\n@withPickedProperties(Keys)\nmodel PickProperties<Source, Keys extends string> {\n  ...Source;\n}\n\n/**\n * Represents a collection of properties with default values omitted.\n *\n * @template Source An object whose spread property defaults are all omitted.\n */\n@withoutDefaultValues\nmodel OmitDefaults<Source> {\n  ...Source;\n}\n\n/**\n * Applies a visibility setting to a collection of properties.\n *\n * @template Source An object whose properties are spread.\n * @template Visibility The visibility to apply to all properties.\n */\n@doc(\"The template for setting the default visibility of key properties.\")\n@withDefaultKeyVisibility(Visibility)\nmodel DefaultKeyVisibility<Source, Visibility extends valueof string> {\n  ...Source;\n}\n",
  "../compiler/lib/std/decorators.tsp": "import \"../../dist/src/lib/decorators.js\";\n\nusing TypeSpec.Reflection;\n\nnamespace TypeSpec;\n\n/**\n * Typically a short, single-line description.\n * @param summary Summary string.\n *\n * @example\n * ```typespec\n * @summary(\"This is a pet\")\n * model Pet {}\n * ```\n */\nextern dec summary(target: unknown, summary: valueof string);\n\n/**\n * Attach a documentation string.\n * @param doc Documentation string\n * @param formatArgs Record with key value pair that can be interpolated in the doc.\n *\n * @example\n * ```typespec\n * @doc(\"Represent a Pet available in the PetStore\")\n * model Pet {}\n * ```\n */\nextern dec doc(target: unknown, doc: valueof string, formatArgs?: {});\n\n/**\n * Attach a documentation string to describe the successful return types of an operation.\n * If an operation returns a union of success and errors it only describes the success. See `@errorsDoc` for error documentation.\n * @param doc Documentation string\n *\n * @example\n * ```typespec\n * @returnsDoc(\"Returns doc\")\n * op get(): Pet | NotFound;\n * ```\n */\nextern dec returnsDoc(target: Operation, doc: valueof string);\n\n/**\n * Attach a documentation string to describe the error return types of an operation.\n * If an operation returns a union of success and errors it only describes the errors. See `@returnsDoc` for success documentation.\n * @param doc Documentation string\n *\n * @example\n * ```typespec\n * @errorsDoc(\"Errors doc\")\n * op get(): Pet | NotFound;\n * ```\n */\nextern dec errorsDoc(target: Operation, doc: valueof string);\n\n/**\n * Mark this type as deprecated.\n *\n * NOTE: This decorator **should not** be used, use the `#deprecated` directive instead.\n *\n * @deprecated Use the `#deprecated` directive instead.\n * @param message Deprecation message.\n *\n * @example\n *\n * Use the `#deprecated` directive instead:\n *\n * ```typespec\n * #deprecated \"Use ActionV2\"\n * op Action<Result>(): Result;\n * ```\n */\n#deprecated \"@deprecated decorator is deprecated. Use the `#deprecated` directive instead.\"\nextern dec deprecated(target: unknown, message: valueof string);\n\n/**\n * Service options.\n */\nmodel ServiceOptions {\n  /**\n   * Title of the service.\n   */\n  title?: string;\n\n  /**\n   * Version of the service.\n   */\n  version?: string;\n}\n\n/**\n * Mark this namespace as describing a service and configure service properties.\n * @param options Optional configuration for the service.\n *\n * @example\n * ```typespec\n * @service\n * namespace PetStore;\n * ```\n *\n * @example Setting service title\n * ```typespec\n * @service({title: \"Pet store\"})\n * namespace PetStore;\n * ```\n *\n * @example Setting service version\n * ```typespec\n * @service({version: \"1.0\"})\n * namespace PetStore;\n * ```\n */\nextern dec service(target: Namespace, options?: ServiceOptions);\n\n/**\n * Specify that this model is an error type. Operations return error types when the operation has failed.\n *\n * @example\n * ```typespec\n * @error\n * model PetStoreError {\n *   code: string;\n *   message: string;\n * }\n * ```\n */\nextern dec error(target: Model);\n\n/**\n * Specify a known data format hint for this string type. For example `uuid`, `uri`, etc.\n * This differs from the `@pattern` decorator which is meant to specify a regular expression while `@format` accepts a known format name.\n * The format names are open ended and are left to emitter to interpret.\n *\n * @param format format name.\n *\n * @example\n * ```typespec\n * @format(\"uuid\")\n * scalar uuid extends string;\n * ```\n */\nextern dec format(target: string | bytes | ModelProperty, format: valueof string);\n\n/**\n * Specify the the pattern this string should respect using simple regular expression syntax.\n * The following syntax is allowed: alternations (`|`), quantifiers (`?`, `*`, `+`, and `{ }`), wildcard (`.`), and grouping parentheses.\n * Advanced features like look-around, capture groups, and references are not supported.\n *\n * This decorator may optionally provide a custom validation _message_. Emitters may choose to use the message to provide\n * context when pattern validation fails. For the sake of consistency, the message should be a phrase that describes in\n * plain language what sort of content the pattern attempts to validate. For example, a complex regular expression that\n * validates a GUID string might have a message like \"Must be a valid GUID.\"\n *\n * @param pattern Regular expression.\n * @param validationMessage Optional validation message that may provide context when validation fails.\n *\n * @example\n * ```typespec\n * @pattern(\"[a-z]+\", \"Must be a string consisting of only lower case letters and of at least one character.\")\n * scalar LowerAlpha extends string;\n * ```\n */\nextern dec pattern(\n  target: string | bytes | ModelProperty,\n  pattern: valueof string,\n  validationMessage?: valueof string\n);\n\n/**\n * Specify the minimum length this string type should be.\n * @param value Minimum length\n *\n * @example\n * ```typespec\n * @minLength(2)\n * scalar Username extends string;\n * ```\n */\nextern dec minLength(target: string | ModelProperty, value: valueof integer);\n\n/**\n * Specify the maximum length this string type should be.\n * @param value Maximum length\n *\n * @example\n * ```typespec\n * @maxLength(20)\n * scalar Username extends string;\n * ```\n */\nextern dec maxLength(target: string | ModelProperty, value: valueof integer);\n\n/**\n * Specify the minimum number of items this array should have.\n * @param value Minimum number\n *\n * @example\n * ```typespec\n * @minItems(1)\n * model Endpoints is string[];\n * ```\n */\nextern dec minItems(target: unknown[] | ModelProperty, value: valueof integer);\n\n/**\n * Specify the maximum number of items this array should have.\n * @param value Maximum number\n *\n * @example\n * ```typespec\n * @maxItems(5)\n * model Endpoints is string[];\n * ```\n */\nextern dec maxItems(target: unknown[] | ModelProperty, value: valueof integer);\n\n/**\n * Specify the minimum value this numeric type should be.\n * @param value Minimum value\n *\n * @example\n * ```typespec\n * @minValue(18)\n * scalar Age is int32;\n * ```\n */\nextern dec minValue(target: numeric | ModelProperty, value: valueof numeric);\n\n/**\n * Specify the maximum value this numeric type should be.\n * @param value Maximum value\n *\n * @example\n * ```typespec\n * @maxValue(200)\n * scalar Age is int32;\n * ```\n */\nextern dec maxValue(target: numeric | ModelProperty, value: valueof numeric);\n\n/**\n * Specify the minimum value this numeric type should be, exclusive of the given\n * value.\n * @param value Minimum value\n *\n * @example\n * ```typespec\n * @minValueExclusive(0)\n * scalar distance is float64;\n * ```\n */\nextern dec minValueExclusive(target: numeric | ModelProperty, value: valueof numeric);\n\n/**\n * Specify the maximum value this numeric type should be, exclusive of the given\n * value.\n * @param value Maximum value\n *\n * @example\n * ```typespec\n * @maxValueExclusive(50)\n * scalar distance is float64;\n * ```\n */\nextern dec maxValueExclusive(target: numeric | ModelProperty, value: valueof numeric);\n\n/**\n * Mark this string as a secret value that should be treated carefully to avoid exposure\n *\n * @example\n * ```typespec\n * @secret\n * scalar Password is string;\n * ```\n */\nextern dec secret(target: string | ModelProperty);\n\n/**\n * Mark this operation as a `list` operation for resource types.\n * @deprecated Use the `listsResource` decorator in `@typespec/rest` instead.\n * @param listedType Optional type of the items in the list.\n */\nextern dec list(target: Operation, listedType?: Model);\n\n/**\n * Attaches a tag to an operation, interface, or namespace. Multiple `@tag` decorators can be specified to attach multiple tags to a TypeSpec element.\n * @param tag Tag value\n */\nextern dec tag(target: Namespace | Interface | Operation, tag: valueof string);\n\n/**\n * Specifies how a templated type should name their instances.\n * @param name name the template instance should take\n * @param formatArgs Model with key value used to interpolate the name\n *\n * @example\n * ```typespec\n * @friendlyName(\"{name}List\", T)\n * model List<Item> {\n *   value: Item[];\n *   nextLink: string;\n * }\n * ```\n */\nextern dec friendlyName(target: unknown, name: valueof string, formatArgs?: unknown);\n\n/**\n * Provide a set of known values to a string type.\n * @param values Known values enum.\n *\n * @example\n * ```typespec\n * @knownValues(KnownErrorCode)\n * scalar ErrorCode extends string;\n *\n * enum KnownErrorCode {\n *   NotFound,\n *   Invalid,\n * }\n * ```\n */\n#deprecated \"This decorator has been deprecated. Use a named union of string literals with a string variant to achieve the same result without a decorator.\"\nextern dec knownValues(target: string | numeric | ModelProperty, values: Enum);\n\n/**\n * Mark a model property as the key to identify instances of that type\n * @param altName Name of the property. If not specified, the decorated property name is used.\n *\n * @example\n * ```typespec\n * model Pet {\n *   @key id: string;\n * }\n * ```\n */\nextern dec key(target: ModelProperty, altName?: valueof string);\n\n/**\n * Specify this operation is an overload of the given operation.\n * @param overloadbase Base operation that should be a union of all overloads\n *\n * @example\n * ```typespec\n * op upload(data: string | bytes, @header contentType: \"text/plain\" | \"application/octet-stream\"): void;\n * @overload(upload)\n * op uploadString(data: string, @header contentType: \"text/plain\" ): void;\n * @overload(upload)\n * op uploadBytes(data: bytes, @header contentType: \"application/octet-stream\"): void;\n * ```\n */\nextern dec overload(target: Operation, overloadbase: Operation);\n\n/**\n * DEPRECATED: Use `@encodedName` instead.\n *\n * Provide an alternative name for this type.\n * @param targetName Projection target\n * @param projectedName Alternative name\n *\n * @example\n * ```typespec\n * model Certificate {\n *   @projectedName(\"json\", \"exp\")\n *   expireAt: int32;\n * }\n * ```\n */\n#deprecated \"Use `@encodedName` instead for changing the name over the wire.\"\nextern dec projectedName(\n  target: unknown,\n  targetName: valueof string,\n  projectedName: valueof string\n);\n\n/**\n * Provide an alternative name for this type when serialized to the given mime type.\n * @param mimeType Mime type this should apply to. The mime type should be a known mime type as described here https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types without any suffix (e.g. `+json`)\n * @param name Alternative name\n *\n * @example\n *\n * ```typespec\n * model Certificate {\n *   @encodedName(\"application/json\", \"exp\")\n *   @encodedName(\"application/xml\", \"expiry\")\n *   expireAt: int32;\n * }\n * ```\n *\n * @example Invalid values\n *\n * ```typespec\n * @encodedName(\"application/merge-patch+json\", \"exp\")\n *              ^ error cannot use subtype\n * ```\n */\nextern dec encodedName(target: unknown, mimeType: valueof string, name: valueof string);\n\n/**\n * Specify the property to be used to discriminate this type.\n * @param propertyName The property name to use for discrimination\n *\n * @example\n *\n * ```typespec\n * @discriminator(\"kind\")\n * union Pet{ cat: Cat, dog: Dog }\n *\n * model Cat {kind: \"cat\", meow: boolean}\n * model Dog {kind: \"dog\", bark: boolean}\n * ```\n *\n * ```typespec\n * @discriminator(\"kind\")\n * model Pet{ kind: string }\n *\n * model Cat extends Pet {kind: \"cat\", meow: boolean}\n * model Dog extends Pet  {kind: \"dog\", bark: boolean}\n * ```\n */\nextern dec discriminator(target: Model | Union, propertyName: valueof string);\n\n/**\n * Known encoding to use on utcDateTime or offsetDateTime\n */\nenum DateTimeKnownEncoding {\n  /**\n   * RFC 3339 standard. https://www.ietf.org/rfc/rfc3339.txt\n   * Encode to string.\n   */\n  rfc3339: \"rfc3339\",\n\n  /**\n   * RFC 7231 standard. https://www.ietf.org/rfc/rfc7231.txt\n   * Encode to string.\n   */\n  rfc7231: \"rfc7231\",\n\n  /**\n   * Encode a datetime to a unix timestamp.\n   * Unix timestamps are represented as an integer number of seconds since the Unix epoch and usually encoded as an int32.\n   */\n  unixTimestamp: \"unixTimestamp\",\n}\n\n/**\n * Known encoding to use on duration\n */\nenum DurationKnownEncoding {\n  /**\n   * ISO8601 duration\n   */\n  ISO8601: \"ISO8601\",\n\n  /**\n   * Encode to integer or float\n   */\n  seconds: \"seconds\",\n}\n\n/**\n * Known encoding to use on bytes\n */\nenum BytesKnownEncoding {\n  /**\n   * Encode to Base64\n   */\n  base64: \"base64\",\n\n  /**\n   * Encode to Base64 Url\n   */\n  base64url: \"base64url\",\n}\n\n/**\n * Specify how to encode the target type.\n * @param encoding Known name of an encoding.\n * @param encodedAs What target type is this being encoded as. Default to string.\n *\n * @example offsetDateTime encoded with rfc7231\n *\n * ```tsp\n * @encode(\"rfc7231\")\n * scalar myDateTime extends offsetDateTime;\n * ```\n *\n * @example utcDateTime encoded with unixTimestamp\n *\n * ```tsp\n * @encode(\"unixTimestamp\", int32)\n * scalar myDateTime extends unixTimestamp;\n * ```\n */\nextern dec encode(\n  target: Scalar | ModelProperty,\n  encoding: string | EnumMember,\n  encodedAs?: Scalar\n);\n\n/** Options for example decorators */\nmodel ExampleOptions {\n  /** The title of the example */\n  title?: string;\n\n  /** Description of the example */\n  description?: string;\n}\n\n/**\n * Provide an example value for a data type.\n *\n * @param example Example value.\n * @param options Optional metadata for the example.\n *\n * @example\n *\n * ```tsp\n * @example(#{name: \"Fluffy\", age: 2})\n * model Pet {\n *  name: string;\n *  age: int32;\n * }\n * ```\n */\nextern dec example(\n  target: Model | Enum | Scalar | Union | ModelProperty | UnionVariant,\n  example: valueof unknown,\n  options?: valueof ExampleOptions\n);\n\n/**\n * Operation example configuration.\n */\nmodel OperationExample {\n  /** Example request body. */\n  parameters?: unknown;\n\n  /** Example response body. */\n  returnType?: unknown;\n}\n\n/**\n * Provide example values for an operation's parameters and corresponding return type.\n *\n * @param example Example value.\n * @param options Optional metadata for the example.\n *\n * @example\n *\n * ```tsp\n * @example(#{parameters: #{name: \"Fluffy\", age: 2}, returnType: #{name: \"Fluffy\", age: 2, id: \"abc\"})\n * op createPet(pet: Pet): Pet;\n * ```\n */\nextern dec opExample(\n  target: Operation,\n  example: valueof OperationExample,\n  options?: valueof ExampleOptions\n);\n/**\n * Indicates that a property is only considered to be present or applicable (\"visible\") with\n * the in the given named contexts (\"visibilities\"). When a property has no visibilities applied\n * to it, it is implicitly visible always.\n *\n * As far as the TypeSpec core library is concerned, visibilities are open-ended and can be arbitrary\n * strings, but  the following visibilities are well-known to standard libraries and should be used\n * with standard emitters that interpret them as follows:\n *\n * - \"read\": output of any operation.\n * - \"create\": input to operations that create an entity..\n * - \"query\": input to operations that read data.\n * - \"update\": input to operations that update data.\n * - \"delete\": input to operations that delete data.\n *\n * See also: [Automatic visibility](https://typespec.io/docs/libraries/http/operations#automatic-visibility)\n *\n * @param visibilities List of visibilities which apply to this property.\n *\n * @example\n *\n * ```typespec\n * model Dog {\n *   // the service will generate an ID, so you don't need to send it.\n *   @visibility(\"read\") id: int32;\n *   // the service will store this secret name, but won't ever return it\n *   @visibility(\"create\", \"update\") secretName: string;\n *   // the regular name is always present\n *   name: string;\n * }\n * ```\n */\nextern dec visibility(target: ModelProperty, ...visibilities: valueof string[]);\n\n/**\n * Removes properties that are not considered to be present or applicable\n * (\"visible\") in the given named contexts (\"visibilities\"). Can be used\n * together with spread to effectively spread only visible properties into\n * a new model.\n *\n * See also: [Automatic visibility](https://typespec.io/docs/libraries/http/operations#automatic-visibility)\n *\n * When using an emitter that applies visibility automatically, it is generally\n * not necessary to use this decorator.\n *\n * @param visibilities List of visibilities which apply to this property.\n *\n * @example\n * ```typespec\n * model Dog {\n *   @visibility(\"read\") id: int32;\n *   @visibility(\"create\", \"update\") secretName: string;\n *   name: string;\n * }\n *\n * // The spread operator will copy all the properties of Dog into DogRead,\n * // and @withVisibility will then remove those that are not visible with\n * // create or update visibility.\n * //\n * // In this case, the id property is removed, and the name and secretName\n * // properties are kept.\n * @withVisibility(\"create\", \"update\")\n * model DogCreateOrUpdate {\n *   ...Dog;\n * }\n *\n * // In this case the id and name properties are kept and the secretName property\n * // is removed.\n * @withVisibility(\"read\")\n * model DogRead {\n *   ...Dog;\n * }\n * ```\n */\nextern dec withVisibility(target: Model, ...visibilities: valueof string[]);\n\n/**\n * Set the visibility of key properties in a model if not already set.\n *\n * @param visibility The desired default visibility value. If a key property already has a `visibility` decorator then the default visibility is not applied.\n */\nextern dec withDefaultKeyVisibility(target: Model, visibility: valueof string);\n\n/**\n * Returns the model with non-updateable properties removed.\n */\nextern dec withUpdateableProperties(target: Model);\n\n/**\n * Returns the model with required properties removed.\n */\nextern dec withOptionalProperties(target: Model);\n\n/**\n * Returns the model with any default values removed.\n */\nextern dec withoutDefaultValues(target: Model);\n\n/**\n * Returns the model with the given properties omitted.\n * @param omit List of properties to omit\n */\nextern dec withoutOmittedProperties(target: Model, omit: string | Union);\n\n/**\n * Returns the model with only the given properties included.\n * @param pick List of properties to include\n */\nextern dec withPickedProperties(target: Model, pick: string | Union);\n\n//---------------------------------------------------------------------------\n// Debugging\n//---------------------------------------------------------------------------\n\n/**\n * A debugging decorator used to inspect a type.\n * @param text Custom text to log\n */\nextern dec inspectType(target: unknown, text: valueof string);\n\n/**\n * A debugging decorator used to inspect a type name.\n * @param text Custom text to log\n */\nextern dec inspectTypeName(target: unknown, text: valueof string);\n\n/**\n * Sets which visibilities apply to parameters for the given operation.\n * @param visibilities List of visibility strings which apply to this operation.\n */\nextern dec parameterVisibility(target: Operation, ...visibilities: valueof string[]);\n\n/**\n * Sets which visibilities apply to the return type for the given operation.\n * @param visibilities List of visibility strings which apply to this operation.\n */\nextern dec returnTypeVisibility(target: Operation, ...visibilities: valueof string[]);\n",
  "../compiler/lib/std/reflection.tsp": "namespace TypeSpec.Reflection;\n\nmodel Enum {}\nmodel EnumMember {}\nmodel Interface {}\nmodel Model {}\nmodel ModelProperty {}\nmodel Namespace {}\nmodel Operation {}\nmodel Scalar {}\nmodel Union {}\nmodel UnionVariant {}\nmodel StringTemplate {}\n",
  "../compiler/lib/std/projected-names.tsp": "// Set of projections consuming the @projectedName decorator\n#suppress \"projections-are-experimental\"\nprojection op#target {\n  to(targetName) {\n    if hasProjectedName(self, targetName) {\n      self::rename(getProjectedName(self, targetName));\n    };\n  }\n  from(targetName) {\n    if hasProjectedName(self, targetName) {\n      self::rename(self::projectionBase::name);\n    };\n  }\n}\n\n#suppress \"projections-are-experimental\"\nprojection interface#target {\n  to(targetName) {\n    if hasProjectedName(self, targetName) {\n      self::rename(getProjectedName(self, targetName));\n    };\n  }\n  from(targetName) {\n    if hasProjectedName(self, targetName) {\n      self::rename(self::projectionBase::name);\n    };\n  }\n}\n\n#suppress \"projections-are-experimental\"\nprojection model#target {\n  to(targetName) {\n    if hasProjectedName(self, targetName) {\n      self::rename(getProjectedName(self, targetName));\n    };\n\n    self::properties::forEach((p) => {\n      if hasProjectedName(p, targetName) {\n        self::renameProperty(p::name, getProjectedName(p, targetName));\n      };\n    });\n  }\n  from(targetName) {\n    if hasProjectedName(self, targetName) {\n      self::rename(self::projectionBase::name);\n    };\n\n    self::projectionBase::properties::forEach((p) => {\n      if hasProjectedName(p, targetName) {\n        self::renameProperty(getProjectedName(p, targetName), p::name);\n      };\n    });\n  }\n}\n\n#suppress \"projections-are-experimental\"\nprojection enum#target {\n  to(targetName) {\n    if hasProjectedName(self, targetName) {\n      self::rename(getProjectedName(self, targetName));\n    };\n\n    self::members::forEach((p) => {\n      if hasProjectedName(p, targetName) {\n        self::renameMember(p::name, getProjectedName(p, targetName));\n      };\n    });\n  }\n  from(targetName) {\n    if hasProjectedName(self, targetName) {\n      self::rename(self::projectionBase::name);\n    };\n\n    self::projectionBase::members::forEach((p) => {\n      if hasProjectedName(p, targetName) {\n        self::renameMember(getProjectedName(p, targetName), p::name);\n      };\n    });\n  }\n}\n\n#suppress \"projections-are-experimental\"\nprojection union#target {\n  to(targetName) {\n    if hasProjectedName(self, targetName) {\n      self::rename(getProjectedName(self, targetName));\n    };\n  }\n  from(targetName) {\n    if hasProjectedName(self, targetName) {\n      self::rename(self::projectionBase::name);\n    };\n  }\n}\n",
  "lib/main.tsp": "import \"./decorators.tsp\";\n",
  "lib/decorators.tsp": "import \"../dist/src/index.js\";\n\nnamespace TypeSpec.OpenAPI;\n\nusing TypeSpec.Reflection;\n\n/**\n * Specify that `oneOf` should be used instead of `anyOf` for that union.\n */\nextern dec oneOf(target: Union | ModelProperty);\n/**\n * Specify an external reference that should be used inside of emitting this type.\n * @param ref External reference(e.g. \"../../common.json#/components/schemas/Foo\")\n */\nextern dec useRef(target: Model | ModelProperty, ref: valueof string);\n"
};
const _TypeSpecLibrary_ = {
  jsSourceFiles: TypeSpecJSSources,
  typespecSourceFiles: TypeSpecSources,
};

export { $lib, $onEmit, $oneOf, $useRef, _TypeSpecLibrary_, convertOpenAPI3Document, getOneOf, getOpenAPI3, getRef, namespace, resolveOptions };
