import { identifier } from "./nodes.js";
import { parseCst, parserInstance } from "./cst.js";
const BaseVisitor = parserInstance.getBaseCstVisitorConstructorWithDefaults();
class ToAstVisitor extends BaseVisitor {
    constructor() {
        super();
        this.validateVisitor();
    }
    select_statement(ctx) {
        const select = ctx.select?.[0];
        if (select) {
            return this.visit(select);
        }
        const insert = ctx.insert?.[0];
        if (insert) {
            return this.visit(insert);
        }
        const update = ctx.update?.[0];
        if (update) {
            return this.visit(update);
        }
        const del = ctx.delete?.[0];
        if (del) {
            return this.visit(del);
        }
        throw new Error("statement missing recognized root");
    }
    select_compound(ctx) {
        const coreNodes = ctx.cores ?? [];
        if (coreNodes.length === 0) {
            throw new Error("compound select requires at least one core");
        }
        const selectNodes = coreNodes.map((core) => this.visit(core));
        const withClause = ctx.with_clause?.[0]
            ? this.visit(ctx.with_clause[0])
            : null;
        const orderByItems = ctx.order_by?.[0]
            ? this.visit(ctx.order_by[0])
            : [];
        const limitNode = ctx.limit?.[0]
            ? this.visit(ctx.limit[0])
            : null;
        const offsetNode = ctx.offset?.[0]
            ? this.visit(ctx.offset[0])
            : null;
        const operatorNodes = ctx.operators ?? [];
        if (operatorNodes.length === 0) {
            const single = { ...selectNodes[0] };
            return {
                ...single,
                order_by: orderByItems,
                limit: limitNode,
                offset: offsetNode,
                with_clause: withClause,
            };
        }
        if (operatorNodes.length !== selectNodes.length - 1) {
            throw new Error("compound select operator mismatch");
        }
        const compounds = selectNodes.slice(1).map((select, index) => {
            const op = this.visit(operatorNodes[index]);
            return {
                operator: op,
                select,
            };
        });
        const first = { ...selectNodes[0] };
        first.order_by = [];
        first.limit = null;
        first.offset = null;
        return {
            node_kind: "compound_select",
            first,
            compounds,
            order_by: orderByItems,
            limit: limitNode,
            offset: offsetNode,
            with_clause: withClause,
        };
    }
    select_core(ctx) {
        const selectList = ctx.select_list?.[0];
        if (!selectList) {
            throw new Error("select list is missing");
        }
        const projection = this.visit(selectList);
        const fromNode = ctx.from?.[0];
        const fromClauses = [];
        if (fromNode) {
            const relation = this.visit(fromNode);
            const joins = ctx.joins?.map((join) => this.visit(join)) ?? [];
            fromClauses.push({
                node_kind: "from_clause",
                relation,
                joins,
            });
        }
        const whereNode = ctx.where_clause?.[0];
        const whereClause = whereNode
            ? this.visit(whereNode)
            : null;
        const groupBy = ctx.group_by?.map((node) => this.visit(node)) ?? [];
        return {
            node_kind: "select_statement",
            distinct: Boolean(ctx.distinct?.length),
            projection,
            from_clauses: fromClauses,
            where_clause: whereClause,
            group_by: groupBy,
            order_by: [],
            limit: null,
            offset: null,
            with_clause: null,
        };
    }
    with_clause(ctx) {
        const expressions = ctx.ctes?.map((cte) => this.visit(cte));
        return {
            node_kind: "with_clause",
            recursive: Boolean(ctx.recursive?.length),
            ctes: expressions ?? [],
        };
    }
    common_table_expression(ctx) {
        const name = this.visit(ctx.name[0]);
        const columns = ctx.columns?.map((column) => this.visit(column));
        const statement = this.visit(ctx.statement[0]);
        return {
            node_kind: "common_table_expression",
            name,
            columns: columns ?? [],
            statement,
        };
    }
    compound_operator(ctx) {
        if (ctx.Union?.length) {
            return ctx.All?.length ? "union_by_version" : "union";
        }
        if (ctx.Intersect?.length) {
            return "intersect";
        }
        if (ctx.Except?.length) {
            return "except";
        }
        throw new Error("unrecognised compound operator");
    }
    insert_statement(ctx) {
        const tableNode = ctx.table?.[0];
        if (!tableNode) {
            throw new Error("insert statement missing target table");
        }
        const target = this.visit(tableNode);
        const columnNodes = ctx.columns?.map((column) => this.visit(column)) ?? [];
        const hasDefaultValues = (ctx.default_keyword?.length ?? 0) > 0;
        let source;
        if (hasDefaultValues) {
            source = { node_kind: "insert_default_values" };
        }
        else {
            const valuesListNode = ctx.rows?.[0];
            if (!valuesListNode) {
                throw new Error("insert statement missing values list");
            }
            const rowNodes = this.visit(valuesListNode);
            source = {
                node_kind: "insert_values",
                rows: rowNodes,
            };
        }
        const conflictNode = ctx.conflict?.[0];
        const onConflict = conflictNode
            ? this.visit(conflictNode)
            : null;
        return {
            node_kind: "insert_statement",
            target,
            columns: columnNodes,
            source,
            on_conflict: onConflict,
        };
    }
    on_conflict_clause(ctx) {
        const expressionNodes = ctx.target_expressions ?? [];
        const expressions = expressionNodes.map((node) => this.visit(node));
        const targetWhereNode = ctx.target_where?.[0];
        const targetWhere = targetWhereNode
            ? this.visit(targetWhereNode)
            : null;
        const target = expressions.length > 0 || targetWhere
            ? {
                node_kind: "on_conflict_target",
                expressions,
                where: targetWhere,
            }
            : null;
        let action;
        if (ctx.do_nothing?.length) {
            action = { node_kind: "on_conflict_do_nothing" };
        }
        else {
            const assignmentNodes = ctx.assignments ?? [];
            const assignments = assignmentNodes.map((node) => this.visit(node));
            if (assignments.length === 0) {
                throw new Error("ON CONFLICT DO UPDATE requires assignments");
            }
            const actionWhereNode = ctx.action_where?.[0];
            const actionWhere = actionWhereNode
                ? this.visit(actionWhereNode)
                : null;
            action = {
                node_kind: "on_conflict_do_update",
                assignments,
                where: actionWhere,
            };
        }
        return {
            node_kind: "on_conflict_clause",
            target,
            action,
        };
    }
    limit_clause(ctx) {
        const expressionNode = ctx.value?.[0];
        if (!expressionNode) {
            throw new Error("limit clause missing value");
        }
        return this.visit(expressionNode);
    }
    offset_clause(ctx) {
        const expressionNode = ctx.value?.[0];
        if (!expressionNode) {
            throw new Error("offset clause missing value");
        }
        return this.visit(expressionNode);
    }
    select_list(ctx) {
        const items = ctx.items ?? [];
        if (items.length === 0) {
            throw new Error("select list is missing");
        }
        return items.map((item) => this.visit(item));
    }
    select_item(ctx) {
        if (ctx.star?.[0]) {
            const star = { node_kind: "select_star" };
            return star;
        }
        if (ctx.qualifiedStar?.[0]) {
            const qualifiers = ctx.qualifier?.map((node) => this.visit(node)) ?? [];
            const qualified = {
                node_kind: "select_qualified_star",
                qualifier: qualifiers,
            };
            return qualified;
        }
        const expressionNode = ctx.expression?.[0];
        if (!expressionNode) {
            throw new Error("select item missing expression");
        }
        const expression = this.visit(expressionNode);
        const aliasNode = ctx.alias?.[0];
        const alias = aliasNode ? this.visit(aliasNode) : null;
        return {
            node_kind: "select_expression",
            expression,
            alias,
        };
    }
    table_reference(ctx) {
        const nestedNode = ctx.nested?.[0];
        if (nestedNode) {
            const relation = this.visit(nestedNode);
            const aliasToken = ctx.alias?.[0] ?? null;
            if (!aliasToken) {
                return relation;
            }
            const aliasIdentifier = this.visit(aliasToken);
            if (relation.node_kind === "table_reference") {
                return {
                    ...relation,
                    alias: aliasIdentifier,
                };
            }
            if (relation.node_kind === "subquery") {
                return {
                    ...relation,
                    alias: aliasIdentifier,
                };
            }
            return relation;
        }
        const selectNode = ctx.select?.[0];
        if (selectNode) {
            const aliasToken = ctx.alias?.[0] ?? null;
            const alias = aliasToken
                ? this.visit(aliasToken)
                : null;
            const statement = this.visit(selectNode);
            const subquery = {
                node_kind: "subquery",
                statement,
                alias,
            };
            return subquery;
        }
        const tableNode = ctx.table?.[0];
        if (!tableNode) {
            throw new Error("table reference is missing");
        }
        const name = this.visit(tableNode);
        const aliasNode = ctx.alias?.[0];
        const alias = aliasNode ? this.visit(aliasNode) : null;
        const tableRef = {
            node_kind: "table_reference",
            name,
            alias,
        };
        return tableRef;
    }
    table_name(ctx) {
        const parts = ctx.parts?.map((node) => this.visit(node)) ?? [];
        if (parts.length === 0) {
            throw new Error("table name is missing");
        }
        return {
            node_kind: "object_name",
            parts,
        };
    }
    join_clause(ctx) {
        const tableNode = ctx.table?.[0];
        if (!tableNode) {
            throw new Error("join clause is missing table reference");
        }
        const relation = this.visit(tableNode);
        const joinTypeToken = ctx.join_type?.[0];
        const joinType = joinTypeToken
            ? normalizeJoinType(joinTypeToken.image)
            : "inner";
        const onNode = ctx.on_expression?.[0];
        const onExpression = onNode ? this.visit(onNode) : null;
        return {
            node_kind: "join_clause",
            join_type: joinType,
            relation,
            on_expression: onExpression,
        };
    }
    where_clause(ctx) {
        const expression = ctx.expression?.[0];
        if (!expression) {
            throw new Error("where clause is empty");
        }
        return this.visit(expression);
    }
    or_expression(ctx) {
        const operands = ctx.operands?.map((operand) => this.visit(operand)) ??
            [];
        if (operands.length === 0) {
            throw new Error("logical expression missing operands");
        }
        return foldBinaryExpression("or", operands);
    }
    and_expression(ctx) {
        const operands = ctx.operands?.map((operand) => this.visit(operand)) ??
            [];
        if (operands.length === 0) {
            throw new Error("logical expression missing operands");
        }
        return foldBinaryExpression("and", operands);
    }
    atomic_predicate(ctx) {
        if (ctx.unary_not?.[0]) {
            const inner = ctx.negated?.[0];
            if (!inner) {
                throw new Error("not predicate missing operand");
            }
            const expression = this.visit(inner);
            return createUnaryExpression("not", expression);
        }
        if (ctx.exists?.[0]) {
            const subqueryNode = ctx
                .exists_subquery?.[0];
            if (!subqueryNode) {
                throw new Error("exists predicate missing subquery");
            }
            const statement = this.visit(subqueryNode);
            return createExistsExpression(statement);
        }
        const leftExpressionNode = ctx.left_expression?.[0];
        if (leftExpressionNode) {
            const leftOperand = this.visit(leftExpressionNode);
            const comparisonOperatorNode = ctx.comparison_operator?.[0];
            if (comparisonOperatorNode) {
                const comparisonValueNode = ctx.comparison_value?.[0];
                if (!comparisonValueNode) {
                    throw new Error("comparison predicate missing right operand");
                }
                const operator = this.visit(comparisonOperatorNode);
                const rightOperand = this.visit(comparisonValueNode);
                return createBinaryExpression(leftOperand, operator, rightOperand);
            }
            const betweenStartNode = ctx.between_start?.[0];
            const betweenEndNode = ctx.between_end?.[0];
            if (betweenStartNode && betweenEndNode) {
                const start = this.visit(betweenStartNode);
                const end = this.visit(betweenEndNode);
                return createBetweenExpression(leftOperand, start, end, false);
            }
            const inListNode = ctx.in_list?.[0];
            if (inListNode) {
                const listItems = this.visit(inListNode);
                return createInListExpression(leftOperand, listItems, Boolean(ctx.in_not?.length));
            }
            const inSubqueryNode = ctx.in_subquery?.[0];
            if (inSubqueryNode) {
                const statement = this.visit(inSubqueryNode);
                const subqueryExpression = {
                    node_kind: "subquery_expression",
                    statement,
                };
                return createInListExpression(leftOperand, [subqueryExpression], Boolean(ctx.in_not?.length));
            }
            const likePatternNode = ctx.like_pattern?.[0];
            if (likePatternNode) {
                const pattern = this.visit(likePatternNode);
                const operator = ctx.like_not?.length
                    ? "not_like"
                    : "like";
                return createBinaryExpression(leftOperand, operator, pattern);
            }
            if (ctx.is_operator?.length) {
                const operator = ctx.is_not?.length ? "is_not" : "is";
                const nullLiteral = createLiteralNode(null);
                return createBinaryExpression(leftOperand, operator, nullLiteral);
            }
            return leftOperand;
        }
        const inner = ctx.inner?.[0];
        if (!inner) {
            throw new Error("predicate is neither comparison nor grouped expression");
        }
        const expression = this.visit(inner);
        return expression.node_kind === "grouped_expression"
            ? expression
            : {
                node_kind: "grouped_expression",
                expression,
            };
    }
    additive_expression(ctx) {
        const operands = ctx.operands?.map((operand) => this.visit(operand)) ??
            [];
        if (operands.length === 0) {
            throw new Error("additive expression missing operands");
        }
        const operators = ctx.operators ?? [];
        let current = operands[0];
        for (let index = 0; index < operators.length; index += 1) {
            const operatorToken = operators[index];
            const operator = mapArithmeticOperator(operatorToken.image);
            const right = operands[index + 1];
            current = createBinaryExpression(current, operator, right);
        }
        return current;
    }
    expression(ctx) {
        const node = ctx.expression?.[0];
        if (!node) {
            throw new Error("expression node missing");
        }
        return this.visit(node);
    }
    multiplicative_expression(ctx) {
        const operands = ctx.operands?.map((operand) => this.visit(operand)) ??
            [];
        if (operands.length === 0) {
            throw new Error("multiplicative expression missing operands");
        }
        const operators = ctx.operators ?? [];
        let current = operands[0];
        for (let index = 0; index < operators.length; index += 1) {
            const operatorToken = operators[index];
            const operator = mapArithmeticOperator(operatorToken.image);
            const right = operands[index + 1];
            current = createBinaryExpression(current, operator, right);
        }
        return current;
    }
    concatenation_expression(ctx) {
        const operands = ctx.operands?.map((operand) => this.visit(operand)) ??
            [];
        if (operands.length === 0) {
            throw new Error("concatenation expression missing operands");
        }
        const operators = ctx.operators ?? [];
        let current = operands[0];
        for (let index = 0; index < operators.length; index += 1) {
            const operatorToken = operators[index];
            const operator = mapArithmeticOperator(operatorToken.image);
            const right = operands[index + 1];
            current = createBinaryExpression(current, operator, right);
        }
        return current;
    }
    unary_expression(ctx) {
        const operandNode = ctx.operand?.[0];
        if (!operandNode) {
            throw new Error("unary expression missing operand");
        }
        const operand = this.visit(operandNode);
        const operatorToken = ctx.unaryOperator?.[0];
        if (!operatorToken) {
            return operand;
        }
        const image = operatorToken.image.toLowerCase();
        let unaryOperator;
        if (image === "-") {
            unaryOperator = "minus";
        }
        else if (image === "not") {
            unaryOperator = "not";
        }
        else {
            throw new Error("unsupported unary operator");
        }
        return createUnaryExpression(unaryOperator, operand);
    }
    primary_expression(ctx) {
        const parameter = ctx.parameter?.[0];
        if (parameter) {
            const parameterNode = {
                node_kind: "parameter",
                placeholder: parameter.image ?? "?",
                position: -1,
            };
            return parameterNode;
        }
        const stringLiteral = ctx.string?.[0];
        if (stringLiteral?.image) {
            return createLiteralNode(normalizeStringLiteral(stringLiteral.image));
        }
        const numberLiteral = ctx.number?.[0];
        if (numberLiteral?.image) {
            return createLiteralNode(Number(numberLiteral.image));
        }
        const nullLiteral = ctx.nullLiteral?.[0];
        if (nullLiteral) {
            return createLiteralNode(null);
        }
        const caseExpression = ctx.caseExpression?.[0];
        if (caseExpression) {
            return this.visit(caseExpression);
        }
        const callIdentifier = ctx.callIdentifier?.[0];
        if (callIdentifier?.image) {
            let args = [];
            const starToken = ctx.callStar?.[0];
            if (starToken) {
                args = [{ node_kind: "all_columns" }];
            }
            else {
                args =
                    ctx.callArguments?.map((argument) => this.visit(argument)) ?? [];
            }
            const overClause = ctx.overClause?.[0];
            const over = overClause
                ? this.visit(overClause)
                : null;
            const functionCall = {
                node_kind: "function_call",
                name: identifier(callIdentifier.image),
                arguments: args,
                over,
            };
            return functionCall;
        }
        const reference = ctx.reference?.[0];
        if (reference) {
            return this.visit(reference);
        }
        const subselect = ctx.subselect?.[0];
        if (subselect) {
            const statement = this.visit(subselect);
            return {
                node_kind: "subquery_expression",
                statement,
            };
        }
        const inner = ctx.inner?.[0];
        if (!inner) {
            throw new Error("primary expression is empty");
        }
        const expression = this.visit(inner);
        if (expression.node_kind === "grouped_expression") {
            return expression;
        }
        const grouped = {
            node_kind: "grouped_expression",
            expression,
        };
        return grouped;
    }
    over_clause(ctx) {
        const nameToken = ctx.windowName?.[0];
        if (nameToken?.image) {
            return {
                node_kind: "window_reference",
                name: identifier(nameToken.image),
            };
        }
        const specNode = ctx.windowSpec?.[0];
        if (specNode) {
            return this.visit(specNode);
        }
        return {
            node_kind: "window_specification",
            name: null,
            partition_by: [],
            order_by: [],
            frame: null,
        };
    }
    window_specification(ctx) {
        const nameNode = ctx.name?.[0];
        const name = nameNode ? this.visit(nameNode) : null;
        const partitionExpressions = ctx.partitionBy?.map((node) => this.visit(node)) ?? [];
        const orderNodes = ctx.orderBy?.[0]
            ? this.visit(ctx.orderBy[0])
            : [];
        const frameNode = ctx.frame?.[0]
            ? this.visit(ctx.frame[0])
            : null;
        return {
            node_kind: "window_specification",
            name,
            partition_by: partitionExpressions,
            order_by: orderNodes,
            frame: frameNode,
        };
    }
    window_frame(ctx) {
        const unitToken = ctx.units[0];
        const unitImage = unitToken?.image?.toLowerCase() ?? "";
        const units = unitImage === "range"
            ? "range"
            : unitImage === "groups"
                ? "groups"
                : "rows";
        const startNode = ctx.start?.[0];
        if (!startNode) {
            throw new Error("window frame requires a starting bound");
        }
        const start = this.visit(startNode);
        const endNode = ctx.end?.[0];
        const end = endNode ? this.visit(endNode) : null;
        return {
            node_kind: "window_frame",
            units,
            start,
            end,
        };
    }
    window_frame_bound(ctx) {
        const directionToken = ctx.direction?.[0];
        if (ctx.unbounded?.length) {
            if (!directionToken?.image) {
                throw new Error("UNBOUNDED bound requires direction");
            }
            const direction = directionToken.image.toLowerCase();
            return {
                node_kind: "window_frame_bound",
                type: direction === "following"
                    ? "unbounded_following"
                    : "unbounded_preceding",
                offset: null,
            };
        }
        if (ctx.current?.length) {
            return {
                node_kind: "window_frame_bound",
                type: "current_row",
                offset: null,
            };
        }
        const offsetNode = ctx.offset?.[0];
        if (!offsetNode || !directionToken?.image) {
            throw new Error("window frame bound requires offset and direction");
        }
        const direction = directionToken.image.toLowerCase();
        const offset = this.visit(offsetNode);
        return {
            node_kind: "window_frame_bound",
            type: direction === "following" ? "following" : "preceding",
            offset,
        };
    }
    case_expression(ctx) {
        const operandNode = ctx.operand?.[0];
        const operand = operandNode
            ? this.visit(operandNode)
            : null;
        const conditions = ctx.when_condition?.map((node) => this.visit(node)) ??
            ctx.when_value?.map((node) => this.visit(node)) ??
            [];
        const results = ctx.then_expression?.map((node) => this.visit(node)) ??
            [];
        if (conditions.length !== results.length) {
            throw new Error("CASE expression requires matching WHEN and THEN clauses");
        }
        const branches = conditions.map((condition, index) => ({
            condition,
            result: results[index],
        }));
        const elseNode = ctx.else_expression?.[0];
        const elseExpression = elseNode
            ? this.visit(elseNode)
            : null;
        return {
            node_kind: "case_expression",
            operand,
            branches,
            else_result: elseExpression,
        };
    }
    column_reference(ctx) {
        const parts = ctx.parts?.map((part) => this.visit(part)) ?? [];
        if (parts.length === 0) {
            throw new Error("column reference is missing");
        }
        if (parts.length === 1 || parts.length === 2) {
            return {
                node_kind: "column_reference",
                path: parts,
            };
        }
        throw new Error("column references with nested paths are not supported yet");
    }
    assignment_item(ctx) {
        const columnNode = ctx.column?.[0];
        if (!columnNode) {
            throw new Error("assignment missing column");
        }
        const valueNode = ctx.value?.[0];
        if (!valueNode) {
            throw new Error("assignment missing value");
        }
        const column = this.visit(columnNode);
        const value = this.visit(valueNode);
        return {
            node_kind: "set_clause",
            column,
            value,
        };
    }
    update_statement(ctx) {
        const tableNode = ctx.table?.[0];
        if (!tableNode) {
            throw new Error("update statement missing table reference");
        }
        const targetRelation = this.visit(tableNode);
        if (targetRelation.node_kind !== "table_reference") {
            throw new Error("update statement requires a base table reference");
        }
        const assignments = ctx.assignments?.map((assignment) => this.visit(assignment)) ?? [];
        if (assignments.length === 0) {
            throw new Error("update statement missing assignments");
        }
        const whereNode = ctx.where_clause?.[0];
        const whereClause = whereNode
            ? this.visit(whereNode)
            : null;
        return {
            node_kind: "update_statement",
            target: targetRelation,
            assignments,
            where_clause: whereClause,
        };
    }
    delete_statement(ctx) {
        const tableNode = ctx.table?.[0];
        if (!tableNode) {
            throw new Error("delete statement missing table reference");
        }
        const targetRelation = this.visit(tableNode);
        if (targetRelation.node_kind !== "table_reference") {
            throw new Error("delete statement requires a base table reference");
        }
        const whereNode = ctx.where_clause?.[0];
        const whereClause = whereNode
            ? this.visit(whereNode)
            : null;
        return {
            node_kind: "delete_statement",
            target: targetRelation,
            where_clause: whereClause,
        };
    }
    expression_list(ctx) {
        return ctx.items?.map((item) => this.visit(item)) ?? [];
    }
    values_list(ctx) {
        const rows = ctx.rows ?? [];
        if (rows.length === 0) {
            throw new Error("values list is empty");
        }
        return rows.map((row) => this.visit(row));
    }
    values_clause(ctx) {
        const valuesNode = ctx.values?.[0];
        if (!valuesNode) {
            throw new Error("values clause missing expressions");
        }
        return this.visit(valuesNode);
    }
    order_by_clause(ctx) {
        return ctx.items?.map((item) => this.visit(item)) ?? [];
    }
    order_by_item(ctx) {
        const expressionNode = ctx.expression?.[0];
        if (!expressionNode) {
            throw new Error("order by item missing expression");
        }
        const expression = this.visit(expressionNode);
        const directionToken = ctx.direction?.[0];
        const direction = directionToken
            ? normalizeOrderDirection(directionToken.image)
            : null;
        return {
            node_kind: "order_by_item",
            expression,
            direction,
        };
    }
    comparison_operator(ctx) {
        const token = ctx.operator?.[0];
        if (!token) {
            throw new Error("comparison operator token missing");
        }
        return mapComparisonOperator(token.image);
    }
    identifier(ctx) {
        const token = ctx.Identifier?.[0] ?? ctx.QuotedIdentifier?.[0];
        if (!token?.image) {
            throw new Error("identifier token missing");
        }
        const isQuoted = token.tokenType?.name === "QuotedIdentifier";
        const value = isQuoted
            ? token.image.slice(1, -1).replace(/""/g, '"')
            : token.image;
        return {
            node_kind: "identifier",
            value,
            quoted: isQuoted,
        };
    }
}
const visitor = new ToAstVisitor();
function createLiteralNode(value) {
    return {
        node_kind: "literal",
        value,
    };
}
function createBinaryExpression(left, operator, right) {
    return {
        node_kind: "binary_expression",
        left,
        operator,
        right,
    };
}
function foldBinaryExpression(operator, operands) {
    let current = operands[0];
    for (let index = 1; index < operands.length; index += 1) {
        current = createBinaryExpression(current, operator, operands[index]);
    }
    return current;
}
function createInListExpression(operand, items, negated) {
    return {
        node_kind: "in_list_expression",
        operand,
        items,
        negated,
    };
}
function createExistsExpression(statement) {
    return {
        node_kind: "exists_expression",
        statement,
    };
}
function createBetweenExpression(operand, start, end, negated) {
    return {
        node_kind: "between_expression",
        operand,
        start,
        end,
        negated,
    };
}
function createUnaryExpression(operator, operand) {
    return {
        node_kind: "unary_expression",
        operator,
        operand,
    };
}
function mapArithmeticOperator(image) {
    switch (image) {
        case "||":
        case "+":
        case "-":
        case "*":
        case "/":
        case "%":
        case "->":
        case "->>":
            return image;
        default:
            throw new Error(`unsupported arithmetic operator '${image}'`);
    }
}
function mapComparisonOperator(image) {
    switch (image) {
        case "=":
        case "!=":
        case "<>":
        case ">":
        case ">=":
        case "<":
        case "<=":
            return image;
    }
    const keyword = image.toLowerCase();
    if (keyword === "match" || keyword === "glob" || keyword === "regexp") {
        return keyword;
    }
    throw new Error(`unsupported comparison operator '${image}'`);
}
function normalizeJoinType(value) {
    switch (value.toLowerCase()) {
        case "inner":
            return "inner";
        case "left":
            return "left";
        case "right":
            return "right";
        case "full":
            return "full";
        default:
            return "inner";
    }
}
function normalizeOrderDirection(value) {
    const normalized = value.toLowerCase();
    if (normalized === "asc" || normalized === "desc") {
        return normalized;
    }
    throw new Error(`unsupported order by direction '${value}'`);
}
function normalizeStringLiteral(image) {
    const inner = image.slice(1, -1);
    return inner.replace(/''/g, "'");
}
function toStatementNode(root) {
    return visitor.visit(root);
}
function assignParameterPositions(statement) {
    const state = { nextSequential: 0 };
    assignPositionsInStatement(statement, state);
    return statement;
}
function resolveParameterPosition(placeholder, state) {
    if (isSequentialPlaceholder(placeholder)) {
        const position = state.nextSequential;
        state.nextSequential += 1;
        return position;
    }
    const numericMatch = placeholder.match(/^\?(\d+)$/);
    if (numericMatch) {
        const numeric = Number(numericMatch[1]) - 1;
        if (Number.isInteger(numeric) && numeric >= 0) {
            if (numeric + 1 > state.nextSequential) {
                state.nextSequential = numeric + 1;
            }
            return numeric;
        }
    }
    // Fall back to sequential ordering for unsupported placeholder formats.
    const position = state.nextSequential;
    state.nextSequential += 1;
    return position;
}
function isSequentialPlaceholder(placeholder) {
    if (placeholder === "" || placeholder === "?") {
        return true;
    }
    if (placeholder.startsWith("?")) {
        const rest = placeholder.slice(1);
        return rest.length === 0 || /\D/.test(rest);
    }
    return true;
}
function countSequentialPlaceholders(sql) {
    let count = 0;
    let index = 0;
    let inSingle = false;
    let inDouble = false;
    let inBracket = false;
    let inLineComment = false;
    let inBlockComment = false;
    const advance = (step = 1) => {
        index += step;
    };
    while (index < sql.length) {
        const ch = sql[index];
        const next = sql[index + 1];
        if (inLineComment) {
            if (ch === "\n") {
                inLineComment = false;
            }
            advance();
            continue;
        }
        if (inBlockComment) {
            if (ch === "*" && next === "/") {
                inBlockComment = false;
                advance(2);
                continue;
            }
            advance();
            continue;
        }
        if (!inSingle && !inDouble && !inBracket) {
            if (ch === "-" && next === "-") {
                inLineComment = true;
                advance(2);
                continue;
            }
            if (ch === "/" && next === "*") {
                inBlockComment = true;
                advance(2);
                continue;
            }
        }
        if (!inDouble && !inBracket && ch === "'") {
            if (inSingle && next === "'") {
                advance(2);
                continue;
            }
            inSingle = !inSingle;
            advance();
            continue;
        }
        if (!inSingle && !inBracket && ch === '"') {
            if (inDouble && next === '"') {
                advance(2);
                continue;
            }
            inDouble = !inDouble;
            advance();
            continue;
        }
        if (!inSingle && !inDouble && ch === "[" && !inBracket) {
            inBracket = true;
            advance();
            continue;
        }
        if (inBracket) {
            if (ch === "]") {
                inBracket = false;
            }
            advance();
            continue;
        }
        if (!inSingle && !inDouble && !inBracket && ch === "?") {
            let j = index + 1;
            while (j < sql.length && /\d/.test(sql[j])) {
                j += 1;
            }
            if (j === index + 1) {
                count += 1;
                advance();
                continue;
            }
            index = j;
            continue;
        }
        advance();
    }
    return count;
}
function assignPositionsInStatement(statement, state) {
    switch (statement.node_kind) {
        case "select_statement":
            assignPositionsInSelect(statement, state);
            break;
        case "compound_select":
            assignPositionsInCompound(statement, state);
            break;
        case "insert_statement":
            assignPositionsInInsert(statement, state);
            break;
        case "update_statement":
            assignPositionsInUpdate(statement, state);
            break;
        case "delete_statement":
            assignPositionsInDelete(statement, state);
            break;
        default:
            break;
    }
}
function assignPositionsInSelect(select, state) {
    if (select.with_clause) {
        assignPositionsInWithClause(select.with_clause, state);
    }
    for (const item of select.projection) {
        assignPositionsInSelectItem(item, state);
    }
    for (const clause of select.from_clauses) {
        assignPositionsInFromClause(clause, state);
    }
    assignPositionsInExpressionOrFragment(select.where_clause, state);
    for (const order of select.order_by) {
        assignPositionsInExpression(order.expression, state);
    }
    assignPositionsInExpressionOrFragment(select.limit, state);
    assignPositionsInExpressionOrFragment(select.offset, state);
}
function assignPositionsInCompound(compound, state) {
    if (compound.with_clause) {
        assignPositionsInWithClause(compound.with_clause, state);
    }
    assignPositionsInSelect(compound.first, state);
    for (const branch of compound.compounds) {
        assignPositionsInSelect(branch.select, state);
    }
    for (const item of compound.order_by) {
        assignPositionsInExpression(item.expression, state);
    }
    assignPositionsInExpressionOrFragment(compound.limit, state);
    assignPositionsInExpressionOrFragment(compound.offset, state);
}
function assignPositionsInWithClause(withClause, state) {
    for (const cte of withClause.ctes) {
        assignPositionsInStatement(cte.statement, state);
    }
}
function assignPositionsInSelectItem(item, state) {
    if (item.node_kind === "select_expression") {
        assignPositionsInExpression(item.expression, state);
    }
}
function assignPositionsInFromClause(clause, state) {
    assignPositionsInRelation(clause.relation, state);
    for (const join of clause.joins) {
        assignPositionsInJoin(join, state);
    }
}
function assignPositionsInJoin(join, state) {
    assignPositionsInRelation(join.relation, state);
    assignPositionsInExpressionOrFragment(join.on_expression, state);
}
function assignPositionsInRelation(relation, state) {
    switch (relation.node_kind) {
        case "subquery":
            assignPositionsInStatement(relation.statement, state);
            break;
        case "raw_fragment":
            state.nextSequential += countSequentialPlaceholders(relation.sql_text);
            break;
        default:
            break;
    }
}
function assignPositionsInInsert(statement, state) {
    assignPositionsInInsertSource(statement.source, state);
}
function assignPositionsInInsertSource(source, state) {
    if (source.node_kind === "insert_default_values") {
        return;
    }
    for (const row of source.rows) {
        for (const expression of row) {
            assignPositionsInExpression(expression, state);
        }
    }
}
function assignPositionsInUpdate(statement, state) {
    for (const clause of statement.assignments) {
        assignPositionsInExpression(clause.value, state);
    }
    assignPositionsInExpressionOrFragment(statement.where_clause, state);
}
function assignPositionsInDelete(statement, state) {
    assignPositionsInExpressionOrFragment(statement.where_clause, state);
}
function assignPositionsInExpressionOrFragment(expression, state) {
    if (!expression) {
        return;
    }
    if ("sql_text" in expression) {
        state.nextSequential += countSequentialPlaceholders(expression.sql_text);
        return;
    }
    assignPositionsInExpression(expression, state);
}
function assignPositionsInExpression(expression, state) {
    switch (expression.node_kind) {
        case "parameter":
            assignPositionToParameter(expression, state);
            break;
        case "grouped_expression":
            assignPositionsInExpression(expression.expression, state);
            break;
        case "binary_expression":
            assignPositionsInExpression(expression.left, state);
            assignPositionsInExpression(expression.right, state);
            break;
        case "unary_expression":
            assignPositionsInExpression(expression.operand, state);
            break;
        case "in_list_expression":
            assignPositionsInExpression(expression.operand, state);
            for (const item of expression.items) {
                assignPositionsInExpression(item, state);
            }
            break;
        case "between_expression":
            assignPositionsInExpression(expression.operand, state);
            assignPositionsInExpression(expression.start, state);
            assignPositionsInExpression(expression.end, state);
            break;
        case "function_call":
            for (const argument of expression.arguments) {
                if (argument.node_kind === "all_columns") {
                    continue;
                }
                assignPositionsInExpression(argument, state);
            }
            break;
        case "subquery_expression":
            assignPositionsInStatement(expression.statement, state);
            break;
        case "raw_fragment":
            state.nextSequential += countSequentialPlaceholders(expression.sql_text);
            break;
        default:
            break;
    }
}
function assignPositionToParameter(parameter, state) {
    const position = resolveParameterPosition(parameter.placeholder ?? "?", state);
    parameter.position =
        position;
}
/**
 * Parses SQL text into one or more segmented statements, falling back to raw
 * fragments when the grammar cannot represent portions of the input.
 *
 * @example
 * ```ts
 * const [statement] = parse("SELECT 1");
 * ```
 */
