UNPKG

rawsql-ts

Version:

High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.

3,285 lines 165 kB
import { PartitionByClause, OrderByClause, OrderByItem, SelectClause, SelectItem, Distinct, DistinctOn, SortDirection, NullsSortDirection, TableSource, SourceExpression, FromClause, JoinClause, JoinOnClause, JoinUsingClause, FunctionSource, SourceAliasExpression, WhereClause, GroupByClause, HavingClause, SubQuerySource, WindowFrameClause, LimitClause, ForClause, OffsetClause, WindowsClause as WindowClause, CommonTable, WithClause, FetchClause, FetchExpression, InsertClause, UpdateClause, DeleteClause, UsingClause, SetClause, ReturningClause, SetClauseItem } from "../models/Clause";
import { HintClause } from "../models/HintClause";
import { BinarySelectQuery, SimpleSelectQuery, ValuesQuery } from "../models/SelectQuery";
import { SqlPrintToken, SqlPrintTokenType, SqlPrintTokenContainerType } from "../models/SqlPrintToken";
import { SelectQueryWithClauseHelper } from "../utils/SelectQueryWithClauseHelper";
import { ValueList, ColumnReference, FunctionCall, UnaryExpression, BinaryExpression, LiteralValue, ParameterExpression, SwitchCaseArgument, CaseKeyValuePair, RawString, IdentifierString, ParenExpression, CastExpression, CaseExpression, ArrayExpression, ArrayQueryExpression, ArraySliceExpression, ArrayIndexExpression, BetweenExpression, StringSpecifierExpression, TypeValue, TupleExpression, WindowFrameExpression, QualifiedName, InlineQuery, WindowFrameSpec, WindowFrameBoundStatic, WindowFrameBoundaryValue } from "../models/ValueComponent";
import { ParameterCollector } from "../transformers/ParameterCollector";
import { IdentifierDecorator } from "./IdentifierDecorator";
import { ParameterDecorator } from "./ParameterDecorator";
import { InsertQuery } from "../models/InsertQuery";
import { UpdateQuery } from "../models/UpdateQuery";
import { DeleteQuery } from "../models/DeleteQuery";
import { CreateTableQuery, TableColumnDefinition, ColumnConstraintDefinition, TableConstraintDefinition, ReferenceDefinition } from "../models/CreateTableQuery";
import { MergeQuery, MergeWhenClause, MergeUpdateAction, MergeDeleteAction, MergeInsertAction, MergeDoNothingAction } from "../models/MergeQuery";
import { DropTableStatement, DropIndexStatement, CreateIndexStatement, IndexColumnDefinition, AlterTableStatement, AlterTableAddConstraint, AlterTableDropConstraint, AlterTableAddColumn, AlterTableDropColumn, AlterTableAlterColumnDefault, DropConstraintStatement, ExplainStatement, AnalyzeStatement, CreateSequenceStatement, AlterSequenceStatement } from "../models/DDLStatements";
export var ParameterStyle;
(function (ParameterStyle) {
    ParameterStyle["Anonymous"] = "anonymous";
    ParameterStyle["Indexed"] = "indexed";
    ParameterStyle["Named"] = "named";
})(ParameterStyle || (ParameterStyle = {}));
export const PRESETS = {
    mysql: {
        identifierEscape: { start: '`', end: '`' },
        parameterSymbol: '?',
        parameterStyle: ParameterStyle.Anonymous,
        constraintStyle: 'mysql',
    },
    postgres: {
        identifierEscape: { start: '"', end: '"' },
        parameterSymbol: '$',
        parameterStyle: ParameterStyle.Indexed,
        castStyle: 'postgres',
        constraintStyle: 'postgres',
    },
    postgresWithNamedParams: {
        identifierEscape: { start: '"', end: '"' },
        parameterSymbol: ':',
        parameterStyle: ParameterStyle.Named,
        castStyle: 'postgres',
        constraintStyle: 'postgres',
    },
    sqlserver: {
        identifierEscape: { start: '[', end: ']' },
        parameterSymbol: '@',
        parameterStyle: ParameterStyle.Named,
        constraintStyle: 'postgres',
    },
    sqlite: {
        identifierEscape: { start: '"', end: '"' },
        parameterSymbol: ':',
        parameterStyle: ParameterStyle.Named,
        constraintStyle: 'postgres',
    },
    oracle: {
        identifierEscape: { start: '"', end: '"' },
        parameterSymbol: ':',
        parameterStyle: ParameterStyle.Named,
        constraintStyle: 'postgres',
    },
    clickhouse: {
        identifierEscape: { start: '`', end: '`' },
        parameterSymbol: '?',
        parameterStyle: ParameterStyle.Anonymous,
        constraintStyle: 'postgres',
    },
    firebird: {
        identifierEscape: { start: '"', end: '"' },
        parameterSymbol: '?',
        parameterStyle: ParameterStyle.Anonymous,
    },
    db2: {
        identifierEscape: { start: '"', end: '"' },
        parameterSymbol: '?',
        parameterStyle: ParameterStyle.Anonymous,
    },
    snowflake: {
        identifierEscape: { start: '"', end: '"' },
        parameterSymbol: '?',
        parameterStyle: ParameterStyle.Anonymous,
    },
    cloudspanner: {
        identifierEscape: { start: '`', end: '`' },
        parameterSymbol: '@',
        parameterStyle: ParameterStyle.Named,
    },
    duckdb: {
        identifierEscape: { start: '"', end: '"' },
        parameterSymbol: '?',
        parameterStyle: ParameterStyle.Anonymous,
    },
    cockroachdb: {
        identifierEscape: { start: '"', end: '"' },
        parameterSymbol: '$',
        parameterStyle: ParameterStyle.Indexed,
        castStyle: 'postgres',
    },
    athena: {
        identifierEscape: { start: '"', end: '"' },
        parameterSymbol: '?',
        parameterStyle: ParameterStyle.Anonymous,
    },
    bigquery: {
        identifierEscape: { start: '`', end: '`' },
        parameterSymbol: '@',
        parameterStyle: ParameterStyle.Named,
    },
    hive: {
        identifierEscape: { start: '`', end: '`' },
        parameterSymbol: '?',
        parameterStyle: ParameterStyle.Anonymous,
    },
    mariadb: {
        identifierEscape: { start: '`', end: '`' },
        parameterSymbol: '?',
        parameterStyle: ParameterStyle.Anonymous,
    },
    redshift: {
        identifierEscape: { start: '"', end: '"' },
        parameterSymbol: '$',
        parameterStyle: ParameterStyle.Indexed,
        castStyle: 'postgres',
    },
    flinksql: {
        identifierEscape: { start: '`', end: '`' },
        parameterSymbol: '?',
        parameterStyle: ParameterStyle.Anonymous,
    },
    mongodb: {
        identifierEscape: { start: '"', end: '"' },
        parameterSymbol: '?',
        parameterStyle: ParameterStyle.Anonymous,
    },
};
export class SqlPrintTokenParser {
    static getSelfHandlingComponentTypes() {
        if (!this._selfHandlingComponentTypes) {
            this._selfHandlingComponentTypes = new Set([
                SimpleSelectQuery.kind,
                SelectItem.kind,
                CaseKeyValuePair.kind,
                SwitchCaseArgument.kind,
                ColumnReference.kind,
                LiteralValue.kind,
                ParameterExpression.kind,
                TableSource.kind,
                SourceAliasExpression.kind,
                TypeValue.kind,
                FunctionCall.kind,
                IdentifierString.kind,
                QualifiedName.kind
            ]);
        }
        return this._selfHandlingComponentTypes;
    }
    constructor(options) {
        var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
        this.handlers = new Map();
        this.index = 1;
        this.joinConditionContexts = [];
        if (options === null || options === void 0 ? void 0 : options.preset) {
            const preset = options.preset;
            options = Object.assign(Object.assign({}, preset), options);
        }
        this.parameterDecorator = new ParameterDecorator({
            prefix: typeof (options === null || options === void 0 ? void 0 : options.parameterSymbol) === 'string' ? options.parameterSymbol : (_b = (_a = options === null || options === void 0 ? void 0 : options.parameterSymbol) === null || _a === void 0 ? void 0 : _a.start) !== null && _b !== void 0 ? _b : ':',
            suffix: typeof (options === null || options === void 0 ? void 0 : options.parameterSymbol) === 'object' ? options.parameterSymbol.end : '',
            style: (_c = options === null || options === void 0 ? void 0 : options.parameterStyle) !== null && _c !== void 0 ? _c : 'named'
        });
        this.identifierDecorator = new IdentifierDecorator({
            start: (_e = (_d = options === null || options === void 0 ? void 0 : options.identifierEscape) === null || _d === void 0 ? void 0 : _d.start) !== null && _e !== void 0 ? _e : '"',
            end: (_g = (_f = options === null || options === void 0 ? void 0 : options.identifierEscape) === null || _f === void 0 ? void 0 : _f.end) !== null && _g !== void 0 ? _g : '"'
        });
        this.castStyle = (_h = options === null || options === void 0 ? void 0 : options.castStyle) !== null && _h !== void 0 ? _h : 'standard';
        this.constraintStyle = (_j = options === null || options === void 0 ? void 0 : options.constraintStyle) !== null && _j !== void 0 ? _j : 'postgres';
        this.normalizeJoinConditionOrder = (_k = options === null || options === void 0 ? void 0 : options.joinConditionOrderByDeclaration) !== null && _k !== void 0 ? _k : false;
        this.handlers.set(ValueList.kind, (expr) => this.visitValueList(expr));
        this.handlers.set(ColumnReference.kind, (expr) => this.visitColumnReference(expr));
        this.handlers.set(QualifiedName.kind, (expr) => this.visitQualifiedName(expr));
        this.handlers.set(FunctionCall.kind, (expr) => this.visitFunctionCall(expr));
        this.handlers.set(UnaryExpression.kind, (expr) => this.visitUnaryExpression(expr));
        this.handlers.set(BinaryExpression.kind, (expr) => this.visitBinaryExpression(expr));
        this.handlers.set(LiteralValue.kind, (expr) => this.visitLiteralValue(expr));
        this.handlers.set(ParameterExpression.kind, (expr) => this.visitParameterExpression(expr));
        this.handlers.set(SwitchCaseArgument.kind, (expr) => this.visitSwitchCaseArgument(expr));
        this.handlers.set(CaseKeyValuePair.kind, (expr) => this.visitCaseKeyValuePair(expr));
        this.handlers.set(RawString.kind, (expr) => this.visitRawString(expr));
        this.handlers.set(IdentifierString.kind, (expr) => this.visitIdentifierString(expr));
        this.handlers.set(ParenExpression.kind, (expr) => this.visitParenExpression(expr));
        this.handlers.set(CastExpression.kind, (expr) => this.visitCastExpression(expr));
        this.handlers.set(CaseExpression.kind, (expr) => this.visitCaseExpression(expr));
        this.handlers.set(ArrayExpression.kind, (expr) => this.visitArrayExpression(expr));
        this.handlers.set(ArrayQueryExpression.kind, (expr) => this.visitArrayQueryExpression(expr));
        this.handlers.set(ArraySliceExpression.kind, (expr) => this.visitArraySliceExpression(expr));
        this.handlers.set(ArrayIndexExpression.kind, (expr) => this.visitArrayIndexExpression(expr));
        this.handlers.set(BetweenExpression.kind, (expr) => this.visitBetweenExpression(expr));
        this.handlers.set(StringSpecifierExpression.kind, (expr) => this.visitStringSpecifierExpression(expr));
        this.handlers.set(TypeValue.kind, (expr) => this.visitTypeValue(expr));
        this.handlers.set(TupleExpression.kind, (expr) => this.visitTupleExpression(expr));
        this.handlers.set(InlineQuery.kind, (expr) => this.visitInlineQuery(expr));
        this.handlers.set(WindowFrameExpression.kind, (expr) => this.visitWindowFrameExpression(expr));
        this.handlers.set(WindowFrameSpec.kind, (expr) => this.visitWindowFrameSpec(expr));
        this.handlers.set(WindowFrameBoundStatic.kind, (expr) => this.visitWindowFrameBoundStatic(expr));
        this.handlers.set(WindowFrameBoundaryValue.kind, (expr) => this.visitWindowFrameBoundaryValue(expr));
        this.handlers.set(PartitionByClause.kind, (expr) => this.visitPartitionByClause(expr));
        this.handlers.set(OrderByClause.kind, (expr) => this.visitOrderByClause(expr));
        this.handlers.set(OrderByItem.kind, (expr) => this.visitOrderByItem(expr));
        // select
        this.handlers.set(SelectItem.kind, (expr) => this.visitSelectItem(expr));
        this.handlers.set(SelectClause.kind, (expr) => this.visitSelectClause(expr));
        this.handlers.set(Distinct.kind, (expr) => this.visitDistinct(expr));
        this.handlers.set(DistinctOn.kind, (expr) => this.visitDistinctOn(expr));
        this.handlers.set(HintClause.kind, (expr) => this.visitHintClause(expr));
        // from
        this.handlers.set(TableSource.kind, (expr) => this.visitTableSource(expr));
        this.handlers.set(FunctionSource.kind, (expr) => this.visitFunctionSource(expr));
        this.handlers.set(SourceExpression.kind, (expr) => this.visitSourceExpression(expr));
        this.handlers.set(SourceAliasExpression.kind, (expr) => this.visitSourceAliasExpression(expr));
        this.handlers.set(FromClause.kind, (expr) => this.visitFromClause(expr));
        this.handlers.set(JoinClause.kind, (expr) => this.visitJoinClause(expr));
        this.handlers.set(JoinOnClause.kind, (expr) => this.visitJoinOnClause(expr));
        this.handlers.set(JoinUsingClause.kind, (expr) => this.visitJoinUsingClause(expr));
        // where
        this.handlers.set(WhereClause.kind, (expr) => this.visitWhereClause(expr));
        // group
        this.handlers.set(GroupByClause.kind, (expr) => this.visitGroupByClause(expr));
        this.handlers.set(HavingClause.kind, (expr) => this.visitHavingClause(expr));
        this.handlers.set(WindowClause.kind, (expr) => this.visitWindowClause(expr));
        this.handlers.set(WindowFrameClause.kind, (expr) => this.visitWindowFrameClause(expr));
        this.handlers.set(LimitClause.kind, (expr) => this.visitLimitClause(expr));
        this.handlers.set(OffsetClause.kind, (expr) => this.visitOffsetClause(expr));
        this.handlers.set(FetchClause.kind, (expr) => this.visitFetchClause(expr));
        this.handlers.set(FetchExpression.kind, (expr) => this.visitFetchExpression(expr));
        this.handlers.set(ForClause.kind, (expr) => this.visitForClause(expr));
        // With
        this.handlers.set(WithClause.kind, (expr) => this.visitWithClause(expr));
        this.handlers.set(CommonTable.kind, (expr) => this.visitCommonTable(expr));
        // Query
        this.handlers.set(SimpleSelectQuery.kind, (expr) => this.visitSimpleQuery(expr));
        this.handlers.set(SubQuerySource.kind, (expr) => this.visitSubQuerySource(expr));
        this.handlers.set(BinarySelectQuery.kind, (expr) => this.visitBinarySelectQuery(expr));
        this.handlers.set(ValuesQuery.kind, (expr) => this.visitValuesQuery(expr));
        this.handlers.set(TupleExpression.kind, (expr) => this.visitTupleExpression(expr));
        this.handlers.set(InsertQuery.kind, (expr) => this.visitInsertQuery(expr));
        this.handlers.set(InsertClause.kind, (expr) => this.visitInsertClause(expr));
        this.handlers.set(UpdateQuery.kind, (expr) => this.visitUpdateQuery(expr));
        this.handlers.set(UpdateClause.kind, (expr) => this.visitUpdateClause(expr));
        this.handlers.set(DeleteQuery.kind, (expr) => this.visitDeleteQuery(expr));
        this.handlers.set(DeleteClause.kind, (expr) => this.visitDeleteClause(expr));
        this.handlers.set(UsingClause.kind, (expr) => this.visitUsingClause(expr));
        this.handlers.set(SetClause.kind, (expr) => this.visitSetClause(expr));
        this.handlers.set(SetClauseItem.kind, (expr) => this.visitSetClauseItem(expr));
        this.handlers.set(ReturningClause.kind, (expr) => this.visitReturningClause(expr));
        this.handlers.set(CreateTableQuery.kind, (expr) => this.visitCreateTableQuery(expr));
        this.handlers.set(TableColumnDefinition.kind, (expr) => this.visitTableColumnDefinition(expr));
        this.handlers.set(ColumnConstraintDefinition.kind, (expr) => this.visitColumnConstraintDefinition(expr));
        this.handlers.set(TableConstraintDefinition.kind, (expr) => this.visitTableConstraintDefinition(expr));
        this.handlers.set(ReferenceDefinition.kind, (expr) => this.visitReferenceDefinition(expr));
        this.handlers.set(CreateIndexStatement.kind, (expr) => this.visitCreateIndexStatement(expr));
        this.handlers.set(IndexColumnDefinition.kind, (expr) => this.visitIndexColumnDefinition(expr));
        this.handlers.set(CreateSequenceStatement.kind, (expr) => this.visitCreateSequenceStatement(expr));
        this.handlers.set(AlterSequenceStatement.kind, (expr) => this.visitAlterSequenceStatement(expr));
        this.handlers.set(DropTableStatement.kind, (expr) => this.visitDropTableStatement(expr));
        this.handlers.set(DropIndexStatement.kind, (expr) => this.visitDropIndexStatement(expr));
        this.handlers.set(AlterTableStatement.kind, (expr) => this.visitAlterTableStatement(expr));
        this.handlers.set(AlterTableAddConstraint.kind, (expr) => this.visitAlterTableAddConstraint(expr));
        this.handlers.set(AlterTableDropConstraint.kind, (expr) => this.visitAlterTableDropConstraint(expr));
        this.handlers.set(AlterTableAddColumn.kind, (expr) => this.visitAlterTableAddColumn(expr));
        this.handlers.set(AlterTableDropColumn.kind, (expr) => this.visitAlterTableDropColumn(expr));
        this.handlers.set(AlterTableAlterColumnDefault.kind, (expr) => this.visitAlterTableAlterColumnDefault(expr));
        this.handlers.set(DropConstraintStatement.kind, (expr) => this.visitDropConstraintStatement(expr));
        this.handlers.set(ExplainStatement.kind, (expr) => this.visitExplainStatement(expr));
        this.handlers.set(AnalyzeStatement.kind, (expr) => this.visitAnalyzeStatement(expr));
        this.handlers.set(MergeQuery.kind, (expr) => this.visitMergeQuery(expr));
        this.handlers.set(MergeWhenClause.kind, (expr) => this.visitMergeWhenClause(expr));
        this.handlers.set(MergeUpdateAction.kind, (expr) => this.visitMergeUpdateAction(expr));
        this.handlers.set(MergeDeleteAction.kind, (expr) => this.visitMergeDeleteAction(expr));
        this.handlers.set(MergeInsertAction.kind, (expr) => this.visitMergeInsertAction(expr));
        this.handlers.set(MergeDoNothingAction.kind, (expr) => this.visitMergeDoNothingAction(expr));
    }
    /**
     * Pretty-prints a BinarySelectQuery (e.g., UNION, INTERSECT, EXCEPT).
     * This will recursively print left and right queries, separated by the operator.
     * @param arg BinarySelectQuery
     */
    visitBinarySelectQuery(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '');
        // Handle positioned comments for BinarySelectQuery (unified spec)
        if (arg.positionedComments && arg.positionedComments.length > 0) {
            this.addPositionedCommentsToToken(token, arg);
            // Clear positioned comments to prevent duplicate processing
            arg.positionedComments = null;
        }
        else if (arg.headerComments && arg.headerComments.length > 0) {
            if (this.shouldMergeHeaderComments(arg.headerComments)) {
                const mergedHeaderComment = this.createHeaderMultiLineCommentBlock(arg.headerComments);
                token.innerTokens.push(mergedHeaderComment);
            }
            else {
                const headerCommentBlocks = this.createCommentBlocks(arg.headerComments, true);
                token.innerTokens.push(...headerCommentBlocks);
            }
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        }
        token.innerTokens.push(this.visit(arg.left));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, arg.operator.value, SqlPrintTokenContainerType.BinarySelectQueryOperator));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(this.visit(arg.right));
        return token;
    }
    /**
     * Returns an array of tokens representing a comma followed by a space.
     * This is a common pattern in SQL pretty-printing.
     */
    static commaSpaceTokens() {
        return [SqlPrintTokenParser.COMMA_TOKEN, SqlPrintTokenParser.SPACE_TOKEN];
    }
    static argumentCommaSpaceTokens() {
        return [SqlPrintTokenParser.ARGUMENT_SPLIT_COMMA_TOKEN, SqlPrintTokenParser.SPACE_TOKEN];
    }
    visitQualifiedName(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.QualifiedName);
        if (arg.namespaces) {
            for (let i = 0; i < arg.namespaces.length; i++) {
                token.innerTokens.push(arg.namespaces[i].accept(this));
                token.innerTokens.push(SqlPrintTokenParser.DOT_TOKEN);
            }
        }
        // Handle name and its comments carefully
        // We need to prevent double processing by temporarily clearing the name's comments,
        // then process them at the QualifiedName level
        const originalNameComments = arg.name.positionedComments;
        const originalNameLegacyComments = arg.name.comments;
        // Temporarily clear name's comments to prevent double processing
        arg.name.positionedComments = null;
        arg.name.comments = null;
        const nameToken = arg.name.accept(this);
        token.innerTokens.push(nameToken);
        // Restore original comments
        arg.name.positionedComments = originalNameComments;
        arg.name.comments = originalNameLegacyComments;
        // Apply the name's comments to the qualified name token
        if (this.hasPositionedComments(arg.name) || this.hasLegacyComments(arg.name)) {
            this.addComponentComments(token, arg.name);
        }
        // Also handle any comments directly on the QualifiedName itself
        this.addComponentComments(token, arg);
        return token;
    }
    visitPartitionByClause(arg) {
        // Print as: partition by ...
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, 'partition by', SqlPrintTokenContainerType.PartitionByClause);
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(this.visit(arg.value));
        return token;
    }
    visitOrderByClause(arg) {
        // Print as: order by ...
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, 'order by', SqlPrintTokenContainerType.OrderByClause);
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        for (let i = 0; i < arg.order.length; i++) {
            if (i > 0)
                token.innerTokens.push(...SqlPrintTokenParser.commaSpaceTokens());
            token.innerTokens.push(this.visit(arg.order[i]));
        }
        return token;
    }
    /**
     * Print an OrderByItem (expression [asc|desc] [nulls first|last])
     */
    visitOrderByItem(arg) {
        // arg: OrderByItem
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.OrderByItem);
        token.innerTokens.push(this.visit(arg.value));
        if (arg.sortDirection && arg.sortDirection !== SortDirection.Ascending) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'desc'));
        }
        if (arg.nullsPosition) {
            if (arg.nullsPosition === NullsSortDirection.First) {
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'nulls first'));
            }
            else if (arg.nullsPosition === NullsSortDirection.Last) {
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'nulls last'));
            }
        }
        return token;
    }
    parse(arg) {
        // reset parameter index before parsing
        this.index = 1;
        const token = this.visit(arg);
        const paramsRaw = ParameterCollector.collect(arg).sort((a, b) => { var _a, _b; return ((_a = a.index) !== null && _a !== void 0 ? _a : 0) - ((_b = b.index) !== null && _b !== void 0 ? _b : 0); });
        const style = this.parameterDecorator.style;
        if (style === ParameterStyle.Named) {
            // Named: { name: value, ... }
            const paramsObj = {};
            for (const p of paramsRaw) {
                const key = p.name.value;
                if (paramsObj.hasOwnProperty(key)) {
                    if (paramsObj[key] !== p.value) {
                        throw new Error(`Duplicate parameter name '${key}' with different values detected during query composition.`);
                    }
                    // If value is the same, skip (already set)
                    continue;
                }
                paramsObj[key] = p.value;
            }
            return { token, params: paramsObj };
        }
        else if (style === ParameterStyle.Indexed) {
            // Indexed: [value1, value2, ...] (sorted by index)
            const paramsArr = paramsRaw.map(p => p.value);
            return { token, params: paramsArr };
        }
        else if (style === ParameterStyle.Anonymous) {
            // Anonymous: [value1, value2, ...] (sorted by index, name is empty)
            const paramsArr = paramsRaw.map(p => p.value);
            return { token, params: paramsArr };
        }
        // Fallback (just in case)
        return { token, params: [] };
    }
    /**
     * Check if a component handles its own comments
     */
    componentHandlesOwnComments(component) {
        // First check if component has a handlesOwnComments method
        if ('handlesOwnComments' in component && typeof component.handlesOwnComments === 'function') {
            return component.handlesOwnComments();
        }
        return SqlPrintTokenParser.getSelfHandlingComponentTypes().has(component.getKind());
    }
    visit(arg) {
        const handler = this.handlers.get(arg.getKind());
        if (handler) {
            const token = handler(arg);
            if (!this.componentHandlesOwnComments(arg)) {
                this.addComponentComments(token, arg);
            }
            return token;
        }
        throw new Error(`[SqlPrintTokenParser] No handler for kind: ${arg.getKind().toString()}`);
    }
    /**
     * Check if a component has positioned comments
     */
    hasPositionedComments(component) {
        var _a, _b;
        return ((_b = (_a = component.positionedComments) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0;
    }
    /**
     * Check if a component has legacy comments
     */
    hasLegacyComments(component) {
        var _a, _b;
        return ((_b = (_a = component.comments) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0;
    }
    /**
     * Centralized comment handling - checks positioned comments first, falls back to legacy
     */
    addComponentComments(token, component) {
        if (this.hasPositionedComments(component)) {
            this.addPositionedCommentsToToken(token, component);
        }
        else if (this.hasLegacyComments(component)) {
            this.addCommentsToToken(token, component.comments);
        }
    }
    /**
     * Adds positioned comment tokens to a SqlPrintToken for inline formatting
     */
    addPositionedCommentsToToken(token, component) {
        if (!this.hasPositionedComments(component)) {
            return;
        }
        // Handle 'before' comments - add inline at the beginning with spaces
        const beforeComments = component.getPositionedComments('before');
        if (beforeComments.length > 0) {
            const commentBlocks = this.createCommentBlocks(beforeComments);
            for (let i = commentBlocks.length - 1; i >= 0; i--) {
                token.innerTokens.unshift(commentBlocks[i]);
            }
        }
        // Handle 'after' comments - add inline after the main content
        const afterComments = component.getPositionedComments('after');
        if (afterComments.length > 0) {
            const commentBlocks = this.createCommentBlocks(afterComments);
            // Append after comments with spaces for inline formatting
            for (const commentBlock of commentBlocks) {
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                token.innerTokens.push(commentBlock);
            }
        }
        // Clear positioned comments to prevent duplicate processing (unified spec)
        // Only clear for specific component types that are known to have duplication issues
        const componentsWithDuplicationIssues = [
            SqlPrintTokenContainerType.CaseExpression,
            SqlPrintTokenContainerType.SwitchCaseArgument,
            SqlPrintTokenContainerType.CaseKeyValuePair,
            SqlPrintTokenContainerType.SelectClause, // SELECT clauses have manual + automatic processing
            SqlPrintTokenContainerType.LiteralValue,
            SqlPrintTokenContainerType.IdentifierString,
            SqlPrintTokenContainerType.DistinctOn,
            SqlPrintTokenContainerType.SourceAliasExpression,
            SqlPrintTokenContainerType.SimpleSelectQuery,
            SqlPrintTokenContainerType.WhereClause // WHERE clauses also have duplication issues
        ];
        if (token.containerType && componentsWithDuplicationIssues.includes(token.containerType)) {
            component.positionedComments = null;
        }
    }
    /**
     * Adds comment tokens to a SqlPrintToken based on the comments array
     */
    addCommentsToToken(token, comments) {
        if (!(comments === null || comments === void 0 ? void 0 : comments.length)) {
            return;
        }
        const commentBlocks = this.createCommentBlocks(comments);
        this.insertCommentBlocksWithSpacing(token, commentBlocks);
    }
    /**
     * Creates inline comment sequence for multiple comments without newlines
     */
    createInlineCommentSequence(comments) {
        const commentTokens = [];
        for (let i = 0; i < comments.length; i++) {
            const comment = comments[i];
            if (comment.trim()) {
                // Add comment token directly
                const commentToken = new SqlPrintToken(SqlPrintTokenType.comment, this.formatComment(comment));
                commentTokens.push(commentToken);
                // Add space between comments (except after last comment)
                if (i < comments.length - 1) {
                    const spaceToken = new SqlPrintToken(SqlPrintTokenType.space, ' ');
                    commentTokens.push(spaceToken);
                }
            }
        }
        return commentTokens;
    }
    /**
     * Creates CommentBlock containers for the given comments.
     * Each CommentBlock contains: Comment -> CommentNewline -> Space.
     * @param comments Raw comment strings to convert into CommentBlock tokens.
     * @param isHeaderComment Marks the generated blocks as originating from header comments when true.
     */
    createCommentBlocks(comments, isHeaderComment = false) {
        // Create individual comment blocks for each comment entry
        const commentBlocks = [];
        for (const comment of comments) {
            // Accept comments that have content after trim OR are separator lines OR are empty (for structure preservation)
            const trimmed = comment.trim();
            const isSeparatorLine = /^[-=_+*#]+$/.test(trimmed);
            if (trimmed || isSeparatorLine || comment === '') {
                commentBlocks.push(this.createSingleCommentBlock(comment, isHeaderComment));
            }
        }
        return commentBlocks;
    }
    /**
     * Determines if a comment should be merged with consecutive comments
     */
    shouldMergeComment(trimmed) {
        const isSeparatorLine = /^[-=_+*#]+$/.test(trimmed);
        // Don't merge line comments unless they are separator-only lines
        if (!isSeparatorLine && trimmed.startsWith('--')) {
            return false;
        }
        // Don't merge if it's already a proper multi-line block comment
        if (trimmed.startsWith('/*') && trimmed.endsWith('*/')) {
            const inner = trimmed.slice(2, -2).trim();
            if (!inner) {
                return false;
            }
            if (trimmed.includes('\n')) {
                return false;
            }
        }
        // Merge all other content including separator lines, plain text, and single-line block comments
        // Separator lines within comment blocks should be merged together
        return true;
    }
    /**
     * Creates a multi-line block comment structure from consecutive comments
     * Returns a CommentBlock containing multiple comment lines for proper LinePrinter integration
     */
    /**
     * Creates a single CommentBlock with the standard structure:
     * Comment -> CommentNewline -> Space
     *
     * This structure supports both formatting modes:
     * - Multiline mode: Comment + newline (space is filtered as leading space)
     * - Oneliner mode: Comment + space (commentNewline is skipped)
     */
    createSingleCommentBlock(comment, isHeaderComment = false) {
        const commentBlock = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.CommentBlock);
        if (isHeaderComment) {
            commentBlock.markAsHeaderComment();
        }
        // Add comment token - preserve original format for line comments
        const commentToken = new SqlPrintToken(SqlPrintTokenType.comment, this.formatComment(comment));
        commentBlock.innerTokens.push(commentToken);
        // Add conditional newline token for multiline mode
        const commentNewlineToken = new SqlPrintToken(SqlPrintTokenType.commentNewline, '');
        commentBlock.innerTokens.push(commentNewlineToken);
        // Add space token for oneliner mode spacing
        const spaceToken = new SqlPrintToken(SqlPrintTokenType.space, ' ');
        commentBlock.innerTokens.push(spaceToken);
        return commentBlock;
    }
    /**
     * Formats a comment, preserving line comment format for -- comments
     * and converting others to block format for safety
     */
    formatComment(comment) {
        const trimmed = comment.trim();
        if (!trimmed) {
            return '/* */';
        }
        const isSeparatorLine = /^[-=_+*#]+$/.test(trimmed);
        if (isSeparatorLine) {
            return this.formatBlockComment(trimmed);
        }
        if (trimmed.startsWith('--')) {
            return this.formatLineComment(trimmed.slice(2));
        }
        if (trimmed.startsWith('/*') && trimmed.endsWith('*/')) {
            return this.formatBlockComment(trimmed);
        }
        return this.formatBlockComment(trimmed);
    }
    /**
     * Inserts comment blocks into a token and handles spacing logic.
     * Adds separator spaces for clause-level containers and manages duplicate space removal.
     */
    insertCommentBlocksWithSpacing(token, commentBlocks) {
        // For SelectItem, append comment blocks after ensuring spacing
        if (token.containerType === SqlPrintTokenContainerType.SelectItem) {
            if (token.innerTokens.length > 0) {
                const lastToken = token.innerTokens[token.innerTokens.length - 1];
                if (lastToken.type !== SqlPrintTokenType.space) {
                    token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                }
            }
            token.innerTokens.push(...commentBlocks);
            return;
        }
        // Special handling for SelectClause to add space between keyword and comment
        if (token.containerType === SqlPrintTokenContainerType.SelectClause) {
            // For SelectClause, comments need to be inserted after the keyword with a space separator
            // Current structure: [keyword text, space, other tokens...]
            // Desired structure: [keyword text, space, comments, space, other tokens...]
            token.innerTokens.unshift(SqlPrintTokenParser.SPACE_TOKEN, ...commentBlocks);
            return;
        }
        // Special handling for IdentifierString to add space before comment
        if (token.containerType === SqlPrintTokenContainerType.IdentifierString) {
            if (token.innerTokens.length > 0) {
                const lastToken = token.innerTokens[token.innerTokens.length - 1];
                if (lastToken.type !== SqlPrintTokenType.space) {
                    token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                }
            }
            token.innerTokens.push(...commentBlocks);
            return;
        }
        token.innerTokens.unshift(...commentBlocks);
        // Add a separator space after comments only for certain container types
        // where comments need to be separated from main content
        const needsSeparatorSpace = this.shouldAddSeparatorSpace(token.containerType);
        if (needsSeparatorSpace) {
            const separatorSpace = new SqlPrintToken(SqlPrintTokenType.space, ' ');
            token.innerTokens.splice(commentBlocks.length, 0, separatorSpace);
            // Remove the original space token after our separator if it exists 
            // This prevents duplicate spaces when comments are added
            if (token.innerTokens.length > commentBlocks.length + 1 &&
                token.innerTokens[commentBlocks.length + 1].type === SqlPrintTokenType.space) {
                token.innerTokens.splice(commentBlocks.length + 1, 1);
            }
        }
        else {
            // For containers that don't need separator space, still remove duplicate spaces
            if (token.innerTokens.length > commentBlocks.length &&
                token.innerTokens[commentBlocks.length].type === SqlPrintTokenType.space) {
                token.innerTokens.splice(commentBlocks.length, 1);
            }
        }
    }
    /**
     * Handles positioned comments for ParenExpression with special spacing rules.
     * ParenExpression comments should be adjacent to parentheses without separator spaces.
     */
    addPositionedCommentsToParenExpression(token, component) {
        if (!component.positionedComments) {
            return;
        }
        // For ParenExpression: (/* comment */ content /* comment */)
        // Comments should be placed immediately after opening paren and before closing paren
        // Handle 'before' comments - place after opening parenthesis without space
        const beforeComments = component.getPositionedComments('before');
        if (beforeComments.length > 0) {
            const commentBlocks = this.createCommentBlocks(beforeComments);
            // Insert after opening paren (index 1) without separator space
            let insertIndex = 1;
            for (const commentBlock of commentBlocks) {
                token.innerTokens.splice(insertIndex, 0, commentBlock);
                insertIndex++;
            }
        }
        // Handle 'after' comments - place before closing parenthesis without space
        const afterComments = component.getPositionedComments('after');
        if (afterComments.length > 0) {
            const commentBlocks = this.createCommentBlocks(afterComments);
            const closingIndex = token.innerTokens.length - 1;
            let insertIndex = closingIndex + 1;
            for (const commentBlock of commentBlocks) {
                token.innerTokens.splice(insertIndex, 0, SqlPrintTokenParser.SPACE_TOKEN, commentBlock);
                insertIndex += 2;
            }
        }
    }
    /**
     * Determines whether a separator space should be added after comments for the given container type.
     *
     * Clause-level containers (SELECT, FROM, WHERE, etc.) need separator spaces because:
     * - Comments appear before the main clause content
     * - A space is needed to separate comment block from SQL tokens
     *
     * Item-level containers (SelectItem, etc.) don't need separator spaces because:
     * - Comments are inline with the item content
     * - Spacing is handled by existing token structure
     */
    shouldAddSeparatorSpace(containerType) {
        return this.isClauseLevelContainer(containerType);
    }
    /**
     * Checks if the container type represents a SQL clause (as opposed to an item within a clause).
     */
    isClauseLevelContainer(containerType) {
        switch (containerType) {
            case SqlPrintTokenContainerType.SelectClause:
            case SqlPrintTokenContainerType.FromClause:
            case SqlPrintTokenContainerType.WhereClause:
            case SqlPrintTokenContainerType.GroupByClause:
            case SqlPrintTokenContainerType.HavingClause:
            case SqlPrintTokenContainerType.OrderByClause:
            case SqlPrintTokenContainerType.LimitClause:
            case SqlPrintTokenContainerType.OffsetClause:
            case SqlPrintTokenContainerType.WithClause:
            case SqlPrintTokenContainerType.SimpleSelectQuery:
                return true;
            default:
                return false;
        }
    }
    /**
     * Formats a comment string as a block comment with security sanitization.
     * Prevents SQL injection by removing dangerous comment sequences.
     */
    formatBlockComment(comment) {
        const hasDelimiters = comment.startsWith('/*') && comment.endsWith('*/');
        const rawContent = hasDelimiters ? comment.slice(2, -2) : comment;
        const escapedContent = this.escapeCommentDelimiters(rawContent);
        const normalized = escapedContent.replace(/\r?\n/g, '\n');
        const lines = normalized
            .split('\n')
            .map(line => line.replace(/\s+/g, ' ').trim())
            .filter(line => line.length > 0);
        if (lines.length === 0) {
            return '/* */';
        }
        const isSeparatorLine = lines.length === 1 && /^[-=_+*#]+$/.test(lines[0]);
        if (!hasDelimiters) {
            // Flatten free-form comments to a single block to avoid leaking multi-line structures.
            if (isSeparatorLine) {
                return `/* ${lines[0]} */`;
            }
            const flattened = lines.join(' ');
            return `/* ${flattened} */`;
        }
        if (isSeparatorLine || lines.length === 1) {
            return `/* ${lines[0]} */`;
        }
        const body = lines.map(line => `  ${line}`).join('\n');
        return `/*\n${body}\n*/`;
    }
    shouldMergeHeaderComments(comments) {
        if (comments.length <= 1) {
            return false;
        }
        return comments.some(comment => {
            const trimmed = comment.trim();
            return /^[-=_+*#]{3,}$/.test(trimmed) || trimmed.startsWith('- ') || trimmed.startsWith('* ');
        });
    }
    createHeaderMultiLineCommentBlock(headerComments) {
        const commentBlock = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.CommentBlock);
        commentBlock.markAsHeaderComment();
        if (headerComments.length === 0) {
            const commentToken = new SqlPrintToken(SqlPrintTokenType.comment, '/* */');
            commentBlock.innerTokens.push(commentToken);
        }
        else {
            const openToken = new SqlPrintToken(SqlPrintTokenType.comment, '/*');
            commentBlock.innerTokens.push(openToken);
            commentBlock.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.commentNewline, ''));
            for (const line of headerComments) {
                const sanitized = this.escapeCommentDelimiters(line);
                const lineToken = new SqlPrintToken(SqlPrintTokenType.comment, `  ${sanitized}`);
                commentBlock.innerTokens.push(lineToken);
                commentBlock.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.commentNewline, ''));
            }
            const closeToken = new SqlPrintToken(SqlPrintTokenType.comment, '*/');
            commentBlock.innerTokens.push(closeToken);
        }
        commentBlock.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.commentNewline, ''));
        commentBlock.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.space, ' '));
        return commentBlock;
    }
    /**
     * Formats text as a single-line comment while sanitizing unsafe sequences.
     */
    formatLineComment(content) {
        // Normalize content to a single line and remove dangerous sequences
        const sanitized = this.sanitizeLineCommentContent(content);
        if (!sanitized) {
            return '--';
        }
        return `-- ${sanitized}`;
    }
    /**
     * Sanitizes content intended for a single-line comment.
     */
    sanitizeLineCommentContent(content) {
        // Replace comment delimiters to avoid nested comment injection
        let sanitized = this.escapeCommentDelimiters(content)
            .replace(/\r?\n/g, ' ')
            .replace(/\u2028|\u2029/g, ' ')
            .replace(/\s+/g, ' ')
            .trim();
        if (sanitized.startsWith('--')) {
            sanitized = sanitized.slice(2).trimStart();
        }
        return sanitized;
    }
    escapeCommentDelimiters(content) {
        return content
            .replace(/\/\*/g, '\\/\\*')
            .replace(/\*\//g, '*\\/');
    }
    visitValueList(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.ValueList);
        for (let i = 0; i < arg.values.length; i++) {
            if (i > 0) {
                token.innerTokens.push(...SqlPrintTokenParser.argumentCommaSpaceTokens());
            }
            token.innerTokens.push(this.visit(arg.values[i]));
        }
        return token;
    }
    visitColumnReference(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.ColumnReference);
        token.innerTokens.push(arg.qualifiedName.accept(this));
        this.addComponentComments(token, arg);
        return token;
    }
    visitFunctionCall(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.FunctionCall);
        token.innerTokens.push(arg.qualifiedName.accept(this));
        token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
        if (arg.argument) {
            this.relocateGroupingSetComments(arg);
            token.innerTokens.push(this.visit(arg.argument));
        }
        if (arg.internalOrderBy) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(this.visit(arg.internalOrderBy));
        }
        // Use FunctionCall comments if available, otherwise use static token
        if (arg.comments && arg.comments.length > 0) {
            const closingParenToken = new SqlPrintToken(SqlPrintTokenType.parenthesis, ')');
            this.addCommentsToToken(closingParenToken, arg.comments);
            token.innerTokens.push(closingParenToken);
            // Clear the comments from arg to prevent duplicate output by the general comment handler
            arg.comments = null;
        }
        else {
            token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
        }
        if (arg.filterCondition) {
            // Emit FILTER clause so the aggregate preserves its predicate.
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'filter'));
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'where'));
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(this.visit(arg.filterCondition));
            token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
        }
        if (arg.withOrdinality) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'with ordinality'));
        }
        if (arg.over) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'over'));
            if (arg.over instanceof IdentifierString) {
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                token.innerTokens.push(arg.over.accept(this));
            }
            else {
                token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
                token.innerTokens.push(this.visit(arg.over));
                token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
            }
        }
        this.addComponentComments(token, arg);
        return token;
    }
    relocateGroupingSetComments(arg) {
        if (!this.isGroupingSetsFunction(arg)) {
            return;
        }
        const argument = arg.argument;
        if (!(argument instanceof ValueList)) {
            return;
        }
        const values = argument.values;
        for (let i = 1; i < values.length; i++) {
            const current = values[i];
            const previous = values[i - 1];
            const leadingComments = this.extractPositionedComments(current, 'before');
            if (leadingComments.length === 0) {
                continue;
            }
            const trailingBlock = leadingComments.map(comment => ({
                position: 'after',
                comments: [...comment.comments],
            }));
            // Append the moved comments after the previous grouping set entry.
            previous.positionedComments = previous.positionedComments
                ? [...previous.positionedComments, ...trailingBlock]
                : trailingBlock;
        }
    }
    isGroupingSetsFunction(arg) {
        const nameComponent = arg.qualifiedName.name;
        const rawName = nameComponent instanceof RawString ? nameComponent.value : nameComponent.name;
        return rawName.trim().toLowerCase() === 'grouping sets';
    }
    extractPositionedComments(component, position) {
        if (!component.positionedComments || component.positionedComments.length === 0) {
            return [];
        }
        const kept = [];
        const extracted = [];
        for (const comment of component.positionedComments) {
            if (comment.position === position) {
                extracted.push({
                    position: comment.position,
                    comments: [...comment.comments],
                });
            }
            else {
                kept.push(comment);
            }
        }
        component.positionedComments = kept.length > 0 ? kept : null;
        return extracted;
    }
    visitUnaryExpression(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.UnaryExpression);
        token.innerTokens.push(this.visit(arg.operator));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(this.visit(arg.expression));
        return token;
    }
    visitBinaryExpression(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.BinaryExpression);
        token.innerTokens.push(this.visit(arg.left));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        // Visit the operator to handle its comments properly
        const operatorToken = this.visit(arg.operator);
        const operatorLower = operatorToken.text.toLowerCase();
        if (operatorLower === 'and' || operatorLower === 'or') {
            operatorToken.type = SqlPrintTokenType.operator;
        }
        token.innerTokens.push(operatorToken);
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(this.visit(arg.right));
        return token;
    }
    visitLiteralValue(arg) {
        let text;
        if (arg.value === null) {
            text = "null";
        }
        else if (arg.isStringLiteral) {
            // For originally quoted string literals, preserve quotes
            text = `'${arg.value.replace(/'/g, "''")}'`;
        }
        else if (typeof arg.value === "string") {
            // For dollar-quoted strings or other string values, use as-is
            text = arg.value;
        }
        else {
            text = arg.value.toString();
        }
        const token = new SqlPrintToken(SqlPrintTokenType.value, text, SqlPrintTokenContainerType.LiteralValue);
        // Handle positioned comments for LiteralValue
        if (arg.positionedComments && arg.positionedComments.length > 0) {
            this.addPositionedCommentsToToken(token, arg);
            // Clear positioned comments to prevent duplicate processing
            arg.positionedComments = null;
        }
        else if (arg.comments && arg.comments.length > 0) {
            this.addCommentsToToken(token, arg.comments);
        }
        return token;
    }
    visitParameterExpression(arg) {
        // Create a parameter token and decorate it using the parameterDecorator
        arg.index = this.index;
        const text = this.parameterDecorator.decorate(arg.name.value, arg.index);
        const token = new SqlPrintToken(SqlPrintTokenType.parameter, text);
        this.addComponentComments(token, arg);
        this.index++;
        return token;
    }
    visitSwitchCaseArgument(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.SwitchCaseArgument);
        this.addComponentComments(token, arg);
        // Add each WHEN/THEN clause
        for (const kv of arg.cases) {
            // Create a new line for each WHEN clause
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(kv.accept(this));
        }
        // Add ELSE clause if present
        if (arg.elseValue) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(this.createElseToken(arg.elseValue, arg.comments));
        }
        // Add SwitchCaseArgument comments (END keyword) if present and no elseValue
        else if (arg.comments && arg.comments.length > 0) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            const commentTokens = this.createInlineCommentSequence(arg.comments);
            token.innerTokens.push(...commentTokens);
        }
        return token;
    }
    createElseToken(elseValue, switchCaseComments) {
        // Creates a token for the ELSE clause in a CASE expression.
        const elseToken = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.ElseClause); // Add the ELSE keyword
        elseToken.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'else'));
        // Add ELSE and END keyword comments if present
        // The switchCaseComments contains both ELSE and END comments in order ['e1', 'end']
        if (switchCaseComments && switchCaseComments.length > 0) {
            elseToken.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            const commentTokens = this.createInlineCommentSequence(switchCaseComments);
            elseToken.innerTokens.push(...commentTokens);
        }
        // Create a container for the ELSE value to enable proper indentation
        elseToken.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        const elseValueContainer = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.CaseElseValue);
        elseValueContainer.innerTokens.push(this.visit(elseValue));
        elseToken.innerTokens.push(elseValueContainer);
        return elseToken;
    }
    visitCaseKeyValuePair(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.CaseKeyValuePair);
        // Handle positioned comments for CaseKeyValuePair
        if (arg.positionedComments && arg.positionedComments.length > 0) {
            this.addPositionedCommentsToToken(token, arg);
            // Clear positioned comments to prevent duplicate processing
            arg.positionedComments = null;
        }
        // Create WHEN clause
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'when'));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(this.visit(arg.key)); // Create THEN clause
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'then'));
        // Add THEN keyword comments if present
        if (arg.comments && arg.comments.length > 0) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            const commentTokens = this.createInlineCommentSequence(arg.comments);
            token.innerTokens.push(...commentTokens);
        }
        // Create a container for the THEN value to enable proper indentation
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        const thenValueContainer = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.CaseThenValue);
        thenValueContainer.innerTokens.push(this.visit(arg.value));
        token.innerTokens.push(thenValueContainer);
        return token;
    }
    visitRawString(arg) {
        // Even for non-container tokens, set the container type for context
        return new SqlPrintToken(SqlPrintTokenType.value, arg.value, SqlPrintTokenContainerType.RawString);
    }
    visitIdentifierString(arg) {
        // Create an identifier token and decorate it using the identifierDecorator
        const text = arg.name === "*" ? arg.name : this.identifierDecorator.decorate(arg.name);
        // Handle positioned comments for IdentifierString
        if (arg.positionedComments && arg.positionedComments.length > 0) {
            const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.IdentifierString);
            // Add positioned comments
            this.addPositionedCommentsToToken(token, arg);
            // Clear positioned comments to prevent duplicate processing
            arg.positionedComments = null;
            // Add the identifier text as the main token
            const valueToken = new SqlPrintToken(SqlPrintTokenType.value, text);
            token.innerTokens.push(valueToken);
            return token;
        }
        // If there are legacy comments, create a container instead of a simple value token
        if (arg.comments && arg.comments.length > 0) {
            const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.IdentifierString);
            // Add the identifier text as the main token
            const valueToken = new SqlPrintToken(SqlPrintTokenType.value, text);
            token.innerTokens.push(valueToken);
            // Add legacy comments to the token
            this.addComponentComments(token, arg);
            return token;
        }
        const token = new SqlPrintToken(SqlPrintTokenType.value, text, SqlPrintTokenContainerType.IdentifierString);
        return token;
    }
    visitParenExpression(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.ParenExpression);
        // Handle positioned comments for ParenExpression - check both self and inner expression
        const hasOwnComments = arg.positionedComments && arg.positionedComments.length > 0;
        const hasInnerComments = arg.expression.positionedComments && arg.expression.positionedComments.length > 0;
        // Store inner comments for later processing and clear to prevent duplicate processing
        let innerBeforeComments = [];
        let innerAfterComments = [];
        if (hasInnerComments) {
            innerBeforeComments = arg.expression.getPositionedComments('before');
            innerAfterComments = arg.expression.getPositionedComments('after');
            arg.expression.positionedComments = null;
        }
        // Build basic structure first
        token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
        token.innerTokens.push(this.visit(arg.expression));
        token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
        // Now add positioned comments in the correct positions manually
        if (innerBeforeComments.length > 0) {
            const commentBlocks = this.createCommentBlocks(innerBeforeComments);
            // Insert after opening paren (index 1) without separator space
            let insertIndex = 1;
            for (const commentBlock of commentBlocks) {
                token.innerTokens.splice(insertIndex, 0, commentBlock);
                insertIndex++;
            }
        }
        if (innerAfterComments.length > 0) {
            const commentBlocks = this.createCommentBlocks(innerAfterComments);
            // Insert before closing paren (last position) without separator space
            const insertIndex = token.innerTokens.length;
            for (const commentBlock of commentBlocks) {
                token.innerTokens.splice(insertIndex - 1, 0, commentBlock);
            }
        }
        if (hasOwnComments) {
            this.addPositionedCommentsToParenExpression(token, arg);
            // Clear positioned comments to prevent duplicate processing in parent containers
            arg.positionedComments = null;
        }
        return token;
    }
    visitCastExpression(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.CastExpression);
        // Use PostgreSQL-specific :: casts only when the preset explicitly opts in.
        if (this.castStyle === 'postgres') {
            token.innerTokens.push(this.visit(arg.input));
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.operator, '::'));
            token.innerTokens.push(this.visit(arg.castType));
            return token;
        }
        // Default to ANSI-compliant CAST(expression AS type) syntax for broader compatibility.
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'cast'));
        token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
        token.innerTokens.push(this.visit(arg.input));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'as'));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(this.visit(arg.castType));
        token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
        return token;
    }
    visitCaseExpression(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.CaseExpression);
        // Handle positioned comments for CaseExpression (unified spec: positioned comments only)
        if (arg.positionedComments && arg.positionedComments.length > 0) {
            this.addPositionedCommentsToToken(token, arg);
            // Clear positioned comments to prevent duplicate processing
            arg.positionedComments = null;
        }
        const promotedComments = [];
        const trailingSwitchComments = this.extractSwitchAfterComments(arg.switchCase);
        let conditionToken = null;
        if (arg.condition) {
            conditionToken = this.visit(arg.condition);
            promotedComments.push(...this.collectCaseLeadingCommentBlocks(conditionToken));
        }
        const switchToken = this.visit(arg.switchCase);
        promotedComments.push(...this.collectCaseLeadingCommentsFromSwitch(switchToken));
        if (promotedComments.length > 0) {
            token.innerTokens.push(...promotedComments);
        }
        // Add the CASE keyword
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'case'));
        // Add the condition if exists
        if (conditionToken) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(conditionToken);
        }
        // Add the WHEN/THEN pairs and ELSE
        token.innerTokens.push(switchToken);
        // Add the END keyword
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'end'));
        if (trailingSwitchComments.length > 0) {
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.commentNewline, ''));
            const trailingBlocks = this.createCommentBlocks(trailingSwitchComments);
            token.innerTokens.push(...trailingBlocks);
        }
        return token;
    }
    extractSwitchAfterComments(arg) {
        if (!arg.positionedComments || arg.positionedComments.length === 0) {
            return [];
        }
        const trailing = [];
        const retained = [];
        for (const entry of arg.positionedComments) {
            if (entry.position === 'after') {
                trailing.push(...entry.comments);
            }
            else {
                retained.push(entry);
            }
        }
        arg.positionedComments = retained.length > 0 ? retained : null;
        return trailing;
    }
    collectCaseLeadingCommentsFromSwitch(token) {
        if (!token.innerTokens || token.innerTokens.length === 0) {
            return [];
        }
        const pairToken = token.innerTokens.find(child => child.containerType === SqlPrintTokenContainerType.CaseKeyValuePair);
        if (!pairToken) {
            return [];
        }
        const keyToken = this.findCaseKeyToken(pairToken);
        if (!keyToken) {
            return [];
        }
        return this.collectCaseLeadingCommentBlocks(keyToken);
    }
    findCaseKeyToken(pairToken) {
        for (const child of pairToken.innerTokens) {
            if (child.containerType === SqlPrintTokenContainerType.CommentBlock) {
                continue;
            }
            if (child.type === SqlPrintTokenType.space) {
                continue;
            }
            if (child.type === SqlPrintTokenType.keyword) {
                continue;
            }
            if (child.containerType === SqlPrintTokenContainerType.CaseThenValue) {
                continue;
            }
            return child;
        }
        return undefined;
    }
    collectCaseLeadingCommentBlocks(token) {
        if (!token.innerTokens || token.innerTokens.length === 0) {
            return [];
        }
        const collected = [];
        this.collectCaseLeadingCommentBlocksRecursive(token, collected, new Set(), 0);
        return collected;
    }
    collectCaseLeadingCommentBlocksRecursive(token, collected, seen, depth) {
        if (!token.innerTokens || token.innerTokens.length === 0) {
            return;
        }
        let removedAny = false;
        while (token.innerTokens.length > 0) {
            const first = token.innerTokens[0];
            if (first.containerType === SqlPrintTokenContainerType.CommentBlock) {
                token.innerTokens.shift();
                const signature = this.commentBlockSignature(first);
                if (!(depth > 0 && seen.has(signature))) {
                    collected.push(first);
                    seen.add(signature);
                }
                removedAny = true;
                continue;
            }
            if (!removedAny && first.type === SqlPrintTokenType.space) {
                return;
            }
            break;
        }
        if (!token.innerTokens || token.innerTokens.length === 0) {
            return;
        }
        const firstChild = token.innerTokens[0];
        if (this.isTransparentCaseWrapper(firstChild)) {
            this.collectCaseLeadingCommentBlocksRecursive(firstChild, collected, seen, depth + 1);
        }
    }
    isTransparentCaseWrapper(token) {
        if (!token) {
            return false;
        }
        const transparentContainers = [
            SqlPrintTokenContainerType.ColumnReference,
            SqlPrintTokenContainerType.QualifiedName,
            SqlPrintTokenContainerType.IdentifierString,
            SqlPrintTokenContainerType.RawString,
            SqlPrintTokenContainerType.LiteralValue,
            SqlPrintTokenContainerType.ParenExpression,
            SqlPrintTokenContainerType.UnaryExpression,
        ];
        return transparentContainers.includes(token.containerType);
    }
    commentBlockSignature(commentBlock) {
        if (!commentBlock.innerTokens || commentBlock.innerTokens.length === 0) {
            return '';
        }
        return commentBlock.innerTokens
            .filter(inner => inner.text !== '')
            .map(inner => inner.text)
            .join('|');
    }
    visitArrayExpression(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.ArrayExpression);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'array'));
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.parenthesis, '['));
        token.innerTokens.push(this.visit(arg.expression));
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.parenthesis, ']'));
        return token;
    }
    visitArrayQueryExpression(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.ArrayExpression);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'array'));
        // ARRAY(SELECT ...)
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.parenthesis, '('));
        token.innerTokens.push(this.visit(arg.query));
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.parenthesis, ')'));
        return token;
    }
    visitArraySliceExpression(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.ArrayExpression);
        // array expression
        token.innerTokens.push(this.visit(arg.array));
        // opening bracket
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.parenthesis, '['));
        // start index (optional)
        if (arg.startIndex) {
            token.innerTokens.push(this.visit(arg.startIndex));
        }
        // colon separator
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.operator, ':'));
        // end index (optional)
        if (arg.endIndex) {
            token.innerTokens.push(this.visit(arg.endIndex));
        }
        // closing bracket
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.parenthesis, ']'));
        return token;
    }
    visitArrayIndexExpression(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.ArrayExpression);
        // array expression
        token.innerTokens.push(this.visit(arg.array));
        // opening bracket
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.parenthesis, '['));
        // index
        token.innerTokens.push(this.visit(arg.index));
        // closing bracket
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.parenthesis, ']'));
        return token;
    }
    visitBetweenExpression(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.BetweenExpression);
        token.innerTokens.push(this.visit(arg.expression));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        if (arg.negated) {
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'not'));
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        }
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'between'));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(this.visit(arg.lower));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'and'));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(this.visit(arg.upper));
        return token;
    }
    visitStringSpecifierExpression(arg) {
        // Combine specifier and value into a single token
        const specifier = arg.specifier.accept(this).text;
        const value = arg.value.accept(this).text;
        return new SqlPrintToken(SqlPrintTokenType.value, specifier + value, SqlPrintTokenContainerType.StringSpecifierExpression);
    }
    visitTypeValue(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.TypeValue);
        this.addComponentComments(token, arg);
        token.innerTokens.push(arg.qualifiedName.accept(this));
        if (arg.argument) {
            token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
            token.innerTokens.push(this.visit(arg.argument));
            token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
        }
        return token;
    }
    visitTupleExpression(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.TupleExpression);
        const requiresMultiline = this.tupleRequiresMultiline(arg);
        token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
        for (let i = 0; i < arg.values.length; i++) {
            if (i > 0) {
                token.innerTokens.push(...SqlPrintTokenParser.argumentCommaSpaceTokens());
            }
            token.innerTokens.push(this.visit(arg.values[i]));
        }
        if (requiresMultiline) {
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.commentNewline, '', SqlPrintTokenContainerType.TupleExpression));
        }
        token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
        return token;
    }
    tupleRequiresMultiline(tuple) {
        for (const value of tuple.values) {
            if (this.hasInlineComments(value)) {
                return true;
            }
        }
        return false;
    }
    hasInlineComments(component) {
        if (this.hasLeadingComments(component)) {
            return true;
        }
        if (component instanceof TupleExpression) {
            return this.tupleRequiresMultiline(component);
        }
        return false;
    }
    hasLeadingComments(component) {
        var _a;
        const positioned = (_a = component.positionedComments) !== null && _a !== void 0 ? _a : [];
        const before = positioned.find(pc => pc.position === 'before');
        if (before && before.comments.some(comment => comment.trim().length > 0)) {
            return true;
        }
        return false;
    }
    visitWindowFrameExpression(arg) {
        // Compose window frame expression: over(partition by ... order by ... rows ...)
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.WindowFrameExpression);
        let first = true;
        if (arg.partition) {
            token.innerTokens.push(this.visit(arg.partition));
            first = false;
        }
        if (arg.order) {
            if (!first) {
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            }
            else {
                first = false;
            }
            token.innerTokens.push(this.visit(arg.order));
        }
        if (arg.frameSpec) {
            if (!first) {
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            }
            else {
                first = false;
            }
            token.innerTokens.push(this.visit(arg.frameSpec));
        }
        return token;
    }
    visitWindowFrameSpec(arg) {
        // This method prints a window frame specification, such as "rows between ... and ..." or "range ...".
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.WindowFrameSpec);
        // Add frame type (e.g., "rows", "range", "groups")
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, arg.frameType));
        if (arg.endBound === null) {
            // Only start bound: e.g., "rows unbounded preceding"
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.startBound.accept(this));
        }
        else {
            // Between: e.g., "rows between unbounded preceding and current row"
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'between'));
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.startBound.accept(this));
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'and'));
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.endBound.accept(this));
        }
        return token;
    }
    /**
     * Prints a window frame boundary value, such as "5 preceding" or "3 following".
     * @param arg WindowFrameBoundaryValue
     */
    visitWindowFrameBoundaryValue(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.WindowFrameBoundaryValue);
        token.innerTokens.push(arg.value.accept(this));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        //  true for "FOLLOWING", false for "PRECEDING"
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, arg.isFollowing ? 'following' : 'preceding'));
        return token;
    }
    /**
     * Prints a static window frame bound, such as "unbounded preceding", "current row", or "unbounded following".
     * @param arg WindowFrameBoundStatic
     */
    visitWindowFrameBoundStatic(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, arg.bound);
        return token;
    }
    visitSelectItem(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.SelectItem);
        // Preserve original positioned comments to avoid mutating the source object
        const originalSelectItemPositionedComments = arg.positionedComments;
        const originalValuePositionedComments = arg.value.positionedComments;
        const isParenExpression = arg.value instanceof ParenExpression;
        // Clear positioned comments from the value to avoid duplication when SelectItem itself renders them.
        // ParenExpression handles trailing comments internally, so we must keep its metadata intact.
        if (!isParenExpression) {
            arg.value.positionedComments = null;
        }
        // Add positioned comments in recorded order
        const beforeComments = arg.getPositionedComments('before');
        const afterComments = arg.getPositionedComments('after');
        if (beforeComments.length > 0) {
            const commentTokens = this.createInlineCommentSequence(beforeComments);
            token.innerTokens.push(...commentTokens);
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        }
        token.innerTokens.push(this.visit(arg.value));
        if (afterComments.length > 0 && !isParenExpression) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            const commentTokens = this.createInlineCommentSequence(afterComments);
            token.innerTokens.push(...commentTokens);
        }
        // Restore original positioned comments to avoid side effects
        arg.positionedComments = originalSelectItemPositionedComments;
        arg.value.positionedComments = originalValuePositionedComments;
        if (!arg.identifier) {
            return token;
        }
        // No alias needed if it matches the default name
        if (arg.value instanceof ColumnReference) {
            const defaultName = arg.value.column.name;
            if (arg.identifier.name === defaultName) {
                return token;
            }
        }
        // Add alias if it is different from the default name
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        // Handle AS keyword positioned comments (before AS)
        const asKeywordPositionedComments = 'asKeywordPositionedComments' in arg ? arg.asKeywordPositionedComments : null;
        if (asKeywordPositionedComments) {
            const beforeComments = asKeywordPositionedComments.filter((pc) => pc.position === 'before');
            if (beforeComments.length > 0) {
                for (const posComment of beforeComments) {
                    const commentTokens = this.createInlineCommentSequence(posComment.comments);
                    token.innerTokens.push(...commentTokens);
                    token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                }
            }
        }
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'as'));
        // Handle AS keyword positioned comments (after AS)
        if (asKeywordPositionedComments) {
            const afterComments = asKeywordPositionedComments.filter((pc) => pc.position === 'after');
            if (afterComments.length > 0) {
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                for (const posComment of afterComments) {
                    const commentTokens = this.createInlineCommentSequence(posComment.comments);
                    token.innerTokens.push(...commentTokens);
                }
            }
        }
        // Fallback: Add AS keyword legacy comments if present
        const asKeywordComments = 'asKeywordComments' in arg ? arg.asKeywordComments : null;
        if (asKeywordComments && asKeywordComments.length > 0) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            const commentTokens = this.createInlineCommentSequence(asKeywordComments);
            token.innerTokens.push(...commentTokens);
        }
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        // Visit identifier to get alias with proper spacing
        const identifierToken = this.visit(arg.identifier);
        token.innerTokens.push(identifierToken);
        // Handle alias positioned comments (after alias)
        const aliasPositionedComments = 'aliasPositionedComments' in arg ? arg.aliasPositionedComments : null;
        if (aliasPositionedComments) {
            const afterComments = aliasPositionedComments.filter((pc) => pc.position === 'after');
            if (afterComments.length > 0) {
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                for (const posComment of afterComments) {
                    const commentTokens = this.createInlineCommentSequence(posComment.comments);
                    token.innerTokens.push(...commentTokens);
                }
            }
        }
        // Fallback: Add alias legacy comments if present
        const aliasComments = arg.aliasComments;
        if (aliasComments && aliasComments.length > 0) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            const commentTokens = this.createInlineCommentSequence(aliasComments);
            token.innerTokens.push(...commentTokens);
        }
        return token;
    }
    visitSelectClause(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, 'select', SqlPrintTokenContainerType.SelectClause);
        // Handle positioned comments for SelectClause (unified spec)
        if (arg.positionedComments && arg.positionedComments.length > 0) {
            this.addPositionedCommentsToToken(token, arg);
            // Clear positioned comments to prevent duplicate processing
            arg.positionedComments = null;
        }
        // Handle hints and DISTINCT as part of the keyword line
        let selectKeywordText = 'select';
        // Add hint clauses immediately after SELECT (before DISTINCT)
        for (const hint of arg.hints) {
            selectKeywordText += ' ' + this.visit(hint).text;
        }
        // Add DISTINCT after hints (if present)  
        if (arg.distinct) {
            const distinctToken = arg.distinct.accept(this);
            if (distinctToken.innerTokens && distinctToken.innerTokens.length > 0) {
                // For compound DISTINCT tokens (like DISTINCT ON), concatenate all parts
                let distinctText = distinctToken.text;
                for (const innerToken of distinctToken.innerTokens) {
                    distinctText += this.flattenTokenText(innerToken);
                }
                selectKeywordText += ' ' + distinctText;
            }
            else {
                selectKeywordText += ' ' + distinctToken.text;
            }
        }
        // Update the token text to include hints and DISTINCT
        token.text = selectKeywordText;
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        for (let i = 0; i < arg.items.length; i++) {
            if (i > 0) {
                token.innerTokens.push(...SqlPrintTokenParser.commaSpaceTokens());
            }
            token.innerTokens.push(this.visit(arg.items[i]));
        }
        return token;
    }
    flattenTokenText(token) {
        let result = token.text;
        if (token.innerTokens) {
            for (const innerToken of token.innerTokens) {
                result += this.flattenTokenText(innerToken);
            }
        }
        return result;
    }
    visitHintClause(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.value, arg.getFullHint());
        return token;
    }
    visitDistinct(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, 'distinct');
        // Handle positioned comments for Distinct (unified spec)
        if (arg.positionedComments && arg.positionedComments.length > 0) {
            this.addPositionedCommentsToToken(token, arg);
            // Clear positioned comments to prevent duplicate processing
            arg.positionedComments = null;
        }
        return token;
    }
    visitDistinctOn(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.DistinctOn);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'distinct on'));
        token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
        token.innerTokens.push(arg.value.accept(this));
        token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
        return token;
    }
    visitTableSource(arg) {
        // Print table name with optional namespaces and alias
        let fullName = '';
        if (Array.isArray(arg.namespaces) && arg.namespaces.length > 0) {
            fullName = arg.namespaces.map(ns => ns.accept(this).text).join('.') + '.';
        }
        fullName += arg.table.accept(this).text;
        const token = new SqlPrintToken(SqlPrintTokenType.value, fullName);
        this.addComponentComments(token, arg);
        // alias (if present and different from table name)
        if (arg.identifier && arg.identifier.name !== arg.table.name) {
        }
        return token;
    }
    visitSourceExpression(arg) {
        // Print source expression (e.g. "table", "table as t", "schema.table t")
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.SourceExpression);
        token.innerTokens.push(arg.datasource.accept(this));
        if (!arg.aliasExpression) {
            return token;
        }
        if (arg.datasource instanceof TableSource) {
            // No alias needed if it matches the default name
            const defaultName = arg.datasource.table.name;
            if (arg.aliasExpression.table.name === defaultName) {
                return token;
            }
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'as'));
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            // exclude column aliases
            token.innerTokens.push(arg.aliasExpression.accept(this));
            return token;
        }
        else {
            // For other source types, just print the alias
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'as'));
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            // included column aliases
            token.innerTokens.push(arg.aliasExpression.accept(this));
            return token;
        }
    }
    visitFromClause(arg) {
        // Build a declaration order map so JOIN ON operands can be normalized later.
        let contextPushed = false;
        if (this.normalizeJoinConditionOrder) {
            const aliasOrder = this.buildJoinAliasOrder(arg);
            if (aliasOrder.size > 0) {
                this.joinConditionContexts.push({ aliasOrder });
                contextPushed = true;
            }
        }
        try {
            const token = new SqlPrintToken(SqlPrintTokenType.keyword, 'from', SqlPrintTokenContainerType.FromClause);
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(this.visit(arg.source));
            if (arg.joins) {
                for (let i = 0; i < arg.joins.length; i++) {
                    token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                    token.innerTokens.push(this.visit(arg.joins[i]));
                }
            }
            return token;
        }
        finally {
            if (contextPushed) {
                this.joinConditionContexts.pop();
            }
        }
    }
    visitJoinClause(arg) {
        // Print join clause: [joinType] [lateral] [source] [on/using ...]
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.JoinClause);
        // Handle JOIN keyword positioned comments (before JOIN)
        const joinKeywordPositionedComments = arg.joinKeywordPositionedComments;
        if (joinKeywordPositionedComments) {
            const beforeComments = joinKeywordPositionedComments.filter((pc) => pc.position === 'before');
            if (beforeComments.length > 0) {
                for (const posComment of beforeComments) {
                    const commentTokens = this.createInlineCommentSequence(posComment.comments);
                    token.innerTokens.push(...commentTokens);
                    token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                }
            }
        }
        // join type (e.g. inner join, left join, etc)
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, arg.joinType.value));
        // Handle JOIN keyword positioned comments (after JOIN)
        if (joinKeywordPositionedComments) {
            const afterComments = joinKeywordPositionedComments.filter((pc) => pc.position === 'after');
            if (afterComments.length > 0) {
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                for (const posComment of afterComments) {
                    const commentTokens = this.createInlineCommentSequence(posComment.comments);
                    token.innerTokens.push(...commentTokens);
                }
            }
        }
        // Fallback: Add JOIN keyword legacy comments if present
        const joinKeywordComments = arg.joinKeywordComments;
        if (joinKeywordComments && joinKeywordComments.length > 0) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            const commentTokens = this.createInlineCommentSequence(joinKeywordComments);
            token.innerTokens.push(...commentTokens);
        }
        if (arg.lateral) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'lateral'));
        }
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(this.visit(arg.source));
        if (arg.condition) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(this.visit(arg.condition));
        }
        return token;
    }
    visitJoinOnClause(arg) {
        // Normalize JOIN ON predicate columns to follow declaration order when enabled.
        if (this.normalizeJoinConditionOrder) {
            const aliasOrder = this.getCurrentJoinAliasOrder();
            if (aliasOrder) {
                this.normalizeJoinConditionValue(arg.condition, aliasOrder);
            }
        }
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.JoinOnClause);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'on'));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(this.visit(arg.condition));
        return token;
    }
    getCurrentJoinAliasOrder() {
        if (this.joinConditionContexts.length === 0) {
            return null;
        }
        return this.joinConditionContexts[this.joinConditionContexts.length - 1].aliasOrder;
    }
    buildJoinAliasOrder(fromClause) {
        const aliasOrder = new Map();
        let nextIndex = 0;
        const registerSource = (source) => {
            const identifiers = this.collectSourceIdentifiers(source);
            if (identifiers.length === 0) {
                return;
            }
            // Track the earliest declaration index for each identifier found in the FROM clause.
            for (const identifier of identifiers) {
                const key = identifier.toLowerCase();
                if (!aliasOrder.has(key)) {
                    aliasOrder.set(key, nextIndex);
                }
            }
            nextIndex++;
        };
        registerSource(fromClause.source);
        if (fromClause.joins) {
            for (const joinClause of fromClause.joins) {
                registerSource(joinClause.source);
            }
        }
        return aliasOrder;
    }
    collectSourceIdentifiers(source) {
        const identifiers = [];
        const aliasName = source.getAliasName();
        if (aliasName) {
            identifiers.push(aliasName);
        }
        // Capture table identifiers so unaliased tables can still be matched.
        if (source.datasource instanceof TableSource) {
            const tableComponent = source.datasource.table.name;
            identifiers.push(tableComponent);
            const fullName = source.datasource.getSourceName();
            if (fullName && fullName !== tableComponent) {
                identifiers.push(fullName);
            }
        }
        return identifiers;
    }
    normalizeJoinConditionValue(condition, aliasOrder) {
        // Walk the value tree so every comparison within the JOIN predicate is inspected.
        const kind = condition.getKind();
        if (kind === ParenExpression.kind) {
            const paren = condition;
            this.normalizeJoinConditionValue(paren.expression, aliasOrder);
            return;
        }
        if (kind === BinaryExpression.kind) {
            const binary = condition;
            this.normalizeJoinConditionValue(binary.left, aliasOrder);
            this.normalizeJoinConditionValue(binary.right, aliasOrder);
            this.normalizeBinaryEquality(binary, aliasOrder);
        }
    }
    normalizeBinaryEquality(binary, aliasOrder) {
        // Only normalize simple equality comparisons, leaving other operators untouched.
        const operatorValue = binary.operator.value.toLowerCase();
        if (operatorValue !== '=') {
            return;
        }
        const leftOwner = this.resolveColumnOwner(binary.left);
        const rightOwner = this.resolveColumnOwner(binary.right);
        if (!leftOwner || !rightOwner || leftOwner === rightOwner) {
            return;
        }
        const leftOrder = aliasOrder.get(leftOwner);
        const rightOrder = aliasOrder.get(rightOwner);
        if (leftOrder === undefined || rightOrder === undefined) {
            return;
        }
        if (leftOrder > rightOrder) {
            // Swap operands so the earlier declared table appears on the left.
            const originalLeft = binary.left;
            binary.left = binary.right;
            binary.right = originalLeft;
        }
    }
    resolveColumnOwner(value) {
        var _a;
        const kind = value.getKind();
        if (kind === ColumnReference.kind) {
            // Column references expose their qualifier namespace, which we normalize for lookups.
            const columnRef = value;
            const namespace = columnRef.getNamespace();
            if (!namespace) {
                return null;
            }
            const qualifier = namespace.includes('.') ? (_a = namespace.split('.').pop()) !== null && _a !== void 0 ? _a : '' : namespace;
            return qualifier.toLowerCase();
        }
        if (kind === ParenExpression.kind) {
            return this.resolveColumnOwner(value.expression);
        }
        return null;
    }
    visitJoinUsingClause(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.JoinUsingClause);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'using'));
        token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
        token.innerTokens.push(this.visit(arg.condition));
        token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
        return token;
    }
    visitFunctionSource(arg) {
        // Print function source: [functionName]([args])
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.FunctionSource);
        token.innerTokens.push(arg.qualifiedName.accept(this));
        token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
        if (arg.argument) {
            token.innerTokens.push(this.visit(arg.argument));
        }
        token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
        return token;
    }
    visitSourceAliasExpression(arg) {
        // Print source alias expression: [source] as [alias]
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.SourceAliasExpression);
        token.innerTokens.push(this.visit(arg.table));
        if (arg.columns) {
            token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
            for (let i = 0; i < arg.columns.length; i++) {
                if (i > 0) {
                    token.innerTokens.push(...SqlPrintTokenParser.argumentCommaSpaceTokens());
                }
                token.innerTokens.push(this.visit(arg.columns[i]));
            }
            token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
        }
        // Handle positioned comments for SourceAliasExpression (alias name comments)
        if (arg.positionedComments && arg.positionedComments.length > 0) {
            this.addPositionedCommentsToToken(token, arg);
            // Clear positioned comments to prevent duplicate processing
            arg.positionedComments = null;
        }
        else if (arg.comments && arg.comments.length > 0) {
            this.addCommentsToToken(token, arg.comments);
        }
        return token;
    }
    visitWhereClause(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, 'where', SqlPrintTokenContainerType.WhereClause);
        this.addComponentComments(token, arg);
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(this.visit(arg.condition));
        return token;
    }
    visitGroupByClause(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, 'group by', SqlPrintTokenContainerType.GroupByClause);
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        for (let i = 0; i < arg.grouping.length; i++) {
            if (i > 0) {
                token.innerTokens.push(...SqlPrintTokenParser.commaSpaceTokens());
            }
            token.innerTokens.push(this.visit(arg.grouping[i]));
        }
        return token;
    }
    visitHavingClause(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, 'having', SqlPrintTokenContainerType.HavingClause);
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(this.visit(arg.condition));
        return token;
    }
    visitWindowClause(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, 'window', SqlPrintTokenContainerType.WindowClause);
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        for (let i = 0; i < arg.windows.length; i++) {
            if (i > 0) {
                token.innerTokens.push(...SqlPrintTokenParser.commaSpaceTokens());
            }
            token.innerTokens.push(this.visit(arg.windows[i]));
        }
        return token;
    }
    visitWindowFrameClause(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.WindowFrameClause);
        token.innerTokens.push(arg.name.accept(this));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'as'));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
        token.innerTokens.push(this.visit(arg.expression));
        token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
        return token;
    }
    visitLimitClause(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, 'limit', SqlPrintTokenContainerType.LimitClause);
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(this.visit(arg.value));
        return token;
    }
    visitOffsetClause(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, 'offset', SqlPrintTokenContainerType.OffsetClause);
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(this.visit(arg.value));
        return token;
    }
    visitFetchClause(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, 'fetch', SqlPrintTokenContainerType.FetchClause);
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(this.visit(arg.expression));
        return token;
    }
    visitFetchExpression(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.FetchExpression);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, arg.type));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(arg.count.accept(this));
        if (arg.unit) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, arg.unit));
        }
        return token;
    }
    visitForClause(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, 'for', SqlPrintTokenContainerType.ForClause);
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, arg.lockMode));
        return token;
    }
    visitWithClause(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, 'with', SqlPrintTokenContainerType.WithClause);
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        if (arg.recursive) {
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'recursive'));
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        }
        for (let i = 0; i < arg.tables.length; i++) {
            if (i > 0) {
                token.innerTokens.push(...SqlPrintTokenParser.commaSpaceTokens());
            }
            token.innerTokens.push(arg.tables[i].accept(this));
        }
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        this.addComponentComments(token, arg);
        return token;
    }
    visitCommonTable(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.CommonTable);
        // Handle positioned comments for CommonTable (avoid duplication)
        if (arg.positionedComments && arg.positionedComments.length > 0) {
            this.addPositionedCommentsToToken(token, arg);
            // Clear positioned comments to prevent duplicate processing
            arg.positionedComments = null;
        }
        else if (arg.comments && arg.comments.length > 0) {
            this.addCommentsToToken(token, arg.comments);
        }
        token.innerTokens.push(arg.aliasExpression.accept(this));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'as'));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        if (arg.materialized !== null) {
            if (arg.materialized) {
                token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'materialized'));
            }
            else {
                token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'not materialized'));
            }
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        }
        token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
        const query = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.SubQuerySource);
        query.innerTokens.push(arg.query.accept(this));
        token.innerTokens.push(query);
        token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
        return token;
    }
    // query
    visitSimpleQuery(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.SimpleSelectQuery);
        // Handle positioned comments for SimpleSelectQuery (unified spec)
        if (arg.headerComments && arg.headerComments.length > 0) {
            if (this.shouldMergeHeaderComments(arg.headerComments)) {
                const mergedHeaderComment = this.createHeaderMultiLineCommentBlock(arg.headerComments);
                token.innerTokens.push(mergedHeaderComment);
            }
            else {
                const headerCommentBlocks = this.createCommentBlocks(arg.headerComments, true);
                token.innerTokens.push(...headerCommentBlocks);
            }
            if (arg.withClause) {
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            }
        }
        if (arg.positionedComments && arg.positionedComments.length > 0) {
            this.addPositionedCommentsToToken(token, arg);
            // Clear positioned comments to prevent duplicate processing
            arg.positionedComments = null;
        }
        if (arg.withClause) {
            token.innerTokens.push(arg.withClause.accept(this));
        }
        // Add regular comments between WITH clause and SELECT clause if they exist
        if (arg.comments && arg.comments.length > 0) {
            const commentBlocks = this.createCommentBlocks(arg.comments);
            token.innerTokens.push(...commentBlocks);
            // Add a space separator after comments if there are more tokens coming
            if (arg.selectClause) {
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            }
        }
        token.innerTokens.push(arg.selectClause.accept(this));
        if (!arg.fromClause) {
            return token;
        }
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(arg.fromClause.accept(this));
        if (arg.whereClause) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.whereClause.accept(this));
        }
        if (arg.groupByClause) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.groupByClause.accept(this));
        }
        if (arg.havingClause) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.havingClause.accept(this));
        }
        if (arg.orderByClause) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.orderByClause.accept(this));
        }
        if (arg.windowClause) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.windowClause.accept(this));
        }
        if (arg.limitClause) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.limitClause.accept(this));
        }
        if (arg.offsetClause) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.offsetClause.accept(this));
        }
        if (arg.fetchClause) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.fetchClause.accept(this));
        }
        if (arg.forClause) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.forClause.accept(this));
        }
        return token;
    }
    visitSubQuerySource(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '');
        token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
        const subQuery = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.SubQuerySource);
        subQuery.innerTokens.push(arg.query.accept(this));
        token.innerTokens.push(subQuery);
        token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
        return token;
    }
    visitValuesQuery(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, 'values', SqlPrintTokenContainerType.ValuesQuery);
        // Add headerComments before VALUES keyword
        if (arg.headerComments && arg.headerComments.length > 0) {
            if (this.shouldMergeHeaderComments(arg.headerComments)) {
                const mergedHeaderComment = this.createHeaderMultiLineCommentBlock(arg.headerComments);
                token.innerTokens.push(mergedHeaderComment);
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            }
            else {
                const headerCommentBlocks = this.createCommentBlocks(arg.headerComments, true);
                for (const commentBlock of headerCommentBlocks) {
                    token.innerTokens.push(commentBlock);
                    token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                }
            }
        }
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        const values = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.Values);
        for (let i = 0; i < arg.tuples.length; i++) {
            if (i > 0) {
                values.innerTokens.push(...SqlPrintTokenParser.commaSpaceTokens());
            }
            values.innerTokens.push(arg.tuples[i].accept(this));
        }
        token.innerTokens.push(values);
        // Add regular comments to the token
        this.addCommentsToToken(token, arg.comments);
        return token;
    }
    visitInlineQuery(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '');
        token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
        const queryToken = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.InlineQuery);
        queryToken.innerTokens.push(arg.selectQuery.accept(this));
        token.innerTokens.push(queryToken);
        // Add comments from the InlineQuery to the closing parenthesis
        if (arg.comments && arg.comments.length > 0) {
            const closingParenToken = new SqlPrintToken(SqlPrintTokenType.parenthesis, ')');
            this.addCommentsToToken(closingParenToken, arg.comments);
            token.innerTokens.push(closingParenToken);
            // Clear the comments from arg to prevent duplicate output by the general comment handler
            arg.comments = null;
        }
        else {
            token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
        }
        return token;
    }
    visitInsertQuery(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.InsertQuery);
        const selectQuery = arg.selectQuery;
        const extractedWithClause = selectQuery ? SelectQueryWithClauseHelper.detachWithClause(selectQuery) : null;
        if (extractedWithClause) {
            token.innerTokens.push(extractedWithClause.accept(this));
        }
        token.innerTokens.push(this.visit(arg.insertClause));
        // Process the select query if present
        if (arg.selectQuery) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(this.visit(arg.selectQuery));
        }
        if (arg.returningClause) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.returningClause.accept(this));
        }
        if (selectQuery && extractedWithClause) {
            SelectQueryWithClauseHelper.setWithClause(selectQuery, extractedWithClause);
        }
        return token;
    }
    visitInsertClause(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.InsertClause);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'insert into'));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(arg.source.accept(this));
        if (arg.columns && arg.columns.length > 0) {
            token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
            for (let i = 0; i < arg.columns.length; i++) {
                if (i > 0) {
                    token.innerTokens.push(...SqlPrintTokenParser.commaSpaceTokens());
                }
                token.innerTokens.push(arg.columns[i].accept(this));
            }
            token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
        }
        return token;
    }
    visitDeleteQuery(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.DeleteQuery);
        // Attach WITH clause tokens when present before the DELETE command.
        if (arg.withClause) {
            token.innerTokens.push(arg.withClause.accept(this));
        }
        token.innerTokens.push(arg.deleteClause.accept(this));
        // Append USING clause when the DELETE references additional sources.
        if (arg.usingClause) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.usingClause.accept(this));
        }
        // Append WHERE clause to restrict affected rows.
        if (arg.whereClause) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.whereClause.accept(this));
        }
        // Append RETURNING clause when the DELETE yields output columns.
        if (arg.returningClause) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.returningClause.accept(this));
        }
        return token;
    }
    visitDeleteClause(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, 'delete from', SqlPrintTokenContainerType.DeleteClause);
        // Render the target relation immediately after the DELETE FROM keyword.
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(arg.source.accept(this));
        return token;
    }
    visitUsingClause(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, 'using', SqlPrintTokenContainerType.UsingClause);
        if (arg.sources.length > 0) {
            // Attach the first USING source directly after the keyword.
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            for (let i = 0; i < arg.sources.length; i++) {
                if (i > 0) {
                    // Separate subsequent sources with comma and space for clarity.
                    token.innerTokens.push(...SqlPrintTokenParser.commaSpaceTokens());
                }
                token.innerTokens.push(this.visit(arg.sources[i]));
            }
        }
        return token;
    }
    visitMergeQuery(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.MergeQuery);
        if (arg.withClause) {
            token.innerTokens.push(arg.withClause.accept(this));
        }
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'merge into'));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(arg.target.accept(this));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'using'));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(arg.source.accept(this));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        const onClauseToken = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.JoinOnClause);
        onClauseToken.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'on'));
        onClauseToken.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        onClauseToken.innerTokens.push(arg.onCondition.accept(this));
        token.innerTokens.push(onClauseToken);
        for (const clause of arg.whenClauses) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(clause.accept(this));
        }
        return token;
    }
    visitMergeWhenClause(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.MergeWhenClause);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, this.mergeMatchTypeToKeyword(arg.matchType)));
        if (arg.condition) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'and'));
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.condition.accept(this));
        }
        const thenLeadingComments = arg.getThenLeadingComments();
        const thenKeywordToken = new SqlPrintToken(SqlPrintTokenType.keyword, 'then');
        if (thenLeadingComments.length > 0) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            const commentBlocks = this.createCommentBlocks(thenLeadingComments);
            token.innerTokens.push(...commentBlocks);
            token.innerTokens.push(thenKeywordToken);
        }
        else {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(thenKeywordToken);
        }
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(arg.action.accept(this));
        return token;
    }
    visitMergeUpdateAction(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.MergeUpdateAction);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'update'));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(arg.setClause.accept(this));
        if (arg.whereClause) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.whereClause.accept(this));
        }
        return token;
    }
    visitMergeDeleteAction(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.MergeDeleteAction);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'delete'));
        if (arg.whereClause) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.whereClause.accept(this));
        }
        return token;
    }
    visitMergeInsertAction(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.MergeInsertAction);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'insert'));
        if (arg.columns && arg.columns.length > 0) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
            for (let i = 0; i < arg.columns.length; i++) {
                if (i > 0) {
                    token.innerTokens.push(...SqlPrintTokenParser.commaSpaceTokens());
                }
                token.innerTokens.push(arg.columns[i].accept(this));
            }
            token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
        }
        if (arg.defaultValues) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'default values'));
            return token;
        }
        if (arg.values) {
            const leadingValuesComments = arg.getValuesLeadingComments();
            if (leadingValuesComments.length > 0) {
                token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.commentNewline, ''));
                const commentBlocks = this.createCommentBlocks(leadingValuesComments);
                token.innerTokens.push(...commentBlocks);
            }
            else {
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            }
            const valuesKeywordToken = new SqlPrintToken(SqlPrintTokenType.keyword, 'values');
            token.innerTokens.push(valuesKeywordToken);
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
            token.innerTokens.push(arg.values.accept(this));
            token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
        }
        return token;
    }
    visitMergeDoNothingAction(_) {
        return new SqlPrintToken(SqlPrintTokenType.keyword, 'do nothing', SqlPrintTokenContainerType.MergeDoNothingAction);
    }
    mergeMatchTypeToKeyword(matchType) {
        switch (matchType) {
            case 'matched':
                return 'when matched';
            case 'not_matched':
                return 'when not matched';
            case 'not_matched_by_source':
                return 'when not matched by source';
            case 'not_matched_by_target':
                return 'when not matched by target';
            default:
                return 'when';
        }
    }
    visitUpdateQuery(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.UpdateQuery);
        if (arg.withClause) {
            token.innerTokens.push(arg.withClause.accept(this));
        }
        token.innerTokens.push(arg.updateClause.accept(this));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(arg.setClause.accept(this));
        if (arg.fromClause) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.fromClause.accept(this));
        }
        if (arg.whereClause) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.whereClause.accept(this));
        }
        if (arg.returningClause) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.returningClause.accept(this));
        }
        return token;
    }
    visitUpdateClause(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, 'update', SqlPrintTokenContainerType.UpdateClause);
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(arg.source.accept(this));
        return token;
    }
    visitSetClause(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, 'set', SqlPrintTokenContainerType.SetClause);
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        for (let i = 0; i < arg.items.length; i++) {
            if (i > 0) {
                token.innerTokens.push(...SqlPrintTokenParser.commaSpaceTokens());
            }
            token.innerTokens.push(this.visit(arg.items[i]));
        }
        return token;
    }
    visitSetClauseItem(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.SetClauseItem);
        token.innerTokens.push(arg.column.accept(this));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.operator, '='));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(arg.value.accept(this));
        return token;
    }
    visitReturningClause(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, 'returning', SqlPrintTokenContainerType.ReturningClause);
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        for (let i = 0; i < arg.items.length; i++) {
            if (i > 0) {
                token.innerTokens.push(...SqlPrintTokenParser.commaSpaceTokens());
            }
            token.innerTokens.push(this.visit(arg.items[i]));
        }
        return token;
    }
    visitCreateTableQuery(arg) {
        var _a;
        const baseKeyword = arg.isTemporary ? 'create temporary table' : 'create table';
        let keywordText = arg.ifNotExists ? `${baseKeyword} if not exists` : baseKeyword;
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, keywordText, SqlPrintTokenContainerType.CreateTableQuery);
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        const qualifiedName = new QualifiedName((_a = arg.namespaces) !== null && _a !== void 0 ? _a : null, arg.tableName);
        token.innerTokens.push(qualifiedName.accept(this));
        const definitionEntries = [...arg.columns, ...arg.tableConstraints];
        if (definitionEntries.length > 0) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
            const definitionToken = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.CreateTableDefinition);
            for (let i = 0; i < definitionEntries.length; i++) {
                if (i > 0) {
                    definitionToken.innerTokens.push(...SqlPrintTokenParser.commaSpaceTokens());
                }
                definitionToken.innerTokens.push(definitionEntries[i].accept(this));
            }
            token.innerTokens.push(definitionToken);
            token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
        }
        if (arg.tableOptions) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.tableOptions.accept(this));
        }
        if (arg.asSelectQuery) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'as'));
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.asSelectQuery.accept(this));
        }
        if (arg.withDataOption) {
            // Reconstruct WITH [NO] DATA clause to mirror PostgreSQL CREATE TABLE semantics.
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'with'));
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            if (arg.withDataOption === 'with-no-data') {
                token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'no'));
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            }
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'data'));
        }
        return token;
    }
    visitTableColumnDefinition(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.TableColumnDefinition);
        token.innerTokens.push(arg.name.accept(this));
        if (arg.dataType) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.dataType.accept(this));
        }
        for (const constraint of arg.constraints) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(constraint.accept(this));
        }
        return token;
    }
    visitColumnConstraintDefinition(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.ColumnConstraintDefinition);
        if (arg.constraintName) {
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'constraint'));
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.constraintName.accept(this));
        }
        const appendKeyword = (text) => {
            if (token.innerTokens.length > 0) {
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            }
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, text));
        };
        const appendComponent = (component) => {
            if (token.innerTokens.length > 0) {
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            }
            token.innerTokens.push(component.accept(this));
        };
        switch (arg.kind) {
            case 'not-null':
                appendKeyword('not null');
                break;
            case 'null':
                appendKeyword('null');
                break;
            case 'default':
                appendKeyword('default');
                if (arg.defaultValue) {
                    token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                    token.innerTokens.push(arg.defaultValue.accept(this));
                }
                break;
            case 'primary-key':
                appendKeyword('primary key');
                break;
            case 'unique':
                appendKeyword('unique');
                break;
            case 'references':
                if (arg.reference) {
                    appendComponent(arg.reference);
                }
                break;
            case 'check':
                if (arg.checkExpression) {
                    appendKeyword('check');
                    token.innerTokens.push(this.wrapWithParenExpression(arg.checkExpression));
                }
                break;
            case 'generated-always-identity':
            case 'generated-by-default-identity':
            case 'raw':
                if (arg.rawClause) {
                    appendComponent(arg.rawClause);
                }
                break;
        }
        return token;
    }
    visitTableConstraintDefinition(arg) {
        var _a, _b, _c;
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.TableConstraintDefinition);
        const appendKeyword = (text) => {
            if (token.innerTokens.length > 0) {
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            }
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, text));
        };
        const appendComponent = (component) => {
            if (token.innerTokens.length > 0) {
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            }
            token.innerTokens.push(component.accept(this));
        };
        const appendColumns = (columns) => {
            if (!columns || columns.length === 0) {
                return;
            }
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
            const listToken = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.ValueList);
            for (let i = 0; i < columns.length; i++) {
                if (i > 0) {
                    listToken.innerTokens.push(...SqlPrintTokenParser.commaSpaceTokens());
                }
                listToken.innerTokens.push(columns[i].accept(this));
            }
            token.innerTokens.push(listToken);
            token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
        };
        const useMysqlConstraintStyle = this.constraintStyle === 'mysql';
        const inlineNameKinds = new Set(['primary-key', 'unique', 'foreign-key']);
        const shouldInlineConstraintName = useMysqlConstraintStyle && !!arg.constraintName && inlineNameKinds.has(arg.kind);
        if (arg.constraintName && !shouldInlineConstraintName) {
            appendKeyword('constraint');
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.constraintName.accept(this));
        }
        switch (arg.kind) {
            case 'primary-key':
                appendKeyword('primary key');
                if (shouldInlineConstraintName && arg.constraintName) {
                    token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                    token.innerTokens.push(arg.constraintName.accept(this));
                }
                appendColumns((_a = arg.columns) !== null && _a !== void 0 ? _a : []);
                break;
            case 'unique':
                if (useMysqlConstraintStyle) {
                    appendKeyword('unique key');
                    if (shouldInlineConstraintName && arg.constraintName) {
                        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                        token.innerTokens.push(arg.constraintName.accept(this));
                    }
                }
                else {
                    appendKeyword('unique');
                }
                appendColumns((_b = arg.columns) !== null && _b !== void 0 ? _b : []);
                break;
            case 'foreign-key':
                appendKeyword('foreign key');
                if (shouldInlineConstraintName && arg.constraintName) {
                    token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                    token.innerTokens.push(arg.constraintName.accept(this));
                }
                appendColumns((_c = arg.columns) !== null && _c !== void 0 ? _c : []);
                if (arg.reference) {
                    token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                    token.innerTokens.push(arg.reference.accept(this));
                }
                break;
            case 'check':
                if (arg.checkExpression) {
                    appendKeyword('check');
                    token.innerTokens.push(this.wrapWithParenExpression(arg.checkExpression));
                }
                break;
            case 'raw':
                if (arg.rawClause) {
                    appendComponent(arg.rawClause);
                }
                break;
        }
        return token;
    }
    wrapWithParenExpression(expression) {
        // Reuse existing parentheses groups to avoid double-wrapping when callers already provided them.
        if (expression instanceof ParenExpression) {
            return this.visit(expression);
        }
        // Synthesize a ParenExpression wrapper so nested boolean groups render with consistent indentation.
        const synthetic = new ParenExpression(expression);
        return this.visit(synthetic);
    }
    visitReferenceDefinition(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.ReferenceDefinition);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'references'));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(arg.targetTable.accept(this));
        if (arg.columns && arg.columns.length > 0) {
            token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
            const columnList = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.ValueList);
            for (let i = 0; i < arg.columns.length; i++) {
                if (i > 0) {
                    columnList.innerTokens.push(...SqlPrintTokenParser.commaSpaceTokens());
                }
                columnList.innerTokens.push(arg.columns[i].accept(this));
            }
            token.innerTokens.push(columnList);
            token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
        }
        if (arg.matchType) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, `match ${arg.matchType}`));
        }
        if (arg.onDelete) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'on delete'));
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, arg.onDelete));
        }
        if (arg.onUpdate) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'on update'));
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, arg.onUpdate));
        }
        if (arg.deferrable === 'deferrable') {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'deferrable'));
        }
        else if (arg.deferrable === 'not deferrable') {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'not deferrable'));
        }
        if (arg.initially === 'immediate') {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'initially immediate'));
        }
        else if (arg.initially === 'deferred') {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'initially deferred'));
        }
        return token;
    }
    visitCreateIndexStatement(arg) {
        const keywordParts = ['create'];
        if (arg.unique) {
            keywordParts.push('unique');
        }
        keywordParts.push('index');
        if (arg.concurrently) {
            keywordParts.push('concurrently');
        }
        if (arg.ifNotExists) {
            keywordParts.push('if not exists');
        }
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, keywordParts.join(' '), SqlPrintTokenContainerType.CreateIndexStatement);
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(arg.indexName.accept(this));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'on'));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(arg.tableName.accept(this));
        if (arg.usingMethod) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'using'));
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.usingMethod.accept(this));
        }
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
        const columnList = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.IndexColumnList);
        for (let i = 0; i < arg.columns.length; i++) {
            if (i > 0) {
                columnList.innerTokens.push(...SqlPrintTokenParser.commaSpaceTokens());
            }
            columnList.innerTokens.push(arg.columns[i].accept(this));
        }
        token.innerTokens.push(columnList);
        token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
        if (arg.include && arg.include.length > 0) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'include'));
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
            const includeList = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.ValueList);
            for (let i = 0; i < arg.include.length; i++) {
                if (i > 0) {
                    includeList.innerTokens.push(...SqlPrintTokenParser.commaSpaceTokens());
                }
                includeList.innerTokens.push(arg.include[i].accept(this));
            }
            token.innerTokens.push(includeList);
            token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
        }
        if (arg.withOptions) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.withOptions.accept(this));
        }
        if (arg.tablespace) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'tablespace'));
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.tablespace.accept(this));
        }
        if (arg.where) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'where'));
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(this.visit(arg.where));
        }
        return token;
    }
    visitCreateSequenceStatement(arg) {
        const keywordParts = ['create', 'sequence'];
        if (arg.ifNotExists) {
            keywordParts.push('if not exists');
        }
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, keywordParts.join(' '), SqlPrintTokenContainerType.CreateSequenceStatement);
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(arg.sequenceName.accept(this));
        if (arg.clauses.length > 0) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(this.visitSequenceClauses(arg.clauses));
        }
        return token;
    }
    visitAlterSequenceStatement(arg) {
        const keywordParts = ['alter', 'sequence'];
        if (arg.ifExists) {
            keywordParts.push('if exists');
        }
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, keywordParts.join(' '), SqlPrintTokenContainerType.AlterSequenceStatement);
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(arg.sequenceName.accept(this));
        if (arg.clauses.length > 0) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(this.visitSequenceClauses(arg.clauses));
        }
        return token;
    }
    visitSequenceClauses(clauses) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.SequenceOptionList);
        // Separate each clause with a space to mimic standard SQL layout.
        for (let i = 0; i < clauses.length; i++) {
            if (i > 0) {
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            }
            token.innerTokens.push(this.visitSequenceClause(clauses[i]));
        }
        return token;
    }
    visitSequenceClause(clause) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.SequenceOptionClause);
        // Convert each clause kind into its keyword/value representation.
        switch (clause.kind) {
            case 'increment':
                token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'increment'));
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'by'));
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                token.innerTokens.push(clause.value.accept(this));
                break;
            case 'start':
                token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'start'));
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'with'));
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                token.innerTokens.push(clause.value.accept(this));
                break;
            case 'minValue':
                if (clause.noValue) {
                    token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'no'));
                    token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                    token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'minvalue'));
                }
                else if (clause.value) {
                    token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'minvalue'));
                    token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                    token.innerTokens.push(clause.value.accept(this));
                }
                break;
            case 'maxValue':
                if (clause.noValue) {
                    token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'no'));
                    token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                    token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'maxvalue'));
                }
                else if (clause.value) {
                    token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'maxvalue'));
                    token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                    token.innerTokens.push(clause.value.accept(this));
                }
                break;
            case 'cache':
                if (clause.noValue) {
                    token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'no'));
                    token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                    token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'cache'));
                }
                else if (clause.value) {
                    token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'cache'));
                    token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                    token.innerTokens.push(clause.value.accept(this));
                }
                break;
            case 'cycle':
                token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, clause.enabled ? 'cycle' : 'no cycle'));
                break;
            case 'restart':
                token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'restart'));
                if (clause.value) {
                    token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                    token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'with'));
                    token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                    token.innerTokens.push(clause.value.accept(this));
                }
                break;
            case 'ownedBy':
                token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'owned'));
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'by'));
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                if (clause.none) {
                    token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'none'));
                }
                else if (clause.target) {
                    token.innerTokens.push(clause.target.accept(this));
                }
                break;
        }
        return token;
    }
    visitIndexColumnDefinition(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.IndexColumnDefinition);
        token.innerTokens.push(this.visit(arg.expression));
        if (arg.collation) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'collate'));
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.collation.accept(this));
        }
        if (arg.operatorClass) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.operatorClass.accept(this));
        }
        if (arg.sortOrder) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, arg.sortOrder));
        }
        if (arg.nullsOrder) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, `nulls ${arg.nullsOrder}`));
        }
        return token;
    }
    visitDropTableStatement(arg) {
        const keyword = arg.ifExists ? 'drop table if exists' : 'drop table';
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, keyword, SqlPrintTokenContainerType.DropTableStatement);
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        const tableList = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.ValueList);
        for (let i = 0; i < arg.tables.length; i++) {
            if (i > 0) {
                tableList.innerTokens.push(...SqlPrintTokenParser.commaSpaceTokens());
            }
            tableList.innerTokens.push(arg.tables[i].accept(this));
        }
        token.innerTokens.push(tableList);
        if (arg.behavior) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, arg.behavior));
        }
        return token;
    }
    visitDropIndexStatement(arg) {
        const keywordParts = ['drop', 'index'];
        if (arg.concurrently) {
            keywordParts.push('concurrently');
        }
        if (arg.ifExists) {
            keywordParts.push('if exists');
        }
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, keywordParts.join(' '), SqlPrintTokenContainerType.DropIndexStatement);
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        const indexList = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.ValueList);
        for (let i = 0; i < arg.indexNames.length; i++) {
            if (i > 0) {
                indexList.innerTokens.push(...SqlPrintTokenParser.commaSpaceTokens());
            }
            indexList.innerTokens.push(arg.indexNames[i].accept(this));
        }
        token.innerTokens.push(indexList);
        if (arg.behavior) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, arg.behavior));
        }
        return token;
    }
    visitAlterTableStatement(arg) {
        const keywordParts = ['alter', 'table'];
        if (arg.ifExists) {
            keywordParts.push('if exists');
        }
        if (arg.only) {
            keywordParts.push('only');
        }
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, keywordParts.join(' '), SqlPrintTokenContainerType.AlterTableStatement);
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(arg.table.accept(this));
        for (let i = 0; i < arg.actions.length; i++) {
            if (i === 0) {
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            }
            else {
                token.innerTokens.push(SqlPrintTokenParser.COMMA_TOKEN);
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            }
            token.innerTokens.push(arg.actions[i].accept(this));
        }
        return token;
    }
    visitAlterTableAddConstraint(arg) {
        const keyword = arg.ifNotExists ? 'add if not exists' : 'add';
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.AlterTableAddConstraint);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, keyword));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(arg.constraint.accept(this));
        if (arg.notValid) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'not valid'));
        }
        return token;
    }
    visitAlterTableDropConstraint(arg) {
        let keyword = 'drop constraint';
        if (arg.ifExists) {
            keyword += ' if exists';
        }
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.AlterTableDropConstraint);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, keyword));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(arg.constraintName.accept(this));
        if (arg.behavior) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, arg.behavior));
        }
        return token;
    }
    visitAlterTableAddColumn(arg) {
        let keyword = 'add column';
        if (arg.ifNotExists) {
            keyword += ' if not exists';
        }
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.AlterTableAddColumn);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, keyword));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(arg.column.accept(this));
        return token;
    }
    visitAlterTableDropColumn(arg) {
        let keyword = 'drop column';
        if (arg.ifExists) {
            keyword += ' if exists';
        }
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.AlterTableDropColumn);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, keyword));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(arg.columnName.accept(this));
        if (arg.behavior) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, arg.behavior));
        }
        return token;
    }
    visitAlterTableAlterColumnDefault(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.AlterTableAlterColumnDefault);
        // Begin with ALTER COLUMN and the targeted column name.
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'alter column'));
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(arg.columnName.accept(this));
        // Emit either SET DEFAULT or DROP DEFAULT depending on the action.
        if (arg.dropDefault) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'drop default'));
        }
        else if (arg.setDefault) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'set default'));
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(arg.setDefault.accept(this));
        }
        else {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'set default'));
        }
        return token;
    }
    visitDropConstraintStatement(arg) {
        let keyword = 'drop constraint';
        if (arg.ifExists) {
            keyword += ' if exists';
        }
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, keyword, SqlPrintTokenContainerType.DropConstraintStatement);
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(arg.constraintName.accept(this));
        if (arg.behavior) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, arg.behavior));
        }
        return token;
    }
    visitExplainStatement(arg) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.ExplainStatement);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, 'explain'));
        const inlineFlags = [];
        const optionList = [];
        if (arg.options) {
            for (const option of arg.options) {
                if (this.isExplainLegacyFlag(option) && this.isExplainBooleanTrue(option.value)) {
                    inlineFlags.push(option);
                }
                else {
                    optionList.push(option);
                }
            }
        }
        for (const flag of inlineFlags) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, flag.name.name.toLowerCase()));
        }
        if (optionList.length > 0) {
            // Keep the option list immediately after EXPLAIN without an extra space.
            token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
            for (let i = 0; i < optionList.length; i++) {
                if (i > 0) {
                    token.innerTokens.push(SqlPrintTokenParser.COMMA_TOKEN);
                    token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                }
                token.innerTokens.push(this.renderExplainOption(optionList[i]));
            }
            token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
        }
        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
        token.innerTokens.push(arg.statement.accept(this));
        return token;
    }
    visitAnalyzeStatement(arg) {
        const keywordParts = ['analyze'];
        if (arg.verbose) {
            keywordParts.push('verbose');
        }
        const token = new SqlPrintToken(SqlPrintTokenType.keyword, keywordParts.join(' '), SqlPrintTokenContainerType.AnalyzeStatement);
        // Render relation target when provided.
        if (arg.target) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(this.renderQualifiedNameInline(arg.target));
            // Render column list inline (comma space) when present.
            if (arg.columns && arg.columns.length > 0) {
                token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
                for (let i = 0; i < arg.columns.length; i++) {
                    if (i > 0) {
                        token.innerTokens.push(SqlPrintTokenParser.COMMA_TOKEN);
                        token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
                    }
                    token.innerTokens.push(this.renderIdentifierInline(arg.columns[i]));
                }
                token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
            }
        }
        return token;
    }
    renderExplainOption(option) {
        const token = new SqlPrintToken(SqlPrintTokenType.container, '', SqlPrintTokenContainerType.ExplainOption);
        token.innerTokens.push(new SqlPrintToken(SqlPrintTokenType.keyword, option.name.name.toLowerCase()));
        if (option.value && !this.isExplainBooleanTrue(option.value)) {
            token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
            token.innerTokens.push(option.value.accept(this));
        }
        return token;
    }
    isExplainLegacyFlag(option) {
        const name = option.name.name.toLowerCase();
        return name === 'analyze' || name === 'verbose';
    }
    isExplainBooleanTrue(value) {
        if (!value) {
            return false;
        }
        if (value instanceof RawString) {
            const normalized = value.value.toLowerCase();
            return normalized === 'true' || normalized === 't' || normalized === 'on' || normalized === 'yes' || normalized === '1';
        }
        if (value instanceof LiteralValue) {
            if (typeof value.value === 'boolean') {
                return value.value;
            }
            if (typeof value.value === 'number') {
                return value.value !== 0;
            }
            if (typeof value.value === 'string') {
                const normalized = value.value.toLowerCase();
                return normalized === 'true' || normalized === 't' || normalized === 'on' || normalized === 'yes' || normalized === '1';
            }
        }
        return false;
    }
    renderQualifiedNameInline(arg) {
        const parts = [];
        if (arg.namespaces && arg.namespaces.length > 0) {
            for (const ns of arg.namespaces) {
                parts.push(this.renderIdentifierText(ns));
            }
        }
        parts.push(this.renderIdentifierText(arg.name));
        return new SqlPrintToken(SqlPrintTokenType.value, parts.join('.'), SqlPrintTokenContainerType.QualifiedName);
    }
    renderIdentifierInline(component) {
        return new SqlPrintToken(SqlPrintTokenType.value, this.renderIdentifierText(component), SqlPrintTokenContainerType.IdentifierString);
    }
    renderIdentifierText(component) {
        if (component instanceof IdentifierString) {
            if (component.name === '*') {
                return component.name;
            }
            return this.identifierDecorator.decorate(component.name);
        }
        return component.value;
    }
}
// Static tokens for common symbols
SqlPrintTokenParser.SPACE_TOKEN = new SqlPrintToken(SqlPrintTokenType.space, ' ');
SqlPrintTokenParser.COMMA_TOKEN = new SqlPrintToken(SqlPrintTokenType.comma, ',');
SqlPrintTokenParser.ARGUMENT_SPLIT_COMMA_TOKEN = new SqlPrintToken(SqlPrintTokenType.argumentSplitter, ',');
SqlPrintTokenParser.PAREN_OPEN_TOKEN = new SqlPrintToken(SqlPrintTokenType.parenthesis, '(');
SqlPrintTokenParser.PAREN_CLOSE_TOKEN = new SqlPrintToken(SqlPrintTokenType.parenthesis, ')');
SqlPrintTokenParser.DOT_TOKEN = new SqlPrintToken(SqlPrintTokenType.dot, '.');
// Set of component kinds that handle their own positioned comments
// Note: Cannot use static readonly due to circular dependency issues with class initialization
SqlPrintTokenParser._selfHandlingComponentTypes = null;
//# sourceMappingURL=SqlPrintTokenParser.js.map