import { SyntaxKind, forEachChild, isIdentifierNode, } from "../index.js";
import { assertNever, getStart } from "./util.js";
import { getAllowedEdgeOperation, findAllEdges, findNodeAtOffset, isEdgeStatement, isNodeId, getIdentifierText, isAttrStatement, edgeStatementHasAttributes, nodeContainsErrors, } from "../checker.js";
import * as ChangeEdgeOpCommand from "./command/ChangeEdgeOpCommand.js";
import * as ChangeAllOtherEdgeOpsAndFixGraphCommand from "./command/ChangeAllOtherEdgeOpsAndFixGraphCommand.js";
import * as ConsolidateDescendantsCommand from "./command/ConsolidateDescendantsCommand.js";
import * as RemoveSemicolonsCommand from "./command/RemoveSemicolons.js";
import { getOppositeKind, getOppositeEdgeOp, getAllowedOp } from "./command/common.js";
export function getCodeActions(doc, sourceFile, range, _context) {
    let actions = getActionsFromDiagnostics(doc, sourceFile, range);
    const general = getGeneralRefactorings(doc, sourceFile, range);
    if (general) {
        if (!actions)
            actions = general;
        else
            actions.push.apply(actions, general);
    }
    return actions;
}
function getActionsFromDiagnostics(doc, file, range) {
    const ds = file.diagnostics;
    if (!ds || ds.length === 0)
        return undefined;
    const rangeStartOffset = doc.offsetAt(range.start);
    const rangeEndOffset = doc.offsetAt(range.end);
    const res = [];
    for (const d of ds) {
        if (isInRange(rangeStartOffset, rangeEndOffset, d.start, d.end)) {
            const commands = getCommandsForDiagnostic(doc, file, d);
            if (commands && commands.length > 0)
                res.push.apply(res, commands);
        }
    }
    return res.length === 0 ? undefined : res;
}
function getGeneralRefactorings(doc, file, range) {
    if (!file.graph)
        return undefined;
    const g = file.graph;
    const kw = g.keyword;
    const rangeStartOffset = doc.offsetAt(range.start);
    const rangeEndOffset = doc.offsetAt(range.end);
    const keywordStart = getStart(file, kw);
    const res = [];
    if (isInRange(rangeStartOffset, rangeEndOffset, keywordStart, kw.end)) {
        if (!subtreeContainsErrors(g)) {
            const oppositeGraphType = getOppositeKind(kw.kind);
            const convertGraph = convertGraphTypeCommand(file, g, oppositeGraphType);
            res.push(convertGraph);
        }
    }
    if (rangeStartOffset === rangeEndOffset) {
        const candidates = [];
        let clickedNode = findNodeAtOffset(g, rangeStartOffset);
        if (clickedNode && !!clickedNode.parent) {
            if (clickedNode.kind === SyntaxKind.SemicolonToken) {
                res.push(RemoveSemicolonsCommand.create());
            }
            if (isIdentifierNode(clickedNode)) {
                clickedNode = clickedNode.parent;
            }
            const clickedEdgeStatement = clickedNode.parent;
            if (clickedEdgeStatement && !subtreeContainsErrors(clickedEdgeStatement)) {
                if (isEdgeStatement(clickedEdgeStatement)
                    && clickedEdgeStatement.rhs.length === 1
                    && isNodeId(clickedEdgeStatement.source)
                    && !edgeStatementHasAttributes(clickedEdgeStatement)) {
                    candidates.push(clickedEdgeStatement);
                    const source = clickedEdgeStatement.source;
                    const sourceText = getIdentifierText(source.id);
                    const graphParent = clickedEdgeStatement.parent;
                    if (graphParent) {
                        let hasVisitedStatement = false;
                        let hasVisitedNodeModifier = false;
                        forEachChild(graphParent, statement => {
                            if (statement === clickedEdgeStatement) {
                                hasVisitedStatement = true;
                                return undefined;
                            }
                            if (hasVisitedNodeModifier) {
                                return;
                            }
                            else if (hasVisitedStatement) {
                                if (isAttrStatement(statement)
                                    || subtreeContainsErrors(statement)) {
                                    hasVisitedNodeModifier = true;
                                    return true;
                                }
                            }
                            if (hasVisitedStatement) {
                                if (isEdgeStatement(statement)
                                    && statement.rhs.length === 1
                                    && !edgeStatementHasAttributes(statement)) {
                                    const statementSource = statement.source;
                                    if (isNodeId(statementSource)) {
                                        const lowerSourceText = getIdentifierText(statementSource.id);
                                        if (sourceText === lowerSourceText) {
                                            candidates.push(statement);
                                        }
                                    }
                                }
                            }
                            return undefined;
                        });
                    }
                }
                if (candidates.length > 1) {
                    const action = ConsolidateDescendantsCommand.create(candidates, true);
                    res.push(action);
                }
            }
        }
    }
    return res.length === 0 ? undefined : res;
    ;
}
function getCommandsForDiagnostic(doc, file, d) {
    switch (d.code.source) {
        case 1: return getScannerErrorCommand(doc, file, d, d.code);
        case 2: return getParserErrorCommand(doc, file, d, d.code);
        case 4: return getCheckerErrorCommand(doc, file, d, d.code);
        default: return assertNever(d.code);
    }
}
function getScannerErrorCommand(_doc, _file, d, code) {
    console.assert(d.code.source === 1);
    console.assert(code.source === 1);
    return undefined;
}
function getParserErrorCommand(_doc, _file, d, code) {
    console.assert(d.code.source === 2);
    console.assert(code.source === 2);
    return undefined;
}
function getCheckerErrorCommand(_doc, file, d, code) {
    console.assert(d.code.source === 4);
    console.assert(code.source === 4);
    switch (code.sub) {
        case 0: {
            const graph = file.graph;
            if (!graph)
                return undefined;
            const allowedOp = getAllowedEdgeOperation(graph);
            const wrongOp = getOppositeEdgeOp(allowedOp);
            const kwk = graph.keyword.kind;
            const fixSingleEdge = ChangeEdgeOpCommand.create(d.start, d.end, allowedOp, wrongOp);
            const fixAll = convertGraphTypeCommand(file, graph, kwk);
            const convertToThisWrongType = convertGraphTypeCommand(file, graph, getOppositeKind(kwk));
            return [
                fixSingleEdge,
                fixAll,
                convertToThisWrongType,
            ];
        }
    }
}
function convertGraphTypeCommand(file, graph, changeToGraphType) {
    const changeToEdgeOp = getAllowedOp(changeToGraphType);
    const allEdges = findAllEdges(graph);
    const edgeOffsets = allEdges
        .filter(e => e.operation.kind !== changeToEdgeOp)
        .map(e => ({
        start: getStart(file, e.operation),
        end: e.operation.end
    }));
    const graphTypeOffset = {
        start: getStart(file, graph.keyword),
        end: graph.keyword.end
    };
    return ChangeAllOtherEdgeOpsAndFixGraphCommand.create(edgeOffsets, changeToEdgeOp, graphTypeOffset, graph.keyword.kind, changeToGraphType);
}
function isInRange(rangeStartOffset, rangeEndOffset, startOffset, endOffset) {
    if (rangeStartOffset === rangeEndOffset)
        return startOffset <= rangeStartOffset && rangeEndOffset <= endOffset;
    if (rangeStartOffset === startOffset && rangeEndOffset === endOffset)
        return true;
    return false;
}
const commandHandlers = {
    ["DOT.changeEdgeOp"]: ChangeEdgeOpCommand.execute,
    ["DOT.convertGraphType"]: ChangeAllOtherEdgeOpsAndFixGraphCommand.execute,
    ["DOT.consolidateDescendants"]: ConsolidateDescendantsCommand.execute,
    ["DOT.removeSemicolons"]: RemoveSemicolonsCommand.execute,
};
export function getAvailableCommands() {
    return Object.keys(commandHandlers);
}
export function executeCommand(doc, sourceFile, cmd) {
    const handler = commandHandlers[cmd.command];
    return handler === undefined
        ? undefined
        : handler(doc, sourceFile, cmd);
}
function subtreeContainsErrors(node) {
    if (nodeContainsErrors(node))
        return true;
    let hasError = false;
    forEachChild(node, child => {
        if (nodeContainsErrors(child)) {
            hasError = true;
        }
        if (!hasError) {
            hasError = subtreeContainsErrors(child);
        }
    });
    return hasError;
}
//# sourceMappingURL=codeAction.js.map