import { funPrefixNamer } from "../../Naming.js";
import { modifySource, multiWord, parenIfNeeded, singleWord, } from "../../Source.js";
import { camelCase, utf16StringEscape } from "../../support/Strings.js";
import { panic } from "../../support/Support.js";
import { ArrayType, EnumType, UnionType, } from "../../Type/index.js";
import { matchType, nullableFromUnion } from "../../Type/TypeUtils.js";
import { JavaScriptRenderer, } from "../JavaScript/index.js";
import { quotePropertyName } from "./utils.js";
export class TypeScriptFlowBaseRenderer extends JavaScriptRenderer {
    constructor(targetLanguage, renderContext, _tsFlowOptions) {
        super(targetLanguage, renderContext, _tsFlowOptions);
        this._tsFlowOptions = _tsFlowOptions;
    }
    namerForObjectProperty() {
        if (this._tsFlowOptions.nicePropertyNames) {
            return funPrefixNamer("properties", (s) => this.nameStyle(s, false));
        }
        return super.namerForObjectProperty();
    }
    sourceFor(t) {
        var _a;
        if (this._tsFlowOptions.preferConstValues &&
            t.kind === "enum" &&
            t instanceof EnumType &&
            t.cases.size === 1) {
            const item = (_a = t.cases.values().next().value) !== null && _a !== void 0 ? _a : "";
            return singleWord(`"${utf16StringEscape(item)}"`);
        }
        if (["class", "object", "enum"].includes(t.kind)) {
            return singleWord(this.nameForNamedType(t));
        }
        return matchType(t, (_anyType) => singleWord("any"), (_nullType) => singleWord("null"), (_boolType) => singleWord("boolean"), (_integerType) => singleWord("number"), (_doubleType) => singleWord("number"), (_stringType) => singleWord("string"), (arrayType) => {
            const itemType = this.sourceFor(arrayType.items);
            if ((arrayType.items instanceof UnionType &&
                !this._tsFlowOptions.declareUnions) ||
                arrayType.items instanceof ArrayType) {
                return singleWord(["Array<", itemType.source, ">"]);
            }
            return singleWord([parenIfNeeded(itemType), "[]"]);
        }, (_classType) => panic("We handled this above"), (mapType) => singleWord([
            "{ [key: string]: ",
            this.sourceFor(mapType.values).source,
            " }",
        ]), (_enumType) => panic("We handled this above"), (unionType) => {
            if (!this._tsFlowOptions.declareUnions ||
                nullableFromUnion(unionType) !== null) {
                const children = Array.from(unionType.getChildren()).map((c) => parenIfNeeded(this.sourceFor(c)));
                return multiWord(" | ", ...children);
            }
            return singleWord(this.nameForNamedType(unionType));
        }, (transformedStringType) => {
            if (transformedStringType.kind === "date-time") {
                return singleWord("Date");
            }
            return singleWord("string");
        });
    }
    emitClassBlockBody(c) {
        this.emitPropertyTable(c, (name, _jsonName, p) => {
            const t = p.type;
            let propertyName = name;
            propertyName = modifySource(quotePropertyName, name);
            if (this._tsFlowOptions.readonly) {
                propertyName = modifySource((_propertyName) => `readonly ${_propertyName}`, propertyName);
            }
            return [
                [propertyName, p.isOptional ? "?" : "", ": "],
                [this.sourceFor(t).source, ";"],
            ];
        });
        const additionalProperties = c.getAdditionalProperties();
        if (additionalProperties) {
            this.emitTable([
                [
                    "[property: string]",
                    ": ",
                    this.sourceFor(additionalProperties).source,
                    ";",
                ],
            ]);
        }
    }
    emitClass(c, className) {
        this.emitDescription(this.descriptionForType(c));
        this.emitClassBlock(c, className);
    }
    emitUnion(u, unionName) {
        if (!this._tsFlowOptions.declareUnions) {
            return;
        }
        this.emitDescription(this.descriptionForType(u));
        const children = multiWord(" | ", ...Array.from(u.getChildren()).map((c) => parenIfNeeded(this.sourceFor(c))));
        this.emitLine("export type ", unionName, " = ", children.source, ";");
    }
    emitTypes() {
        // emit primitive top levels
        this.forEachTopLevel("none", (t, name) => {
            if (!t.isPrimitive()) {
                return;
            }
            this.ensureBlankLine();
            this.emitDescription(this.descriptionForType(t));
            this.emitLine("type ", name, " = ", this.sourceFor(t).source, ";");
        });
        this.forEachNamedType("leading-and-interposing", (c, n) => this.emitClass(c, n), (e, n) => this.emitEnum(e, n), (u, n) => this.emitUnion(u, n));
    }
    emitUsageComments() {
        if (this._tsFlowOptions.justTypes)
            return;
        super.emitUsageComments();
    }
    deserializerFunctionLine(t, name) {
        const jsonType = this._tsFlowOptions.rawType === "json" ? "string" : "any";
        return [
            "function to",
            name,
            "(json: ",
            jsonType,
            "): ",
            this.sourceFor(t).source,
        ];
    }
    serializerFunctionLine(t, name) {
        const camelCaseName = modifySource(camelCase, name);
        const returnType = this._tsFlowOptions.rawType === "json" ? "string" : "any";
        return [
            "function ",
            camelCaseName,
            "ToJson(value: ",
            this.sourceFor(t).source,
            "): ",
            returnType,
        ];
    }
    get moduleLine() {
        return undefined;
    }
    get castFunctionLines() {
        return [
            "function cast<T>(val: any, typ: any): T",
            "function uncast<T>(val: T, typ: any): any",
        ];
    }
    get typeAnnotations() {
        throw new Error("not implemented");
    }
    emitConvertModule() {
        if (this._tsFlowOptions.justTypes)
            return;
        super.emitConvertModule();
    }
    emitConvertModuleHelpers() {
        if (this._tsFlowOptions.justTypes)
            return;
        super.emitConvertModuleHelpers();
    }
    emitModuleExports() {
        if (this._tsFlowOptions.justTypes) {
            return;
        }
        else {
            super.emitModuleExports();
        }
    }
}
