import { __awaiter } from "tslib";
import { BuildSnapshot, IntegrateSnapshot } from './code-snapshot';
import { syntaxTree } from '@codemirror/language';
import { Log } from '../../utils/debug-utils';
import { constants, linkVars, patchVars, turtleVars } from '../keywords';
import { getCodeName } from '../utils/code';
import { getPluralName, getSingularName } from '../parsers/breed';
/** FixGeneratedCode: Fix the code generated by the LLM. */
export function FixGeneratedCode(Editor, Source, Parent, try_again = true) {
    return __awaiter(this, void 0, void 0, function* () {
        Source = Source.trim();
        if (Source == '')
            return Source;
        // Remove the trailing semicolon
        if (Source.endsWith(';'))
            Source = Source.slice(0, -1);
        // First pass: prettify the code
        Editor.SetCode(Source);
        Editor.Semantics.PrettifyAll();
        // John: Shall we make the snapshot more powerful and eliminate some of the hard-coded stuff here?
        // e.g. if we give each global variable a slot for comments, we can just identify extra global statements and send into the snapshot
        // Then, rebuild the global statements from the snapshot.
        // Second pass: clean up global statements
        var Snapshot = BuildSnapshot(Editor);
        var intoProcedure = [];
        let changes = [];
        let state = Editor.CodeMirror.state;
        let comments = [];
        let commentsStart = null;
        let commentFrom = null;
        let procedureStart = null;
        let first = { Globals: null, Extensions: null };
        let breeds = [];
        let globals = [];
        let extensions = [];
        let reservedVars = [...turtleVars, ...patchVars, ...linkVars, ...constants];
        var checkForMisplaced = (node) => {
            if (node.name == 'Procedure' && procedureStart == null) {
                procedureStart = node.from;
            }
            else if (node.name == 'Globals' || node.name == 'Extensions') {
                if (first[node.name] == null) {
                    if (procedureStart != null && procedureStart < node.from) {
                        // console.log('Moving ' + node.name + ' to ' + procedureStart);
                        changes.push({
                            from: 0,
                            to: 0,
                            insert: AddComments(state.sliceDoc(node.from, node.to), comments) + '\n\n',
                        });
                        changes.push({
                            from: commentsStart !== null && commentsStart !== void 0 ? commentsStart : node.from,
                            to: node.to,
                            insert: '',
                        });
                        first[node.name] = AddComments(state.sliceDoc(node.from, node.to), comments).length - 1;
                        comments = [];
                        commentFrom = null;
                        commentsStart = null;
                        return;
                    }
                    else {
                        first[node.name] = node.to - 1;
                    }
                }
                else {
                    let to_add = state
                        .sliceDoc(node.from, node.to)
                        .replace(/globals\s*\[/i, '')
                        .replace(/extensions\s*\[/i, '')
                        .replace(/\]/, '');
                    let index = first[node.name];
                    // console.log('combining statements');
                    if (index) {
                        changes.push({
                            from: index,
                            to: index,
                            insert: ' ' + to_add,
                        });
                        changes.push({
                            from: commentsStart !== null && commentsStart !== void 0 ? commentsStart : node.from,
                            to: node.to,
                            insert: '',
                        });
                        comments = [];
                        commentFrom = null;
                        commentsStart = null;
                        return;
                    }
                }
            }
            else if (procedureStart != null &&
                (node.name == 'BreedsOwn' ||
                    (node.name == 'Misplaced' && node.node.resolveInner(node.from, 1).name == 'BreedToken'))) {
                // console.log('Moving ' + node.name + ' to ' + procedureStart);
                changes.push({
                    from: 0,
                    to: 0,
                    insert: AddComments(state.sliceDoc(node.from, node.to), comments) + '\n\n',
                });
                changes.push({
                    from: commentsStart !== null && commentsStart !== void 0 ? commentsStart : node.from,
                    to: node.to,
                    insert: '',
                });
                comments = [];
                commentFrom = null;
                commentsStart = null;
                return;
            }
            if (node.name == 'LineComment') {
                // take only the one preceding comment
                comments = [state.sliceDoc(node.from, node.to)];
                commentsStart = node.from;
                // take all immediately preceding comments
                // comments.push(state.sliceDoc(node.from, node.to));
                // commentsStart = commentsStart ?? node.from;
            }
            else if (comments.length > 0 && !commentFrom) {
                // Record the position of what comes immediately after the comments
                // (this is in case of nesting, so we don't lose the comments)
                commentFrom = node.from;
            }
            else if (comments.length > 0 && commentFrom && node.from > commentFrom) {
                // Discard the comment info when we move on to the next statement
                comments = [];
                commentFrom = null;
                commentsStart = null;
            }
        };
        // Do some re-ordering at the top level
        let cursor = syntaxTree(state).cursor();
        cursor.firstChild();
        if (cursor.node.name == 'Normal') {
            if (cursor.firstChild()) {
                checkForMisplaced(cursor.node);
                // console.log(cursor.node.name, changes);
                while (cursor.nextSibling()) {
                    checkForMisplaced(cursor.node);
                    // console.log(cursor.node.name, changes);
                }
            }
        }
        Editor.Operations.ChangeCode(changes);
        Editor.ForceParse();
        state = Editor.CodeMirror.state;
        changes = [];
        procedureStart = null;
        comments = [];
        commentFrom = null;
        commentsStart = null;
        // Repeatedly make the entire doc lowercased is expensive. Let's cache it.
        let lowerdoc = state.doc.toString().toLowerCase();
        // Go over the syntax tree
        syntaxTree(state)
            .cursor()
            .iterate((noderef) => {
            var _a, _b, _c, _d, _e;
            // Log(noderef.name, comments);
            // check for misplaced non-global statements at the global level
            // Collect them into intoProcedure, and remove them from the code
            // I don't understand why the parser does not recognize `to test end test` as rouge statements.
            if (noderef.name == 'Misplaced' || (noderef.name == 'Procedure' && noderef.node.getChildren('To').length == 0)) {
                let skip = false;
                Log(noderef.name, noderef.from, commentFrom, comments);
                if (noderef.name == 'Misplaced') {
                    let grandchild = (_a = noderef.node.firstChild) === null || _a === void 0 ? void 0 : _a.firstChild;
                    if (grandchild && grandchild.name.includes('0Args')) {
                        skip = true;
                    }
                }
                if (!skip) {
                    intoProcedure.push(AddComments(state.sliceDoc(noderef.from, noderef.to), comments));
                }
                changes.push({
                    from: commentsStart !== null && commentsStart !== void 0 ? commentsStart : noderef.from,
                    to: Math.min(noderef.to + 1, lowerdoc.length),
                    insert: '',
                });
                return false;
            }
            // check for misplaced global statements
            // Move them to the top of the program
            if (noderef.name == 'Error') {
                Log(noderef.name, comments);
                changes.push({
                    from: commentsStart !== null && commentsStart !== void 0 ? commentsStart : noderef.from,
                    to: noderef.to + 1,
                    insert: '',
                });
                changes.push({
                    from: 0,
                    to: 0,
                    insert: AddComments(state.sliceDoc(noderef.from, noderef.to), comments) + '\n',
                });
                return false;
            }
            else if (noderef.name == 'Globals') {
                let len = noderef.node.getChildren('Identifier').length;
                let deleted = 0;
                let temp_changes = [];
                noderef.node.getChildren('Identifier').map((child) => {
                    let value = getCodeName(state, child);
                    if (globals.includes(value) || reservedVars.includes(value)) {
                        temp_changes.push({
                            from: child.from,
                            to: child.to,
                            insert: '',
                        });
                        deleted += 1;
                    }
                    globals.push(value);
                });
                if (deleted == len || len == 0) {
                    changes.push({
                        from: commentsStart !== null && commentsStart !== void 0 ? commentsStart : noderef.from,
                        to: noderef.to + 1,
                        insert: '',
                    });
                }
                else {
                    changes = changes.concat(temp_changes);
                }
            }
            else if (noderef.name == 'BreedsOwn') {
                let vars = [];
                let len = noderef.node.getChildren('Identifier').length;
                let deleted = 0;
                let temp_changes = [];
                noderef.node.getChildren('Identifier').map((child) => {
                    let value = state.sliceDoc(child.from, child.to);
                    if (vars.includes(value.toLowerCase()) || reservedVars.includes(value.toLowerCase())) {
                        temp_changes.push({
                            from: child.from,
                            to: child.to,
                            insert: '',
                        });
                        deleted += 1;
                    }
                    vars.push(value.toLowerCase());
                });
                if (deleted == len || len == 0) {
                    changes.push({
                        from: commentsStart !== null && commentsStart !== void 0 ? commentsStart : noderef.from,
                        to: noderef.to + 1,
                        insert: '',
                    });
                }
                else {
                    changes = changes.concat(temp_changes);
                }
            }
            else if (noderef.name == 'Breed') {
                let change = FixBreed(noderef.node, state, breeds);
                if (change != null) {
                    changes.push({
                        from: change.from,
                        to: change.to,
                        insert: change.insert,
                    });
                }
                let plural = noderef.node.getChild('BreedPlural');
                let singular = noderef.node.getChild('BreedSingular');
                if (plural && singular) {
                    breeds.push(state.sliceDoc(plural.from, plural.to).toLowerCase());
                    breeds.push(state.sliceDoc(singular.from, singular.to).toLowerCase());
                }
            }
            else if (noderef.name == 'Extensions') {
                let len = noderef.node.getChildren('Identifier').length;
                let deleted = 0;
                let temp_changes = [];
                noderef.node.getChildren('Identifier').map((child) => {
                    let value = getCodeName(state, child);
                    if (lowerdoc.includes('(' + value + ':') ||
                        lowerdoc.includes(' ' + value + ':') ||
                        lowerdoc.includes('\n' + value + ':') ||
                        extensions.includes(value)) {
                        temp_changes.push({
                            from: child.from,
                            to: child.to,
                            insert: '',
                        });
                        deleted += 1;
                    }
                    extensions.push(value);
                });
                if (deleted == len || len == 0) {
                    changes.push({
                        from: commentsStart !== null && commentsStart !== void 0 ? commentsStart : noderef.from,
                        to: noderef.to + 1,
                        insert: '',
                    });
                }
                else {
                    changes = changes.concat(temp_changes);
                }
            }
            else if (noderef.name == 'SpecialCommand0Args' &&
                state.sliceDoc(noderef.from, noderef.to).toLowerCase() == 'setup') {
                let procedure = (_c = (_b = noderef.node.parent) === null || _b === void 0 ? void 0 : _b.parent) === null || _c === void 0 ? void 0 : _c.parent;
                let name = procedure === null || procedure === void 0 ? void 0 : procedure.getChild('ProcedureName');
                if ((procedure === null || procedure === void 0 ? void 0 : procedure.name) == 'Procedure' && state.sliceDoc(name === null || name === void 0 ? void 0 : name.from, name === null || name === void 0 ? void 0 : name.to).toLowerCase() == 'go') {
                    changes.push({
                        from: commentsStart !== null && commentsStart !== void 0 ? commentsStart : noderef.from,
                        to: noderef.to + 1,
                        insert: '',
                    });
                }
            }
            if (noderef.name == 'Procedure' && noderef.node.getChildren('ProcedureContent').length == 1) {
                let child = (_e = (_d = noderef.node
                    .getChild('ProcedureContent')) === null || _d === void 0 ? void 0 : _d.getChild('CommandStatement')) === null || _e === void 0 ? void 0 : _e.getChild('SpecialCommand0Args');
                if (child && lowerdoc.includes('to ' + getCodeName(state, child))) {
                    changes.push({
                        from: noderef.from,
                        to: noderef.to,
                        insert: '',
                    });
                }
            }
            else if (noderef.name == 'Procedure' && noderef.node.getChildren('ProcedureContent').length == 0) {
                // JC: I am confused. What does this one do?
                let child = noderef.node.getChild('ProcedureName');
                let name = getCodeName(state, child);
                let matches = lowerdoc.match(new RegExp(name, 'gi'));
                if (matches && matches.length == 1) {
                    changes.push({ from: noderef.from, to: noderef.to, insert: '' });
                }
            }
            else if (noderef.name == 'ProcedureName') {
                // JC: The fix is too crude here (i.e. will change all ). Disable it temporarily.
                /* let name = state.sliceDoc(noderef.from, noderef.to).toLowerCase();
                if (reserved.includes(name)) {
                  let pieces = name.split('-');
                  pieces[0] = 'setup';
                  changes.push({
                    from: noderef.from,
                    to: noderef.to,
                    insert: pieces.join('-'),
                  });
                } */
            }
            // Record the position of the first procedure to know where to add 'play'
            if (!procedureStart && noderef.name == 'Procedure')
                procedureStart = noderef.from;
            // Record the position of the comments
            if (noderef.name == 'LineComment') {
                // take only the one preceding comment
                comments = [state.sliceDoc(noderef.from, noderef.to)];
                commentsStart = noderef.from;
                // take all immediately preceding comments
                // comments.push(state.sliceDoc(noderef.from, noderef.to));
                // commentsStart = commentsStart ?? noderef.from;
            }
            else if (comments.length > 0 && !commentFrom) {
                // Record the position of what comes immediately after the comments
                // (this is in case of nesting, so we don't lose the comments)
                commentFrom = noderef.from;
            }
            else if (comments.length > 0 && commentFrom && noderef.from > commentFrom) {
                // Discard the comment info when we move on to the next statement
                comments = [];
                commentFrom = null;
                commentsStart = null;
            }
        });
        // If there are rogue statements, wrap them into a procedure
        if (intoProcedure.length != 0) {
            changes.push({
                from: procedureStart !== null && procedureStart !== void 0 ? procedureStart : lowerdoc.length,
                to: procedureStart !== null && procedureStart !== void 0 ? procedureStart : lowerdoc.length,
                insert: '\nto play\n' + intoProcedure.join('\n') + '\nend\n\n',
            });
        }
        // Send in the changes
        Editor.Operations.ChangeCode(changes);
        // I just realized that we cannot do this in the current fashion. e.g. When there are multiple breed fixes with the same name, we might introduce multiple statements.
        /*// Fourth pass: lint the code and apply the actions
        let Errors = await Editor.ForceLintAsync();
        console.log(Errors);
        let did_actions = false;
        Errors.map((error) => {
          if (error.actions) {
            did_actions = true;
            error.actions[0].apply(Editor.CodeMirror, error.from, error.to);
          }
        });
        // If there were actions, run the auto-fix again
        if (did_actions && try_again) await FixGeneratedCode(Editor, Editor.CodeMirror.state.doc.toString(), Parent, false);*/
        // Fifth pass: re-introduce the snapshot
        IntegrateSnapshot(Editor, Snapshot);
        if (Parent)
            IntegrateSnapshot(Editor, Parent);
        // Final pass: prettify the code once again
        Editor.Semantics.PrettifyAll();
        return Editor.GetCode().trim();
    });
}
/** FixBreed: Fix the breed names. */
function FixBreed(node, state, breeds) {
    let singular = node.getChild('BreedSingular');
    let plural = node.getChild('BreedPlural');
    let invalid_sing = ['turtle', 'link', 'patch', ...breeds].includes(state.sliceDoc(singular === null || singular === void 0 ? void 0 : singular.from, singular === null || singular === void 0 ? void 0 : singular.to).toLowerCase()) ||
        state.sliceDoc(singular === null || singular === void 0 ? void 0 : singular.from, singular === null || singular === void 0 ? void 0 : singular.to).toLowerCase() ==
            state.sliceDoc(plural === null || plural === void 0 ? void 0 : plural.from, plural === null || plural === void 0 ? void 0 : plural.to).toLowerCase();
    let invalid_plur = ['turtles', 'links', 'patches', ...breeds].includes(state.sliceDoc(plural === null || plural === void 0 ? void 0 : plural.from, plural === null || plural === void 0 ? void 0 : plural.to).toLowerCase());
    // console.log("BREED FIXING",singular, plural, invalid_sing, invalid_plur);
    if (singular && plural && invalid_sing && invalid_plur) {
        return {
            from: node.from,
            to: node.to,
            insert: '',
        };
    }
    else if (singular && invalid_sing) {
        return {
            from: singular === null || singular === void 0 ? void 0 : singular.from,
            to: singular === null || singular === void 0 ? void 0 : singular.to,
            insert: getSingularName(state.sliceDoc(plural === null || plural === void 0 ? void 0 : plural.from, plural === null || plural === void 0 ? void 0 : plural.to)),
        };
    }
    else if (plural && invalid_plur) {
        return {
            from: plural === null || plural === void 0 ? void 0 : plural.from,
            to: plural === null || plural === void 0 ? void 0 : plural.to,
            insert: getPluralName(state.sliceDoc(singular === null || singular === void 0 ? void 0 : singular.from, singular === null || singular === void 0 ? void 0 : singular.to)),
        };
    }
    else {
        return null;
    }
}
/** AddComments: Add comments to the beginning of the string.*/
function AddComments(str, comments) {
    if (comments.length == 0)
        return str;
    else
        return comments.join('\n') + '\n' + str;
}
//# sourceMappingURL=fix-generated-code.js.map