export function parse(sql) {
    const program = parseWithGrammar(sql);
    if (program.length > 0) {
        return program;
    }
    const segmentedFallback = parseWithSegmentation(sql);
    if (segmentedFallback.length > 0) {
        return segmentedFallback;
    }
    return [createSegmentedStatement([createRawFragment(sql)])];
}
export { parseCst } from "./cst.js";
function parseWithGrammar(sql) {
    const root = parseCst(sql);
    if (!root) {
        return [];
    }
    const statement = toStatementNode(root);
    return [createSegmentedStatement([statement])];
}
function parseWithSegmentation(sql) {
    const segments = segmentSelectStatements(sql);
    if (!segments) {
        return [];
    }
    return [createSegmentedStatement(segments)];
}
function createSegmentedStatement(segments) {
    const state = { nextSequential: 0 };
    assignPositionsInSegments(segments, state);
    return {
        node_kind: "segmented_statement",
        segments,
    };
}
export function normalizeSegmentedStatement(statement) {
    const segments = [...statement.segments];
    const state = { nextSequential: 0 };
    assignPositionsInSegments(segments, state);
    return {
        ...statement,
        segments,
    };
}
export function normalizeSegmentedStatements(statements) {
    return statements.map((statement) => normalizeSegmentedStatement(statement));
}
function assignPositionsInSegments(segments, state) {
    for (const segment of segments) {
        if (segment.node_kind === "raw_fragment") {
            state.nextSequential += countSequentialPlaceholders(segment.sql_text);
            continue;
        }
        assignPositionsInStatement(segment, state);
    }
}
/**
 * Extracts SELECT statements that appear within parenthesised regions while
 * preserving surrounding fragments as raw nodes.
 */
