import { test, describe, assert, afterEach, vi } from "vitest";
import { cleanup, render } from "@self/tootils";
import event from "@testing-library/user-event";
import { setupi18n } from "../core/src/i18n";

import CheckboxGroup from "./Index.svelte";
import type { LoadingStatus } from "@gradio/statustracker";

const loading_status: LoadingStatus = {
	eta: 0,
	queue_position: 1,
	queue_size: 1,
	status: "complete" as LoadingStatus["status"],
	scroll_to_output: false,
	visible: true,
	fn_index: 0,
	show_progress: "full"
};

beforeEach(() => {
	setupi18n();
});

afterEach(cleanup);

describe("Values", () => {
	test("renders correct value when passed as string: single value", async () => {
		const { getByLabelText } = await render(CheckboxGroup, {
			value: ["choice_one"],
			label: "Dropdown",
			choices: [
				["Choice One", "choice_one"],
				["Choice Two", "choice_two"]
			]
		});

		const item_one = getByLabelText("Choice One") as HTMLInputElement;
		const item_two = getByLabelText("Choice Two") as HTMLInputElement;

		expect(item_one).toBeChecked();
		expect(item_two).not.toBeChecked();
	});

	test("renders correct value when passed as string: multiple values", async () => {
		const { getByLabelText } = await render(CheckboxGroup, {
			value: ["choice_one", "choice_three"],
			label: "Dropdown",
			choices: [
				["Choice One", "choice_one"],
				["Choice Two", "choice_two"],
				["Choice Three", "choice_three"]
			]
		});

		const item_one = getByLabelText("Choice One") as HTMLInputElement;
		const item_two = getByLabelText("Choice Two") as HTMLInputElement;
		const item_three = getByLabelText("Choice Three") as HTMLInputElement;
		assert.equal(item_one.checked, true);
		assert.equal(item_two.checked, false);
		assert.equal(item_three.checked, true);

		expect(item_one).toBeChecked();
		expect(item_two).not.toBeChecked();
		expect(item_three).toBeChecked();
	});

	test("renders correct value when passed as number: single value", async () => {
		const { getByLabelText } = await render(CheckboxGroup, {
			value: [1],
			label: "Dropdown",
			choices: [
				["Choice One", 1],
				["Choice Two", 2]
			]
		});

		const item_one = getByLabelText("Choice One") as HTMLInputElement;
		const item_two = getByLabelText("Choice Two") as HTMLInputElement;

		expect(item_one).toBeChecked();
		expect(item_two).not.toBeChecked();
	});

	test("renders correct value when passed as number: multiple values", async () => {
		const { getByLabelText } = await render(CheckboxGroup, {
			value: [1, 3],
			label: "Dropdown",
			choices: [
				["Choice One", 1],
				["Choice Two", 2],
				["Choice Three", 3]
			]
		});

		const item_one = getByLabelText("Choice One") as HTMLInputElement;
		const item_two = getByLabelText("Choice Two") as HTMLInputElement;
		const item_three = getByLabelText("Choice Three") as HTMLInputElement;

		expect(item_one).toBeChecked();
		expect(item_two).not.toBeChecked();
		expect(item_three).toBeChecked();
	});

	test("component value and rendered value are in sync", async () => {
		const { getByLabelText } = await render(CheckboxGroup, {
			value: [1, 3],
			label: "Dropdown",
			choices: [
				["Choice One", 1],
				["Choice Two", 2],
				["Choice Three", 3]
			]
		});

		const item_one = getByLabelText("Choice One") as HTMLInputElement;
		const item_two = getByLabelText("Choice Two") as HTMLInputElement;
		const item_three = getByLabelText("Choice Three") as HTMLInputElement;

		expect(item_one).toBeChecked();
		expect(item_two).not.toBeChecked();
		expect(item_three).toBeChecked();
	});

	test("changing the component value updates the checkboxes", async () => {
		const { getByLabelText, unmount } = await render(CheckboxGroup, {
			value: [],
			label: "Dropdown",
			choices: [
				["Choice One", 1],
				["Choice Two", 2],
				["Choice Three", 3]
			]
		});

		let item_one = getByLabelText("Choice One") as HTMLInputElement;
		let item_two = getByLabelText("Choice Two") as HTMLInputElement;
		let item_three = getByLabelText("Choice Three") as HTMLInputElement;

		expect(item_one).not.toBeChecked();
		expect(item_two).not.toBeChecked();
		expect(item_three).not.toBeChecked();

		unmount();
		const { getByLabelText: getByLabelText2 } = await render(CheckboxGroup, {
			value: [1, 3],
			label: "Dropdown",
			choices: [
				["Choice One", 1],
				["Choice Two", 2],
				["Choice Three", 3]
			]
		});

		item_one = getByLabelText2("Choice One") as HTMLInputElement;
		item_two = getByLabelText2("Choice Two") as HTMLInputElement;
		item_three = getByLabelText2("Choice Three") as HTMLInputElement;

		expect(item_one).toBeChecked();
		expect(item_two).not.toBeChecked();
		expect(item_three).toBeChecked();
	});

	test("setting a value that does not exist does nothing", async () => {
		const { getByLabelText, unmount } = await render(CheckboxGroup, {
			value: [],
			label: "Dropdown",
			choices: [
				["Choice One", 1],
				["Choice Two", 2],
				["Choice Three", 3]
			]
		});

		let item_one = getByLabelText("Choice One") as HTMLInputElement;
		let item_two = getByLabelText("Choice Two") as HTMLInputElement;
		let item_three = getByLabelText("Choice Three") as HTMLInputElement;

		expect(item_one).not.toBeChecked();
		expect(item_two).not.toBeChecked();
		expect(item_three).not.toBeChecked();

		unmount();
		const { getByLabelText: getByLabelText2 } = await render(CheckboxGroup, {
			value: ["choice_one"],
			label: "Dropdown",
			choices: [
				["Choice One", 1],
				["Choice Two", 2],
				["Choice Three", 3]
			]
		});

		item_one = getByLabelText2("Choice One") as HTMLInputElement;
		item_two = getByLabelText2("Choice Two") as HTMLInputElement;
		item_three = getByLabelText2("Choice Three") as HTMLInputElement;

		expect(item_one).not.toBeChecked();
		expect(item_two).not.toBeChecked();
		expect(item_three).not.toBeChecked();
	});
});

