var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
import { createParserPlugin, getValueDeclaration, defaultJSDoc, getSymbolFromNode, getFullJsDocComment, } from '../docParser';
import t from '../traverse';
function capitalize(string) {
    return string[0].toUpperCase() + string.substring(1);
}
export var modelParser = createParserPlugin(function (node, parser) {
    var _a;
    /**
     * Replace submenus with symbols rather than types.
     *
     * For example:
     * ```ts
     * const menu = useMenuModel()
     *
     * const state = {
     *   menu
     * }
     * ```
     *
     * In this example, the `type` of `state.menu` will be `useMenuModel` instead of an object
     * representing the return type of `useMenuModel`
     *
     * Before:
     * ```json
     * {
     *   kind: 'object',
     *   properties: [
     *     {
     *       kind: 'parameter',
     *       name: 'state',
     *   // ...
     * }
     * ```
     *
     * After:
     * ```json
     * {
     *   kind: 'symbol',
     *   name: 'useMenuModel'
     * }
     * ```
     */
    if (t.isShorthandPropertyAssignment(node)) {
        var checker = parser.checker;
        var symbol = getSymbolFromNode(checker, node);
        var declaration = getValueDeclaration(symbol);
        var name_1 = (symbol === null || symbol === void 0 ? void 0 : symbol.name) || t.isIdentifier(node.name) ? node.name.text : '';
        if (declaration && declaration.getSourceFile()) {
            // ShorthandPropertyAssignment doesn't give the original variable declaration
            var originalDeclaration = t(declaration.getSourceFile())
                .find('VariableDeclaration')
                .map(function (node) {
                if (t.isIdentifier(node.name) &&
                    node.name.text === name_1 &&
                    node.initializer &&
                    t.isCallExpression(node.initializer) &&
                    t.isIdentifier(node.initializer.expression) &&
                    node.initializer.expression.text.includes('Model')) {
                    return {
                        kind: 'symbol',
                        name: node.initializer.expression.text.replace('use', ''),
                    };
                }
                return;
            })
                .filter(function (v) { return !!v; })[0];
            if (originalDeclaration) {
                var jsDoc = symbol ? getFullJsDocComment(checker, symbol) : defaultJSDoc;
                return __assign({ kind: 'property', name: name_1, defaultValue: undefined, type: originalDeclaration, required: false }, jsDoc);
            }
        }
    }
    /**
     * Replace `typeof useMyModel.TConfig` with a symbol of `MyModelConfig`. This remaps the entire
     * type to point to a symbol of the model config instead.
     *
     * For example,
     * ```ts
     * defaultConfig: {
     *   menuConfig: {} as typeof useMyModel.TConfig
     * }
     * ```
     *
     * Before:
     * ```json
     * {
     *   kind: 'object',
     *   properties: [...]
     * }
     * ```
     *
     * After:
     * ```json
     * {
     *   kind: 'symbol',
     *   name: 'MyModelConfig'
     * }
     * ```
     */
    if (t.isTypeReference(node)) {
        var firstArgument = (_a = node.typeArguments) === null || _a === void 0 ? void 0 : _a[0];
        if (firstArgument && t.isTypeQuery(firstArgument) && t.isIdentifier(firstArgument.exprName)) {
            var modelName = firstArgument.exprName.text;
            return { kind: 'symbol', name: modelName };
        }
    }
    if (t.isPropertyAssignment(node) &&
        t.isIdentifier(node.name) &&
        t.isAsExpression(node.initializer) &&
        t.isTypeQuery(node.initializer.type) &&
        t.isQualifiedName(node.initializer.type.exprName) &&
        t.isIdentifier(node.initializer.type.exprName.left) &&
        t.isIdentifier(node.initializer.type.exprName.right) &&
        // TConfig is special from models
        node.initializer.type.exprName.right.text === 'TConfig') {
        var symbol = getSymbolFromNode(parser.checker, node);
        var jsDoc = (symbol && getFullJsDocComment(parser.checker, symbol)) || defaultJSDoc;
        return __assign({ kind: 'property', name: node.name.text, defaultValue: parser.getValueFromNode(node.initializer.expression), required: false, type: {
                kind: 'symbol',
                name: node.initializer.type.exprName.left.text.replace('use', '') + 'Config',
            } }, jsDoc);
    }
    /**
     * This parsing matcher handles models created using `createModelHook` and adds additional symbols
     * for use by other models or components.
     *
     * For example:
     * ```ts
     * const useMyModel = createModelHook({
     *   defaultConfig: {
     *     // ...
     *   },
     *   requiredConfig: {
     *     // ...
     *   }
     * })(config => {
     *   return {
     *     state: {
     *       // ...
     *     },
     *     events: {
     *       // ...
     *     }
     *   }
     * })
     * ```
     *
     * This node parser will add the following symbols:
     * - MyModelConfig
     * - MyModelState
     * - MyModelEvents
     *
     * These symbols will be used by the model itself as well as in other models that make this model
     * a subModel (including config) and components that use models. Without this parser, a model
     * would be a generic function.
     */
    if (t.isVariableDeclaration(node) &&
        node.initializer &&
        t.isIdentifier(node.name) &&
        t.isCallExpression(node.initializer) &&
        t.isCallExpression(node.initializer.expression) &&
        t.isIdentifier(node.initializer.expression.expression) &&
        node.initializer.expression.expression.text === 'createModelHook') {
        // we have a model
        var modelName_1 = node.name.text.replace('use', '');
        // get the symbol for the model hook
        var symbol = getSymbolFromNode(parser.checker, node);
        var jsDoc = symbol ? getFullJsDocComment(parser.checker, symbol) : defaultJSDoc;
        var options = node.initializer.expression.arguments[0];
        var optionsType = parser.checker.getTypeAtLocation(options);
        var configProps = optionsType.getProperties().reduce(function (result, symbol) {
            if (['defaultConfig', 'requiredConfig'].includes(symbol.getName())) {
                // The declaration of the config
                var declaration = getValueDeclaration(symbol);
                if (declaration) {
                    result[symbol.getName()] = parser.checker
                        .getTypeAtLocation(declaration)
                        .getProperties()
                        .map(function (p) {
                        // Each property of the config
                        var prop = getValueDeclaration(p);
                        return __assign(__assign({}, parser.getValueFromNode(prop)), { required: symbol.getName() === 'requiredConfig' });
                    });
                }
            }
            return result;
        }, {});
        var modelProps_1 = {};
        var returnProps_1 = {};
        var type = parser.checker.getTypeAtLocation(node.initializer.arguments[0]);
        type.getCallSignatures().forEach(function (signature) {
            signature
                .getReturnType()
                .getProperties()
                .forEach(function (property) {
                var declaration = getValueDeclaration(property);
                if (declaration) {
                    if (['state', 'events'].includes(property.getName())) {
                        modelProps_1[property.getName()] = parser.checker
                            .getTypeAtLocation(declaration)
                            .getProperties()
                            .map(function (prop) {
                            return __assign(__assign({}, parser.getValueFromNode(getValueDeclaration(prop))), { defaultValue: undefined });
                        });
                    }
                    else {
                        returnProps_1[property.getName()] = parser.getValueFromNode(declaration);
                    }
                }
            });
        });
        var state = modelProps_1.state, events = modelProps_1.events;
        var fileName_1 = node.getSourceFile().fileName;
        // Add special symbols
        // Model
        parser.symbols.push(__assign(__assign({ name: modelName_1, fileName: fileName_1 }, jsDoc), { type: {
                kind: 'model',
                state: state,
                events: events,
                modelProperties: Object.keys(returnProps_1).map(function (p) { return returnProps_1[p]; }),
            } }));
        // Model config
        var eventConfig = (events || []).reduce(function (result, event) {
            var _a, _b;
            var type = event.type;
            // callback of the event
            result.push(__assign(__assign({}, event), { defaultValue: undefined, name: "on" + capitalize(event.name), required: false, type: __assign(__assign({}, type), { parameters: (_a = [
                        __assign(__assign({}, defaultJSDoc), { kind: 'parameter', name: 'state', required: true, type: {
                                kind: 'symbol',
                                name: modelName_1 + "State",
                                fileName: fileName_1,
                            } }),
                    ]).concat.apply(_a, type.parameters), returnType: {
                        kind: 'primitive',
                        value: 'void',
                    } }) }));
            // guard of the event
            result.push(__assign(__assign({}, event), { defaultValue: undefined, name: "should" + capitalize(event.name), required: false, type: __assign(__assign({}, type), { parameters: (_b = [
                        __assign(__assign({}, defaultJSDoc), { kind: 'parameter', name: 'state', required: true, type: {
                                kind: 'symbol',
                                name: modelName_1 + "State",
                                fileName: fileName_1,
                            } }),
                    ]).concat.apply(_b, type.parameters), returnType: {
                        kind: 'primitive',
                        value: 'boolean',
                    } }) }));
            return result;
        }, []);
        parser.symbols.push(__assign(__assign({ name: modelName_1 + "Config", fileName: fileName_1 }, jsDoc), { type: {
                kind: 'object',
                properties: (configProps['defaultConfig'] || []).concat(configProps['requiredConfig'], eventConfig),
            } }));
        // Model State
        parser.symbols.push(__assign(__assign({ name: modelName_1 + "State", fileName: fileName_1 }, defaultJSDoc), { type: {
                kind: 'object',
                properties: state,
            } }));
        // Model Events
        parser.symbols.push(__assign(__assign({ name: modelName_1 + "Events", fileName: fileName_1 }, defaultJSDoc), { type: {
                kind: 'object',
                properties: events,
            } }));
        // Model Hook
        return {
            kind: 'modelHook',
            name: "use" + modelName_1,
            defaultConfig: configProps['defaultConfig'] || [],
            requiredConfig: configProps['requiredConfig'] || [],
        };
    }
    return;
});
