import {
  CompiledQuery,
  DatabaseConnection,
  DatabaseIntrospector,
  Dialect,
  Driver,
  Kysely,
  SqliteAdapter,
  SqliteIntrospector,
  SqliteQueryCompiler,
  QueryCompiler,
  QueryResult,
} from 'kysely'
import Database from '@tauri-apps/plugin-sql'

export interface TauriDialectConfig {
  database: Database
}

export class TauriDialect implements Dialect {
  #config: TauriDialectConfig

  constructor(config: TauriDialectConfig) {
    this.#config = config
  }

  createAdapter() {
    return new SqliteAdapter()
  }

  createDriver(): Driver {
    return new D1Driver(this.#config)
  }

  createQueryCompiler(): QueryCompiler {
    return new SqliteQueryCompiler()
  }

  createIntrospector(db: Kysely<any>): DatabaseIntrospector {
    return new SqliteIntrospector(db)
  }
}

class D1Driver implements Driver {
  #config: TauriDialectConfig

  constructor(config: TauriDialectConfig) {
    this.#config = config
  }

  async init(): Promise<void> {}

  async acquireConnection(): Promise<DatabaseConnection> {
    return new TauriConnection(this.#config)
  }

  async beginTransaction(conn: TauriConnection): Promise<void> {
    return await conn.beginTransaction()
  }

  async commitTransaction(conn: TauriConnection): Promise<void> {
    return await conn.commitTransaction()
  }

  async rollbackTransaction(conn: TauriConnection): Promise<void> {
    return await conn.rollbackTransaction()
  }

  async releaseConnection(_conn: TauriConnection): Promise<void> {}

  async destroy(): Promise<void> {}
}

class TauriConnection implements DatabaseConnection {
  #config: TauriDialectConfig
  //   #transactionClient?: D1Connection

  constructor(config: TauriDialectConfig) {
    this.#config = config
  }

  async executeQuery<O>(compiledQuery: CompiledQuery): Promise<QueryResult<O>> {
    if (compiledQuery.sql.startsWith('select')) {
      const results = await this.#config.database.select<O[]>(compiledQuery.sql, [
        ...compiledQuery.parameters,
      ])
      return {
        rows: results,
      }
    }
    const results = await this.#config.database.execute(compiledQuery.sql, [
      ...compiledQuery.parameters,
    ])

    const numAffectedRows = results.rowsAffected > 0 ? BigInt(results.rowsAffected) : undefined

    return {
      insertId:
        results.lastInsertId === undefined || results.lastInsertId === null
          ? undefined
          : BigInt(results.lastInsertId),
      rows: [],
      numAffectedRows,
      numUpdatedOrDeletedRows: numAffectedRows,
    }
  }

  async beginTransaction() {
    throw new Error('Transactions are not supported yet.')
  }

  async commitTransaction() {
    throw new Error('Transactions are not supported yet.')
  }

  async rollbackTransaction() {
    throw new Error('Transactions are not supported yet.')
  }

  async *streamQuery<O>(
    _compiledQuery: CompiledQuery,
    _chunkSize: number,
  ): AsyncIterableIterator<QueryResult<O>> {
    throw new Error('D1 Driver does not support streaming')
  }
}