function segmentSelectStatements(sql) {
    const segments = [];
    let cursor = 0;
    let depth = 0;
    let inSingleQuotedString = false;
    for (let index = 0; index < sql.length; index += 1) {
        const ch = sql[index];
        if (inSingleQuotedString) {
            if (ch === "'") {
                const next = sql[index + 1];
                if (next === "'") {
                    index += 1;
                    continue;
                }
                inSingleQuotedString = false;
            }
            continue;
        }
        if (ch === "'") {
            inSingleQuotedString = true;
            continue;
        }
        if (ch === "(") {
            depth += 1;
            continue;
        }
        if (ch === ")") {
            if (depth > 0) {
                depth -= 1;
            }
            continue;
        }
        const isSelectKeyword = isKeywordAt(sql, index, "select");
        if (!isSelectKeyword || !isIdentifierBoundary(sql, index - 1)) {
            continue;
        }
        const interstitial = sql.slice(cursor, index).trim().toLowerCase();
        const isAfterUnion = /(?:^|\s)union(?:\s+all)?$/.test(interstitial);
        if (!(depth > 0 || segments.length === 0 || isAfterUnion)) {
            continue;
        }
        {
            const endIndex = findSelectBoundary(sql, index, depth);
            if (endIndex === null || endIndex <= index) {
                return null;
            }
            if (index > cursor) {
                const fragmentSql = sql.slice(cursor, index);
                if (fragmentSql.length > 0) {
                    segments.push(createRawFragment(fragmentSql));
                }
            }
            const selectSql = sql.slice(index, endIndex);
            const parsed = parseSimpleStatement(selectSql);
            if (!parsed || parsed.node_kind !== "select_statement") {
                return null;
            }
            segments.push(parsed);
            cursor = endIndex;
            index = endIndex - 1;
        }
    }
    if (segments.length === 0) {
        return null;
    }
    if (cursor < sql.length) {
        const trailing = sql.slice(cursor);
        if (trailing.length > 0) {
            segments.push(createRawFragment(trailing));
        }
    }
    return segments;
}
function parseSimpleStatement(sql) {
    const root = parseCst(sql);
    if (!root) {
        return null;
    }
    return toStatementNode(root);
}
/**
 * Locates the end of a SELECT segment, stopping at UNION keywords or the
 * closing parenthesis that owns the statement.
 *
 * @example
 * ```ts
 * const end = findSelectBoundary("SELECT 1 UNION SELECT 2", 0, 0);
 * ```
 */
