import { z } from 'astro/zod';
import type { SchemaContext } from 'astro:content';
import { HeadConfigSchema } from './schemas/head';
import { PrevNextLinkConfigSchema } from './schemas/prevNextLink';
import { TableOfContentsSchema } from './schemas/tableOfContents';
import { BadgeConfigSchema } from './schemas/badge';
import { HeroSchema } from './schemas/hero';
import { SidebarLinkItemHTMLAttributesSchema } from './schemas/sidebar';
export { i18nSchema } from './schemas/i18n';

/** Default content collection schema for Starlight’s `docs` collection. */
const StarlightFrontmatterSchema = (context: SchemaContext) =>
	z.object({
		/** The title of the current page. Required. */
		title: z.string(),

		/**
		 * A short description of the current page’s content. Optional, but recommended.
		 * A good description is 150–160 characters long and outlines the key content
		 * of the page in a clear and engaging way.
		 */
		description: z.string().optional(),

		/**
		 * Custom URL where a reader can edit this page.
		 * Overrides the `editLink.baseUrl` global config if set.
		 *
		 * Can also be set to `false` to disable showing an edit link on this page.
		 */
		editUrl: z.union([z.string().url(), z.boolean()]).optional().default(true),

		/** Set custom `<head>` tags just for this page. */
		head: HeadConfigSchema(),

		/** Override global table of contents configuration for this page. */
		tableOfContents: TableOfContentsSchema().optional(),

		/**
		 * Set the layout style for this page.
		 * Can be `'doc'` (the default) or `'splash'` for a wider layout without any sidebars.
		 */
		template: z.enum(['doc', 'splash']).default('doc'),

		/** Display a hero section on this page. */
		hero: HeroSchema(context).optional(),

		/**
		 * The last update date of the current page.
		 * Overrides the `lastUpdated` global config or the date generated from the Git history.
		 */
		lastUpdated: z.union([z.date(), z.boolean()]).optional(),

		/**
		 * The previous navigation link configuration.
		 * Overrides the `pagination` global config or the link text and/or URL.
		 */
		prev: PrevNextLinkConfigSchema(),
		/**
		 * The next navigation link configuration.
		 * Overrides the `pagination` global config or the link text and/or URL.
		 */
		next: PrevNextLinkConfigSchema(),

		sidebar: z
			.object({
				/**
				 * The order of this page in the navigation.
				 * Pages are sorted by this value in ascending order. Then by slug.
				 * If not provided, pages will be sorted alphabetically by slug.
				 * If two pages have the same order value, they will be sorted alphabetically by slug.
				 */
				order: z.number().optional(),

				/**
				 * The label for this page in the navigation.
				 * Defaults to the page `title` if not set.
				 */
				label: z.string().optional(),

				/**
				 * Prevents this page from being included in autogenerated sidebar groups.
				 */
				hidden: z.boolean().default(false),
				/**
				 * Adds a badge to the sidebar link.
				 * Can be a string or an object with a variant and text.
				 * Variants include 'note', 'tip', 'caution', 'danger', 'success', and 'default'.
				 * Passing only a string defaults to the 'default' variant which uses the site accent color.
				 */
				badge: BadgeConfigSchema(),
				/** HTML attributes to add to the sidebar link. */
				attrs: SidebarLinkItemHTMLAttributesSchema(),
			})
			.default({}),

		/** Display an announcement banner at the top of this page. */
		banner: z
			.object({
				/** The content of the banner. Supports HTML syntax. */
				content: z.string(),
			})
			.optional(),

		/** Pagefind indexing for this page - set to false to disable. */
		pagefind: z.boolean().default(true),

		/**
		 * Indicates that this page is a draft and will not be included in production builds.
		 * Note that the page will still be available when running Astro in development mode.
		 */
		draft: z.boolean().default(false),
	});
/** Type of Starlight’s default frontmatter schema. */
type DefaultSchema = ReturnType<typeof StarlightFrontmatterSchema>;

/** Plain object, union, and intersection Zod types. */
type BaseSchemaWithoutEffects =
	| z.AnyZodObject
	| z.ZodUnion<[BaseSchemaWithoutEffects, ...BaseSchemaWithoutEffects[]]>
	| z.ZodDiscriminatedUnion<string, z.AnyZodObject[]>
	| z.ZodIntersection<BaseSchemaWithoutEffects, BaseSchemaWithoutEffects>;
/** Base subset of Zod types that we support passing to the `extend` option. */
type BaseSchema = BaseSchemaWithoutEffects | z.ZodEffects<BaseSchemaWithoutEffects>;

/** Type that extends Starlight’s default schema with an optional, user-defined schema. */
type ExtendedSchema<T extends BaseSchema | never = never> = [T] extends [never]
	? DefaultSchema
	: T extends BaseSchema
		? z.ZodIntersection<DefaultSchema, T>
		: DefaultSchema;

interface DocsSchemaOpts<T extends BaseSchema> {
	/**
	 * Extend Starlight’s schema with additional fields.
	 *
	 * @example
	 * // Extend the built-in schema with a Zod schema.
	 * docsSchema({
	 * 	extend: z.object({
	 * 		// Add a new field to the schema.
	 * 		category: z.enum(['tutorial', 'guide', 'reference']).optional(),
	 * 	}),
	 * })
	 *
	 * // Use the Astro image helper.
	 * docsSchema({
	 * 	extend: ({ image }) => {
	 * 		return z.object({
	 * 			cover: image(),
	 * 		});
	 * 	},
	 * })
	 */
	extend?: T | ((context: SchemaContext) => T);
}

/** Content collection schema for Starlight’s `docs` collection. */
export function docsSchema<T extends BaseSchema | never = never>(
	...args: [DocsSchemaOpts<T>?]
): (context: SchemaContext) => ExtendedSchema<T> {
	const [options = {}] = args;
	const { extend } = options;

	return (context: SchemaContext) => {
		const UserSchema = typeof extend === 'function' ? extend(context) : extend;

		return (
			UserSchema
				? StarlightFrontmatterSchema(context).and(UserSchema)
				: StarlightFrontmatterSchema(context)
		) as ExtendedSchema<T>;
	};
}