describe("Events", () => {
	test("changing the value via the UI emits a change event", async () => {
		const { getByLabelText, listen } = await render(CheckboxGroup, {
			loading_status,
			value: [],
			label: "Dropdown",
			interactive: true,
			choices: [
				["Choice One", 1],
				["Choice Two", 2],
				["Choice Three", 3]
			]
		});

		const item_one = getByLabelText("Choice One") as HTMLInputElement;
		const item_two = getByLabelText("Choice Two") as HTMLInputElement;
		const item_three = getByLabelText("Choice Three") as HTMLInputElement;

		expect(item_one).not.toBeChecked();
		expect(item_two).not.toBeChecked();
		expect(item_three).not.toBeChecked();

		const mock = listen("change");

		await event.click(item_one);
		expect(mock.callCount).toBe(1);

		await event.click(item_three);
		expect(mock.callCount).toBe(2);
	});

	test("changing the value from outside emits a change event", async () => {
		const { getByLabelText, listen } = await render(CheckboxGroup, {
			value: [],
			label: "Dropdown",
			interactive: true,
			choices: [
				["Choice One", 1],
				["Choice Two", 2],
				["Choice Three", 3]
			]
		});

		const item_one = getByLabelText("Choice One") as HTMLInputElement;

		expect(item_one).not.toBeChecked();

		const mock = listen("change");

		await event.click(item_one);
		expect(mock.callCount).toBe(1);
	});

	test("changing the value from the UI emits an input event", async () => {
		const { getByLabelText, listen } = await render(CheckboxGroup, {
			value: [],
			label: "Dropdown",
			interactive: true,
			choices: [
				["Choice One", 1],
				["Choice Two", 2],
				["Choice Three", 3]
			]
		});

		const item_one = getByLabelText("Choice One") as HTMLInputElement;

		const mock = listen("input");
		await event.click(item_one);

		expect(mock.callCount).toBe(1);
	});

	test("changing the value from outside DOES NOT emit an input event", async () => {
		const { listen, unmount } = await render(CheckboxGroup, {
			value: [],
			label: "Dropdown",
			choices: [
				["Choice One", 1],
				["Choice Two", 2],
				["Choice Three", 3]
			]
		});

		const mock = listen("input");
		unmount();
		await render(CheckboxGroup, {
			value: [1],
			label: "Dropdown",
			choices: [
				["Choice One", 1],
				["Choice Two", 2],
				["Choice Three", 3]
			]
		});

		expect(mock.callCount).toBe(0);
	});

	test("changing the value via the UI emits a select event", async () => {
		const { getByLabelText, listen } = await render(CheckboxGroup, {
			value: [],
			label: "Dropdown",
			interactive: true,
			choices: [
				["Choice One", 1],
				["Choice Two", 2],
				["Choice Three", 3]
			]
		});

		const item_one = getByLabelText("Choice One") as HTMLInputElement;

		const mock = listen("select");
		await event.click(item_one);

		expect(mock.callCount).toBe(1);
	});

	test("select event payload contains the selected value and index", async () => {
		const { getByLabelText, listen } = await render(CheckboxGroup, {
			value: [],
			label: "Dropdown",
			interactive: true,
			choices: [
				["Choice One", "val"],
				["Choice Two", "val_two"],
				["Choice Three", 3]
			]
		});

		const item = getByLabelText("Choice Two") as HTMLInputElement;

		const mock = listen("select");
		await event.click(item);

		expect(mock.calls[0][0].detail.data.value).toBe("val_two");
		expect(mock.calls[0][0].detail.data.index).toBe(1);
	});

	test("select event payload contains the correct selected state", async () => {
		const { getByLabelText, listen } = await render(CheckboxGroup, {
			value: [],
			label: "Dropdown",
			interactive: true,
			choices: [
				["Choice One", "val"],
				["Choice Two", "val_two"],
				["Choice Three", 3]
			]
		});

		const item = getByLabelText("Choice Two") as HTMLInputElement;

		const mock = listen("select");

		await event.click(item);
		expect(mock.calls[0][0].detail.data.selected).toBe(true);

		await event.click(item);
		expect(mock.calls[1][0].detail.data.selected).toBe(false);
	});
});

