import { query, prerender, command, form, requested } from '$app/server';
import { StandardSchemaV1 } from '@standard-schema/spec';
import {
	RemoteForm,
	RemoteFormEnhanceCallback,
	RemoteFormEnhanceInstance,
	RemoteFormFields,
	RemoteFormInput,
	RemoteLiveQueryFunction,
	RemotePrerenderFunction,
	RemoteQueryFunction,
	invalid
} from '@sveltejs/kit';

const schema: StandardSchemaV1<string> = null as any;
const schema2: StandardSchemaV1<string, number> = null as any;
const schema3: StandardSchemaV1<string | undefined, number> = null as any;

function query_tests() {
	const no_args: RemoteQueryFunction<void, string> = query(() => 'Hello world');
	void no_args();
	// @ts-expect-error
	void no_args('');

	const one_arg: RemoteQueryFunction<number, string> = query('unchecked', (a: number) =>
		a.toString()
	);
	void one_arg(1);
	// @ts-expect-error
	void one_arg('1');
	// @ts-expect-error
	void one_arg();

	async function query_without_args() {
		const q = query(() => 'Hello world');
		const result: string = await q();
		result;
		// @ts-expect-error
		const wrong: number = await q();
		wrong;
		// @ts-expect-error
		void q(1);
		// @ts-expect-error
		query((_: string) => 'hi');
	}
	void query_without_args();

	async function query_with_optional_arg() {
		const q = query(schema3, () => 'Hello world');
		void q();
		void q('hi');
		// @ts-expect-error
		void q(1);

		const q2 = query('unchecked', (a?: string) => a);
		void q2();
		void q2('hi');
		// @ts-expect-error
		void q2(1);
	}
	void query_with_optional_arg();

	async function query_unsafe() {
		const q = query('unchecked', (a: number) => a);
		const result: number = await q(1);
		result;
		// @ts-expect-error
		void q(1, 2, 3);
		// @ts-expect-error
		void q('1', '2');
	}
	void query_unsafe();

	async function query_schema_input_only() {
		const q = query(schema, (a) => a);
		const result: string = await q('1');
		result;
	}
	void query_schema_input_only();

	async function query_schema_input_and_output() {
		const q = query(schema2, (a) => a);
		const result: number = await q('1');
		result;
	}
	void query_schema_input_and_output();
}
query_tests();

function live_query_tests() {
	const no_args: RemoteLiveQueryFunction<void, string> = query.live(async function* () {
		yield 'hello';
	});
	void no_args();
	// @ts-expect-error
	void no_args('');

	const one_arg: RemoteLiveQueryFunction<number, string> = query.live(
		'unchecked',
		async function* (a: number) {
			yield a.toString();
		}
	);
	void one_arg(1);
	// @ts-expect-error
	void one_arg('1');
	// @ts-expect-error
	void one_arg();

	async function live_without_args() {
		const q = query.live(async function* () {
			yield 'Hello world';
		});

		const result: string = await q();
		result;

		for await (const value of q()) {
			const _: string = value;
			_;
			break;
		}

		q().connected === true;
		q().done === false;
		q().reconnect();
		// @ts-expect-error
		q().refresh();
		// @ts-expect-error
		q().set('x');
		// @ts-expect-error
		q().fail(new Error('x'));
	}
	void live_without_args();

	async function live_with_schema() {
		const q: RemoteLiveQueryFunction<string, string> = query.live(schema, async function* (a) {
			yield a;
		});

		const result: string = await q('x');
		result;
		// @ts-expect-error
		void q(123);
	}
	void live_with_schema();
}
live_query_tests();

