import { expect, test, afterAll, beforeEach, afterEach } from 'vitest';
import { connect, Database } from './promise-default.js';
import { MainWorker } from './index-default.js';
beforeEach((ctx) => {
    console.log(`[test:start] ${ctx.task.name}`);
});
afterEach((ctx) => {
    console.log(`[test:end] ${ctx.task.name} (${ctx.task.result?.state})`);
});
afterAll(() => {
    console.log('[afterAll] terminating MainWorker');
    MainWorker?.terminate();
    console.log('[afterAll] MainWorker terminated');
});
test('big schema test', async () => {
    const N = 1024;
    {
        const db = await connect("local.db");
        for (let i = 0; i < N; i++) {
            console.info(`i = ${i}`);
            const stmt = await db.prepare(`CREATE TABLE t_${i} (x)`);
            await stmt.all();
            await stmt.close();
        }
        await db.close();
    }
    {
        console.info('open db again');
        const db = await connect("local.db");
        const rows = await (await db.prepare("SELECT * FROM sqlite_master")).all();
        expect(rows).toHaveLength(N);
    }
});
test('vector-test', async () => {
    const db = await connect(":memory:");
    const v1 = new Array(1024).fill(0).map((_, i) => i);
    const v2 = new Array(1024).fill(0).map((_, i) => 1024 - i);
    const result = await (await db.prepare(`SELECT
        vector_distance_cos(vector32('${JSON.stringify(v1)}'), vector32('${JSON.stringify(v2)}')) as cosf32,
        vector_distance_cos(vector64('${JSON.stringify(v1)}'), vector64('${JSON.stringify(v2)}')) as cosf64,
        vector_distance_l2(vector32('${JSON.stringify(v1)}'), vector32('${JSON.stringify(v2)}')) as l2f32,
        vector_distance_l2(vector64('${JSON.stringify(v1)}'), vector64('${JSON.stringify(v2)}')) as l2f64
    `)).all();
    console.info(result);
    await db.close();
});
test('explain', async () => {
    const db = await connect(":memory:");
    const stmt = await db.prepare("EXPLAIN SELECT 1");
    expect(stmt.columns()).toEqual([
        {
            "name": "addr",
            "type": "INTEGER",
        },
        {
            "name": "opcode",
            "type": "TEXT",
        },
        {
            "name": "p1",
            "type": "INTEGER",
        },
        {
            "name": "p2",
            "type": "INTEGER",
        },
        {
            "name": "p3",
            "type": "INTEGER",
        },
        {
            "name": "p4",
            "type": "TEXT",
        },
        {
            "name": "p5",
            "type": "INTEGER",
        },
        {
            "name": "comment",
            "type": "TEXT",
        },
    ].map(x => ({ ...x, column: null, database: null, table: null })));
    expect(await stmt.all()).toEqual([
        {
            "addr": 0,
            "comment": "Start at 3",
            "opcode": "Init",
            "p1": 0,
            "p2": 3,
            "p3": 0,
            "p4": "",
            "p5": 0,
        },
        {
            "addr": 1,
            "comment": "output=r[1]",
            "opcode": "ResultRow",
            "p1": 1,
            "p2": 1,
            "p3": 0,
            "p4": "",
            "p5": 0,
        },
        {
            "addr": 2,
            "comment": "",
            "opcode": "Halt",
            "p1": 0,
            "p2": 0,
            "p3": 0,
            "p4": "",
            "p5": 0,
        },
        {
            "addr": 3,
            "comment": "r[1]=1",
            "opcode": "Integer",
            "p1": 1,
            "p2": 1,
            "p3": 0,
            "p4": "",
            "p5": 0,
        },
        {
            "addr": 4,
            "comment": "",
            "opcode": "Goto",
            "p1": 0,
            "p2": 1,
            "p3": 0,
            "p4": "",
            "p5": 0,
        },
    ]);
    await db.close();
});
test('in-memory db', async () => {
    const db = await connect(":memory:");
    await db.exec("CREATE TABLE t(x)");
    await db.exec("INSERT INTO t VALUES (1), (2), (3)");
    const stmt = await db.prepare("SELECT * FROM t WHERE x % 2 = ?");
    const rows = await stmt.all([1]);
    expect(rows).toEqual([{ x: 1 }, { x: 3 }]);
    await db.close();
});
test('implicit connect', async () => {
    const db = new Database(':memory:');
    const defer = await db.prepare("SELECT * FROM t");
    await expect(async () => await defer.all()).rejects.toThrowError(/no such table: t/);
    await expect(async () => await db.prepare("SELECT * FROM t")).rejects.toThrowError(/no such table: t/);
    expect(await (await db.prepare("SELECT 1 as x")).all()).toEqual([{ x: 1 }]);
    await db.close();
});
test('on-disk db large inserts', async () => {
    const path = `test-${(Math.random() * 10000) | 0}.db`;
    const db1 = await connect(path);
    await (await db1.prepare("CREATE TABLE t(x)")).run();
    await (await db1.prepare("INSERT INTO t VALUES (randomblob(10 * 4096 + 0))")).run();
    await (await db1.prepare("INSERT INTO t VALUES (randomblob(10 * 4096 + 1))")).run();
    await (await db1.prepare("INSERT INTO t VALUES (randomblob(10 * 4096 + 2))")).run();
    const stmt1 = await db1.prepare("SELECT length(x) as l FROM t");
    expect(stmt1.columns()).toEqual([{ name: "l", column: null, database: null, table: null, type: null }]);
    const rows1 = await stmt1.all();
    expect(rows1).toEqual([{ l: 10 * 4096 }, { l: 10 * 4096 + 1 }, { l: 10 * 4096 + 2 }]);
    await db1.exec("BEGIN");
    await db1.exec("INSERT INTO t VALUES (1)");
    await db1.exec("ROLLBACK");
    const rows2 = await (await db1.prepare("SELECT length(x) as l FROM t")).all();
    expect(rows2).toEqual([{ l: 10 * 4096 }, { l: 10 * 4096 + 1 }, { l: 10 * 4096 + 2 }]);
    await (await db1.prepare("PRAGMA wal_checkpoint(TRUNCATE)")).run();
    await db1.close();
});
test('on-disk db', async () => {
    const path = `test-${(Math.random() * 10000) | 0}.db`;
    const db1 = await connect(path);
    await db1.exec("CREATE TABLE t(x)");
    await db1.exec("INSERT INTO t VALUES (1), (2), (3)");
    const stmt1 = await db1.prepare("SELECT * FROM t WHERE x % 2 = ?");
    expect(stmt1.columns()).toEqual([{ name: "x", column: null, database: null, table: null, type: null }]);
    const rows1 = await stmt1.all([1]);
    expect(rows1).toEqual([{ x: 1 }, { x: 3 }]);
    stmt1.close();
    await db1.close();
    const db2 = await connect(path);
    const stmt2 = await db2.prepare("SELECT * FROM t WHERE x % 2 = ?");
    expect(stmt2.columns()).toEqual([{ name: "x", column: null, database: null, table: null, type: null }]);
    const rows2 = await stmt2.all([1]);
    expect(rows2).toEqual([{ x: 1 }, { x: 3 }]);
    await db2.close();
});
// attach is not supported in browser for now
// test('attach', async () => {
//     const path1 = `test-${(Math.random() * 10000) | 0}.db`;
//     const path2 = `test-${(Math.random() * 10000) | 0}.db`;
//     const db1 = await connect(path1);
//     await db1.exec("CREATE TABLE t(x)");
//     await db1.exec("INSERT INTO t VALUES (1), (2), (3)");
//     const db2 = await connect(path2);
//     await db2.exec("CREATE TABLE q(x)");
//     await db2.exec("INSERT INTO q VALUES (4), (5), (6)");
//     await db1.exec(`ATTACH '${path2}' as secondary`);
//     const stmt = db1.prepare("SELECT * FROM t UNION ALL SELECT * FROM secondary.q");
//     expect(stmt.columns()).toEqual([{ name: "x", column: null, database: null, table: null, type: null }]);
//     const rows = await stmt.all([1]);
//     expect(rows).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }, { x: 5 }, { x: 6 }]);
// })
test('blobs', async () => {
    const db = await connect(":memory:");
    const rows = await (await db.prepare("SELECT x'1020' as x")).all();
    expect(rows).toEqual([{ x: new Uint8Array([16, 32]) }]);
    await db.close();
});
test("datetime('now')", async () => {
    const db = await connect(":memory:");
    const rows = await (await db.prepare("SELECT datetime('now') as now")).all();
    expect(rows).toHaveLength(1);
    expect(rows[0].now).toMatch(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/);
    await db.close();
});
test('example-1', async () => {
    const db = await connect(':memory:');
    await db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)');
    const insert = await db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');
    await insert.run('Alice', 'alice@example.com');
    await insert.run('Bob', 'bob@example.com');
    const users = await (await db.prepare('SELECT * FROM users')).all();
    expect(users).toEqual([
        { id: 1, name: 'Alice', email: 'alice@example.com' },
        { id: 2, name: 'Bob', email: 'bob@example.com' }
    ]);
    await db.close();
});
test('example-2', async () => {
    const db = await connect(':memory:');
    await db.exec('CREATE TABLE users (name, email)');
    // Using transactions for atomic operations
    const transaction = db.transaction(async (users) => {
        const insert = await db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');
        for (const user of users) {
            await insert.run(user.name, user.email);
        }
    });
    // Execute transaction
    await transaction([
        { name: 'Alice', email: 'alice@example.com' },
        { name: 'Bob', email: 'bob@example.com' }
    ]);
    const rows = await (await db.prepare('SELECT * FROM users')).all();
    expect(rows).toEqual([
        { name: 'Alice', email: 'alice@example.com' },
        { name: 'Bob', email: 'bob@example.com' }
    ]);
    await db.close();
});
test('sorter-wasm', async () => {
    const db = await connect(':memory:');
    await db.exec('CREATE TABLE t (k, v)');
    for (let i = 0; i < 1024; i++) {
        await db.exec(`INSERT INTO t VALUES (${i}, randomblob(10 * 1024))`);
    }
    expect(await (await db.prepare("SELECT length(v) as l FROM (SELECT v FROM t ORDER BY k)")).all()).toEqual(new Array(1024).fill({ l: 10 * 1024 }));
    await db.close();
});
test('fts-base', async (ctx) => {
    const db = await connect(":memory:", { experimental: ["index_method"] });
    await db.exec(`
        CREATE TABLE documents (id INTEGER PRIMARY KEY, title TEXT, body TEXT);
        INSERT INTO documents VALUES (1, 'Introduction to Rust', 'Rust is a systems programming language focused on safety and performance');
        INSERT INTO documents VALUES (2, 'JavaScript Guide', 'JavaScript is a dynamic programming language used for web development');
        INSERT INTO documents VALUES (3, 'Database Internals', 'Understanding how databases store and retrieve data efficiently');
    `);
    try {
        await db.exec(`CREATE INDEX documents_fts ON documents USING fts (title, body)`);
    }
    catch {
        await db.close();
        ctx.skip();
        return;
    }
    // fts_match search
    const matchResults = await (await db.prepare("SELECT id, title, fts_score(title, body, 'programming language') as score FROM documents WHERE fts_match(title, body, 'programming language')")).all();
    expect(matchResults.length).toBe(2);
    expect(matchResults.map(r => r.id).sort()).toEqual([1, 2]);
    for (const row of matchResults) {
        expect(row.score).toBeGreaterThan(0);
    }
    // fts_highlight
    const highlightResults = await (await db.prepare("SELECT id, fts_highlight(title, '<b>', '</b>', 'Rust') as highlighted FROM documents WHERE fts_match(title, body, 'Rust')")).all();
    expect(highlightResults.length).toBe(1);
    expect(highlightResults[0].id).toBe(1);
    expect(highlightResults[0].highlighted).toContain('<b>');
    expect(highlightResults[0].highlighted).toContain('Rust');
    // no match
    const noResults = await (await db.prepare("SELECT * FROM documents WHERE fts_match(title, body, 'nonexistentterm')")).all();
    expect(noResults.length).toBe(0);
    await db.close();
});
test('fts-delete', async (ctx) => {
    const db = await connect(":memory:", { experimental: ["index_method"] });
    await db.exec('CREATE TABLE notes (id INTEGER PRIMARY KEY, text TEXT)');
    try {
        await db.exec('CREATE INDEX idx_notes_fts ON notes USING fts (text)');
    }
    catch {
        await db.close();
        ctx.skip();
        return;
    }
    await db.exec("INSERT INTO notes (text) VALUES ('important meeting notes')");
    await db.exec("INSERT INTO notes (text) VALUES ('grocery list items')");
    await db.exec('DELETE FROM notes WHERE id = 2');
    const rows = await (await db.prepare("SELECT text FROM notes WHERE fts_match(text, 'meeting')")).all();
    expect(rows).toHaveLength(1);
    expect(rows[0].text).toContain('meeting');
    const all = await (await db.prepare('SELECT * FROM notes')).all();
    expect(all).toHaveLength(1);
    await db.close();
});
test('fts-insert', async (ctx) => {
    const db = await connect(":memory:", { experimental: ["index_method"] });
    await db.exec('CREATE TABLE notes (id INTEGER PRIMARY KEY, text TEXT)');
    try {
        await db.exec('CREATE INDEX idx_notes_fts ON notes USING fts (text)');
    }
    catch {
        await db.close();
        ctx.skip();
        return;
    }
    await db.exec("INSERT INTO notes (text) VALUES ('first note about testing')");
    let rows = await (await db.prepare("SELECT id FROM notes WHERE fts_match(text, 'testing')")).all();
    expect(rows).toHaveLength(1);
    await db.exec("INSERT INTO notes (text) VALUES ('second note about testing strategies')");
    rows = await (await db.prepare("SELECT id FROM notes WHERE fts_match(text, 'testing')")).all();
    expect(rows).toHaveLength(2);
    await db.close();
});
test('hash-join-wasm', { timeout: 60_000 }, async () => {
    const db = await connect(':memory:');
    await db.exec('CREATE TABLE a (k, v)');
    await db.exec('CREATE TABLE b (k, v)');
    for (let i = 0; i < 1024; i++) {
        await db.exec(`INSERT INTO a VALUES (${i}, randomblob(100 * 1024))`);
    }
    for (let i = 0; i < 1024; i++) {
        await db.exec(`INSERT INTO b VALUES (${i}, randomblob(100 * 1024))`);
    }
    expect(await (await db.prepare("SELECT length(a) as a, length(b) as b FROM (SELECT a.v as a, b.v as b FROM a INNER JOIN b ON a.k = b.k)")).all()).toEqual(new Array(1024).fill({ a: 100 * 1024, b: 100 * 1024 }));
    await db.close();
});
