import { shapes as validShapes } from "./service/languageFacts.js";
import { assertNever, getStart } from "./service/util.js";
import { DiagnosticCategory, SyntaxKind, } from "./types.js";
import { forEachChild } from "./visitor.js";
export function checkSourceFile(file) {
    const g = file.graph;
    if (g) {
        const messages = checkGraphSemantics(file, g);
        if (messages) {
            file.diagnostics.push.apply(file.diagnostics, messages);
        }
    }
}
function getNarrowerNode(offset, prev, toCheck) {
    const prevRange = prev.end - prev.pos;
    if (toCheck.pos <= offset && offset <= toCheck.end) {
        const nrange = toCheck.end - toCheck.pos;
        if (nrange < prevRange) {
            return toCheck;
        }
    }
    return prev;
}
function rangeContainsOffset(range, offset, inclusiveEnd) {
    return inclusiveEnd
        ? range.pos <= offset && offset <= range.end
        : range.pos <= offset && offset < range.end;
}
export function findNodeAtOffset(root, offset, inclusiveEnd = false) {
    if (root.pos === offset && root.pos === root.end)
        return root;
    if (rangeContainsOffset(root, offset, inclusiveEnd)) {
        const narrowerChild = forEachChild(root, child => findNodeAtOffset(child, offset, inclusiveEnd));
        return narrowerChild ? narrowerChild : root;
    }
    return undefined;
}
export function getAllowedEdgeOperation(graph) {
    return graph.kind === SyntaxKind.DirectedGraph
        ? SyntaxKind.DirectedEdgeOp
        : SyntaxKind.UndirectedEdgeOp;
}
function checkGraphSemantics(file, root) {
    const expectedEdgeOp = getAllowedEdgeOperation(root);
    const invalidEdgeRhses = findEdgeErrors(expectedEdgeOp, root);
    const invalidShapes = checkShapeLabelValues(root);
    const invalidEdgeDiagnostics = invalidEdgeRhses === undefined || invalidEdgeRhses.length === 0
        ? []
        : createEdgeViolationDiagnostics(file, expectedEdgeOp, invalidEdgeRhses);
    return [...invalidEdgeDiagnostics, ...invalidShapes];
}
function forEachAssignmentTransitive(root, cb) {
    forEachChild(root, child => {
        if (child.kind === SyntaxKind.Assignment) {
            cb(child);
            return;
        }
        forEachChild(child, c => forEachAssignmentTransitive(c, cb));
    });
}
function checkShapeLabelValues(root) {
    const invalidShapes = [];
    forEachAssignmentTransitive(root, assignment => {
        const { leftId, rightId } = assignment;
        if (leftId.kind !== SyntaxKind.TextIdentifier ||
            rightId.kind !== SyntaxKind.TextIdentifier) {
            return;
        }
        const leftText = leftId.text.trim();
        if (leftText.toLocaleLowerCase() !== "shape") {
            return;
        }
        const rightText = rightId.text.trim();
        const shapeCandidate = rightText.toLowerCase();
        if (validShapes.includes(shapeCandidate)) {
            return;
        }
        invalidShapes.push({
            category: DiagnosticCategory.Warning,
            code: createCheckerError(1),
            message: `Unknown shape "${rightText}".`,
            start: rightId.pos,
            end: rightId.end,
        });
    });
    return invalidShapes;
}
export function findAllEdges(node) {
    const allEdges = [];
    forEachChild(node, child => {
        if (isEdgeStatement(child)) {
            if (child.rhs && child.rhs.length > 0) {
                for (const edgeRhs of child.rhs)
                    allEdges.push(edgeRhs);
            }
        }
        const childEdges = findAllEdges(child);
        if (childEdges && childEdges.length > 0)
            allEdges.push.apply(allEdges, childEdges);
    });
    return allEdges;
}
export function findOptionalSemicolons(node) {
    const statements = findAllStatements(node);
    const terminators = statements.map(p => p.terminator);
    return terminators.filter(t => !!t);
}
function isStatement(node) {
    return (node.kind === SyntaxKind.SubGraphStatement ||
        node.kind === SyntaxKind.EdgeStatement ||
        node.kind === SyntaxKind.NodeStatement ||
        node.kind === SyntaxKind.IdEqualsIdStatement ||
        node.kind === SyntaxKind.AttributeStatement);
}
export function findAllStatements(node, kind) {
    const allStatements = [];
    forEachChild(node, child => {
        if ((kind === undefined && isStatement(child)) || child.kind === kind) {
            allStatements.push(child);
        }
        const childStatements = findAllStatements(child, kind);
        if (childStatements && childStatements.length > 0)
            allStatements.push.apply(allStatements, childStatements);
    });
    return allStatements;
}
function findEdgeErrors(expectedEdgeOp, node) {
    const edges = findAllEdges(node);
    const wrongEdges = edges && edges.length > 0
        ? edges.filter(e => e.operation.kind !== expectedEdgeOp)
        : undefined;
    if (wrongEdges && wrongEdges.length > 0) {
        for (const edge of wrongEdges) {
            edge.operation.flags |= 2;
        }
        return wrongEdges;
    }
    return undefined;
}
function createEdgeViolationDiagnostics(file, expectedEdgeOp, violators) {
    const op = expectedEdgeOp === SyntaxKind.UndirectedEdgeOp ? "--" : "->";
    const graphType = expectedEdgeOp === SyntaxKind.UndirectedEdgeOp ? "undirected" : "directed";
    const message = `Invalid edge operation, use "${op}" in ${graphType} graph`;
    const code = createCheckerError(0);
    const category = DiagnosticCategory.Error;
    for (const edge of violators) {
        edge.operation.flags |= 2;
    }
    return violators.map(edge => {
        const start = getStart(file, edge.operation);
        const end = edge.operation.end;
        return {
            message,
            code,
            category,
            start,
            end,
        };
    });
}
function getInvalidEdgeRhs(allowedOp, edges) {
    const res = [];
    for (const e of edges) {
        if (e.operation.kind !== allowedOp)
            res.push(e);
    }
    return res;
}
export function isAttrStatement(node) {
    return node.kind === SyntaxKind.AttributeStatement;
}
export function isEdgeStatement(node) {
    return node.kind === SyntaxKind.EdgeStatement;
}
export function isSubGraphStatement(node) {
    return node.kind === SyntaxKind.SubGraphStatement;
}
function isGraph(node) {
    return node.kind === SyntaxKind.DirectedGraph || node.kind === SyntaxKind.UndirectedGraph;
}
export function isNodeId(node) {
    return node.kind === SyntaxKind.NodeId;
}
export function edgeStatementHasAttributes(es) {
    return (es.attributes &&
        es.attributes.length > 0 &&
        es.attributes.some(a => a.assignments && a.assignments.length > 0));
}
export function getIdentifierText(n) {
    switch (n.kind) {
        case SyntaxKind.HtmlIdentifier:
            return n.htmlContent;
        case SyntaxKind.TextIdentifier:
            return n.text;
        case SyntaxKind.NumericIdentifier:
            return n.text;
        case SyntaxKind.QuotedTextIdentifier:
            return n.concatenation;
        default:
            return assertNever(n);
    }
}
function createCheckerError(sub) {
    return {
        source: 4,
        sub,
    };
}
export function nodeContainsErrors(node) {
    return (node.flags & 2) === 2;
}
//# sourceMappingURL=checker.js.map