"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.JavaScriptPropTypesRenderer = void 0;
const collection_utils_1 = require("collection-utils");
const ConvenienceRenderer_js_1 = require("../../ConvenienceRenderer.js");
const Naming_js_1 = require("../../Naming.js");
const Acronyms_js_1 = require("../../support/Acronyms.js");
const Strings_js_1 = require("../../support/Strings.js");
const Support_js_1 = require("../../support/Support.js");
const index_js_1 = require("../../Type/index.js");
const TypeUtils_js_1 = require("../../Type/TypeUtils.js");
const unicodeMaps_js_1 = require("../JavaScript/unicodeMaps.js");
const utils_js_1 = require("../JavaScript/utils.js");
const identityNamingFunction = (0, Naming_js_1.funPrefixNamer)("properties", (s) => s);
class JavaScriptPropTypesRenderer extends ConvenienceRenderer_js_1.ConvenienceRenderer {
    constructor(targetLanguage, renderContext, _jsOptions) {
        super(targetLanguage, renderContext);
        this._jsOptions = _jsOptions;
    }
    nameStyle(original, upper) {
        const acronyms = (0, Acronyms_js_1.acronymStyle)(this._jsOptions.acronymStyle);
        const words = (0, Strings_js_1.splitIntoWords)(original);
        return (0, Strings_js_1.combineWords)(words, utils_js_1.legalizeName, upper ? Strings_js_1.firstUpperWordStyle : Strings_js_1.allLowerWordStyle, Strings_js_1.firstUpperWordStyle, upper ? (s) => (0, Strings_js_1.capitalize)(acronyms(s)) : Strings_js_1.allLowerWordStyle, acronyms, "", unicodeMaps_js_1.isES3IdentifierStart);
    }
    makeNamedTypeNamer() {
        return (0, Naming_js_1.funPrefixNamer)("types", (s) => this.nameStyle(s, true));
    }
    namerForObjectProperty() {
        return identityNamingFunction;
    }
    makeUnionMemberNamer() {
        return null;
    }
    makeEnumCaseNamer() {
        return (0, Naming_js_1.funPrefixNamer)("enum-cases", (s) => this.nameStyle(s, false));
    }
    namedTypeToNameForTopLevel(type) {
        return (0, TypeUtils_js_1.directlyReachableSingleNamedType)(type);
    }
    makeNameForProperty(c, className, p, jsonName, _assignedName) {
        // Ignore the assigned name
        return super.makeNameForProperty(c, className, p, jsonName, undefined);
    }
    typeMapTypeFor(t, required = true) {
        if (["class", "object", "enum"].includes(t.kind)) {
            return ["_", this.nameForNamedType(t)];
        }
        const match = (0, TypeUtils_js_1.matchType)(t, (_anyType) => "PropTypes.any", (_nullType) => "PropTypes.any", (_boolType) => "PropTypes.bool", (_integerType) => "PropTypes.number", (_doubleType) => "PropTypes.number", (_stringType) => "PropTypes.string", (arrayType) => [
            "PropTypes.arrayOf(",
            this.typeMapTypeFor(arrayType.items, false),
            ")",
        ], (_classType) => (0, Support_js_1.panic)("Should already be handled."), (_mapType) => "PropTypes.object", (_enumType) => (0, Support_js_1.panic)("Should already be handled."), (unionType) => {
            const children = Array.from(unionType.getChildren()).map((type) => this.typeMapTypeFor(type, false));
            return [
                "PropTypes.oneOfType([",
                ...(0, collection_utils_1.arrayIntercalate)(", ", children),
                "])",
            ];
        }, (_transformedStringType) => {
            return "PropTypes.string";
        });
        if (required) {
            return [match];
        }
        return match;
    }
    typeMapTypeForProperty(p) {
        return this.typeMapTypeFor(p.type);
    }
    importStatement(lhs, moduleName) {
        if (this._jsOptions.moduleSystem) {
            return ["import ", lhs, " from ", moduleName, ";"];
        }
        return ["const ", lhs, " = require(", moduleName, ");"];
    }
    emitUsageComments() {
        // FIXME: Use the correct type name
        this.emitCommentLines([
            "Example usage:",
            "",
            this.importStatement("{ MyShape }", "./myShape.js"),
            "",
            "class MyComponent extends React.Component {",
            "  //",
            "}",
            "",
            "MyComponent.propTypes = {",
            "  input: MyShape",
            "};",
        ], { lineStart: "// " });
    }
    emitBlock(source, end, emit) {
        this.emitLine(source, "{");
        this.indent(emit);
        this.emitLine("}", end);
    }
    emitImports() {
        this.ensureBlankLine();
        this.emitLine(this.importStatement("PropTypes", '"prop-types"'));
    }
    emitExport(name, value) {
        if (this._jsOptions.moduleSystem) {
            this.emitLine("export const ", name, " = ", value, ";");
        }
        else {
            this.emitLine("module.exports = exports = { ", name, ": ", value, " };");
        }
    }
    emitTypes() {
        this.ensureBlankLine();
        this.forEachObject("none", (_type, name) => {
            this.emitLine("let _", name, ";");
        });
        this.forEachEnum("none", (enumType, enumName) => {
            const options = [];
            this.forEachEnumCase(enumType, "none", (name, _jsonName, _position) => {
                options.push("'");
                options.push(name);
                options.push("'");
                options.push(", ");
            });
            options.pop();
            this.emitLine([
                "const _",
                enumName,
                " = PropTypes.oneOfType([",
                ...options,
                "]);",
            ]);
        });
        const order = [];
        const mapKey = [];
        const mapValue = [];
        this.forEachObject("none", (type, name) => {
            mapKey.push(name);
            mapValue.push(this.gatherSource(() => this.emitObject(name, type)));
        });
        // order these
        mapKey.forEach((_, index) => {
            // assume first
            let ordinal = 0;
            // pull out all names
            const source = mapValue[index];
            const names = source.filter((value) => value);
            // must be behind all these names
            names.forEach((name) => {
                const depName = name;
                // find this name's ordinal, if it has already been added
                order.forEach((orderItem) => {
                    const depIndex = orderItem;
                    if (mapKey[depIndex] === depName) {
                        // this is the index of the dependency, so make sure we come after it
                        ordinal = Math.max(ordinal, depIndex + 1);
                    }
                });
            });
            // insert index
            order.splice(ordinal, 0, index);
        });
        // now emit ordered source
        order.forEach((i) => this.emitGatheredSource(mapValue[i]));
        // now emit top levels
        this.forEachTopLevel("none", (type, name) => {
            if (type instanceof index_js_1.PrimitiveType) {
                this.ensureBlankLine();
                this.emitExport(name, this.typeMapTypeFor(type));
            }
            else {
                if (type.kind === "array") {
                    this.ensureBlankLine();
                    this.emitExport(name, [
                        "PropTypes.arrayOf(",
                        this.typeMapTypeFor(type.items),
                        ")",
                    ]);
                }
                else {
                    this.ensureBlankLine();
                    this.emitExport(name, ["_", name]);
                }
            }
        });
    }
    emitObject(name, t) {
        this.ensureBlankLine();
        this.emitLine("_", name, " = PropTypes.shape({");
        this.indent(() => {
            this.forEachClassProperty(t, "none", (_, jsonName, property) => {
                this.emitLine(`"${(0, Strings_js_1.utf16StringEscape)(jsonName)}"`, ": ", this.typeMapTypeForProperty(property), ",");
            });
        });
        this.emitLine("});");
    }
    emitSourceStructure() {
        if (this.leadingComments !== undefined) {
            this.emitComments(this.leadingComments);
        }
        else {
            this.emitUsageComments();
        }
        this.emitImports();
        this.emitTypes();
    }
}
exports.JavaScriptPropTypesRenderer = JavaScriptPropTypesRenderer;
