import { getTableName } from "drizzle-orm";
import { escapeName } from "../dialect.js";
// ---------------------------------------------------------------------------
// Builder
// ---------------------------------------------------------------------------
export class SybaseSelectBuilder {
    dialect;
    session;
    config;
    constructor(dialect, session, fields) {
        this.dialect = dialect;
        this.session = session;
        this.config = {
            fields: fields ?? [{ expression: "*" }]
        };
    }
    /**
     * Add a Common Table Expression (CTE) to the query.
     *
     * @example
     * ```ts
     * const result = await db.select()
     *   .with("active_users", sql`select * from [users] where active = 1`)
     *   .from("active_users");
     * ```
     */
    with(alias, query) {
        if (!this.config.withList) {
            this.config.withList = [];
        }
        const sqlStr = typeof query === "string" ? query : this.dialect.sqlToQuery(query);
        this.config.withList.push({ alias, sql: sqlStr });
        return this;
    }
    from(table) {
        if (typeof table === "string") {
            this.config.table = escapeName(table);
        }
        else {
            this.config.table = escapeName(getTableName(table));
        }
        return this;
    }
    where(condition) {
        this.config.where = this.dialect.sqlToQuery(condition);
        return this;
    }
    orderBy(...columns) {
        this.config.orderBy = this.resolveColumns(columns);
        return this;
    }
    groupBy(...columns) {
        this.config.groupBy = this.resolveColumns(columns);
        return this;
    }
    having(condition) {
        this.config.having = this.dialect.sqlToQuery(condition);
        return this;
    }
    innerJoin(table, on) {
        return this.addJoin("inner", table, on);
    }
    leftJoin(table, on) {
        return this.addJoin("left", table, on);
    }
    rightJoin(table, on) {
        return this.addJoin("right", table, on);
    }
    fullJoin(table, on) {
        return this.addJoin("full", table, on);
    }
    distinct() {
        this.config.distinct = true;
        return this;
    }
    /**
     * Set max rows to return. Sybase uses SET ROWCOUNT instead of LIMIT.
     *
     * - `limit(0)` returns zero rows (SET ROWCOUNT 0 before query, then reset)
     * - Negative values are ignored
     */
    limit(count) {
        if (count < 0) {
            return this;
        }
        this.config.limit = count;
        return this;
    }
    /**
     * Set offset for pagination.
     *
     * **Implementation:** Sybase ASE does not support OFFSET natively.
     * This is implemented by fetching `offset + limit` rows via SET ROWCOUNT,
     * then slicing off the first `offset` rows in application code.
     *
     * For best performance with large offsets, consider cursor-based pagination
     * using WHERE clauses on indexed columns instead.
     *
     * ORDER BY is required for deterministic results with offset.
     */
    offset(count) {
        if (count < 0) {
            return this;
        }
        this.config.offset = count;
        return this;
    }
    /**
     * Get the generated SQL string (for testing/debugging).
     */
    toSQL() {
        return this.buildSql();
    }
    /**
     * Execute the query and return typed results.
     */
    async execute() {
        // Safety: offset without limit would fetch entire table into memory then slice.
        if (this.config.offset !== undefined &&
            this.config.offset > 0 &&
            this.config.limit === undefined) {
            throw new Error("offset() requires limit() to be set. " +
                "Without a limit, the entire table would be fetched into memory before slicing. " +
                "Use cursor-based pagination (WHERE id > ?) for large offsets.");
        }
        const rawSql = this.buildSql();
        const maxRows = this.getMaxRowsHint();
        const result = await this.session.execute(rawSql, maxRows ? { maxRows } : undefined);
        // If offset is set, skip the first N rows in application code
        // (Sybase ASE has no native OFFSET support)
        if (this.config.offset !== undefined && this.config.offset > 0) {
            return result.rows.slice(this.config.offset);
        }
        return result.rows;
    }
    /**
     * Alias for execute() — enables `await db.select()...` pattern.
     */
    then(onfulfilled, onrejected) {
        return this.execute().then(onfulfilled, onrejected);
    }
    // ---------------------------------------------------------------------------
    // Private
    // ---------------------------------------------------------------------------
    resolveColumns(columns) {
        return columns.map(col => {
            if (typeof col === "string") {
                return col;
            }
            // Check for SybaseColumn (has name + table) before getSQL to avoid
            // calling getSQL() on column objects that don't return a proper SQL instance
            if (col && col.name && col.table) {
                return `${escapeName(getTableName(col.table))}.${escapeName(col.name)}`;
            }
            if (col && typeof col.getSQL === "function") {
                return this.dialect.sqlToQuery(col.getSQL());
            }
            if (col && typeof col.toQuery === "function") {
                return this.dialect.sqlToQuery(col);
            }
            if (col && col.name) {
                return escapeName(col.name);
            }
            return String(col);
        });
    }
    addJoin(type, table, on) {
        if (!this.config.joins) {
            this.config.joins = [];
        }
        let tableName;
        if (typeof table === "string") {
            tableName = escapeName(table);
        }
        else {
            tableName = escapeName(getTableName(table));
        }
        const onStr = this.dialect.sqlToQuery(on);
        this.config.joins.push({ type, table: tableName, on: onStr });
        return this;
    }
    buildSql() {
        // Special case: limit(0) means return no rows
        // In Sybase, SET ROWCOUNT 0 means "no limit", so we add WHERE 1=0
        if (this.config.limit === 0) {
            const configWithFalseWhere = {
                ...this.config,
                where: this.config.where ? `1=0 and (${this.config.where})` : "1=0"
            };
            return this.dialect.buildSelectQuery(configWithFalseWhere);
        }
        const paginationParts = [];
        if (this.config.limit !== undefined && this.config.offset !== undefined) {
            paginationParts.push(`SET ROWCOUNT ${this.config.offset + this.config.limit}`);
        }
        else if (this.config.limit !== undefined) {
            paginationParts.push(`SET ROWCOUNT ${this.config.limit}`);
        }
        const selectSql = this.dialect.buildSelectQuery(this.config);
        if (paginationParts.length > 0) {
            return `${paginationParts.join("\n")}\n${selectSql}\nSET ROWCOUNT 0`;
        }
        return selectSql;
    }
    /**
     * Get a hint for the native maxRows parameter to limit memory usage.
     * When offset+limit is known, we can tell the native layer the max rows expected.
     */
    getMaxRowsHint() {
        if (this.config.limit !== undefined && this.config.offset !== undefined) {
            return this.config.offset + this.config.limit;
        }
        if (this.config.limit !== undefined) {
            return this.config.limit;
        }
        return undefined;
    }
}
//# sourceMappingURL=select.js.map