// {{generatedComment}}
/* eslint-disable */
// biome-ignore-all lint: generated code
import { DuckDBListType, listValue, VARCHAR, INTEGER, BIGINT, DOUBLE, FLOAT, BOOLEAN, SMALLINT, TINYINT, type DuckDBConnection, type DuckDBMaterializedResult, type DuckDBAppender, type DuckDBDateValue, type DuckDBTimeValue, type DuckDBTimestampValue, type DuckDBBlobValue, type DuckDBValue } from "@duckdb/node-api";

export class {{className}} {
    
    constructor(private conn: DuckDBConnection) {}

    static getMigrations(): string[] {
        return [
            {{#each migrations}}
            {{{quote sqlQuery}}},
            {{/each}}
        ];
    }

{{#if config.migrations}}
    static async applyMigrations(conn: DuckDBConnection, projectName = '{{projectName}}'): Promise<void> {
        await conn.run(`CREATE TABLE IF NOT EXISTS _sqg_migrations (
            project TEXT NOT NULL,
            migration_id TEXT NOT NULL,
            applied_at TIMESTAMP NOT NULL DEFAULT now(),
            PRIMARY KEY (project, migration_id)
        )`);
        await conn.run('BEGIN');
        try {
            const result = await conn.runAndReadAll(
                'SELECT migration_id FROM _sqg_migrations WHERE project = $1', [projectName]
            );
            const applied = new Set(result.getRows().map((row) => row[0] as string));
            const migrations: [string, string][] = [
                {{#each migrations}}
                ['{{{id}}}', {{{quote sqlQuery}}}],
                {{/each}}
            ];
            for (const [id, sql] of migrations) {
                if (!applied.has(id)) {
                    await conn.run(sql);
                    await conn.run(
                        'INSERT INTO _sqg_migrations (project, migration_id) VALUES ($1, $2)',
                        [projectName, id]
                    );
                }
            }
            await conn.run('COMMIT');
        } catch (e) {
            await conn.run('ROLLBACK');
            throw e;
        }
    }
{{/if}}

    static getQueryNames(): Map<string, keyof {{className}}> {
        return new Map([
            {{#each queries}} {{#unless skipGenerateFunction}}
            ["{{id}}", "{{functionName}}"],{{/unless}}{{/each}}]
        );
    }

    {{#each attachers}}
    /** Attach the "{{alias}}" postgres source under the same alias used during generation. Loads the postgres extension (idempotent) so callers need not rely on DuckDB autoloading. */
    async {{functionName}}(connectionString: string): Promise<void> {
        await this.conn.run("INSTALL postgres");
        await this.conn.run("LOAD postgres");
        await this.conn.run(`ATTACH '${connectionString}' AS {{alias}} (TYPE postgres)`);
    }

    {{/each}}
    {{#each queries}}
    {{#unless skipGenerateFunction}}
    async {{functionName}}({{#each variables}}{{name}}: {{type}}{{#unless @last}}, {{/unless}}{{/each}}): Promise<{{> returnType }}> {
        const sql = {{{sqlExpr this}}};
        {{#if (hasListParams this)}}
        const stmt = await this.conn.prepare(sql);
        {{{bindStatements this}}}
        {{#if isQuery}}
        const reader = await stmt.runAndReadAll();
        {{#if isPluck}}
        {{#if isOne}}
        return reader.getRows()[0]?.[0] as {{> rowType}} | undefined;
        {{else}}
        return reader.getRows().map((row) => row[0] as {{> rowType}});
        {{/if}}
        {{else}}
        {{#if isOne}}
        return reader.getRowObjects()[0] as {{> rowType}} | undefined;
        {{else}}
        return reader.getRowObjects() as {{> rowType}}[];
        {{/if}}
        {{/if}}
        {{else}}
        return await stmt.run();
        {{/if}}
        {{else}}
        {{#if isQuery}}
        const reader = await this.conn.runAndReadAll(sql,[{{> params}}]);
        {{#if isPluck}}
        {{#if isOne}}
        return reader.getRows()[0]?.[0] as {{> rowType}} | undefined;
        {{else}}
        return reader.getRows().map((row) => row[0] as {{> rowType}});
        {{/if}}
        {{else}}
        {{#if isOne}}
        return reader.getRowObjects()[0] as {{> rowType}} | undefined;
        {{else}}
        return reader.getRowObjects() as {{> rowType}}[];
        {{/if}}
        {{/if}}
        {{else}}
        return await this.conn.run(sql,[{{> params}}]);
        {{/if}}
        {{/if}}
    }
    {{/unless}}

    {{/each}}

    {{#if tables.length}}
    // ==================== Appenders ====================
    {{#each tables}}

    async {{functionName}}(): Promise<{{className}}> {
        return new {{className}}(await this.conn.createAppender('{{tableName}}'));
    }
    {{/each}}
    {{/if}}
}

{{#each tables}}
/** Row type for {{tableName}} appender */
export interface {{rowTypeName}} {
    {{#each columns}}
    {{name}}: {{{tsTypeForAppender this}}};
    {{/each}}
}

/** Appender for bulk inserts into {{tableName}} */
export class {{className}} {
    constructor(public readonly appender: DuckDBAppender) {}

    /** Append a single row */
    append(row: {{rowTypeName}}): this {
        {{#each columns}}
        {{#if nullable}}
        if (row.{{name}} === null || row.{{name}} === undefined) {
            this.appender.appendNull();
        } else {
            this.appender.append{{appendMethod this}}(row.{{name}}{{{appendListTypeArg this}}});
        }
        {{else}}
        this.appender.append{{appendMethod this}}(row.{{name}}{{{appendListTypeArg this}}});
        {{/if}}
        {{/each}}
        this.appender.endRow();
        return this;
    }

    /** Append multiple rows */
    appendMany(rows: {{rowTypeName}}[]): this {
        for (const row of rows) {
            this.append(row);
        }
        return this;
    }

    /** Flush buffered data to the table */
    flush(): this {
        this.appender.flushSync();
        return this;
    }

    /** Flush and close the appender */
    close(): void {
        this.appender.closeSync();
    }
}
{{/each}}

{{#*inline "params"}}{{#each parameterNames}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}{{/inline}}

{{#*inline "paramTypes"}}{{#each parameters}}{{type}} {{#unless @last}}, {{/unless}}{{/each}}{{/inline}}

{{#*inline "columnTypes"}}{{#each columns}}{{name}}: {{{tsType .}}}{{#unless @last}}, {{/unless}}{{/each}}{{/inline}}

{{#*inline "rowType"}}
{{#if isQuery~}}
{{#if isPluck~}}
{{#if columns.length~}} {{{tsType (lookup columns 0)}}} {{else}}any{{/if~}}
{{~else~}}
{{#if columns.length}}{ {{> columnTypes}} }{{else}}any{{/if~}}
{{/if~}}
{{~else~}}
any
{{~/if~}}
{{/inline~}}

{{#*inline "resultType"}}
{{#if isQuery~}}
{{> rowType}}
{{~else~}}
any
{{~/if~}}
{{/inline~}}


{{#*inline "returnType"}}
{{#if isQuery~}}
{{#if isOne~}}
{{> rowType}} | undefined
{{~else~}}
({{> rowType}})[]
{{/if~}}
{{~else~}}
DuckDBMaterializedResult
{{~/if~}}
{{/inline~}}

