import * as fs from 'fs';
import * as path from 'path';
import * as globby from 'globby';
import * as babel from '@babel/core';
import generator from '@babel/generator';
import traverse from '@babel/traverse';

const isFirstUpperCase = (str: string) => str[0].toUpperCase() === str[0];
const firstToLowerCase = (str: string) => str[0].toLowerCase() + str.slice(1);

interface ConceptProperty {
    name: string,
    value?: string,
    type?: string,
    immutable?: boolean,
    circular?: boolean,
    excludedInJSON?: boolean,
    action?: string,
    inherited?: boolean,
}
interface Concept {
    name: string,
    propertyList: Array<ConceptProperty>,
}

const conceptMap: { [name: string]: Concept } = {};

function parse() {
    globby.sync(['./src/types/**/*.ts']).forEach((filePath) => {
        const baseName = path.basename(filePath, '.ts');
        if (!isFirstUpperCase(baseName))
            return;

        const code = fs.readFileSync(filePath, 'utf8');
        const ast = babel.parse(code, {
            filename: filePath,
            plugins: [
                require('@babel/plugin-syntax-typescript'),
                [require('@babel/plugin-proposal-decorators'), {
                    decoratorsBeforeExport: false,
                    legacy: false,
                }],
            ],
        });

        traverse(ast, {
            enter(path) {
                if (path.isClassDeclaration()) {
                    const concept: Concept = {
                        name: path.node.id.name,
                        propertyList: path.node.body.body
                            .filter((property) => property.type === 'ClassProperty')
                            .map((property) => {
                                property = property as babel.types.ClassProperty;
                                const result: ConceptProperty = {
                                    name: (property.key as babel.types.Identifier).name,
                                    type: property.typeAnnotation ? generator(property.typeAnnotation).code.replace(/^:\s*/, '') : undefined,
                                };

                                property.decorators && property.decorators.forEach((decorator) => {
                                    if (decorator.expression.type === 'CallExpression' && decorator.expression.callee.type === 'Identifier') {
                                        if (decorator.expression.callee.name === 'immutable')
                                            result.immutable = true;
                                        else if (decorator.expression.callee.name === 'circular')
                                            result.circular = true;
                                        else if (decorator.expression.callee.name === 'excludedInJSON')
                                            result.excludedInJSON = true;
                                        else if (decorator.expression.callee.name === 'action')
                                            result.action = 'true';
                                    }
                                });
                                return result;
                            }),
                    };
                    conceptMap[concept.name] = concept;
                }
            },
        });
    });
}

function generateConcept(concept: Concept) {
    const outputs = [];
    outputs.push(`${concept.name}`);
    concept.propertyList.forEach((property) => {
        outputs.push(`  ${property.name}: ${property.type}`);
    });

    return outputs.join('\n');
}

function generate() {
    const json = JSON.stringify(conceptMap, null, 4);
    fs.writeFileSync('./spec/conceptMap.json', json, 'utf8');
}

parse();
generate();