function findSelectBoundary(sql, startIndex, startDepth) {
    let depth = startDepth;
    let inSingleQuotedString = false;
    for (let index = startIndex; index < sql.length; index += 1) {
        const ch = sql[index];
        if (inSingleQuotedString) {
            if (ch === "'") {
                const next = sql[index + 1];
                if (next === "'") {
                    index += 1;
                    continue;
                }
                inSingleQuotedString = false;
            }
            continue;
        }
        if (ch === "'") {
            inSingleQuotedString = true;
            continue;
        }
        if (ch === "(") {
            depth += 1;
            continue;
        }
        if (ch === ")") {
            depth -= 1;
            if (depth < startDepth) {
                return index;
            }
            continue;
        }
        if (depth === startDepth && isKeywordAt(sql, index, "union")) {
            return index;
        }
    }
    return sql.length;
}
/**
 * Checks whether the given keyword appears at the specified index.
 *
 * @example
 * ```ts
 * isKeywordAt("UNION", 0, "union") === true;
 * ```
 */
function isKeywordAt(sql, index, keyword) {
    if (index < 0) {
        return false;
    }
    const remaining = sql.length - index;
    if (remaining < keyword.length) {
        return false;
    }
    const candidate = sql.slice(index, index + keyword.length).toLowerCase();
    return candidate === keyword;
}
/**
 * Determines whether the character at a position can precede an identifier.
 *
 * @example
 * ```ts
 * isIdentifierBoundary(" SELECT", 0) === true;
 * ```
 */
function isIdentifierBoundary(sql, index) {
    if (index < 0) {
        return true;
    }
    const ch = sql[index];
    return !/[a-zA-Z0-9_]/.test(ch);
}
/**
 * Wraps SQL text in a raw fragment node.
 *
 * @example
 * ```ts
 * const fragment = createRawFragment("UNSUPPORTED()");
 * ```
 */
function createRawFragment(sql) {
    return {
        node_kind: "raw_fragment",
        sql_text: sql,
    };
}
//# sourceMappingURL=parse.js.map