function prerender_tests() {
	const no_args: RemotePrerenderFunction<void, string> = prerender(() => 'Hello world');
	void no_args();
	// @ts-expect-error
	void no_args('');
	const one_arg: RemotePrerenderFunction<number, string> = prerender('unchecked', (a: number) =>
		a.toString()
	);
	void one_arg(1);
	// @ts-expect-error
	void one_arg('1');
	// @ts-expect-error
	void one_arg();

	async function prerender_without_args() {
		const q = prerender(() => 'Hello world');
		const result: string = await q();
		result;
		// @ts-expect-error
		const wrong: number = await q();
		wrong;
		// @ts-expect-error
		void q(1);
		// @ts-expect-error
		query((_: string) => 'hi');
	}
	void prerender_without_args();

	async function prerender_unsafe() {
		const q = prerender('unchecked', (a: number) => a);
		const result: number = await q(1);
		result;
		// @ts-expect-error
		void q(1, 2, 3);
		// @ts-expect-error
		void q('1', '2');
	}
	void prerender_unsafe();

	async function prerender_with_optional_arg() {
		const q = prerender(schema3, () => 'Hello world');
		void q();
		void q('hi');
		// @ts-expect-error
		void q(1);

		const q2 = prerender('unchecked', (a?: string) => a);
		void q2();
		void q2('hi');
		// @ts-expect-error
		void q2(1);
	}
	void prerender_with_optional_arg();

	async function prerender_schema() {
		const q = prerender(schema, (a) => a);
		const result: string = await q('1');
		result;
	}
	void prerender_schema();

	function prerender_schema_entries() {
		const q = prerender(schema, (a) => a, { inputs: () => ['1'] });
		q;
		// @ts-expect-error
		const q2 = prerender(schema, (a) => a, { inputs: () => [1] });
		q2;
	}
	prerender_schema_entries();
}
prerender_tests();

function command_tests() {
	async function command_without_args() {
		const q = query(() => '');
		const lq = query.live(async function* () {
			yield '';
		});
		const cmd = command(() => 'Hello world');
		const result: string = await cmd();
		result;
		const result2: string = await cmd().updates(
			q,
			lq,
			q(),
			lq(),
			q().withOverride(() => '')
		);
		result2;
		// @ts-expect-error
		const wrong: number = await cmd();
		wrong;

		for (const { arg, query: bound } of requested(q, 5)) {
			const arg_output: void = arg;
			arg_output;
			void bound.refresh();
		}

		for await (const { arg, query: bound } of requested(q, 5)) {
			const arg_output: void = arg;
			arg_output;
			void bound.refresh();
		}

		for (const { arg, query: bound } of requested(q, 1)) {
			const arg_output: void = arg;
			arg_output;
			void bound.refresh();
		}

		// @ts-expect-error — `limit` is required
		requested(q);

		const refreshes = requested(q, 5);
		const refreshed: Promise<void> = refreshes.refreshAll();
		refreshed;

		const reconnects = requested(lq, 5);
		const reconnected: Promise<void> = reconnects.reconnectAll();
		reconnected;
	}
	void command_without_args();

	async function requested_validated_type_tracks_schema_output() {
		// schema2 declares InferInput = string, InferOutput = number, so `arg`
		// in `requested(...)` must be `number` (the post-validation / post-transform
		// value), NOT the pre-validation `string`.
		const q = query(schema2, (a) => a);
		for (const { arg, query: bound } of requested(q, 5)) {
			const arg_output: number = arg;
			arg_output;
			void bound.refresh();
			// @ts-expect-error — `arg` is number, not string
			const wrong: string = arg;
			wrong;
		}

		// `'unchecked'` queries: `arg` is the caller's Input type
		const unchecked = query('unchecked', (a: number) => a);
		for (const { arg } of requested(unchecked, 5)) {
			const arg_output: number = arg;
			arg_output;
		}

		// Queries without arguments: `arg` is `void`
		const no_arg = query(() => 'hi');
		for (const { arg } of requested(no_arg, 5)) {
			const arg_output: void = arg;
			arg_output;
		}
	}
	void requested_validated_type_tracks_schema_output();

	async function command_with_optional_arg() {
		const q = command(schema3, () => 'Hello world');
		void q();
		void q('hi');
		// @ts-expect-error
		void q(1);

		const q2 = command('unchecked', (a?: string) => a);
		void q2();
		void q2('hi');
		// @ts-expect-error
		void q2(1);
	}
	void command_with_optional_arg();

	async function command_unsafe() {
		const cmd = command('unchecked', (a: string) => a);
		const result: string = await cmd('test');
		result;
		// @ts-expect-error
		void cmd(1);
		// @ts-expect-error
		void cmd('1', 2);
	}
	void command_unsafe();

	async function command_schema() {
		const cmd = command(schema, (a) => a);
		const result: string = await cmd('foo');
		result;
		// @ts-expect-error
		void cmd(123);
	}
	void command_schema();
}
command_tests();

