"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.removeEdgesByNodeId = exports.addFromCreateContext = exports.ignoreInsertSource = exports.FlinkStructureListener = exports.StructureNodeTypeEnum = void 0;
const lsp = __importStar(require("vscode-languageserver"));
const protocol_translation_1 = require("../protocol-translation");
const utils_1 = require("../utils");
var StructureNodeTypeEnum;
(function (StructureNodeTypeEnum) {
    StructureNodeTypeEnum["SINK"] = "sink";
    StructureNodeTypeEnum["SINK_DB"] = "sink_db";
    StructureNodeTypeEnum["SOURCE"] = "source";
    StructureNodeTypeEnum["SOURCE_DB"] = "source_db";
    StructureNodeTypeEnum["DIM"] = "dim";
    StructureNodeTypeEnum["SNAPSHOT"] = "snapshot";
    StructureNodeTypeEnum["VIEW"] = "view";
    StructureNodeTypeEnum["SELECT"] = "select";
    StructureNodeTypeEnum["FILTER"] = "filter";
    StructureNodeTypeEnum["GROUP"] = "group";
    StructureNodeTypeEnum["UNION"] = "union";
    StructureNodeTypeEnum["EXCEPT"] = "except";
    StructureNodeTypeEnum["INTERSECT"] = "intersect";
    StructureNodeTypeEnum["JOIN"] = "join";
    StructureNodeTypeEnum["TOP_N"] = "top_n";
    StructureNodeTypeEnum["CORRELATE"] = "correlate";
    StructureNodeTypeEnum["UD_TF"] = "ud_tf";
    StructureNodeTypeEnum["CEP"] = "cep";
})(StructureNodeTypeEnum = exports.StructureNodeTypeEnum || (exports.StructureNodeTypeEnum = {}));
class FlinkStructureListener {
    constructor(document) {
        this.document = document;
        this.nodes = [];
        this.edges = [];
        this.id = 0;
        this.originalText = document.getText();
    }
    getId() {
        this.id++;
        return this.id;
    }
    addStructureNode(ctx, type, customName) {
        if (!ctx.stop) {
            return;
        }
        const range = lsp.Range.create(this.document.positionAt(ctx.start.startIndex), this.document.positionAt(ctx.stop.stopIndex + 1));
        // TODO: add specific name, not simply the whole node's name
        const newNode = {
            id: this.getId(),
            name: customName ?? this.document.getText(range).trim(),
            type,
            range
        };
        // add respective edges
        this.nodes.forEach(node => {
            if ((0, protocol_translation_1.rangeInRange)(this.document, node.range, newNode.range)) {
                const distance = (0, protocol_translation_1.rangeDistance)(this.document, node.range, newNode.range);
                const existingEdges = this.edges.filter(e => e.to === newNode.id);
                if (existingEdges.length === 0) {
                    this.edges.push({ from: node.id, to: newNode.id, distance });
                }
                else {
                    let minDistance = Infinity;
                    let directEdge;
                    existingEdges.forEach(edge => {
                        if (distance < edge.distance && minDistance > edge.distance) {
                            minDistance = edge.distance;
                            directEdge = edge;
                        }
                    });
                    if (directEdge) {
                        const index = this.edges.findIndex(e => e === directEdge);
                        this.edges.splice(index, 1);
                        this.edges.push({ from: node.id, to: newNode.id, distance });
                    }
                }
            }
        });
        // push the new node itself to the list
        this.nodes.push(newNode);
    }
    getNodes() {
        return this.nodes;
    }
    getEdges() {
        return this.edges.map(edge => ({
            from: edge.from,
            to: edge.to
        }));
    }
    enterInsert(ctx) {
        this.addStructureNode(ctx, StructureNodeTypeEnum.SINK);
    }
    enterQuery(ctx) {
        this.addStructureNode(ctx, StructureNodeTypeEnum.SELECT);
    }
    enterWhere(ctx) {
        this.addStructureNode(ctx, StructureNodeTypeEnum.FILTER);
    }
    enterGroupBy(ctx) {
        this.addStructureNode(ctx, StructureNodeTypeEnum.GROUP);
    }
    enterOrderBy(ctx) {
        this.addStructureNode(ctx, StructureNodeTypeEnum.TOP_N);
    }
    enterMatchRecognize(ctx) {
        this.addStructureNode(ctx, StructureNodeTypeEnum.CEP);
    }
    enterMapQuery(ctx) {
        if (ctx.UNION()) {
            this.addStructureNode(ctx, StructureNodeTypeEnum.UNION);
        }
        else if (ctx.INTERSECT()) {
            this.addStructureNode(ctx, StructureNodeTypeEnum.INTERSECT);
        }
        else if (ctx.EXCEPT()) {
            this.addStructureNode(ctx, StructureNodeTypeEnum.EXCEPT);
        }
    }
    enterMapTable(ctx) {
        // TODO: correlate
        this.addStructureNode(ctx, StructureNodeTypeEnum.JOIN);
    }
    enterTableReference(ctx) {
        if (ctx.tableDim()) {
            this.addStructureNode(ctx, StructureNodeTypeEnum.DIM);
        }
    }
    enterTableSource(ctx) {
        this.addStructureNode(ctx, StructureNodeTypeEnum.SOURCE);
    }
    enterTableLateral(ctx) {
        this.addStructureNode(ctx, StructureNodeTypeEnum.UD_TF);
    }
    enterCommaTable(ctx) {
        if (ctx.tableExpr().tableReference().tableDim()) {
            this.addStructureNode(ctx, StructureNodeTypeEnum.CORRELATE);
        }
        else {
            this.addStructureNode(ctx, StructureNodeTypeEnum.JOIN);
        }
    }
    enterAsTable(ctx) {
        this.addStructureNode(ctx, StructureNodeTypeEnum.SOURCE);
    }
    enterCreateTable(ctx) {
        if (ctx.asTable()) {
            this.addStructureNode(ctx, StructureNodeTypeEnum.SINK);
        }
    }
    enterAsDatabase(ctx) {
        this.addStructureNode(ctx, StructureNodeTypeEnum.SOURCE_DB);
    }
    enterCreateDatabase(ctx) {
        if (ctx.asDatabase()) {
            this.addStructureNode(ctx, StructureNodeTypeEnum.SINK_DB);
        }
    }
    enterCreateView(ctx) {
        this.addStructureNode(ctx, StructureNodeTypeEnum.VIEW, ctx.view().text.trim());
    }
}
exports.FlinkStructureListener = FlinkStructureListener;
function ignoreInsertSource(nodes, edges) {
    for (let i = 0; i < edges.length; i++) {
        const edge = edges[i];
        const fromNode = nodes.find(node => node.id === edge.from);
        const toNode = nodes.find(node => node.id === edge.to);
        if (!fromNode || !toNode) {
            return { nodes, edges };
        }
        if (fromNode.type === StructureNodeTypeEnum.SINK && toNode.type === StructureNodeTypeEnum.SOURCE) {
            const newEdges = [...edges];
            newEdges.splice(i, 1);
            return { edges: newEdges, nodes: nodes.filter(n => n.id !== toNode.id) };
        }
    }
    return { nodes, edges };
}
exports.ignoreInsertSource = ignoreInsertSource;
function addFromCreateContext(nodes, edges) {
    const viewNodes = nodes.filter(node => node.type === StructureNodeTypeEnum.VIEW);
    if (viewNodes.length > 0) {
        nodes.forEach(node => {
            if (node.type === StructureNodeTypeEnum.SOURCE) {
                const matchedViewNode = viewNodes.find(view => view.name === node.name);
                if (matchedViewNode) {
                    const sourceEdge = edges.find(edge => edge.to === node.id);
                    if (sourceEdge) {
                        const newEdge = {
                            from: sourceEdge.from,
                            to: matchedViewNode.id
                        };
                        // remove redundant nodes and edges after merged the context
                        (0, utils_1.removeElement)(node, nodes);
                        edges = removeEdgesByNodeId([node.id], [...edges]);
                        edges.push(newEdge);
                    }
                }
            }
        });
    }
    return { nodes, edges };
}
exports.addFromCreateContext = addFromCreateContext;
// Remove all edges that includes id in ids
function removeEdgesByNodeId(ids, edges) {
    return edges.filter(edge => !ids.includes(edge.to) && !ids.includes(edge.from));
}
exports.removeEdgesByNodeId = removeEdgesByNodeId;
//# sourceMappingURL=structure.listener.js.map