import { syntaxTree } from '@codemirror/language';
import { getDiagnostic } from './linter-builder';
import { BreedType } from '../classes/structures';
import { getLocalVariables } from '../utils/check-identifier';
import { PrimitiveManager } from '../primitives/primitives';
import { turtleVars, patchVars, linkVars } from '../keywords';
import { removeAction } from '../utils/actions';
import { stateExtension } from 'src/codemirror/extension-state-netlogo';
import { GetAllBreedPrimitives } from '../parsers/breed';
import { getCodeName } from '../utils/code';
import { ParseMode } from '../../editor-config';
let primitives = PrimitiveManager;
// NamingLinter: Ensures no duplicate breed names
export const NamingLinter = (view, preprocessContext, lintContext) => {
    const diagnostics = [];
    let defined = [];
    let breedDefined = [];
    // Reserved keywords
    let reserved = ['turtles', 'turtle', 'patches', 'patch', 'links', 'link'];
    let reservedVars = GetAllBreedPrimitives(lintContext);
    reservedVars.push(...turtleVars);
    reservedVars.push(...patchVars);
    reservedVars.push(...linkVars);
    // Used & reserved
    var NameCheck = (node, type, extra, isBreed = false) => {
        const value = getCodeName(view.state, node);
        // For breeds, we ignore other breed variables since you can re-define them in NetLogo
        if (defined.includes(value) || (extra === null || extra === void 0 ? void 0 : extra.includes(value)) || (!isBreed && breedDefined.includes(value))) {
            diagnostics.push(getDiagnostic(view, node, 'Term _ already used', 'error', value, type));
        }
        else if (reservedVars.includes(value)) {
            if (type.includes('variable')) {
                if (type != 'Local variable')
                    diagnostics.push(removeAction(getDiagnostic(view, node, 'Variable _ reserved', 'error', value, type)));
                else
                    diagnostics.push(getDiagnostic(view, node, 'Variable _ reserved', 'error', value, type));
            }
            else {
                diagnostics.push(getDiagnostic(view, node, 'Term _ reserved', 'error', value, type));
            }
        }
        else if (reserved.includes(value) || primitives.GetNamedPrimitive(value)) {
            diagnostics.push(getDiagnostic(view, node, 'Term _ reserved', 'error', value, type));
        }
        else {
            if (extra)
                extra.push(value);
            else {
                defined.push(value);
            }
        }
    };
    var Mode = view.state.field(stateExtension).Mode;
    if (Mode != ParseMode.Normal && Mode != ParseMode.Generative) {
        defined.push(...lintContext.GetDefined());
        syntaxTree(view.state)
            .cursor()
            .iterate((noderef) => {
            var _a;
            if (noderef.name == 'NewVariableDeclaration') {
                let child = (_a = noderef.node.getChild('Identifier')) !== null && _a !== void 0 ? _a : noderef.node.getChild('UnsupportedPrim');
                if (!child)
                    return;
                let localvars = getLocalVariables(child, view.state, lintContext);
                NameCheck(child, 'Local variable', localvars);
            }
        });
    }
    else {
        // Go through the syntax tree
        syntaxTree(view.state)
            .cursor()
            .iterate((noderef) => {
            var _a, _b, _c, _d;
            if (noderef.name == 'BreedSingular' || noderef.name == 'BreedPlural') {
                NameCheck(noderef, 'Breed');
            }
            else if (noderef.name == 'Identifier' && ((_a = noderef.node.parent) === null || _a === void 0 ? void 0 : _a.name) == 'Globals') {
                NameCheck(noderef, 'Global variable');
            }
            else if (noderef.name == 'ProcedureName') {
                if (((_b = noderef.node.parent) === null || _b === void 0 ? void 0 : _b.getChildren('To').length) == 0) {
                    diagnostics.push(getDiagnostic(view, noderef, 'Unrecognized global statement _', 'error'));
                }
                else {
                    NameCheck(noderef, 'Procedure');
                }
            }
            else if (noderef.name == 'BreedsOwn') {
                let own = noderef.node.getChild('Own');
                let breedvars = [];
                if (!own)
                    return;
                let breedName = getCodeName(view.state, noderef);
                breedName = breedName.substring(0, breedName.length - 4);
                noderef.node.getChildren('Identifier').map((child) => {
                    if (breedName == 'turtles') {
                        NameCheck(child, 'Turtle variable');
                    }
                    else if (breedName == 'links') {
                        NameCheck(child, 'Link variable');
                    }
                    else if (breedName == 'patches') {
                        NameCheck(child, 'Patch variable');
                    }
                    else if (isLinkBreed(breedName, lintContext)) {
                        NameCheck(child, 'Link variable', breedvars, true);
                    }
                    else {
                        NameCheck(child, 'Turtle variable', breedvars, true);
                    }
                });
                breedDefined.push(...breedvars);
            }
            else if (noderef.name == 'NewVariableDeclaration' || noderef.name == 'SetVariable') {
                // TODO: Optimize it so that whenever we see a procedure, we check the local variables
                // Now, for each new variable declaration, we look back again
                // It would also solve the issue of arguments & local variables using the same name
                // Since the new variable definition is typically few, not a high priority
                let child = (_c = noderef.node.firstChild) === null || _c === void 0 ? void 0 : _c.nextSibling;
                if (!child)
                    return;
                if (child.name.includes('Reporter') || child.name.includes('Command')) {
                    const value = getCodeName(view.state, child);
                    diagnostics.push(getDiagnostic(view, child, 'Term _ reserved', 'error', value, 'Local variable'));
                }
                else if (noderef.name == 'NewVariableDeclaration') {
                    let localvars = getLocalVariables(child, view.state, lintContext);
                    NameCheck(child, 'Local variable', localvars);
                }
            }
            else if (noderef.name == 'Arguments') {
                let current = [];
                if (((_d = noderef.node.parent) === null || _d === void 0 ? void 0 : _d.name) == 'AnonArguments') {
                    let parent = noderef.node.parent.parent;
                    if (parent) {
                        let prev_node = parent === null || parent === void 0 ? void 0 : parent.cursor().moveTo(parent.from - 2).node;
                        current = getLocalVariables(prev_node, view.state, lintContext);
                    }
                }
                for (var key of ['Identifier', 'UnsupportedPrim']) {
                    noderef.node.getChildren(key).map((child) => {
                        NameCheck(child, 'Argument', current);
                    });
                }
            }
        });
    }
    return diagnostics;
};
const isLinkBreed = (breedName, lintContext) => {
    for (var [name, breed] of lintContext.Breeds) {
        if (breed.Plural.toLowerCase() == breedName.toLowerCase()) {
            return breed.BreedType == BreedType.DirectedLink || breed.BreedType == BreedType.UndirectedLink;
        }
    }
    return null;
};
//# sourceMappingURL=naming-linter.js.map