import { anyTypeIssueAnnotation, nullTypeIssueAnnotation, } from "../../Annotation.js";
import { ConvenienceRenderer, } from "../../ConvenienceRenderer.js";
import { funPrefixNamer } from "../../Naming.js";
import { maybeAnnotated } from "../../Source.js";
import { acronymStyle } from "../../support/Acronyms.js";
import { ArrayType, MapType, } from "../../Type/index.js";
import { matchType, nullableFromUnion, removeNullFromUnion, } from "../../Type/TypeUtils.js";
import { keywords } from "./constants.js";
import { kotlinNameStyle } from "./utils.js";
export class KotlinRenderer extends ConvenienceRenderer {
    constructor(targetLanguage, renderContext, _kotlinOptions) {
        super(targetLanguage, renderContext);
        this._kotlinOptions = _kotlinOptions;
    }
    forbiddenNamesForGlobalNamespace() {
        return keywords;
    }
    forbiddenForObjectProperties(_o, _classNamed) {
        return { names: [], includeGlobalForbidden: true };
    }
    forbiddenForEnumCases(_e, _enumName) {
        return { names: [], includeGlobalForbidden: true };
    }
    forbiddenForUnionMembers(_u, _unionName) {
        return { names: [], includeGlobalForbidden: false };
    }
    topLevelNameStyle(rawName) {
        return kotlinNameStyle(true, rawName);
    }
    makeNamedTypeNamer() {
        return funPrefixNamer("upper", (s) => kotlinNameStyle(true, s, acronymStyle(this._kotlinOptions.acronymStyle)));
    }
    namerForObjectProperty() {
        return funPrefixNamer("lower", (s) => kotlinNameStyle(false, s, acronymStyle(this._kotlinOptions.acronymStyle)));
    }
    makeUnionMemberNamer() {
        return funPrefixNamer("upper", (s) => `${kotlinNameStyle(true, s)}Value`);
    }
    makeEnumCaseNamer() {
        return funPrefixNamer("upper", (s) => kotlinNameStyle(true, s, acronymStyle(this._kotlinOptions.acronymStyle)));
    }
    emitDescriptionBlock(lines) {
        this.emitCommentLines(lines, {
            lineStart: " * ",
            beforeComment: "/**",
            afterComment: " */",
        });
    }
    emitBlock(line, f, delimiter = "curly") {
        const [open, close] = delimiter === "curly"
            ? ["{", "}"]
            : delimiter === "paren"
                ? ["(", ")"]
                : ["{", "})"];
        this.emitLine(line, " ", open);
        this.indent(f);
        this.emitLine(close);
    }
    anySourceType(optional) {
        return ["Any", optional];
    }
    // (asarazan): I've broken out the following two functions
    // because some renderers, such as kotlinx, can cope with `any`, while some get mad.
    arrayType(arrayType, withIssues = false, _noOptional = false) {
        return ["List<", this.kotlinType(arrayType.items, withIssues), ">"];
    }
    mapType(mapType, withIssues = false, _noOptional = false) {
        return [
            "Map<String, ",
            this.kotlinType(mapType.values, withIssues),
            ">",
        ];
    }
    kotlinType(t, withIssues = false, noOptional = false) {
        const optional = noOptional ? "" : "?";
        return matchType(t, (_anyType) => {
            return maybeAnnotated(withIssues, anyTypeIssueAnnotation, this.anySourceType(optional));
        }, (_nullType) => {
            return maybeAnnotated(withIssues, nullTypeIssueAnnotation, this.anySourceType(optional));
        }, (_boolType) => "Boolean", (_integerType) => "Long", (_doubleType) => "Double", (_stringType) => "String", (arrayType) => this.arrayType(arrayType, withIssues), (classType) => this.nameForNamedType(classType), (mapType) => this.mapType(mapType, withIssues), (enumType) => this.nameForNamedType(enumType), (unionType) => {
            const nullable = nullableFromUnion(unionType);
            if (nullable !== null)
                return [this.kotlinType(nullable, withIssues), optional];
            return this.nameForNamedType(unionType);
        });
    }
    emitUsageHeader() {
        // To be overridden
    }
    emitHeader() {
        if (this.leadingComments !== undefined) {
            this.emitComments(this.leadingComments);
        }
        else {
            this.emitUsageHeader();
        }
        this.ensureBlankLine();
        this.emitLine("package ", this._kotlinOptions.packageName);
        this.ensureBlankLine();
    }
    emitTopLevelPrimitive(t, name) {
        const elementType = this.kotlinType(t);
        this.emitLine(["typealias ", name, " = ", elementType, ""]);
    }
    emitTopLevelArray(t, name) {
        const elementType = this.kotlinType(t.items);
        this.emitLine(["typealias ", name, " = ArrayList<", elementType, ">"]);
    }
    emitTopLevelMap(t, name) {
        const elementType = this.kotlinType(t.values);
        this.emitLine([
            "typealias ",
            name,
            " = HashMap<String, ",
            elementType,
            ">",
        ]);
    }
    emitEmptyClassDefinition(c, className) {
        this.emitDescription(this.descriptionForType(c));
        this.emitClassAnnotations(c, className);
        this.emitLine("class ", className, "()");
    }
    emitClassDefinition(c, className) {
        if (c.getProperties().size === 0) {
            this.emitEmptyClassDefinition(c, className);
            return;
        }
        const kotlinType = (p) => {
            if (p.isOptional) {
                return [this.kotlinType(p.type, true, true), "?"];
            }
            return this.kotlinType(p.type, true);
        };
        this.emitDescription(this.descriptionForType(c));
        this.emitClassAnnotations(c, className);
        this.emitLine("data class ", className, " (");
        this.indent(() => {
            let count = c.getProperties().size;
            let first = true;
            this.forEachClassProperty(c, "none", (name, jsonName, p) => {
                const nullable = p.type.kind === "union" &&
                    nullableFromUnion(p.type) !== null;
                const nullableOrOptional = p.isOptional || p.type.kind === "null" || nullable;
                const last = --count === 0;
                const meta = [];
                const description = this.descriptionForClassProperty(c, jsonName);
                if (description !== undefined) {
                    meta.push(() => this.emitDescription(description));
                }
                this.renameAttribute(name, jsonName, !nullableOrOptional, meta);
                if (meta.length > 0 && !first) {
                    this.ensureBlankLine();
                }
                for (const emit of meta) {
                    emit();
                }
                this.emitLine("val ", name, ": ", kotlinType(p), nullableOrOptional ? " = null" : "", last ? "" : ",");
                if (meta.length > 0 && !last) {
                    this.ensureBlankLine();
                }
                first = false;
            });
        });
        this.emitClassDefinitionMethods(c, className);
    }
    emitClassDefinitionMethods(_c, _className) {
        this.emitLine(")");
    }
    emitClassAnnotations(_c, _className) {
        // to be overridden
    }
    renameAttribute(_name, _jsonName, _required, _meta) {
        // to be overridden
    }
    emitEnumDefinition(e, enumName) {
        this.emitDescription(this.descriptionForType(e));
        this.emitBlock(["enum class ", enumName], () => {
            let count = e.cases.size;
            this.forEachEnumCase(e, "none", (name) => {
                this.emitLine(name, --count === 0 ? "" : ",");
            });
        });
    }
    emitUnionDefinition(u, unionName) {
        function sortBy(t) {
            const kind = t.kind;
            if (kind === "class")
                return kind;
            return `_${kind}`;
        }
        this.emitDescription(this.descriptionForType(u));
        const [maybeNull, nonNulls] = removeNullFromUnion(u, sortBy);
        this.emitClassAnnotations(u, unionName);
        this.emitBlock(["sealed class ", unionName], () => {
            {
                const table = [];
                this.forEachUnionMember(u, nonNulls, "none", null, (name, t) => {
                    table.push([
                        [
                            "class ",
                            name,
                            "(val value: ",
                            this.kotlinType(t),
                            ")",
                        ],
                        [" : ", unionName, "()"],
                    ]);
                });
                if (maybeNull !== null) {
                    table.push([
                        ["class ", this.nameForUnionMember(u, maybeNull), "()"],
                        [" : ", unionName, "()"],
                    ]);
                }
                this.emitTable(table);
            }
            this.emitUnionDefinitionMethods(u, nonNulls, maybeNull, unionName);
        });
    }
    emitUnionDefinitionMethods(_u, _nonNulls, _maybeNull, _unionName) {
        // to be overridden
    }
    emitSourceStructure() {
        this.emitHeader();
        // Top-level arrays, maps
        this.forEachTopLevel("leading", (t, name) => {
            if (t instanceof ArrayType) {
                this.emitTopLevelArray(t, name);
            }
            else if (t instanceof MapType) {
                this.emitTopLevelMap(t, name);
            }
            else if (t.isPrimitive()) {
                this.emitTopLevelPrimitive(t, name);
            }
        });
        this.forEachNamedType("leading-and-interposing", (c, n) => this.emitClassDefinition(c, n), (e, n) => this.emitEnumDefinition(e, n), (u, n) => this.emitUnionDefinition(u, n));
    }
}
