"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LineageVisitor = void 0;
const flinksql_relation_visitor_1 = require("./flinksql-relation.visitor");
class LineageVisitor extends flinksql_relation_visitor_1.FlinkSQLRelationVisitor {
    constructor(options, getTableMetadata) {
        super(getTableMetadata);
        this.options = options;
        this.getTableMetadata = getTableMetadata;
        this.relations = [];
        this.edges = [];
        this.edgeIdSeq = 1;
        this.queryIdSeq = 1;
        this.viewIdSeq = 1;
    }
    findColumnByEdgeIdentity(identity) {
        return this.relations
            .find(r => r.relation.id === identity.relationId)
            ?.relation.columns.find(c => c.id === identity.columnId);
    }
    defaultResult() {
        return;
    }
    getLineage() {
        const cleanedNodes = [];
        const edges = [...this.edges];
        if (this.options.mergeLeaves) {
            const deduplicateTable = new Map();
            const usedRelations = new Map();
            this.relations.forEach(r => {
                if (r.relationPrimary === undefined) {
                    cleanedNodes.push(r.relation);
                    return;
                }
                const key = (0, flinksql_relation_visitor_1.relationPrimaryToString)(r.relationPrimary);
                const entry = usedRelations.get(key);
                if (entry !== undefined) {
                    if (r.relation.type === 'view') {
                        usedRelations.delete(key);
                        usedRelations.set(key, r.relation.id);
                        deduplicateTable.set(entry, r.relation.id);
                    }
                    else {
                        deduplicateTable.set(r.relation.id, entry);
                    }
                }
                else {
                    usedRelations.set(key, r.relation.id);
                    r.relation.label = r.relationPrimary.relationName;
                    cleanedNodes.push(r.relation);
                }
            });
            const usedColumns = new Map();
            edges.forEach(e => {
                const remapEdge = (identity) => {
                    const originalIdentity = { ...identity };
                    const remappedSourceTable = deduplicateTable.get(identity.relationId);
                    if (remappedSourceTable !== undefined) {
                        identity.relationId = remappedSourceTable;
                    }
                    if (identity.columnId !== undefined) {
                        const column = this.findColumnByEdgeIdentity(originalIdentity);
                        if (column !== undefined) {
                            const columns = usedColumns.get(identity.relationId);
                            if (columns !== undefined) {
                                const sameColumn = columns.find(c => c.label === column.label);
                                if (sameColumn !== undefined) {
                                    identity.columnId = sameColumn.id;
                                    return;
                                }
                                columns.push(column);
                            }
                            else {
                                usedColumns.set(identity.relationId, [column]);
                            }
                        }
                    }
                };
                remapEdge(e.source);
                remapEdge(e.target);
            });
            cleanedNodes.forEach(n => {
                if (n.data !== undefined) {
                    const tableColumns = usedColumns.get(n.id);
                    if (tableColumns !== undefined) {
                        n.columns = [...new Set([...n.columns, ...tableColumns])];
                    }
                }
            });
        }
        else {
            this.relations.forEach(t => cleanedNodes.push(t.relation));
        }
        const sourceNodes = new Set();
        const targetNodes = new Set();
        const isStarOrCountStar = (label) => {
            return label === flinksql_relation_visitor_1.STAR_LABEL || label === flinksql_relation_visitor_1.COUNT_STAR_LABEL;
        };
        edges.forEach(e => {
            sourceNodes.add(e.source.relationId);
            targetNodes.add(e.target.relationId);
        });
        cleanedNodes.forEach(t => {
            if (!sourceNodes.has(t.id)) {
                t.isTargetOnly = true;
            }
            if (!targetNodes.has(t.id)) {
                t.isSourceOnly = true;
            }
            if (t.data !== undefined) {
                const ddlColumns = this.relationDDLColumns.get((0, flinksql_relation_visitor_1.relationPrimaryToString)(t.data)) ?? this.getTableMetadata?.(t.data)?.columns;
                if (!this.options.ignoreDDLs) {
                    t.columns = t.columns.map(col => ({
                        ...col,
                        inconsistent: !isStarOrCountStar(col.label) && !!ddlColumns && ddlColumns.every(d => d.label !== col.label)
                    }));
                }
            }
        });
        return {
            nodes: cleanedNodes,
            edges
        };
    }
    aggregateResult(_aggregate, _nextResult) {
        return;
    }
    onColumnReference(source, target, currentClause) {
        this.edges.push({
            id: `${this.edgeIdSeq++}`,
            type: 'edge',
            edgeType: currentClause ?? this.currentQueryRelation?.currentClause,
            source: {
                relationId: source.relationId,
                columnId: source.columnId
            },
            target: {
                relationId: target.relationId,
                columnId: target.columnId
            }
        });
    }
    onRelation(relation, alias) {
        const old = this.relations.findIndex(rel => rel.relation.id === relation.id);
        if (old !== -1) {
            this.relations.splice(old, 1);
        }
        const columns = relation.columns.map(c => {
            return {
                id: c.id,
                label: c.label,
                range: c.range,
                isAssumed: c.isAssumed
            };
        });
        if (relation instanceof flinksql_relation_visitor_1.TableRelation) {
            this.relations.push({
                relationPrimary: relation.tablePrimary,
                relation: {
                    type: 'table',
                    id: relation.id,
                    label: alias !== undefined && alias != relation.tablePrimary.relationName
                        ? `${relation.tablePrimary.relationName} -> ${alias}`
                        : relation.tablePrimary.relationName,
                    range: relation.range,
                    isSourceOnly: false,
                    isTargetOnly: false,
                    data: relation.tablePrimary,
                    columns
                }
            });
        }
        else if (relation instanceof flinksql_relation_visitor_1.QueryRelation) {
            let label = `query_${this.queryIdSeq++}`;
            if (alias !== undefined) {
                label += ` -> ${alias}`;
            }
            this.relations.push({
                relation: {
                    type: 'query',
                    id: relation.id,
                    label,
                    isSourceOnly: false,
                    isTargetOnly: false,
                    range: relation.range,
                    columns
                }
            });
        }
        else if (relation instanceof flinksql_relation_visitor_1.InsertRelation) {
            this.relations.push({
                relationPrimary: relation.sinkTableRelation?.tablePrimary,
                relation: {
                    type: 'insert',
                    id: relation.id,
                    label: relation.sinkTableRelation?.tablePrimary.relationName || relation.id,
                    range: relation.range,
                    isSourceOnly: false,
                    isTargetOnly: false,
                    data: relation.tablePrimary,
                    columns
                }
            });
        }
        else {
            let label = `view_${this.viewIdSeq++}`;
            if (alias !== undefined) {
                label = alias;
            }
            this.relations.push({
                relationPrimary: relation.tablePrimary,
                relation: {
                    type: 'view',
                    id: relation.id,
                    label,
                    range: relation.range,
                    isSourceOnly: false,
                    isTargetOnly: false,
                    data: relation.tablePrimary,
                    columns
                }
            });
        }
    }
}
exports.LineageVisitor = LineageVisitor;
//# sourceMappingURL=lineage.visitor.js.map