// Vitest coverage for the toast primitive in
// `src/lib/toast.svelte.ts`. The store keeps one active toast at a
// time, replaces it on a fresh `show`, auto-dismisses after the
// configured duration, runs the `onAction` callback once on the
// action button, and ignores a stale `dismiss(id)` whose target was
// already replaced.

import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { toast } from "../src/lib/toast.svelte";

beforeEach(() => {
	toast.resetForTests();
	vi.useFakeTimers();
});

afterEach(() => {
	vi.useRealTimers();
	toast.resetForTests();
});

describe("toast.show", () => {
	test("sets current and assigns a fresh id", () => {
		const id = toast.show({
			messageKey: "common.loading",
		});
		expect(id).toBeTruthy();
		expect(toast.current).not.toBeNull();
		expect(toast.current?.id).toBe(id);
		expect(toast.current?.messageKey).toBe("common.loading");
	});

	test("a second show replaces the previous descriptor", () => {
		const first = toast.show({ messageKey: "common.loading" });
		const second = toast.show({
			messageKey: "common.dismiss",
		});
		expect(second).not.toBe(first);
		expect(toast.current?.id).toBe(second);
		expect(toast.current?.messageKey).toBe("common.dismiss");
	});

	test("auto-dismisses after durationMs", () => {
		toast.show({
			messageKey: "common.loading",
			durationMs: 2_000,
		});
		expect(toast.current).not.toBeNull();
		vi.advanceTimersByTime(1_999);
		expect(toast.current).not.toBeNull();
		vi.advanceTimersByTime(1);
		expect(toast.current).toBeNull();
	});

	test("durationMs=null makes the toast sticky", () => {
		toast.show({
			messageKey: "common.loading",
			durationMs: null,
		});
		vi.advanceTimersByTime(60_000);
		expect(toast.current).not.toBeNull();
	});
});

describe("toast.dismiss", () => {
	test("clears current when called without an id", () => {
		toast.show({ messageKey: "common.loading" });
		toast.dismiss();
		expect(toast.current).toBeNull();
	});

	test("ignores a stale id whose target was replaced", () => {
		const first = toast.show({
			messageKey: "common.loading",
			durationMs: 5_000,
		});
		const second = toast.show({ messageKey: "common.dismiss" });
		toast.dismiss(first);
		expect(toast.current?.id).toBe(second);
		expect(toast.current?.messageKey).toBe("common.dismiss");
	});

	test("auto-dismiss timer of the replaced toast does not clobber the live one", () => {
		toast.show({ messageKey: "common.loading", durationMs: 500 });
		const second = toast.show({
			messageKey: "common.dismiss",
			durationMs: null,
		});
		vi.advanceTimersByTime(500);
		// The first toast's timer fired but the dismiss is a no-op
		// because `current.id !== first`. The sticky second toast
		// stays alive.
		expect(toast.current?.id).toBe(second);
	});
});

describe("onAction", () => {
	test("ignored unless the action button is invoked manually", () => {
		const onAction = vi.fn();
		toast.show({
			messageKey: "common.loading",
			actionLabelKey: "common.dismiss",
			onAction,
		});
		vi.advanceTimersByTime(1_000);
		expect(onAction).not.toHaveBeenCalled();
	});

	test("toast-host wiring is exercised by the layout: callback fires when host calls onAction then dismiss", () => {
		// The host component runs `onAction()` and then `dismiss(id)`.
		// We simulate that sequence here to pin the contract the host
		// relies on: a single invocation of the user callback per
		// descriptor, and the toast clears afterwards.
		const onAction = vi.fn();
		const id = toast.show({
			messageKey: "common.loading",
			actionLabelKey: "common.dismiss",
			onAction,
		});
		const current = toast.current;
		current?.onAction?.();
		toast.dismiss(current?.id);
		expect(onAction).toHaveBeenCalledTimes(1);
		expect(toast.current).toBeNull();
		// id stays unique — a follow-up show must return a different one.
		expect(toast.show({ messageKey: "common.loading" })).not.toBe(id);
	});
});
