import type { AstroBuiltinAttributes } from 'astro';
import type { HTMLAttributes } from 'astro/types';
import { z } from 'astro/zod';
import { I18nBadgeConfigSchema } from './badge';
import { stripLeadingAndTrailingSlashes } from '../utils/path';

const SidebarBaseSchema = z.object({
	/** The visible label for this item in the sidebar. */
	label: z.string(),
	/** Translations of the `label` for each supported language. */
	translations: z.record(z.string(), z.string()).default({}),
	/** Adds a badge to the item */
	badge: I18nBadgeConfigSchema(),
});

const SidebarGroupSchema = z.object({
	...SidebarBaseSchema.shape,
	/**
	 * Explicitly prevent custom attributes on groups as the final type for supported sidebar item
	 * is a non-discriminated union where TypeScript will not perform excess property checks.
	 * This means that a user could define a sidebar group with custom attributes, not getting a
	 * TypeScript error, and only have it fail at runtime.
	 * @see https://github.com/microsoft/TypeScript/issues/20863
	 */
	attrs: z.never().optional(),
	/** Whether this item should be collapsed by default. */
	collapsed: z.boolean().default(false),
});

// HTML attributes that can be added to an anchor element, validated as
// `Record<string, string | number | boolean | undefined>` but typed as `HTMLAttributes<'a'>`
// for user convenience.
const linkHTMLAttributesSchema = z.record(
	z.string(),
	z.union([z.string(), z.number(), z.boolean(), z.undefined(), z.null()])
) as z.ZodType<LinkHTMLAttributes, LinkHTMLAttributes>;
export type LinkHTMLAttributes = Omit<
	HTMLAttributes<'a'>,
	keyof AstroBuiltinAttributes | 'children'
>;

export const SidebarLinkItemHTMLAttributesSchema = () => linkHTMLAttributesSchema.default({});

const SidebarLinkItemSchema = z.strictObject({
	...SidebarBaseSchema.shape,
	/** The link to this item’s content. Can be a relative link to local files or the full URL of an external page. */
	link: z.string(),
	/** HTML attributes to add to the link item. */
	attrs: SidebarLinkItemHTMLAttributesSchema(),
});
export type SidebarLinkItem = z.infer<typeof SidebarLinkItemSchema>;

const AutoSidebarEntriesSchema = z
	.object({
		/**
		 * Explicitly prevent autogenerated groups which are no longer supported as the final type for
		 * supported sidebar item is a non-discriminated union where TypeScript will not perform excess
		 * property checks. This means that a user could define a sidebar group with an autogenerated
		 * property, not getting a TypeScript error, and only have it fail at runtime.
		 * @see https://github.com/microsoft/TypeScript/issues/20863
		 */
		label: z.custom<never>().optional(),
		/** Enable autogenerating entries from a specific docs directory. */
		autogenerate: z.object({
			/** The directory to generate sidebar items for. */
			directory: z.string().transform(stripLeadingAndTrailingSlashes),
			/** Whether the autogenerated subgroups should be collapsed by default. Default: `false`. */
			collapsed: z.boolean().optional(),
			/** HTML attributes to add to the autogenerated link items. */
			attrs: SidebarLinkItemHTMLAttributesSchema(),
			// TODO: not supported by Docusaurus but would be good to have
			/** How many directories deep to include from this directory in the sidebar. Default: `Infinity`. */
			// depth: z.number().optional(),
		}),
	})
	.strict()
	.superRefine((config, ctx) => {
		if (!('label' in config)) return;

		// TODO: Remove this error message in a future release once most users have migrated
		ctx.addIssue({
			code: 'custom',
			message:
				`Found an \`autogenerate\` object with a \`label\`. Support for autogenerated sidebar groups was removed in Starlight v0.39.0.\n` +
				`You should instead create a group with the desired \`label\` and an \`items\` array containing the autogenerate config:\n\n` +
				`{\n` +
				`  label: '${config.label}',\n` +
				`  items: [{ autogenerate: ${JSON.stringify(
					config.autogenerate,
					// Hide empty attrs object that is automatically added by the schema default value.
					(key, value: unknown) =>
						key === 'attrs' &&
						typeof value === 'object' &&
						value !== null &&
						Object.keys(value).length === 0
							? undefined
							: value,
					' '
				).replace(/\n\s*/g, ' ')} }]\n` +
				`}`,
		});
	});
export type AutoSidebarEntries = z.infer<typeof AutoSidebarEntriesSchema>;

type ManualSidebarGroupInput = z.input<typeof SidebarGroupSchema> & {
	/** Array of links and subcategories to display in this category. */
	items: Array<
		| z.input<typeof SidebarLinkItemSchema>
		| z.input<typeof AutoSidebarEntriesSchema>
		| z.input<typeof InternalSidebarLinkItemSchema>
		| z.input<typeof InternalSidebarLinkItemShorthandSchema>
		| ManualSidebarGroupInput
	>;
};

type ManualSidebarGroupOutput = z.output<typeof SidebarGroupSchema> & {
	/** Array of links and subcategories to display in this category. */
	items: Array<
		| z.output<typeof SidebarLinkItemSchema>
		| z.output<typeof AutoSidebarEntriesSchema>
		| z.output<typeof InternalSidebarLinkItemSchema>
		| z.output<typeof InternalSidebarLinkItemShorthandSchema>
		| ManualSidebarGroupOutput
	>;
};

const ManualSidebarGroupSchema: z.ZodType<ManualSidebarGroupOutput, ManualSidebarGroupInput> =
	z.strictObject({
		...SidebarGroupSchema.shape,
		/** Array of links and subcategories to display in this category. */
		items: z.lazy(() =>
			z
				.union([
					SidebarLinkItemSchema,
					ManualSidebarGroupSchema,
					AutoSidebarEntriesSchema,
					InternalSidebarLinkItemSchema,
					InternalSidebarLinkItemShorthandSchema,
				])
				.array()
		),
	});

const InternalSidebarLinkItemSchema = z.object({
	...SidebarBaseSchema.partial({ label: true }).shape,
	/** The link to this item’s content. Must be a slug of a Content Collection entry. */
	slug: z.string(),
	/** HTML attributes to add to the link item. */
	attrs: SidebarLinkItemHTMLAttributesSchema(),
});
const InternalSidebarLinkItemShorthandSchema = z
	.string()
	.transform((slug) => InternalSidebarLinkItemSchema.parse({ slug }));
export type InternalSidebarLinkItem = z.output<typeof InternalSidebarLinkItemSchema>;

export const SidebarItemSchema = z.union([
	SidebarLinkItemSchema,
	ManualSidebarGroupSchema,
	AutoSidebarEntriesSchema,
	InternalSidebarLinkItemSchema,
	InternalSidebarLinkItemShorthandSchema,
]);
export type SidebarItem = z.infer<typeof SidebarItemSchema>;
