// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
// ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors.                              ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

import { test, expect } from "@perspective-dev/test";
import perspective from "./perspective_client";

const _query = async (should_cache, table, config = {}, callback) => {
    let cached_view;
    if (should_cache) {
        cached_view = await table.view(config);
    }

    const data_getter = async () => {
        if (should_cache) {
            return await cached_view.to_columns();
        } else {
            const view = await table.view(config);
            const data = await view.to_columns();
            await view.delete();
            return data;
        }
    };

    const v = await callback(data_getter);
    if (cached_view) {
        cached_view.delete();
    }

    return v;
};

const SCHEMA = {
    str: "string",
    int: "integer",
    float: "float",
};

test.describe("Removes", () => {
    for (const cond of [true, false]) {
        const query = _query.bind(undefined, cond);

        for (const idx of ["str", "int"]) {
            const string_pkey = idx === "str";

            test.describe(`View cached? ${cond}, index: ${idx}`, () => {
                test("Output consistent with filter", async () => {
                    const table = await perspective.table(SCHEMA, {
                        index: idx,
                    });
                    const data = {
                        str: [1, 2, 3, 4, 5, 6].map((x) => x.toString()),
                        int: [1, 2, 3, 4, 5, 6],
                        float: [1, 2, 3, 4, 5, 6].map((x) => x * 0.5),
                    };

                    await table.update(data);

                    await query(
                        table,
                        {
                            filter: [["str", "!=", "2"]],
                        },
                        async (getter) => {
                            const pkey = 3;
                            await table.remove([
                                string_pkey ? pkey.toString() : pkey,
                            ]);

                            expect(await getter()).toEqual({
                                str: ["1", "4", "5", "6"],
                                int: [1, 4, 5, 6],
                                float: [0.5, 2, 2.5, 3],
                            });
                        },
                    );

                    await table.delete();
                });

                test("Output consistent with filter, empty string", async () => {
                    const table = await perspective.table(SCHEMA, {
                        index: idx,
                    });
                    const data = {
                        str: [1, 2, 3, 4, 5, 6].map((x) => x.toString()),
                        int: [1, 2, 3, 4, 5, 6],
                        float: [1, 2, 3, 4, 5, 6].map((x) => x * 0.5),
                    };

                    data.str.push("");
                    data.int.push(7);
                    data.float.push(3.5);

                    await table.update(data);

                    await query(
                        table,
                        {
                            filter: [["str", "!=", "2"]],
                        },
                        async (getter) => {
                            const pkey = 3;
                            await table.remove([
                                string_pkey ? pkey.toString() : pkey,
                            ]);

                            if (string_pkey) {
                                expect(await getter()).toEqual({
                                    str: ["", "1", "4", "5", "6"],
                                    int: [7, 1, 4, 5, 6],
                                    float: [3.5, 0.5, 2, 2.5, 3],
                                });
                            } else {
                                expect(await getter()).toEqual({
                                    str: ["1", "4", "5", "6", ""],
                                    int: [1, 4, 5, 6, 7],
                                    float: [0.5, 2, 2.5, 3, 3.5],
                                });
                            }
                        },
                    );

                    await table.delete();
                });

                test("Output consistent with filter, null", async () => {
                    const table = await perspective.table(SCHEMA, {
                        index: idx,
                    });
                    const data = {
                        str: [1, 2, 3, 4, 5, 6].map((x) => x.toString()),
                        int: [1, 2, 3, 4, 5, 6],
                        float: [1, 2, 3, 4, 5, 6].map((x) => x * 0.5),
                    };

                    data.str.push(null);
                    data.int.push(7);
                    data.float.push(3.5);

                    await table.update(data);

                    await query(
                        table,
                        {
                            filter: [["str", "!=", "2"]],
                        },
                        async (getter) => {
                            const pkey = 3;
                            await table.remove([
                                string_pkey ? pkey.toString() : pkey,
                            ]);

                            if (string_pkey) {
                                expect(await getter()).toEqual({
                                    str: [null, "1", "4", "5", "6"],
                                    int: [7, 1, 4, 5, 6],
                                    float: [3.5, 0.5, 2, 2.5, 3],
                                });
                            } else {
                                expect(await getter()).toEqual({
                                    str: ["1", "4", "5", "6", null],
                                    int: [1, 4, 5, 6, 7],
                                    float: [0.5, 2, 2.5, 3, 3.5],
                                });
                            }
                        },
                    );

                    await table.delete();
                });

                test("Output consistent with filter, remove before view construction, sorted", async () => {
                    const table = await perspective.table(SCHEMA, {
                        index: idx,
                    });
                    const data = {
                        str: [1, 2, 3, 4, 5, 6].map((x) => x.toString()),
                        int: [1, 2, 3, 4, 5, 6],
                        float: [1, 2, 3, 4, 5, 6].map((x) => x * 0.5),
                    };

                    await table.update(data);
                    await table.remove(
                        [1, 3, 5].map((x) => (string_pkey ? x.toString() : x)),
                    );

                    await query(
                        table,
                        {
                            sort: [["str", "desc"]],
                        },
                        async (getter) => {
                            expect(await getter()).toEqual({
                                str: ["6", "4", "2"],
                                int: [6, 4, 2],
                                float: [3, 2, 1],
                            });

                            await table.update({
                                str: ["7", "9", "8"],
                                int: [7, 9, 8],
                                float: [3.5, 4.5, 4],
                            });

                            expect(await getter()).toEqual({
                                str: ["9", "8", "7", "6", "4", "2"],
                                int: [9, 8, 7, 6, 4, 2],
                                float: [4.5, 4, 3.5, 3, 2, 1],
                            });
                        },
                    );

                    await table.delete();
                });

                test("Output consistent with filter, remove and add identical", async () => {
                    const table = await perspective.table(SCHEMA, {
                        index: idx,
                    });
                    const data = {
                        str: [1, 2, 3, 4, 5, 6].map((x) => x.toString()),
                        int: [1, 2, 3, 4, 5, 6],
                        float: [1, 2, 3, 4, 5, 6].map((x) => x * 0.5),
                    };

                    await table.update(data);

                    await query(
                        table,
                        {
                            filter: [["str", "!=", "2"]],
                        },
                        async (getter) => {
                            const pkey = 3;
                            await table.remove([
                                string_pkey ? pkey.toString() : pkey,
                            ]);

                            expect(await getter()).toEqual({
                                str: ["1", "4", "5", "6"],
                                int: [1, 4, 5, 6],
                                float: [0.5, 2, 2.5, 3],
                            });

                            await table.update({
                                str: ["7", "9", "8"],
                                int: [7, 9, 8],
                                float: [3.5, 4.5, 4],
                            });

                            expect(await getter()).toEqual({
                                str: ["1", "4", "5", "6", "7", "8", "9"],
                                int: [1, 4, 5, 6, 7, 8, 9],
                                float: [0.5, 2, 2.5, 3, 3.5, 4, 4.5],
                            });
                        },
                    );

                    await table.delete();
                });

                test("Output consistent with filter, add null and remove other", async () => {
                    const table = await perspective.table(SCHEMA, {
                        index: idx,
                    });
                    const data = {
                        str: [1, 2, 3, 4, 5, 6].map((x) => x.toString()),
                        int: [1, 2, 3, 4, 5, 6],
                        float: [1, 2, 3, 4, 5, 6].map((x) => x * 0.5),
                    };

                    await table.update(data);

                    await query(table, {}, async (getter) => {
                        expect(await getter()).toEqual(data);

                        await table.update({
                            str: [string_pkey ? null : "7"],
                            int: [string_pkey ? 7 : null],
                            float: [3.5],
                        });

                        if (string_pkey) {
                            expect(await getter()).toEqual({
                                str: [null, 1, 2, 3, 4, 5, 6].map((x) =>
                                    x ? x.toString() : x,
                                ),
                                int: [7, 1, 2, 3, 4, 5, 6],
                                float: [7, 1, 2, 3, 4, 5, 6].map(
                                    (x) => x * 0.5,
                                ),
                            });
                        } else {
                            expect(await getter()).toEqual({
                                str: [7, 1, 2, 3, 4, 5, 6].map((x) =>
                                    x.toString(),
                                ),
                                int: [null, 1, 2, 3, 4, 5, 6],
                                float: [7, 1, 2, 3, 4, 5, 6].map(
                                    (x) => x * 0.5,
                                ),
                            });
                        }

                        await table.remove([string_pkey ? "3" : 3]);

                        if (string_pkey) {
                            expect(await getter()).toEqual({
                                str: [null, 1, 2, 4, 5, 6].map((x) =>
                                    x ? x.toString() : x,
                                ),
                                int: [7, 1, 2, 4, 5, 6],
                                float: [7, 1, 2, 4, 5, 6].map((x) => x * 0.5),
                            });
                        } else {
                            expect(await getter()).toEqual({
                                str: [7, 1, 2, 4, 5, 6].map((x) =>
                                    x.toString(),
                                ),
                                int: [null, 1, 2, 4, 5, 6],
                                float: [7, 1, 2, 4, 5, 6].map((x) => x * 0.5),
                            });
                        }
                    });

                    await table.delete();
                });

                test("Output consistent with filter, remove and add new dataset", async () => {
                    const table = await perspective.table(SCHEMA, {
                        index: idx,
                    });

                    const data = {
                        str: [1, 2, 3, 4, 5, 6].map((x) => x.toString()),
                        int: [1, 2, 3, 4, 5, 6],
                        float: [1, 2, 3, 4, 5, 6].map((x) => x * 0.5),
                    };

                    await table.update(data);

                    await query(
                        table,
                        {
                            filter: [["int", "!=", 3]],
                        },
                        async (getter) => {
                            await table.remove(
                                [1, 2, 3, 4, 5, 6].map((x) =>
                                    string_pkey ? x.toString() : x,
                                ),
                            );

                            expect(await getter()).toEqual({
                                float: [],
                                int: [],
                                str: [],
                            });

                            await table.update({
                                str: ["def", "abc", "deff"],
                                int: [1, 2, 4],
                                float: [100.5, 200.5, 300.5],
                            });

                            if (string_pkey) {
                                expect(await getter()).toEqual({
                                    str: ["abc", "def", "deff"],
                                    int: [2, 1, 4],
                                    float: [200.5, 100.5, 300.5],
                                });
                            } else {
                                expect(await getter()).toEqual({
                                    str: ["def", "abc", "deff"],
                                    int: [1, 2, 4],
                                    float: [100.5, 200.5, 300.5],
                                });
                            }
                        },
                    );

                    await table.delete();
                });

                test.skip("Output consistent with filter, remove and add null", async () => {
                    // FIXME
                    const table = await perspective.table(SCHEMA, {
                        index: idx,
                    });
                    const data = {
                        str: [1, 2, 3, 4, 5, 6].map((x) => x.toString()),
                        int: [1, 2, 3, 4, 5, 6],
                        float: [1, 2, 3, 4, 5, 6].map((x) => x * 0.5),
                    };

                    const splice_idx = Math.floor(Math.random() * 6);
                    data.str.splice(splice_idx, 0, string_pkey ? null : "7");
                    data.int.splice(splice_idx, 0, string_pkey ? 7 : null);
                    data.float.splice(splice_idx, 0, 3.5);

                    await table.update(data);

                    await query(
                        table,
                        {
                            filter: [["float", "==", 3.5]],
                        },
                        async (getter) => {
                            await table.remove([null]);
                            const v = await table.view();
                            console.log(
                                "str?",
                                string_pkey,
                                await v.to_columns(),
                            );
                            await v.delete();

                            expect(await getter()).toEqual({});

                            await table.update({
                                str: [string_pkey ? null : "7"],
                                int: [string_pkey ? 7 : null],
                                float: [3.5],
                            });

                            expect(await getter()).toEqual({
                                str: [string_pkey ? null : "7"],
                                int: [string_pkey ? 7 : null],
                                float: [3.5],
                            });
                        },
                    );

                    await table.delete();
                });

                test.skip("Output consistent with filter, update null", async () => {
                    const table = await perspective.table(SCHEMA, {
                        index: idx,
                    });
                    const data = {
                        str: [1, 2, 3, 4, 5, 6].map((x) => x.toString()),
                        int: [1, 2, 3, 4, 5, 6],
                        float: [1, 2, 3, 4, 5, 6].map((x) => x * 0.5),
                    };

                    const splice_idx = Math.floor(Math.random() * 6);
                    data.str.splice(splice_idx, 0, string_pkey ? null : "7");
                    data.int.splice(splice_idx, 0, string_pkey ? 7 : null);
                    data.float.splice(splice_idx, 0, 3.5);

                    await table.update(data);

                    await query(table, {}, async (getter) => {
                        const expected = {
                            str: data.str.slice(),
                            int: data.int.slice(),
                            float: data.float.slice(),
                        };

                        expect(await getter()).toEqual(expected);

                        await table.update([
                            {
                                str: string_pkey ? null : "7",
                                int: string_pkey ? 7 : null,
                                float: 4.5,
                            },
                        ]);

                        expected.float[splice_idx] = 4.5;

                        expect(await getter()).toEqual(expected);
                    });

                    await table.delete();
                });
            });
        }
    }
});
