/**
 * Traverses and optionally transforms a statement node.
 *
 * @example
 * ```ts
 * const rewritten = visitStatement(statement, visitor);
 * ```
 */
export function visitStatement(statement, visitor) {
    return visitNode(statement, visitor, {
        parent: null,
        property: null,
    });
}
/**
 * Specialised helper for visiting select statements.
 *
 * @example
 * ```ts
 * const rewritten = visitSelectStatement(select, visitor);
 * ```
 */
export function visitSelectStatement(statement, visitor) {
    return visitStatement(statement, visitor);
}
function visitNode(node, visitor, context) {
    let current = applyEnterHandlers(node, visitor, context);
    current = traverseNode(current, visitor, context);
    current = applyExitHandlers(current, visitor, context);
    return current;
}
function applyEnterHandlers(node, visitor, context) {
    let current = node;
    const genericResult = visitor.enter?.(current, context);
    if (genericResult) {
        current = genericResult;
    }
    const specific = getHandler(visitor, current.node_kind);
    if (specific) {
        const result = specific(current, context);
        if (result) {
            current = result;
        }
    }
    return current;
}
function applyExitHandlers(node, visitor, context) {
    let current = node;
    const specific = getExitHandler(visitor, current.node_kind);
    if (specific) {
        const result = specific(current, context);
        if (result) {
            current = result;
        }
    }
    const genericResult = visitor.exit?.(current, context);
    if (genericResult) {
        current = genericResult;
    }
    return current;
}
function getHandler(visitor, kind) {
    return visitor[kind];
}
function getExitHandler(visitor, kind) {
    return visitor[`${kind}_exit`];
}
function traverseNode(node, visitor, context) {
    switch (node.node_kind) {
        case "segmented_statement":
            return traverseSegmentedStatement(node, visitor);
        case "compound_select":
            return traverseCompoundSelect(node, visitor);
        case "select_statement":
            return traverseSelectStatement(node, visitor);
        case "with_clause":
            return traverseWithClause(node, visitor);
        case "common_table_expression":
            return traverseCommonTableExpression(node, visitor);
        case "insert_statement":
            return traverseInsertStatement(node, visitor);
        case "on_conflict_clause":
            return traverseOnConflictClause(node, visitor);
        case "on_conflict_target":
            return traverseOnConflictTarget(node, visitor);
        case "on_conflict_do_update":
            return traverseOnConflictDoUpdate(node, visitor);
        case "on_conflict_do_nothing":
            return node;
        case "update_statement":
            return traverseUpdateStatement(node, visitor);
        case "delete_statement":
            return traverseDeleteStatement(node, visitor);
        case "from_clause":
            return traverseFromClause(node, visitor);
        case "join_clause":
            return traverseJoinClause(node, visitor);
        case "table_reference":
            return traverseTableReference(node, visitor);
        case "subquery":
            return traverseSubquery(node, visitor);
        case "subquery_expression":
            return traverseSubqueryExpression(node, visitor);
        case "order_by_item":
            return traverseOrderByItem(node, visitor);
        case "set_clause":
            return traverseSetClause(node, visitor);
        case "select_expression":
            return traverseSelectExpression(node, visitor);
        case "select_qualified_star":
            return traverseSelectQualifiedStar(node, visitor);
        case "column_reference":
            return traverseColumnReference(node, visitor);
        case "grouped_expression":
            return traverseGroupedExpression(node, visitor);
        case "binary_expression":
            return traverseBinaryExpression(node, visitor);
        case "unary_expression":
            return traverseUnaryExpression(node, visitor);
        case "in_list_expression":
            return traverseInListExpression(node, visitor);
        case "between_expression":
            return traverseBetweenExpression(node, visitor);
        case "case_expression":
            return traverseCaseExpression(node, visitor);
        case "exists_expression":
            return traverseExistsExpression(node, visitor);
        case "function_call":
            return traverseFunctionCall(node, visitor);
        case "insert_values":
            return traverseInsertValues(node, visitor);
        case "object_name":
            return traverseObjectName(node, visitor);
        case "window_specification":
            return traverseWindowSpecification(node, visitor);
        case "window_frame":
            return traverseWindowFrame(node, visitor);
        case "window_frame_bound":
            return traverseWindowFrameBound(node, visitor);
        case "window_reference":
            return traverseWindowReference(node, visitor);
        case "select_star":
        case "literal":
        case "parameter":
        case "identifier":
        case "raw_fragment":
        case "all_columns":
            return node;
        default:
            return node;
    }
}
/**
 * Visits each segment in a statement sequence.
 *
 * @example
 * ```ts
 * const rewritten = traverseStatementSequence(node, visitor);
 * ```
 */