describe("interactive vs static", () => {
	test("interactive component can be checked", async () => {
		const { getByLabelText } = await render(CheckboxGroup, {
			value: [],
			label: "Dropdown",
			interactive: true,
			choices: [
				["Choice One", 1],
				["Choice Two", 2]
			]
		});

		const item_one = getByLabelText("Choice One") as HTMLInputElement;

		await event.click(item_one);

		expect(item_one).toBeChecked();
	});

	test("static component cannot be checked", async () => {
		const { getByLabelText } = await render(CheckboxGroup, {
			value: [],
			label: "Dropdown",
			interactive: false,
			choices: [
				["Choice One", 1],
				["Choice Two", 2]
			]
		});

		const item_one = getByLabelText("Choice One") as HTMLInputElement;

		await event.click(item_one);

		expect(item_one).not.toBeChecked();
	});

	test("interactive component updates the value", async () => {
		const { getByLabelText, listen } = await render(CheckboxGroup, {
			value: [],
			label: "Dropdown",
			interactive: true,
			choices: [
				["Choice One", 1],
				["Choice Two", 2]
			]
		});

		const item_one = getByLabelText("Choice One") as HTMLInputElement;

		const mock = listen("change");
		await event.click(item_one);

		expect(mock.callCount).toBe(1);
		expect(mock.calls[0][0].detail.data).toEqual([1]);
	});

	test("static component does not update the value", async () => {
		const { getByLabelText, listen } = await render(CheckboxGroup, {
			value: [],
			label: "Dropdown",
			interactive: false,
			choices: [
				["Choice One", 1],
				["Choice Two", 2]
			]
		});

		const item_one = getByLabelText("Choice One") as HTMLInputElement;

		const mock = listen("change");
		await event.click(item_one);

		expect(mock.callCount).toBe(0);
		expect(item_one).not.toBeChecked();
	});
});
