import endpoints from "../endpoints.js";
import config from "@/config/index.js";
import { errors as basicErrors } from "./errors.js";
import { schemas, convertSchemaToOpenApiSchema } from "./schemas.js";

export function genOpenapiSpec() {
	const spec = {
		openapi: "3.0.0",

		info: {
			version: "v1",
			title: "Iceshrimp API",
			"x-logo": { url: "/static-assets/api-doc.png" },
		},

		externalDocs: {
			description: "Repository",
			url: "https://iceshrimp.dev/iceshrimp/iceshrimp",
		},

		servers: [
			{
				url: config.apiUrl,
			},
		],

		paths: {} as any,

		components: {
			schemas: schemas,

			securitySchemes: {
				ApiKeyAuth: {
					type: "apiKey",
					in: "body",
					name: "i",
				},
				// TODO: change this to oauth2 when the remaining oauth stuff is set up
				Bearer: {
					type: "http",
					scheme: "bearer",
				},
			},
		},
	};

	for (const endpoint of endpoints.filter((ep) => !ep.meta.secure)) {
		const errors = {} as any;

		if (endpoint.meta.errors) {
			for (const e of Object.values(endpoint.meta.errors)) {
				errors[e.code] = {
					value: {
						error: e,
					},
				};
			}
		}

		const resSchema = endpoint.meta.res
			? convertSchemaToOpenApiSchema(endpoint.meta.res)
			: {};

		let desc =
			(endpoint.meta.description
				? endpoint.meta.description
				: "No description provided.") + "\n\n";
		desc += `**Credential required**: *${
			endpoint.meta.requireCredential ? "Yes" : "No"
		}*`;
		if (endpoint.meta.kind) {
			const kind = endpoint.meta.kind;
			desc += ` / **Permission**: *${kind}*`;
		}

		const requestType = endpoint.meta.requireFile
			? "multipart/form-data"
			: "application/json";
		const schema = endpoint.params;

		if (endpoint.meta.requireFile) {
			schema.properties.file = {
				type: "string",
				format: "binary",
				description: "The file contents.",
			};
			schema.required.push("file");
		}

		const security = [
			{
				ApiKeyAuth: [],
			},
			{
				Bearer: [],
			},
		];
		if (!endpoint.meta.requireCredential) {
			// add this to make authentication optional
			security.push({});
		}

		const info = {
			operationId: endpoint.name,
			summary: endpoint.name,
			description: desc,
			externalDocs: {
				description: "Source code",
				url: `https://iceshrimp.dev/iceshrimp/iceshrimp/src/branch/dev/packages/backend/src/server/api/endpoints/${endpoint.name}.ts`,
			},
			tags: endpoint.meta.tags || undefined,
			security,
			requestBody: {
				required: true,
				content: {
					[requestType]: {
						schema,
					},
				},
			},
			responses: {
				...(endpoint.meta.res
					? {
							"200": {
								description: "OK (with results)",
								content: {
									"application/json": {
										schema: resSchema,
									},
								},
							},
					  }
					: {
							"204": {
								description: "OK (without any results)",
							},
					  }),
				"400": {
					description: "Client error",
					content: {
						"application/json": {
							schema: {
								$ref: "#/components/schemas/Error",
							},
							examples: { ...errors, ...basicErrors["400"] },
						},
					},
				},
				"401": {
					description: "Authentication error",
					content: {
						"application/json": {
							schema: {
								$ref: "#/components/schemas/Error",
							},
							examples: basicErrors["401"],
						},
					},
				},
				"403": {
					description: "Forbidden error",
					content: {
						"application/json": {
							schema: {
								$ref: "#/components/schemas/Error",
							},
							examples: basicErrors["403"],
						},
					},
				},
				"418": {
					description: "I'm Calc",
					content: {
						"application/json": {
							schema: {
								$ref: "#/components/schemas/Error",
							},
							examples: basicErrors["418"],
						},
					},
				},
				...(endpoint.meta.limit
					? {
							"429": {
								description: "Too many requests",
								content: {
									"application/json": {
										schema: {
											$ref: "#/components/schemas/Error",
										},
										examples: basicErrors["429"],
									},
								},
							},
					  }
					: {}),
				"500": {
					description: "Internal server error",
					content: {
						"application/json": {
							schema: {
								$ref: "#/components/schemas/Error",
							},
							examples: basicErrors["500"],
						},
					},
				},
			},
		};

		const path = {
			post: info,
		};
		if (endpoint.meta.allowGet) {
			path.get = { ...info };
			// API Key authentication is not permitted for GET requests
			path.get.security = path.get.security.filter(
				(elem) => !Object.prototype.hasOwnProperty.call(elem, "ApiKeyAuth"),
			);
		}

		spec.paths[`/${endpoint.name}`] = path;
	}

	return spec;
}