function form_tests() {
	const q = query(() => '');
	const f = form('unchecked', (data: { input: string }, issue) => {
		data.input;
		if (Math.random() > 0.5) {
			invalid(
				'foo',
				issue.input('bar'),
				// @ts-expect-error
				issue.nonexistent.prop('baz')
			);
		}
		return { success: true };
	});

	f.result?.success === true;

	const submit: RemoteFormEnhanceCallback<{ input: string }, { success: boolean }> = async (
		form
	) => {
		const result: { success: boolean } | undefined = form.result;
		result;
		// @ts-expect-error
		form.enhance(() => {});
		const x: boolean = await form.submit();
		x;
	};

	f.enhance(submit);

	f.enhance(async (form) => {
		const result: RemoteFormEnhanceInstance<{ input: string }, { success: boolean }> = form;
		result;
		const x: boolean = await form.submit();
		x;
		const y: boolean = await form.submit().updates(
			q,
			q(),
			q().withOverride(() => '')
		);
		y;
		const element: HTMLFormElement = form.element;
		element;
		// @ts-expect-error
		form.enhance(() => {});
	});

	const element: HTMLFormElement | null = f.element;
	element;

	const f2 = form(
		null as any as StandardSchemaV1<{ a: string; nested: { prop: string } }>,
		(data, issue) => {
			data.a === '';
			data.nested.prop === '';
			// @ts-expect-error
			data.nested.nonexistent;
			// @ts-expect-error
			data.nonexistent;
			// @ts-expect-error
			data.a === 123;
			if (Math.random() > 0.5) {
				invalid(
					'foo',
					issue.nested.prop('bar'),
					// @ts-expect-error
					issue.nonexistent.prop('baz')
				);
			}
			return { success: true };
		}
	);
	// @ts-expect-error
	f2.fields.as('text');
	f2.fields.a.issues();
	f2.fields.nested.prop.issues();
	// @ts-expect-error
	f2.fields.nonexistent.issues();
	f2.fields.a.value();
	f2.fields.nested.prop.value();
	// @ts-expect-error
	f2.fields.nonexistent.value();
	// @ts-expect-error
	f2.fields.array[0].array.as('text');

	// all schema properties optional
	const f3 = form(
		null as any as StandardSchemaV1<{ a?: string; nested?: { prop?: string } }>,
		(data, issue) => {
			data.a === '';
			data.nested?.prop === '';
			// @ts-expect-error
			data.nested.prop === '';
			// @ts-expect-error
			data.nested.nonexistent;
			// @ts-expect-error
			data.nonexistent;
			// @ts-expect-error
			data.a === 123;
			if (Math.random() > 0.5) {
				invalid(
					'foo',
					issue.nested.prop('bar'),
					// @ts-expect-error
					issue.nonexistent.prop('baz')
				);
			}
			return { success: true };
		}
	);
	// @ts-expect-error
	f3.fields.as('text');
	f3.fields.a.issues();
	f3.fields.a.value();
	f3.fields.nested.prop.issues();
	f3.fields.nested.prop.value();
	// @ts-expect-error
	f3.fields.nonexistent.as('text');

	// index signature schema
	const f4 = form(null as any as StandardSchemaV1<Record<string, any>>, (data) => {
		data.a === '';
		data.nested?.prop === '';
		return { success: true };
	});
	// @ts-expect-error
	f4.fields.as('text');
	f4.fields.a.issues();
	f4.fields.a.value();
	f4.fields.nested.prop.issues();
	f4.fields.nested.prop.value();

	// schema with union types
	const f5 = form(
		null as any as StandardSchemaV1<{ foo: 'a' | 'b'; bar: 'c' | 'd' }>,
		(data, issue) => {
			data.foo === 'a';
			data.bar === 'c';
			// @ts-expect-error
			data.foo === 'e';
			if (Math.random() > 0.5) {
				invalid(
					'foo',
					issue.bar('bar'),
					// @ts-expect-error
					issue.nonexistent.prop('baz')
				);
			}
			return { success: true };
		}
	);
	// @ts-expect-error
	f5.fields.as('text');
	f5.fields.foo.issues();
	f5.fields.bar.issues();
	f5.fields.foo.value();
	f5.fields.bar.value() === 'c';
	// @ts-expect-error
	f5.fields.foo.value() === 'e';
	// @ts-expect-error
	f5.fields.nonexistent.as('text');

	// schema with arrays
	const f6 = form(
		null as any as StandardSchemaV1<{ array: Array<{ array: string[]; prop: string }> }>,
		(data, issue) => {
			data.array[0].prop === 'a';
			data.array[0].array[0] === 'a';
			// @ts-expect-error
			data.array[0].array[0] === 1;
			if (Math.random() > 0.5) {
				invalid(
					'foo',
					issue.array[0].prop('bar'),
					// @ts-expect-error
					issue.nonexistent.prop('baz')
				);
			}
			return { success: true };
		}
	);
	// @ts-expect-error
	f6.fields.as('text');
	// @ts-expect-error
	f6.field('array[0].array');
	f6.fields.array.issues();
	f6.fields.array[0].prop.issues();
	f6.fields.array[0].array.issues();
	// @ts-expect-error
	f6.fields.nonexistent.issues();
	f6.fields.array[0].prop.value();
	f6.fields.array[0].array.value();
	// @ts-expect-error
	f6.fields.array[0].array.as('text');

	// any
	const f7 = form(null as any, (data, issue) => {
		data.a === '';
		data.nested?.prop === '';
		if (Math.random() > 0.5) {
			invalid('foo', issue.nested.prop('bar'));
		}
		return { success: true };
	});
	// @ts-expect-error
	f7.fields.as('text');
	f7.fields.a.issues();
	f7.fields.a.value();
	f7.fields.nested.prop.issues();
	f7.fields.nested.prop.value();

	// no schema
	const f8 = form(() => {
		invalid('foo');
	});
	f8.fields.issues();
	f8.fields.allIssues();
	// @ts-expect-error
	f8.fields.x;
	// @ts-expect-error
	f6.input!['array[0].prop'] = 123;

	// schema with optional array fields (e.g. Zod's `.default([])` produces an input
	// type where the property is optional, so its value type is `string[] | undefined`).
	// Regression test for #15722.
	const f_optional_arrays = form(
		null as any as StandardSchemaV1<{
			strings?: string[];
			files?: File[];
			objects?: Array<{ prop: string }>;
			nested?: { strings?: string[] };
		}>,
		(data) => {
			data.strings?.[0] === 'a';
			data.files?.[0] instanceof File;
			data.objects?.[0].prop === 'a';
			return { success: true };
		}
	);
	// `.as()` should be available on optional string[] / File[] fields
	f_optional_arrays.fields.strings.as('checkbox', 'value');
	f_optional_arrays.fields.strings.as('select multiple');
	f_optional_arrays.fields.files.as('file multiple');
	// indexed access gives back a typed field
	f_optional_arrays.fields.strings[0].as('text');
	f_optional_arrays.fields.files[0].as('file');
	// optional array of objects still exposes container methods and per-item fields
	f_optional_arrays.fields.objects.allIssues();
	f_optional_arrays.fields.objects[0].prop.as('text');
	// nested optional array inside optional parent also works
	f_optional_arrays.fields.nested.strings.as('checkbox', 'value');
	f_optional_arrays.fields.nested.strings[0].as('text');
	// @ts-expect-error
	f_optional_arrays.fields.strings.as('number');
	// @ts-expect-error
	f_optional_arrays.fields.files.as('text');

	// doesn't use data
	const f9 = form(() => Promise.resolve({ success: true }));
	f9.result?.success === true;

	// generic form
	function f10<
		Schema extends StandardSchemaV1<RemoteFormInput, unknown>,
		Form extends RemoteForm<StandardSchemaV1.InferInput<Schema>, unknown>
	>(data: StandardSchemaV1.InferInput<Schema>, form: Form) {
		form.fields.set(data);
		form.fields.allIssues();
	}
	void f10;

	const f11 = form(
		null as any as StandardSchemaV1<{
			differing: { type: 'a'; propA: string } | { type: 'b'; propB?: string };
		}>,
		(data) => {
			data.differing.type === 'a' || data.differing.type === 'b';
			if (data.differing.type === 'a') {
				data.differing.propA === '';
				// @ts-expect-error
				data.differing.propB;
			} else {
				data.differing.propB === '';
				// @ts-expect-error
				data.differing.propA;
			}
			return { success: true };
		}
	);
	f11.fields.differing.issues();
	f11.fields.differing.value().type === 'a' || f11.fields.differing.value().type === 'b';
	const f11_field = f11.fields.differing.value();
	if (f11_field.type === 'a') {
		f11_field.propA === '';
		// @ts-expect-error
		f11_field.propB;
	}
	f11.fields.differing.propA.value();
	f11.fields.differing.propB.issues();
	// @ts-expect-error
	f11.fields.differing.propC.value();
	f11.fields.differing.set({ type: 'a', propA: 'test' });
	f11.fields.differing.set({ type: 'b', propB: 'test' });
	f11.fields.differing.set({ type: 'b' });
	// @ts-expect-error
	f11.fields.differing.set({ type: 'b', propA: 'test' });
	// @ts-expect-error
	f11.fields.differing.set({ type: 'a', propB: 'test' });
	// test that you can narrow yourself if you want to
	const f11_field2 = f11.fields.differing as RemoteFormFields<{ type: 'a'; propA: string }>;
	f11_field2.propA;
	// @ts-expect-error
	f11_field2.propB;

	// non-optional booleans
	form(
		// @ts-expect-error
		null as unknown as StandardSchemaV1<{
			a: boolean;
		}>,
		() => {}
	);
	form(
		// @ts-expect-error
		null as unknown as StandardSchemaV1<{
			a: boolean[];
		}>,
		() => {}
	);
	form(
		// @ts-expect-error
		null as unknown as StandardSchemaV1<{
			nested?: { a: boolean };
			b?: boolean;
		}>,
		() => {}
	);
	form(
		null as unknown as StandardSchemaV1<{
			a?: boolean;
		}>,
		() => {}
	);
}
form_tests();

function boolean_tests() {
	const q = query(() => 'Hello world');
	const result = q();

	if (!result.ready) {
		result.current === undefined;
		// @ts-expect-error
		result.current.length;
		// @ts-expect-error
		result.current?.length;
	} else {
		result.current === 'a';
		result.current.length;
		// @ts-expect-error
		result.current === true;
	}

	if (result.loading) {
		result.current === undefined;
		result.current?.length;
		// @ts-expect-error
		result.current.length;
		// @ts-expect-error
		result.current === true;
	}

	if (result.error) {
		result.current === 'a';
		result.current?.length;
		// @ts-expect-error
		result.current.length;
		// @ts-expect-error
		result.current === true;
	}
}
boolean_tests();
