import type { Database, Sqlite3Static } from '@sqlite.org/sqlite-wasm';
import type { CompiledQuery as KyselyQuery } from 'kysely';
import type { RunnableQuery as DrizzleQuery } from 'drizzle-orm/runnable-query';
import type { SqliteRemoteResult } from 'drizzle-orm/sqlite-proxy';
import type { sqlTag } from './lib/sql-tag.js';
import type { SQLocalProcessor } from './processor.js';

type IsAny<T> = boolean extends (T extends never ? true : false) ? true : false;

// SQLite

export type Sqlite3 = Sqlite3Static;
export type Sqlite3InitModule = () => Promise<Sqlite3>;
export type Sqlite3Db = Database;
export type Sqlite3Method = 'get' | 'all' | 'run' | 'values';
export type Sqlite3StorageType =
	| (string & {})
	| 'memory'
	| 'opfs'
	| 'local'
	| 'session';

// Queries

export type Statement = {
	sql: string;
	params: unknown[];
};

export type ReturningStatement<Result = unknown> =
	| Statement
	| (IsAny<KyselyQuery<unknown>> extends true ? never : KyselyQuery<Result>)
	| (IsAny<DrizzleQuery<unknown, never>> extends true
			? never
			: DrizzleQuery<
					Result extends SqliteRemoteResult<unknown> ? any : Result[],
					'sqlite'
				>);

export type SqlTag = typeof sqlTag;
export type StatementInput<Result = unknown> =
	| ReturningStatement<Result>
	| ((sql: SqlTag) => ReturningStatement<Result>);

export type Transaction = {
	transactionKey: QueryKey;
	lastAffectedRows?: bigint;
	query: <Result extends Record<string, any>>(
		passStatement: StatementInput<Result>
	) => Promise<Result[]>;
	sql: <Result extends Record<string, any>>(
		queryTemplate: TemplateStringsArray | string,
		...params: unknown[]
	) => Promise<Result[]>;
	batch: <Result extends Record<string, any>>(
		passStatements: (sql: SqlTag) => Statement[]
	) => Promise<Result[][]>;
	commit: () => Promise<void>;
	rollback: () => Promise<void>;
};
export type TransactionHandle = Pick<Transaction, 'query' | 'sql' | 'batch'>;

export type ReactiveQuery<Result = unknown> = {
	readonly value: Result[];
	subscribe: (
		onData: (results: Result[]) => void,
		onError?: (err: Error) => void
	) => {
		unsubscribe: () => void;
	};
};
export type ReactiveQueryStatus = 'pending' | 'ok' | 'error';

export type RawResultData = {
	rows: unknown[] | unknown[][];
	columns: string[];
	numAffectedRows?: bigint;
};

// Driver

export interface SQLocalDriver {
	readonly storageType: Sqlite3StorageType;
	init: (config: DriverConfig) => Promise<void>;
	exec: (statement: DriverStatement) => Promise<RawResultData>;
	execBatch: (
		statements: DriverStatement[],
		method?: 'transaction' | 'savepoint'
	) => Promise<RawResultData[]>;
	onWrite: (callback: (change: DataChange) => void) => () => void;
	isDatabasePersisted: () => Promise<boolean>;
	getDatabaseSizeBytes: () => Promise<number>;
	createFunction: (fn: UserFunction) => Promise<void>;
	import: (
		database:
			| ArrayBuffer
			| Uint8Array<ArrayBuffer>
			| ReadableStream<Uint8Array<ArrayBuffer>>
	) => Promise<void>;
	export: () => Promise<{
		name: string;
		data: ArrayBuffer | Uint8Array<ArrayBuffer>;
	}>;
	clear: () => Promise<void>;
	destroy: () => Promise<void>;
}

export type DriverConfig = {
	databasePath?: DatabasePath;
	reactive?: boolean;
	readOnly?: boolean;
	verbose?: boolean;
};

export type DriverStatement = {
	sql: string;
	params?: any[];
	method?: Sqlite3Method;
};

// Database status

export type DatabasePath =
	| (string & {})
	| ':memory:'
	| 'local'
	| ':localStorage:'
	| 'session'
	| ':sessionStorage:';

export type QueryKey = string;
export type ConnectReason = 'initial' | 'overwrite' | 'delete';

export type ClientConfig = {
	databasePath: DatabasePath;
	reactive?: boolean;
	readOnly?: boolean;
	verbose?: boolean;
	onInit?: (sql: SqlTag) => void | Statement[];
	onConnect?: (reason: ConnectReason) => void;
	processor?: SQLocalProcessor | Worker;
};

export type ProcessorConfig = {
	databasePath?: DatabasePath;
	reactive?: boolean;
	readOnly?: boolean;
	verbose?: boolean;
	clientKey?: QueryKey;
	onInitStatements?: Statement[];
};

export type DatabaseInfo = {
	databasePath?: DatabasePath;
	databaseSizeBytes?: number;
	storageType?: Sqlite3StorageType;
	persisted?: boolean;
};

export type DataChange = {
	operation: 'insert' | 'update' | 'delete';
	table: string;
	rowid: BigInt;
};

// User functions

export type UserFunction =
	| CallbackUserFunction
	| ScalarUserFunction
	| AggregateUserFunction
	| WindowUserFunction;
export type CallbackUserFunction = {
	type: 'callback';
	name: string;
	func: (...args: any[]) => void;
};
export type ScalarUserFunction = {
	type: 'scalar';
	name: string;
	func: (...args: any[]) => any;
};
export type AggregateUserFunction = {
	type: 'aggregate';
	name: string;
	func: {
		step: (...args: any[]) => void;
		final: (...args: any[]) => any;
	};
};
export type WindowUserFunction = {
	type: 'window';
	name: string;
	func: {
		step: (...args: any[]) => void;
		value: (...args: any[]) => any;
		inverse: (...args: any[]) => void;
		final: (...args: any[]) => any;
	};
};
