import type { Column, SQL, SQLWrapper } from "drizzle-orm";
import { t } from "elysia";
import { KErrorT } from "~/models/error";
import { comment } from "~/utils";
import { expression } from "./parser";
import { toDrizzle } from "./to-sql";

export type FilterDef = {
	[key: string]:
		| {
				column: Column | SQLWrapper | ((param: string) => Column | SQLWrapper);
				type: "int" | "float" | "date" | "string" | "bool";
				isArray?: boolean;
		  }
		| {
				column: Column | SQL;
				type: "enum";
				values: string[];
				isArray?: boolean;
		  };
};

export const Filter = ({
	def,
	description = "Filters to apply to the query.",
}: {
	def: FilterDef;
	description?: string;
}) =>
	t
		.Transform(
			t.String({
				description: comment`
					${description}

					This is based on [odata's filter specification](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptionfilter).
					Filters available: ${Object.keys(def)
						.map((x) =>
							typeof def[x].column === "function" ? `${x}:param` : x,
						)
						.join(", ")}.
				`,
				example: "(runtime gt 75 and genres has action) or rating:tmdb ge 50",
			}),
		)
		.Decode((filter) => {
			return parseFilters(filter, def);
		})
		.Encode(() => {
			throw new Error("Can't encode filters");
		});

export const parseFilters = (filter: string | undefined, config: FilterDef) => {
	if (!filter) return undefined;
	const ret = expression.parse(filter);
	if (!ret.isOk) {
		throw new KErrorT(`Invalid filter: ${filter}.`, ret);
	}

	return toDrizzle(ret.value, config);
};