function traverseSegmentedStatement(node, visitor) {
    const segments = visitArray(node.segments, node, "segments", visitor);
    if (segments === node.segments) {
        return node;
    }
    return {
        ...node,
        segments: segments,
    };
}
function traverseCompoundSelect(node, visitor) {
    let changed = false;
    const withClause = visitOptional(node.with_clause, node, "with_clause", visitor);
    if (withClause !== node.with_clause) {
        changed = true;
    }
    const first = visitNode(node.first, visitor, {
        parent: node,
        property: "first",
    });
    if (first !== node.first) {
        changed = true;
    }
    const compounds = node.compounds.map((branch, index) => {
        const visited = visitNode(branch.select, visitor, {
            parent: node,
            property: "compounds",
            index,
        });
        if (visited !== branch.select) {
            changed = true;
            return {
                ...branch,
                select: visited,
            };
        }
        return branch;
    });
    if (!changed) {
        return node;
    }
    return {
        ...node,
        first,
        compounds,
        with_clause: withClause,
    };
}
function traverseSelectStatement(node, visitor) {
    let changed = false;
    const withClause = visitOptional(node.with_clause, node, "with_clause", visitor);
    if (withClause !== node.with_clause) {
        changed = true;
    }
    const projection = visitArray(node.projection, node, "projection", visitor);
    if (projection !== node.projection) {
        changed = true;
    }
    const fromClauses = visitArray(node.from_clauses, node, "from_clauses", visitor);
    if (fromClauses !== node.from_clauses) {
        changed = true;
    }
    const whereClause = visitOptional(node.where_clause, node, "where_clause", visitor);
    if (whereClause !== node.where_clause) {
        changed = true;
    }
    const groupBy = visitArray(node.group_by, node, "group_by", visitor);
    if (groupBy !== node.group_by) {
        changed = true;
    }
    const orderBy = visitArray(node.order_by, node, "order_by", visitor);
    if (orderBy !== node.order_by) {
        changed = true;
    }
    const limit = visitOptional(node.limit, node, "limit", visitor);
    if (limit !== node.limit) {
        changed = true;
    }
    const offset = visitOptional(node.offset, node, "offset", visitor);
    if (offset !== node.offset) {
        changed = true;
    }
    if (!changed) {
        return node;
    }
    return {
        ...node,
        projection: projection,
        from_clauses: fromClauses,
        where_clause: whereClause,
        group_by: groupBy,
        order_by: orderBy,
        limit: limit,
        offset: offset,
        with_clause: withClause,
    };
}
function traverseWithClause(node, visitor) {
    const ctes = visitArray(node.ctes, node, "ctes", visitor);
    if (ctes === node.ctes) {
        return node;
    }
    return {
        ...node,
        ctes: ctes,
    };
}
function traverseCommonTableExpression(node, visitor) {
    let changed = false;
    const name = visitNode(node.name, visitor, createContext(node, "name"));
    if (name !== node.name) {
        changed = true;
    }
    const columns = visitArray(node.columns, node, "columns", visitor);
    if (columns !== node.columns) {
        changed = true;
    }
    const statement = visitNode(node.statement, visitor, createContext(node, "statement"));
    if (statement !== node.statement) {
        changed = true;
    }
    if (!changed) {
        return node;
    }
    return {
        ...node,
        name: name,
        columns: columns,
        statement: statement,
    };
}
function traverseInsertStatement(node, visitor) {
    let changed = false;
    const target = visitNode(node.target, visitor, createContext(node, "target"));
    if (target !== node.target) {
        changed = true;
    }
    const columns = visitArray(node.columns, node, "columns", visitor);
    if (columns !== node.columns) {
        changed = true;
    }
    const source = visitNode(node.source, visitor, createContext(node, "source"));
    if (source !== node.source) {
        changed = true;
    }
    const onConflict = visitOptional(node.on_conflict, node, "on_conflict", visitor);
    if (onConflict !== node.on_conflict) {
        changed = true;
    }
    if (!changed) {
        return node;
    }
    return {
        ...node,
        target: target,
        columns: columns,
        source: source,
        on_conflict: onConflict,
    };
}
function traverseOnConflictClause(node, visitor) {
    let changed = false;
    const target = visitOptional(node.target, node, "target", visitor);
    if (target !== node.target) {
        changed = true;
    }
    const action = visitNode(node.action, visitor, createContext(node, "action"));
    if (action !== node.action) {
        changed = true;
    }
    if (!changed) {
        return node;
    }
    return {
        ...node,
        target: target,
        action: action,
    };
}
function traverseOnConflictTarget(node, visitor) {
    let changed = false;
    const expressions = visitArray(node.expressions, node, "expressions", visitor);
    if (expressions !== node.expressions) {
        changed = true;
    }
    const whereClause = visitOptional(node.where, node, "where", visitor);
    if (whereClause !== node.where) {
        changed = true;
    }
    if (!changed) {
        return node;
    }
    return {
        ...node,
        expressions: expressions,
        where: whereClause,
    };
}
function traverseOnConflictDoUpdate(node, visitor) {
    let changed = false;
    const assignments = visitArray(node.assignments, node, "assignments", visitor);
    if (assignments !== node.assignments) {
        changed = true;
    }
    const whereClause = visitOptional(node.where, node, "where", visitor);
    if (whereClause !== node.where) {
        changed = true;
    }
    if (!changed) {
        return node;
    }
    return {
        ...node,
        assignments: assignments,
        where: whereClause,
    };
}
function traverseUpdateStatement(node, visitor) {
    let changed = false;
    const target = visitNode(node.target, visitor, createContext(node, "target"));
    if (target !== node.target) {
        changed = true;
    }
    const assignments = visitArray(node.assignments, node, "assignments", visitor);
    if (assignments !== node.assignments) {
        changed = true;
    }
    const whereClause = visitOptional(node.where_clause, node, "where_clause", visitor);
    if (whereClause !== node.where_clause) {
        changed = true;
    }
    if (!changed) {
        return node;
    }
    return {
        ...node,
        target: target,
        assignments: assignments,
        where_clause: whereClause,
    };
}
function traverseDeleteStatement(node, visitor) {
    let changed = false;
    const target = visitNode(node.target, visitor, createContext(node, "target"));
    if (target !== node.target) {
        changed = true;
    }
    const whereClause = visitOptional(node.where_clause, node, "where_clause", visitor);
    if (whereClause !== node.where_clause) {
        changed = true;
    }
    if (!changed) {
        return node;
    }
    return {
        ...node,
        target: target,
        where_clause: whereClause,
    };
}
function traverseInsertValues(node, visitor) {
    let changed = false;
    const rows = [];
    for (let rowIndex = 0; rowIndex < node.rows.length; rowIndex += 1) {
        const row = node.rows[rowIndex];
        let rowChanged = false;
        const nextRow = [];
        for (let valueIndex = 0; valueIndex < row.length; valueIndex += 1) {
            const value = row[valueIndex];
            const nextValue = visitNode(value, visitor, createContext(node, `rows.${rowIndex}`, valueIndex));
            if (nextValue !== value) {
                rowChanged = true;
            }
            nextRow.push(nextValue);
        }
        if (rowChanged) {
            changed = true;
            rows.push(nextRow);
        }
        else {
            rows.push(row);
        }
    }
    if (!changed) {
        return node;
    }
    return {
        ...node,
        rows: rows,
    };
}
function traverseFromClause(node, visitor) {
    let changed = false;
    const relation = visitNode(node.relation, visitor, createContext(node, "relation"));
    if (relation !== node.relation) {
        changed = true;
    }
    const joins = visitArray(node.joins, node, "joins", visitor);
    if (joins !== node.joins) {
        changed = true;
    }
    if (!changed) {
        return node;
    }
    return {
        ...node,
        relation: relation,
        joins: joins,
    };
}
function traverseJoinClause(node, visitor) {
    let changed = false;
    const relation = visitNode(node.relation, visitor, createContext(node, "relation"));
    if (relation !== node.relation) {
        changed = true;
    }
    const onExpression = visitOptional(node.on_expression, node, "on_expression", visitor);
    if (onExpression !== node.on_expression) {
        changed = true;
    }
    if (!changed) {
        return node;
    }
    return {
        ...node,
        relation: relation,
        on_expression: onExpression,
    };
}
function traverseTableReference(node, visitor) {
    let changed = false;
    const name = visitNode(node.name, visitor, createContext(node, "name"));
    if (name !== node.name) {
        changed = true;
    }
    const alias = visitOptional(node.alias, node, "alias", visitor);
    if (alias !== node.alias) {
        changed = true;
    }
    if (!changed) {
        return node;
    }
    return {
        ...node,
        name: name,
        alias: alias,
    };
}
function traverseSubquery(node, visitor) {
    let changed = false;
    const statement = visitNode(node.statement, visitor, createContext(node, "statement"));
    if (statement !== node.statement) {
        changed = true;
    }
    const alias = visitOptional(node.alias, node, "alias", visitor);
    if (alias !== node.alias) {
        changed = true;
    }
    if (!changed) {
        return node;
    }
    return {
        ...node,
        statement: statement,
        alias: alias,
    };
}
function traverseSubqueryExpression(node, visitor) {
    const statement = visitNode(node.statement, visitor, createContext(node, "statement"));
    if (statement === node.statement) {
        return node;
    }
    return {
        ...node,
        statement,
    };
}
function traverseExistsExpression(node, visitor) {
    const statement = visitNode(node.statement, visitor, createContext(node, "statement"));
    if (statement === node.statement) {
        return node;
    }
    return {
        ...node,
        statement,
    };
}
function traverseOrderByItem(node, visitor) {
    const expression = visitNode(node.expression, visitor, createContext(node, "expression"));
    if (expression === node.expression) {
        return node;
    }
    return {
        ...node,
        expression: expression,
    };
}
function traverseFunctionCall(node, visitor) {
    let changed = false;
    const name = visitNode(node.name, visitor, createContext(node, "name"));
    if (name !== node.name) {
        changed = true;
    }
    const args = [];
    let argsChanged = false;
    for (let index = 0; index < node.arguments.length; index += 1) {
        const argument = node.arguments[index];
        if (argument.node_kind === "all_columns") {
            args.push(argument);
            continue;
        }
        const nextArgument = visitNode(argument, visitor, createContext(node, "arguments", index));
        if (nextArgument !== argument) {
            argsChanged = true;
        }
        args.push(nextArgument);
    }
    if (argsChanged) {
        changed = true;
    }
    const over = visitOptional(node.over, node, "over", visitor);
    if (over !== node.over) {
        changed = true;
    }
    if (!changed) {
        return node;
    }
    return {
        ...node,
        name: name,
        arguments: args,
        over: over,
    };
}
function traverseWindowSpecification(spec, visitor) {
    let changed = false;
    const name = visitOptional(spec.name, spec, "name", visitor);
    if (name !== spec.name) {
        changed = true;
    }
    const partitionBy = visitArray(spec.partition_by, spec, "partition_by", visitor);
    if (partitionBy !== spec.partition_by) {
        changed = true;
    }
    const orderBy = visitArray(spec.order_by, spec, "order_by", visitor);
    if (orderBy !== spec.order_by) {
        changed = true;
    }
    const frame = visitOptional(spec.frame, spec, "frame", visitor);
    if (frame !== spec.frame) {
        changed = true;
    }
    if (!changed) {
        return spec;
    }
    return {
        ...spec,
        name: name,
        partition_by: partitionBy,
        order_by: orderBy,
        frame: frame,
    };
}
function traverseWindowFrame(frame, visitor) {
    let changed = false;
    const start = visitNode(frame.start, visitor, createContext(frame, "start"));
    if (start !== frame.start) {
        changed = true;
    }
    const end = visitOptional(frame.end, frame, "end", visitor);
    if (end !== frame.end) {
        changed = true;
    }
    if (!changed) {
        return frame;
    }
    return {
        ...frame,
        start: start,
        end: end,
    };
}
function traverseWindowFrameBound(bound, visitor) {
    if (!bound.offset) {
        return bound;
    }
    const offset = visitNode(bound.offset, visitor, createContext(bound, "offset"));
    if (offset === bound.offset) {
        return bound;
    }
    return {
        ...bound,
        offset,
    };
}
function traverseWindowReference(reference, visitor) {
    const name = visitNode(reference.name, visitor, createContext(reference, "name"));
    if (name === reference.name) {
        return reference;
    }
    return {
        ...reference,
        name,
    };
}
function traverseSetClause(node, visitor) {
    let changed = false;
    const column = visitNode(node.column, visitor, createContext(node, "column"));
    if (column !== node.column) {
        changed = true;
    }
    const value = visitNode(node.value, visitor, createContext(node, "value"));
    if (value !== node.value) {
        changed = true;
    }
    if (!changed) {
        return node;
    }
    return {
        ...node,
        column: column,
        value: value,
    };
}
function traverseSelectExpression(node, visitor) {
    let changed = false;
    const expression = visitNode(node.expression, visitor, createContext(node, "expression"));
    if (expression !== node.expression) {
        changed = true;
    }
    const alias = visitOptional(node.alias, node, "alias", visitor);
    if (alias !== node.alias) {
        changed = true;
    }
    if (!changed) {
        return node;
    }
    return {
        ...node,
        expression: expression,
        alias: alias,
    };
}
function traverseSelectQualifiedStar(node, visitor) {
    const qualifier = visitArray(node.qualifier, node, "qualifier", visitor);
    if (qualifier === node.qualifier) {
        return node;
    }
    return {
        ...node,
        qualifier: qualifier,
    };
}
function traverseColumnReference(node, visitor) {
    const path = visitArray(node.path, node, "path", visitor);
    if (path === node.path) {
        return node;
    }
    return {
        ...node,
        path: path,
    };
}
function traverseGroupedExpression(node, visitor) {
    const expression = visitNode(node.expression, visitor, createContext(node, "expression"));
    if (expression === node.expression) {
        return node;
    }
    return {
        ...node,
        expression: expression,
    };
}
function traverseBinaryExpression(node, visitor) {
    let changed = false;
    const left = visitNode(node.left, visitor, createContext(node, "left"));
    if (left !== node.left) {
        changed = true;
    }
    const right = visitNode(node.right, visitor, createContext(node, "right"));
    if (right !== node.right) {
        changed = true;
    }
    if (!changed) {
        return node;
    }
    return {
        ...node,
        left: left,
        right: right,
    };
}
function traverseUnaryExpression(node, visitor) {
    const operand = visitNode(node.operand, visitor, createContext(node, "operand"));
    if (operand === node.operand) {
        return node;
    }
    return {
        ...node,
        operand: operand,
    };
}
function traverseInListExpression(node, visitor) {
    let changed = false;
    const operand = visitNode(node.operand, visitor, createContext(node, "operand"));
    if (operand !== node.operand) {
        changed = true;
    }
    const items = visitArray(node.items, node, "items", visitor);
    if (items !== node.items) {
        changed = true;
    }
    if (!changed) {
        return node;
    }
    return {
        ...node,
        operand: operand,
        items: items,
    };
}
function traverseBetweenExpression(node, visitor) {
    let changed = false;
    const operand = visitNode(node.operand, visitor, createContext(node, "operand"));
    if (operand !== node.operand) {
        changed = true;
    }
    const start = visitNode(node.start, visitor, createContext(node, "start"));
    if (start !== node.start) {
        changed = true;
    }
    const end = visitNode(node.end, visitor, createContext(node, "end"));
    if (end !== node.end) {
        changed = true;
    }
    if (!changed) {
        return node;
    }
    return {
        ...node,
        operand: operand,
        start: start,
        end: end,
    };
}
function traverseCaseExpression(node, visitor) {
    let changed = false;
    const operand = node.operand
        ? visitNode(node.operand, visitor, createContext(node, "operand"))
        : null;
    if (operand !== node.operand) {
        changed = true;
    }
    const branchResults = [];
    for (let index = 0; index < node.branches.length; index += 1) {
        const branch = node.branches[index];
        const condition = visitNode(branch.condition, visitor, createContext(node, "branch_condition", index));
        const result = visitNode(branch.result, visitor, createContext(node, "branch_result", index));
        if (condition !== branch.condition || result !== branch.result) {
            changed = true;
        }
        branchResults.push({
            condition,
            result,
        });
    }
    const elseResult = node.else_result
        ? visitNode(node.else_result, visitor, createContext(node, "else_result"))
        : null;
    if (elseResult !== node.else_result) {
        changed = true;
    }
    if (!changed) {
        return node;
    }
    return {
        ...node,
        operand: operand,
        branches: branchResults,
        else_result: elseResult,
    };
}
function traverseObjectName(node, visitor) {
    const parts = visitArray(node.parts, node, "parts", visitor);
    if (parts === node.parts) {
        return node;
    }
    return {
        ...node,
        parts: parts,
    };
}
function visitOptional(child, parent, property, visitor) {
    if (child === null) {
        return child;
    }
    return visitNode(child, visitor, createContext(parent, property));
}
function visitArray(items, parent, property, visitor) {
    let changed = false;
    const result = [];
    for (let index = 0; index < items.length; index += 1) {
        const item = items[index];
        const next = visitNode(item, visitor, createContext(parent, property, index));
        if (next !== item) {
            changed = true;
        }
        result.push(next);
    }
    return changed ? result : items;
}
function createContext(parent, property, index) {
    return { parent, property, index };
}
//# sourceMappingURL=visitor.js.map