"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SystemTextJsonCSharpRenderer = void 0;
const collection_utils_1 = require("collection-utils");
const ConvenienceRenderer_js_1 = require("../../ConvenienceRenderer.js");
const Naming_js_1 = require("../../Naming.js");
const Source_js_1 = require("../../Source.js");
const Strings_js_1 = require("../../support/Strings.js");
const Support_js_1 = require("../../support/Support.js");
const Transformers_js_1 = require("../../Transformers.js");
const index_js_1 = require("../../Type/index.js");
const TypeUtils_js_1 = require("../../Type/TypeUtils.js");
const CSharpRenderer_js_1 = require("./CSharpRenderer.js");
const utils_js_1 = require("./utils.js");
class SystemTextJsonCSharpRenderer extends CSharpRenderer_js_1.CSharpRenderer {
    constructor(targetLanguage, renderContext, _options) {
        super(targetLanguage, renderContext, _options);
        this._options = _options;
        this._enumExtensionsNames = new Map();
        this._needHelpers = _options.features.helpers;
        this._needAttributes = _options.features.attributes;
        this._needNamespaces = _options.features.namespaces;
    }
    forbiddenNamesForGlobalNamespace() {
        const forbidden = [
            "Converter",
            "JsonConverter",
            "JsonSerializer",
            "JsonWriter",
            "JsonToken",
            "Serialize",
            "JsonSerializerOptions",
            // "Newtonsoft",
            // "MetadataPropertyHandling",
            // "DateParseHandling",
            "FromJson",
            "Required",
        ];
        if (this._options.dense) {
            forbidden.push("J", "R", "N");
        }
        if (this._options.baseclass !== undefined) {
            forbidden.push(this._options.baseclass);
        }
        return super.forbiddenNamesForGlobalNamespace().concat(forbidden);
    }
    forbiddenForObjectProperties(c, className) {
        const result = super.forbiddenForObjectProperties(c, className);
        result.names = result.names.concat(["ToJson", "FromJson", "Required"]);
        return result;
    }
    makeNameForTransformation(xf, typeName) {
        if (typeName === undefined) {
            let xfer = xf.transformer;
            if (xfer instanceof Transformers_js_1.DecodingTransformer &&
                xfer.consumer !== undefined) {
                xfer = xfer.consumer;
            }
            return new Naming_js_1.SimpleName([`${xfer.kind}_converter`], utils_js_1.namingFunction, ConvenienceRenderer_js_1.inferredNameOrder + 30);
        }
        return new Naming_js_1.DependencyName(utils_js_1.namingFunction, typeName.order + 30, (lookup) => `${lookup(typeName)}_converter`);
    }
    makeNamedTypeDependencyNames(t, name) {
        if (!(t instanceof index_js_1.EnumType))
            return [];
        const extensionsName = new Naming_js_1.DependencyName(utils_js_1.namingFunction, name.order + 30, (lookup) => `${lookup(name)}_extensions`);
        this._enumExtensionsNames.set(name, extensionsName);
        return [extensionsName];
    }
    emitUsings() {
        if (!this._needAttributes && !this._needHelpers) {
            this.emitDependencyUsings();
            return;
        }
        super.emitUsings();
        this.ensureBlankLine();
        for (const ns of [
            "System.Text.Json",
            "System.Text.Json.Serialization",
            "System.Globalization",
        ]) {
            this.emitUsing(ns);
        }
        if (this._options.dense) {
            this.emitUsing([
                utils_js_1.denseJsonPropertyName,
                " = System.Text.Json.Serialization.JsonPropertyNameAttribute",
            ]);
            // this.emitUsing([denseRequiredEnumName, " = Newtonsoft.Json.Required"]);
            this.emitUsing([
                utils_js_1.denseNullValueHandlingEnumName,
                " = System.Text.Json.Serialization.JsonIgnoreCondition",
            ]);
        }
        if (this._options.baseclass === "EntityData") {
            this.emitUsing("Microsoft.Azure.Mobile.Server");
        }
    }
    baseclassForType(_t) {
        return this._options.baseclass;
    }
    emitDefaultFollowingComments() {
        if (!this._needHelpers)
            return;
        this.emitLine("#pragma warning restore CS8618");
        this.emitLine("#pragma warning restore CS8601");
        this.emitLine("#pragma warning restore CS8603");
    }
    emitDefaultLeadingComments() {
        if (!this._needHelpers)
            return;
        this.emitLine("// <auto-generated />");
        this.emitLine("//");
        this.emitLine("// To parse this JSON data, add NuGet 'System.Text.Json' then do", this.topLevels.size === 1 ? "" : " one of these", ":");
        this.emitLine("//");
        this.emitLine("//    using ", this._options.namespace, ";");
        this.emitLine("//");
        this.forEachTopLevel("none", (t, topLevelName) => {
            let rhs;
            if (t instanceof index_js_1.EnumType) {
                rhs = [
                    "JsonSerializer.Deserialize<",
                    topLevelName,
                    ">(jsonString)",
                ];
            }
            else {
                rhs = [topLevelName, ".FromJson(jsonString)"];
            }
            this.emitLine("//    var ", (0, Source_js_1.modifySource)(Strings_js_1.camelCase, topLevelName), " = ", rhs, ";");
        });
        // fix: should this be an option? Or respond to an existing option?
        this.emitLine("#nullable enable");
        this.emitLine("#pragma warning disable CS8618");
        this.emitLine("#pragma warning disable CS8601");
        this.emitLine("#pragma warning disable CS8603");
    }
    converterForType(t) {
        let xf = (0, Transformers_js_1.transformationForType)(t);
        if (xf === undefined && t instanceof index_js_1.UnionType) {
            const maybeNullable = (0, TypeUtils_js_1.nullableFromUnion)(t);
            if (maybeNullable !== null) {
                t = maybeNullable;
                xf = (0, Transformers_js_1.transformationForType)(t);
            }
        }
        if (xf === undefined)
            return undefined;
        if ((0, utils_js_1.alwaysApplyTransformation)(xf))
            return undefined;
        return (0, Support_js_1.defined)(this.nameForTransformation(t));
    }
    attributesForProperty(property, _name, _c, jsonName) {
        if (!this._needAttributes)
            return undefined;
        const attributes = [];
        const jsonPropertyName = this._options.dense
            ? utils_js_1.denseJsonPropertyName
            : "JsonPropertyName";
        const escapedName = (0, Strings_js_1.utf16StringEscape)(jsonName);
        const isNullable = (0, Transformers_js_1.followTargetType)(property.type).isNullable;
        const isOptional = property.isOptional;
        if (isOptional && !isNullable) {
            attributes.push([
                "[",
                "JsonIgnore",
                "(Condition = JsonIgnoreCondition.WhenWritingNull)]",
            ]);
        }
        // const requiredClass = this._options.dense ? "R" : "Required";
        // const nullValueHandlingClass = this._options.dense ? "N" : "NullValueHandling";
        // const nullValueHandling = isOptional && !isNullable ? [", NullValueHandling = ", nullValueHandlingClass, ".Ignore"] : [];
        // let required: Sourcelike;
        // if (!this._options.checkRequired || (isOptional && isNullable)) {
        //     required = [nullValueHandling];
        // } else if (isOptional && !isNullable) {
        //     required = [", Required = ", requiredClass, ".DisallowNull", nullValueHandling];
        // } else if (!isOptional && isNullable) {
        //     required = [", Required = ", requiredClass, ".AllowNull"];
        // } else {
        //     required = [", Required = ", requiredClass, ".Always", nullValueHandling];
        // }
        attributes.push(["[", jsonPropertyName, '("', escapedName, '")]']);
        const converter = this.converterForType(property.type);
        if (converter !== undefined) {
            attributes.push(["[JsonConverter(typeof(", converter, "))]"]);
        }
        return attributes;
    }
    blankLinesBetweenAttributes() {
        return this._needAttributes && !this._options.dense;
    }
    // The "this" type can't be `dynamic`, so we have to force it to `object`.
    topLevelResultType(t) {
        return t.kind === "any" || t.kind === "none"
            ? "object"
            : this.csType(t);
    }
    emitFromJsonForTopLevel(t, name) {
        if (t instanceof index_js_1.EnumType)
            return;
        let partial;
        let typeKind;
        const definedType = this.namedTypeToNameForTopLevel(t);
        if (definedType !== undefined) {
            partial = "partial ";
            typeKind = definedType instanceof index_js_1.ClassType ? "class" : "struct";
        }
        else {
            partial = "";
            typeKind = "class";
        }
        const csType = this.topLevelResultType(t);
        this.emitType(undefined, utils_js_1.AccessModifier.Public, [partial, typeKind], name, this.baseclassForType(t), () => {
            // FIXME: Make FromJson a Named
            this.emitExpressionMember(["public static ", csType, " FromJson(string json)"], [
                "JsonSerializer.Deserialize<",
                csType,
                ">(json, ",
                this._options.namespace,
                ".Converter.Settings)",
            ]);
        });
    }
    emitDecoderSwitch(emitBody) {
        this.emitLine("switch (reader.TokenType)");
        this.emitBlock(emitBody);
    }
    emitTokenCase(tokenType) {
        this.emitLine("case JsonTokenType.", tokenType, ":");
    }
    emitThrow(message) {
        this.emitLine("throw new Exception(", message, ");");
    }
    deserializeTypeCode(typeName) {
        switch (typeName) {
            case "bool":
                return ["reader.GetBoolean()"];
            case "long":
                return ["reader.GetInt64()"];
            case "decimal":
                return ["reader.GetDecimal()"];
            case "double":
                return ["reader.GetDouble()"];
            case "string":
                return ["reader.GetString()"];
            default:
                return [
                    "JsonSerializer.Deserialize<",
                    typeName,
                    ">(ref reader, options)",
                ];
        }
    }
    serializeValueCode(value) {
        if (value !== "null") {
            return ["JsonSerializer.Serialize(writer, ", value, ", options)"];
        }
        return ["writer.WriteNullValue()"];
    }
    emitSerializeClass() {
        // FIXME: Make Serialize a Named
        this.emitType(undefined, utils_js_1.AccessModifier.Public, "static class", "Serialize", undefined, () => {
            // Sometimes multiple top-levels will resolve to the same type, so we have to take care
            // not to emit more than one extension method for the same type.
            const seenTypes = new Set();
            this.forEachTopLevel("none", (t) => {
                // FIXME: Make ToJson a Named
                if (!seenTypes.has(t)) {
                    seenTypes.add(t);
                    this.emitExpressionMember([
                        "public static string ToJson(this ",
                        this.topLevelResultType(t),
                        " self)",
                    ], [
                        "JsonSerializer.Serialize(self, ",
                        this._options.namespace,
                        ".Converter.Settings)",
                    ]);
                }
            });
        });
    }
    emitCanConvert(expr) {
        this.emitExpressionMember("public override bool CanConvert(Type t)", expr);
    }
    emitReadJson(emitBody, csType) {
        this.emitLine("public override ", csType, " Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)");
        this.emitBlock(emitBody);
    }
    emitWriteJson(variable, emitBody, csType) {
        this.emitLine("public override void Write(Utf8JsonWriter writer, ", csType, " ", variable, ", JsonSerializerOptions options)");
        this.emitBlock(emitBody);
    }
    converterObject(converterName) {
        // FIXME: Get a singleton
        return [converterName, ".Singleton"];
    }
    emitConverterClass() {
        // FIXME: Make Converter a Named
        const converterName = ["Converter"];
        this.emitType(undefined, utils_js_1.AccessModifier.Internal, "static class", converterName, undefined, () => {
            // Do not use .Web as defaults. That turns on caseInsensitive property names and will fail the keywords test.
            this.emitLine("public static readonly JsonSerializerOptions Settings = new(JsonSerializerDefaults.General)");
            this.emitBlock(() => {
                // this.emitLine("MetadataPropertyHandling = MetadataPropertyHandling.Ignore,");
                // this.emitLine("DateParseHandling = DateParseHandling.None,");
                this.emitLine("Converters =");
                this.emitLine("{");
                this.indent(() => {
                    for (const [t, converter] of this
                        .typesWithNamedTransformations) {
                        if ((0, utils_js_1.alwaysApplyTransformation)((0, Support_js_1.defined)((0, Transformers_js_1.transformationForType)(t)))) {
                            this.emitLine(this.converterObject(converter), ",");
                        }
                    }
                    this.emitLine("new DateOnlyConverter(),");
                    this.emitLine("new TimeOnlyConverter(),");
                    this.emitLine("IsoDateTimeOffsetConverter.Singleton");
                    // this.emitLine("new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }");
                });
                this.emitLine("},");
            }, true);
        });
    }
    emitDecoderTransformerCase(tokenCases, variableName, xfer, targetType, emitFinish, varGen) {
        if (xfer === undefined)
            return;
        for (const tokenCase of tokenCases) {
            this.emitTokenCase(tokenCase);
        }
        this.indent(() => {
            const allHandled = this.emitDecodeTransformer(xfer, targetType, emitFinish, variableName, varGen);
            if (!allHandled) {
                this.emitLine("break;");
            }
        });
    }
    emitConsume(value, consumer, targetType, emitFinish) {
        if (consumer === undefined) {
            emitFinish(value);
            return true;
        }
        return this.emitTransformer(value, consumer, targetType, emitFinish);
    }
    emitDecodeTransformer(xfer, targetType, emitFinish, variableName = "value", varGen = { counter: 0 }) {
        if (xfer instanceof Transformers_js_1.DecodingTransformer) {
            const source = xfer.sourceType;
            const converter = this.converterForType(targetType);
            if (converter !== undefined) {
                const typeSource = this.csType(targetType);
                this.emitLine("var converter = ", this.converterObject(converter), ";");
                // The converter is for the non-null type, so if the target
                // type is nullable we have to handle null ourselves.
                const isNullable = targetType instanceof index_js_1.UnionType &&
                    (0, TypeUtils_js_1.nullableFromUnion)(targetType) !== null;
                this.emitLine("var ", variableName, " = ", isNullable
                    ? "reader.TokenType == JsonTokenType.Null ? null : "
                    : "", "(", typeSource, ")converter.Read(ref reader, typeof(", typeSource, "), options);");
            }
            else if (source.kind !== "null") {
                const output = targetType.kind === "double" ? targetType : source;
                this.emitLine("var ", variableName, " = ", this.deserializeTypeCode(this.csType(output)), ";");
            }
            return this.emitConsume(variableName, xfer.consumer, targetType, emitFinish);
        }
        if (xfer instanceof Transformers_js_1.ArrayDecodingTransformer) {
            // FIXME: Consume StartArray
            if (!(targetType instanceof index_js_1.ArrayType)) {
                return (0, Support_js_1.panic)("Array decoding must produce an array type");
            }
            // FIXME: handle EOF
            this.emitLine("reader.Read();");
            this.emitLine("var ", variableName, " = new List<", this.csType(targetType.items), ">();");
            this.emitLine("while (reader.TokenType != JsonTokenType.EndArray)");
            this.emitBlock(() => {
                this.emitDecodeTransformer(xfer.itemTransformer, xfer.itemTargetType, (v) => this.emitLine(variableName, ".Add(", v, ");"), "arrayItem", varGen);
                // FIXME: handle EOF
                this.emitLine("reader.Read();");
            });
            let result = variableName;
            if (!this._options.useList) {
                result = [result, ".ToArray()"];
            }
            emitFinish(result);
            return true;
        }
        if (xfer instanceof Transformers_js_1.DecodingChoiceTransformer) {
            this.emitDecoderSwitch(() => {
                const nullTransformer = xfer.nullTransformer;
                if (nullTransformer !== undefined) {
                    this.emitTokenCase("Null");
                    this.indent(() => {
                        const allHandled = this.emitDecodeTransformer(nullTransformer, targetType, emitFinish, "null", varGen);
                        if (!allHandled) {
                            this.emitLine("break");
                        }
                    });
                }
                // Handle number (integer/double) union properly
                if (xfer.integerTransformer !== undefined && xfer.doubleTransformer !== undefined) {
                    varGen.counter++;
                    const intTryVar = `intTryValue${varGen.counter}`;
                    varGen.counter++;
                    const intVar = `intValue${varGen.counter}`;
                    varGen.counter++;
                    const doubleVar = `doubleValue${varGen.counter}`;
                    this.emitTokenCase("Number");
                    this.indent(() => {
                        this.emitLine(`if (reader.TryGetInt64(out long ${intTryVar}))`);
                        this.emitBlock(() => {
                            const allHandled = this.emitDecodeTransformer(xfer.integerTransformer, targetType, emitFinish, intVar, varGen);
                            if (!allHandled) {
                                this.emitLine("break;");
                            }
                        });
                        this.emitLine("else");
                        this.emitBlock(() => {
                            const allHandled = this.emitDecodeTransformer(xfer.doubleTransformer, targetType, emitFinish, doubleVar, varGen);
                            if (!allHandled) {
                                this.emitLine("break;");
                            }
                        });
                    });
                }
                else {
                    // Only one present, emit as before
                    if (xfer.integerTransformer !== undefined) {
                        varGen.counter++;
                        const intVar = `intValue${varGen.counter}`;
                        this.emitDecoderTransformerCase(["Number"], intVar, xfer.integerTransformer, targetType, emitFinish, varGen);
                    }
                    else if (xfer.doubleTransformer !== undefined) {
                        varGen.counter++;
                        const doubleVar = `doubleValue${varGen.counter}`;
                        this.emitDecoderTransformerCase(["Number"], doubleVar, xfer.doubleTransformer, targetType, emitFinish, varGen);
                    }
                }
                this.emitDecoderTransformerCase(["True", "False"], "boolValue", xfer.boolTransformer, targetType, emitFinish, varGen);
                this.emitDecoderTransformerCase(
                // ["String", "Date"],
                ["String"], "stringValue", xfer.stringTransformer, targetType, emitFinish, varGen);
                this.emitDecoderTransformerCase(["StartObject"], "objectValue", xfer.objectTransformer, targetType, emitFinish, varGen);
                this.emitDecoderTransformerCase(["StartArray"], "arrayValue", xfer.arrayTransformer, targetType, emitFinish, varGen);
            });
            return false;
        }
        return (0, Support_js_1.panic)("Unknown transformer");
    }
    stringCaseValue(t, stringCase) {
        if (t.kind === "string") {
            return ['"', (0, Strings_js_1.utf16StringEscape)(stringCase), '"'];
        }
        if (t instanceof index_js_1.EnumType) {
            return [
                this.nameForNamedType(t),
                ".",
                this.nameForEnumCase(t, stringCase),
            ];
        }
        return (0, Support_js_1.panic)(`Type ${t.kind} does not have string cases`);
    }
    emitTransformer(variable, xfer, targetType, emitFinish) {
        function directTargetType(continuation) {
            if (continuation === undefined) {
                return targetType;
            }
            return (0, Transformers_js_1.followTargetType)(continuation.sourceType);
        }
        if (xfer instanceof Transformers_js_1.ChoiceTransformer) {
            const caseXfers = xfer.transformers;
            if (caseXfers.length > 1 &&
                caseXfers.every((caseXfer) => caseXfer instanceof Transformers_js_1.StringMatchTransformer)) {
                this.emitLine("switch (", variable, ")");
                this.emitBlock(() => {
                    for (const caseXfer of caseXfers) {
                        const matchXfer = caseXfer;
                        const value = this.stringCaseValue((0, Transformers_js_1.followTargetType)(matchXfer.sourceType), matchXfer.stringCase);
                        this.emitLine("case ", value, ":");
                        this.indent(() => {
                            const allDone = this.emitTransformer(variable, matchXfer.transformer, targetType, emitFinish);
                            if (!allDone) {
                                this.emitLine("break;");
                            }
                        });
                    }
                });
                // FIXME: Can we check for exhaustiveness?  For enums it should be easy.
                return false;
            }
            for (const caseXfer of caseXfers) {
                this.emitTransformer(variable, caseXfer, targetType, emitFinish);
            }
        }
        else if (xfer instanceof Transformers_js_1.UnionMemberMatchTransformer) {
            const memberType = xfer.memberType;
            const maybeNullable = (0, TypeUtils_js_1.nullableFromUnion)(xfer.sourceType);
            let test;
            let member;
            if (maybeNullable !== null) {
                if (memberType.kind === "null") {
                    test = [variable, " == null"];
                    member = "null";
                }
                else {
                    test = [variable, " != null"];
                    member = variable;
                }
            }
            else if (memberType.kind === "null") {
                test = [variable, ".IsNull"];
                member = "null";
            }
            else {
                const memberName = this.nameForUnionMember(xfer.sourceType, memberType);
                member = [variable, ".", memberName];
                test = [member, " != null"];
            }
            if (memberType.kind !== "null" && (0, utils_js_1.isValueType)(memberType)) {
                member = [member, ".Value"];
            }
            this.emitLine("if (", test, ")");
            this.emitBlock(() => this.emitTransformer(member, xfer.transformer, targetType, emitFinish));
        }
        else if (xfer instanceof Transformers_js_1.StringMatchTransformer) {
            const value = this.stringCaseValue((0, Transformers_js_1.followTargetType)(xfer.sourceType), xfer.stringCase);
            this.emitLine("if (", variable, " == ", value, ")");
            this.emitBlock(() => this.emitTransformer(variable, xfer.transformer, targetType, emitFinish));
        }
        else if (xfer instanceof Transformers_js_1.EncodingTransformer) {
            const converter = this.converterForType(xfer.sourceType);
            if (converter !== undefined) {
                this.emitLine("var converter = ", this.converterObject(converter), ";");
                // The converter is for the non-null type, so if the source
                // type is nullable we have to handle null ourselves.
                const maybeNullable = xfer.sourceType instanceof index_js_1.UnionType
                    ? (0, TypeUtils_js_1.nullableFromUnion)(xfer.sourceType)
                    : null;
                if (maybeNullable !== null) {
                    let member = variable;
                    if ((0, utils_js_1.isValueType)((0, Transformers_js_1.followTargetType)(maybeNullable))) {
                        member = [member, ".Value"];
                    }
                    this.emitLine("if (", variable, " == null)");
                    this.emitBlock(() => this.emitLine("writer.WriteNullValue();"));
                    this.emitLine("else");
                    this.emitBlock(() => this.emitLine("converter.Write(writer, ", member, ", options);"));
                }
                else {
                    this.emitLine("converter.Write(writer, ", variable, ", options);");
                }
            }
            else {
                this.emitLine(this.serializeValueCode(variable), ";");
            }
            emitFinish([]);
            return true;
        }
        else if (xfer instanceof Transformers_js_1.ArrayEncodingTransformer) {
            this.emitLine("writer.WriteStartArray();");
            const itemVariable = "arrayItem";
            this.emitLine("foreach (var ", itemVariable, " in ", variable, ")");
            this.emitBlock(() => {
                this.emitTransformer(itemVariable, xfer.itemTransformer, xfer.itemTargetType, () => {
                    return;
                });
            });
            this.emitLine("writer.WriteEndArray();");
            emitFinish([]);
            return true;
        }
        else if (xfer instanceof Transformers_js_1.ParseStringTransformer) {
            const immediateTargetType = xfer.consumer === undefined
                ? targetType
                : xfer.consumer.sourceType;
            switch (immediateTargetType.kind) {
                case "date-time":
                    this.emitLine("DateTimeOffset dt;");
                    this.emitLine("if (DateTimeOffset.TryParse(", variable, ", out dt))");
                    this.emitBlock(() => this.emitConsume("dt", xfer.consumer, targetType, emitFinish));
                    break;
                case "uuid":
                    this.emitLine("Guid guid;");
                    this.emitLine("if (Guid.TryParse(", variable, ", out guid))");
                    this.emitBlock(() => this.emitConsume("guid", xfer.consumer, targetType, emitFinish));
                    break;
                case "uri":
                    this.emitLine("try");
                    this.emitBlock(() => {
                        // this.emitLine("var uri = new Uri(", variable, ");");
                        // The default value about:blank should never happen, but this way we avoid a null reference warning.
                        this.emitLine('var uri = new Uri("about:blank");');
                        this.emitLine("if (!string.IsNullOrEmpty(stringValue))");
                        this.emitBlock(() => {
                            this.emitLine("uri = new Uri(", variable, ");");
                        });
                        this.emitConsume("uri", xfer.consumer, targetType, emitFinish);
                    });
                    this.emitLine("catch (UriFormatException) {}");
                    break;
                case "integer":
                    this.emitLine("long l;");
                    this.emitLine("if (Int64.TryParse(", variable, ", out l))");
                    this.emitBlock(() => this.emitConsume("l", xfer.consumer, targetType, emitFinish));
                    break;
                case "bool":
                    this.emitLine("bool b;");
                    this.emitLine("if (Boolean.TryParse(", variable, ", out b))");
                    this.emitBlock(() => this.emitConsume("b", xfer.consumer, targetType, emitFinish));
                    break;
                default:
                    return (0, Support_js_1.panic)(`Parsing string to ${immediateTargetType.kind} not supported`);
            }
        }
        else if (xfer instanceof Transformers_js_1.StringifyTransformer) {
            switch (xfer.sourceType.kind) {
                case "date-time":
                    return this.emitConsume([
                        variable,
                        '.ToString("o", System.Globalization.CultureInfo.InvariantCulture)',
                    ], xfer.consumer, targetType, emitFinish);
                case "uuid":
                    return this.emitConsume([
                        variable,
                        '.ToString("D", System.Globalization.CultureInfo.InvariantCulture)',
                    ], xfer.consumer, targetType, emitFinish);
                case "integer":
                case "uri":
                    return this.emitConsume([variable, ".ToString()"], xfer.consumer, targetType, emitFinish);
                case "bool":
                    this.emitLine("var boolString = ", variable, ' ? "true" : "false";');
                    return this.emitConsume("boolString", xfer.consumer, targetType, emitFinish);
                default:
                    return (0, Support_js_1.panic)(`Stringifying ${xfer.sourceType.kind} not supported`);
            }
        }
        else if (xfer instanceof Transformers_js_1.StringProducerTransformer) {
            const value = this.stringCaseValue(directTargetType(xfer.consumer), xfer.result);
            return this.emitConsume(value, xfer.consumer, targetType, emitFinish);
        }
        else if (xfer instanceof Transformers_js_1.MinMaxLengthCheckTransformer) {
            const min = xfer.minLength;
            const max = xfer.maxLength;
            const conditions = [];
            if (min !== undefined) {
                conditions.push([variable, ".Length >= ", min.toString()]);
            }
            if (max !== undefined) {
                conditions.push([variable, ".Length <= ", max.toString()]);
            }
            this.emitLine("if (", (0, collection_utils_1.arrayIntercalate)([" && "], conditions), ")");
            this.emitBlock(() => this.emitConsume(variable, xfer.consumer, targetType, emitFinish));
            return false;
        }
        else if (xfer instanceof Transformers_js_1.MinMaxValueTransformer) {
            const min = xfer.minimum;
            const max = xfer.maximum;
            const conditions = [];
            if (min !== undefined) {
                conditions.push([variable, " >= ", min.toString()]);
            }
            if (max !== undefined) {
                conditions.push([variable, " <= ", max.toString()]);
            }
            this.emitLine("if (", (0, collection_utils_1.arrayIntercalate)([" && "], conditions), ")");
            this.emitBlock(() => this.emitConsume(variable, xfer.consumer, targetType, emitFinish));
            return false;
        }
        else if (xfer instanceof Transformers_js_1.UnionInstantiationTransformer) {
            if (!(targetType instanceof index_js_1.UnionType)) {
                return (0, Support_js_1.panic)("Union instantiation transformer must produce a union type");
            }
            const maybeNullable = (0, TypeUtils_js_1.nullableFromUnion)(targetType);
            if (maybeNullable !== null) {
                emitFinish(variable);
            }
            else {
                const unionName = this.nameForNamedType(targetType);
                let initializer;
                if (xfer.sourceType.kind === "null") {
                    initializer = " ";
                }
                else {
                    const memberName = this.nameForUnionMember(targetType, xfer.sourceType);
                    initializer = [" ", memberName, " = ", variable, " "];
                }
                emitFinish(["new ", unionName, " {", initializer, "}"]);
            }
            return true;
        }
        else {
            return (0, Support_js_1.panic)("Unknown transformer");
        }
        return false;
    }
    emitTransformation(converterName, t) {
        const xf = (0, Support_js_1.defined)((0, Transformers_js_1.transformationForType)(t));
        const reverse = xf.reverse;
        const targetType = xf.targetType;
        const xfer = xf.transformer;
        const csType = this.csType(targetType);
        // const haveNullable = isValueType(targetType);
        // if (haveNullable) {
        //     converterName = ['Nullable', converterName];
        //     csType = [csType, "?"];
        // }
        this.emitType(undefined, utils_js_1.AccessModifier.Internal, "class", converterName, ["JsonConverter<", csType, ">"], () => {
            const canConvertExpr = [
                "t == typeof(",
                csType,
                ")",
            ];
            this.emitCanConvert(canConvertExpr);
            this.ensureBlankLine();
            this.emitReadJson(() => {
                // FIXME: It's unsatisfying that we need this.  The reason is that we not
                // only match T, but also T?.  If we didn't, then the T in T? would not be
                // deserialized with our converter but with the default one.  Can we check
                // whether the type is a nullable?
                // FIXME: This could duplicate one of the cases handled below in
                // `emitDecodeTransformer`.
                // if (haveNullable && !(targetType instanceof UnionType)) {
                //     this.emitLine("if (reader.TokenType == JsonTokenType.Null) return null;");
                // }
                const allHandled = this.emitDecodeTransformer(xfer, targetType, (v) => this.emitLine("return ", v, ";"));
                if (!allHandled) {
                    this.emitThrow([
                        '"Cannot unmarshal type ',
                        csType,
                        '"',
                    ]);
                }
            }, csType);
            this.ensureBlankLine();
            this.emitWriteJson("value", () => {
                // FIXME: See above.
                // if (haveNullable && !(targetType instanceof UnionType)) {
                //     this.emitLine("if (value == null)");
                //     this.emitBlock(() => {
                //         this.emitLine("writer.WriteNullValue();");
                //         this.emitLine("return;");
                //     });
                // }
                const allHandled = this.emitTransformer("value", reverse.transformer, reverse.targetType, () => this.emitLine("return;"));
                if (!allHandled) {
                    this.emitThrow([
                        '"Cannot marshal type ',
                        csType,
                        '"',
                    ]);
                }
            }, csType);
            this.ensureBlankLine();
            this.emitLine("public static readonly ", converterName, " Singleton = new ", converterName, "();");
        });
    }
    emitRequiredHelpers() {
        if (this._needHelpers) {
            this.forEachTopLevel("leading-and-interposing", (t, n) => this.emitFromJsonForTopLevel(t, n));
            this.ensureBlankLine();
            this.emitSerializeClass();
        }
        if (this._needHelpers ||
            (this._needAttributes && (this.haveNamedUnions || this.haveEnums))) {
            this.ensureBlankLine();
            this.emitConverterClass();
            this.forEachTransformation("leading-and-interposing", (n, t) => this.emitTransformation(n, t));
            this.emitMultiline(`
public class DateOnlyConverter : JsonConverter<DateOnly>
{
	private readonly string serializationFormat;
	public DateOnlyConverter() : this(null) { }

	public DateOnlyConverter(string? serializationFormat)
	{
			this.serializationFormat = serializationFormat ?? "yyyy-MM-dd";
	}

	public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
	{
			var value = reader.GetString();
			return DateOnly.Parse(value!);
	}

	public override void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options)
			=> writer.WriteStringValue(value.ToString(serializationFormat));
}

public class TimeOnlyConverter : JsonConverter<TimeOnly>
{
	private readonly string serializationFormat;

	public TimeOnlyConverter() : this(null) { }

	public TimeOnlyConverter(string? serializationFormat)
	{
			this.serializationFormat = serializationFormat ?? "HH:mm:ss.fff";
	}

	public override TimeOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
	{
			var value = reader.GetString();
			return TimeOnly.Parse(value!);
	}

	public override void Write(Utf8JsonWriter writer, TimeOnly value, JsonSerializerOptions options)
			=> writer.WriteStringValue(value.ToString(serializationFormat));
}

internal class IsoDateTimeOffsetConverter : JsonConverter<DateTimeOffset>
{
	public override bool CanConvert(Type t) => t == typeof(DateTimeOffset);

	private const string DefaultDateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFK";

	private DateTimeStyles _dateTimeStyles = DateTimeStyles.RoundtripKind;
	private string? _dateTimeFormat;
	private CultureInfo? _culture;

	public DateTimeStyles DateTimeStyles
	{
			get => _dateTimeStyles;
			set => _dateTimeStyles = value;
	}

	public string? DateTimeFormat
	{
			get => _dateTimeFormat ?? string.Empty;
			set => _dateTimeFormat = (string.IsNullOrEmpty(value)) ? null : value;
	}

	public CultureInfo Culture
	{
			get => _culture ?? CultureInfo.CurrentCulture;
			set => _culture = value;
	}

	public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
	{
			string text;


			if ((_dateTimeStyles & DateTimeStyles.AdjustToUniversal) == DateTimeStyles.AdjustToUniversal
					|| (_dateTimeStyles & DateTimeStyles.AssumeUniversal) == DateTimeStyles.AssumeUniversal)
			{
					value = value.ToUniversalTime();
			}

			text = value.ToString(_dateTimeFormat ?? DefaultDateTimeFormat, Culture);

			writer.WriteStringValue(text);
	}

	public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
	{
			string? dateText = reader.GetString();
			
			if (string.IsNullOrEmpty(dateText) == false)
			{
					if (!string.IsNullOrEmpty(_dateTimeFormat))
					{
							return DateTimeOffset.ParseExact(dateText, _dateTimeFormat, Culture, _dateTimeStyles);
					}
					else
					{
							return DateTimeOffset.Parse(dateText, Culture, _dateTimeStyles);
					}
			}
			else
			{
					return default(DateTimeOffset);
			}
	}


	public static readonly IsoDateTimeOffsetConverter Singleton = new IsoDateTimeOffsetConverter();
}`);
        }
    }
    needNamespace() {
        return this._needNamespaces;
    }
}
exports.SystemTextJsonCSharpRenderer = SystemTextJsonCSharpRenderer;
