import {
  OpenApiGeneratorV31,
  OpenAPIRegistry,
  extendZodWithOpenApi,
} from "@asteasolutions/zod-to-openapi";
import { z } from "zod";

// Nothing at module level — Next.js evaluates route modules at build time to
// determine static vs dynamic, which would run registry.register() before
// extendZodWithOpenApi patches the Zod prototype. Wrap everything lazily.
let cachedSpec: object | null = null;

export function generateOpenApiSpec(): object {
  if (cachedSpec) return cachedSpec;

  extendZodWithOpenApi(z);
  const registry = new OpenAPIRegistry();

  const security: Array<Record<string, string[]>> = [{ SessionCookie: [] }, { BearerApiKey: [] }];

  registry.registerComponent("securitySchemes", "SessionCookie", { type: "apiKey", in: "cookie", name: "better-auth.session_token" });
  registry.registerComponent("securitySchemes", "BearerApiKey", { type: "http", scheme: "bearer", bearerFormat: "ek_..." });

  const DietaryTagsRef = registry.register("DietaryTags", z.object({
    vegan: z.boolean().optional(), vegetarian: z.boolean().optional(),
    glutenFree: z.boolean().optional(), dairyFree: z.boolean().optional(),
    nutFree: z.boolean().optional(), halal: z.boolean().optional(), kosher: z.boolean().optional(),
  }));

  const RecipeIngredientRef = registry.register("RecipeIngredient", z.object({
    id: z.string(), rawName: z.string(), quantity: z.number().nullable(),
    unit: z.string().nullable(), note: z.string().nullable(), order: z.number().int(),
  }));

  const RecipeStepRef = registry.register("RecipeStep", z.object({
    id: z.string(), order: z.number().int(), instruction: z.string(), timerSeconds: z.number().int().nullable(),
  }));

  const RecipeRef = registry.register("Recipe", z.object({
    id: z.string(), authorId: z.string(), title: z.string(), description: z.string().nullable(),
    baseServings: z.number().int(), visibility: z.enum(["private", "unlisted", "public"]),
    difficulty: z.enum(["easy", "medium", "hard"]).nullable(),
    prepMins: z.number().int().nullable(), cookMins: z.number().int().nullable(),
    dietaryTags: DietaryTagsRef, aiGenerated: z.boolean(),
    ingredients: z.array(RecipeIngredientRef), steps: z.array(RecipeStepRef),
    createdAt: z.string().datetime(), updatedAt: z.string().datetime(),
  }));

  const CreateRecipeRef = registry.register("CreateRecipe", z.object({
    title: z.string().min(1).max(200), description: z.string().optional(),
    baseServings: z.number().int().positive(), visibility: z.enum(["private", "unlisted", "public"]),
    difficulty: z.enum(["easy", "medium", "hard"]).optional(),
    prepMins: z.number().int().positive().optional(), cookMins: z.number().int().positive().optional(),
    dietaryTags: DietaryTagsRef.optional(),
    ingredients: z.array(z.object({ rawName: z.string(), quantity: z.number().nullable(), unit: z.string().nullable(), note: z.string().nullable(), order: z.number().int() })),
    steps: z.array(z.object({ order: z.number().int(), instruction: z.string(), timerSeconds: z.number().int().nullable() })),
  }));

  const ApiErrorRef = registry.register("ApiError", z.object({ error: z.string() }));

  const Pagination = z.object({ page: z.coerce.number().default(1), limit: z.coerce.number().default(20) });

  const CommentRef = registry.register("Comment", z.object({
    id: z.string(), recipeId: z.string(), userId: z.string(),
    body: z.string(), parentId: z.string().nullable(), createdAt: z.string().datetime(),
  }));

  const CollectionRef = registry.register("Collection", z.object({
    id: z.string(), userId: z.string(), name: z.string(),
    description: z.string().nullable(), isPublic: z.boolean(), createdAt: z.string().datetime(),
  }));

  const MealPlanRef = registry.register("MealPlan", z.object({
    weekStart: z.string(),
    entries: z.array(z.object({ id: z.string(), dayOfWeek: z.number(), mealType: z.string(), recipeId: z.string(), servings: z.number() })),
  }));

  const PantryItemRef = registry.register("PantryItem", z.object({
    id: z.string(), rawName: z.string(), quantity: z.string().nullable(),
    unit: z.string().nullable(), expiresAt: z.string().datetime().nullable(),
  }));

  const ShoppingListRef = registry.register("ShoppingList", z.object({
    id: z.string(), name: z.string(), generatedAt: z.string().datetime().nullable(), createdAt: z.string().datetime(),
    items: z.array(z.object({ id: z.string(), rawName: z.string(), quantity: z.string().nullable(), unit: z.string().nullable(), aisle: z.string().nullable(), checked: z.boolean() })),
  }));

  const ApiKeyRef = registry.register("ApiKey", z.object({
    id: z.string(), name: z.string(), scope: z.enum(["full", "read"]), lastUsedAt: z.string().datetime().nullable(), createdAt: z.string().datetime(),
  }));

  const CreateApiKeyResponseRef = registry.register("CreateApiKeyResponse", z.object({
    id: z.string(), name: z.string(), scope: z.enum(["full", "read"]), key: z.string().describe("Full key — shown once"), createdAt: z.string().datetime(),
  }));

  const AiGeneratedRef = registry.register("AiGeneratedRecipe", z.object({
    title: z.string(), description: z.string(), baseServings: z.number(),
    difficulty: z.enum(["easy", "medium", "hard"]),
    prepMins: z.number().nullable(), cookMins: z.number().nullable(),
    dietaryTags: DietaryTagsRef,
    ingredients: z.array(z.object({ rawName: z.string(), quantity: z.number().nullable(), unit: z.string().nullable(), note: z.string().nullable() })),
    steps: z.array(z.object({ instruction: z.string(), timerSeconds: z.number().nullable() })),
  }));

  const PaginatedRecipes = z.object({
    data: z.array(RecipeRef),
    limit: z.number(),
    offset: z.number(),
  });

  const PaginatedCollections = z.object({
    data: z.array(CollectionRef),
    total: z.number(),
    limit: z.number(),
    offset: z.number(),
  });

  const LimitOffset = z.object({ limit: z.coerce.number().default(20), offset: z.coerce.number().default(0) });

  const idParam = z.object({ id: z.string() });

  registry.registerPath({ method: "get", path: "/api/v1/recipes", summary: "List recipes", security, request: { query: z.object({ limit: z.coerce.number().default(20), offset: z.coerce.number().default(0), visibility: z.enum(["private", "unlisted", "public"]).optional() }) }, responses: { 200: { description: "Paginated", content: { "application/json": { schema: PaginatedRecipes } } }, 401: { description: "Unauthorized", content: { "application/json": { schema: ApiErrorRef } } } } });
  registry.registerPath({ method: "post", path: "/api/v1/recipes", summary: "Create recipe", security, request: { body: { content: { "application/json": { schema: CreateRecipeRef } }, required: true } }, responses: { 201: { description: "Created", content: { "application/json": { schema: RecipeRef } } }, 400: { description: "Bad request", content: { "application/json": { schema: ApiErrorRef } } }, 401: { description: "Unauthorized", content: { "application/json": { schema: ApiErrorRef } } } } });
  registry.registerPath({ method: "get", path: "/api/v1/recipes/{id}", summary: "Get recipe", security, request: { params: idParam }, responses: { 200: { description: "Recipe", content: { "application/json": { schema: RecipeRef } } }, 404: { description: "Not found", content: { "application/json": { schema: ApiErrorRef } } } } });
  registry.registerPath({ method: "put", path: "/api/v1/recipes/{id}", summary: "Update recipe", security, request: { params: idParam, body: { content: { "application/json": { schema: CreateRecipeRef } }, required: true } }, responses: { 200: { description: "Updated", content: { "application/json": { schema: RecipeRef } } }, 401: { description: "Unauthorized", content: { "application/json": { schema: ApiErrorRef } } }, 404: { description: "Not found", content: { "application/json": { schema: ApiErrorRef } } } } });
  registry.registerPath({ method: "delete", path: "/api/v1/recipes/{id}", summary: "Delete recipe", security, request: { params: idParam }, responses: { 204: { description: "Deleted" }, 401: { description: "Unauthorized", content: { "application/json": { schema: ApiErrorRef } } }, 404: { description: "Not found", content: { "application/json": { schema: ApiErrorRef } } } } });
  registry.registerPath({ method: "post", path: "/api/v1/recipes/{id}/favorite", summary: "Toggle favorite", security, request: { params: idParam }, responses: { 200: { description: "Favorited", content: { "application/json": { schema: z.object({ favorited: z.boolean() }) } } }, 401: { description: "Unauthorized", content: { "application/json": { schema: ApiErrorRef } } } } });
  registry.registerPath({ method: "post", path: "/api/v1/recipes/{id}/rate", summary: "Rate recipe (1–5)", security, request: { params: idParam, body: { content: { "application/json": { schema: z.object({ score: z.number().int().min(1).max(5) }) } }, required: true } }, responses: { 200: { description: "Saved", content: { "application/json": { schema: z.object({ score: z.number() }) } } }, 401: { description: "Unauthorized", content: { "application/json": { schema: ApiErrorRef } } } } });
  registry.registerPath({ method: "get", path: "/api/v1/recipes/{id}/comments", summary: "List comments", security, request: { params: idParam, query: Pagination }, responses: { 200: { description: "Comments", content: { "application/json": { schema: z.array(CommentRef) } } } } });
  registry.registerPath({ method: "post", path: "/api/v1/recipes/{id}/comments", summary: "Post comment", security, request: { params: idParam, body: { content: { "application/json": { schema: z.object({ body: z.string().min(1), parentId: z.string().optional() }) } }, required: true } }, responses: { 201: { description: "Created", content: { "application/json": { schema: CommentRef } } }, 401: { description: "Unauthorized", content: { "application/json": { schema: ApiErrorRef } } } } });
  registry.registerPath({ method: "post", path: "/api/v1/ai/generate", summary: "Generate recipe from prompt", description: "Rate-limited: 10 req/min.", security, request: { body: { content: { "application/json": { schema: z.object({ prompt: z.string().min(1) }) } }, required: true } }, responses: { 200: { description: "Generated", content: { "application/json": { schema: AiGeneratedRef } } }, 429: { description: "Rate limited", content: { "application/json": { schema: ApiErrorRef } } } } });
  registry.registerPath({ method: "post", path: "/api/v1/ai/import-url", summary: "Import recipe from URL", description: "Rate-limited: 10 req/min.", security, request: { body: { content: { "application/json": { schema: z.object({ url: z.string().url() }) } }, required: true } }, responses: { 200: { description: "Imported", content: { "application/json": { schema: AiGeneratedRef } } }, 429: { description: "Rate limited", content: { "application/json": { schema: ApiErrorRef } } } } });
  registry.registerPath({ method: "get", path: "/api/v1/feed", summary: "Activity feed (pull-based)", security, request: { query: Pagination }, responses: { 200: { description: "Feed", content: { "application/json": { schema: z.array(RecipeRef) } } }, 401: { description: "Unauthorized", content: { "application/json": { schema: ApiErrorRef } } } } });
  registry.registerPath({ method: "get", path: "/api/v1/collections", summary: "List collections", security, request: { query: LimitOffset }, responses: { 200: { description: "Paginated collections", content: { "application/json": { schema: PaginatedCollections } } }, 401: { description: "Unauthorized", content: { "application/json": { schema: ApiErrorRef } } } } });
  registry.registerPath({ method: "get", path: "/api/v1/meal-plans/{weekStart}", summary: "Get meal plan for week", security, request: { params: z.object({ weekStart: z.string().describe("ISO date YYYY-MM-DD (Monday)") }) }, responses: { 200: { description: "Meal plan", content: { "application/json": { schema: MealPlanRef } } }, 401: { description: "Unauthorized", content: { "application/json": { schema: ApiErrorRef } } } } });
  registry.registerPath({ method: "get", path: "/api/v1/pantry", summary: "List pantry items", security, request: { query: Pagination }, responses: { 200: { description: "Items", content: { "application/json": { schema: z.array(PantryItemRef) } } }, 401: { description: "Unauthorized", content: { "application/json": { schema: ApiErrorRef } } } } });
  registry.registerPath({ method: "get", path: "/api/v1/shopping-lists", summary: "List shopping lists", security, request: { query: Pagination }, responses: { 200: { description: "Lists", content: { "application/json": { schema: z.array(ShoppingListRef) } } }, 401: { description: "Unauthorized", content: { "application/json": { schema: ApiErrorRef } } } } });
  registry.registerPath({ method: "post", path: "/api/v1/shopping-lists", summary: "Create shopping list", security, request: { body: { content: { "application/json": { schema: z.object({ name: z.string().min(1), fromMealPlanWeek: z.string().optional() }) } }, required: true } }, responses: { 201: { description: "Created", content: { "application/json": { schema: ShoppingListRef } } }, 401: { description: "Unauthorized", content: { "application/json": { schema: ApiErrorRef } } } } });
  registry.registerPath({ method: "get", path: "/api/v1/api-keys", summary: "List API keys", security, responses: { 200: { description: "Keys (hash never returned)", content: { "application/json": { schema: z.array(ApiKeyRef) } } }, 401: { description: "Unauthorized", content: { "application/json": { schema: ApiErrorRef } } } } });
  registry.registerPath({ method: "post", path: "/api/v1/api-keys", summary: "Create API key", security, request: { body: { content: { "application/json": { schema: z.object({ name: z.string().min(1).max(100), scope: z.enum(["full", "read"]).default("full").describe("read-only keys can only make GET requests") }) } }, required: true } }, responses: { 201: { description: "Key created — shown once", content: { "application/json": { schema: CreateApiKeyResponseRef } } }, 401: { description: "Unauthorized", content: { "application/json": { schema: ApiErrorRef } } } } });
  registry.registerPath({ method: "delete", path: "/api/v1/api-keys/{id}", summary: "Revoke API key", security, request: { params: idParam }, responses: { 204: { description: "Revoked" }, 401: { description: "Unauthorized", content: { "application/json": { schema: ApiErrorRef } } }, 404: { description: "Not found", content: { "application/json": { schema: ApiErrorRef } } } } });

  const generator = new OpenApiGeneratorV31(registry.definitions);
  cachedSpec = generator.generateDocument({
    openapi: "3.1.0",
    info: { title: "Epicure API", version: "1.0.0", description: "Auth: session cookie or Bearer API key (`ek_...`)." },
    servers: [{ url: process.env["BETTER_AUTH_URL"] ?? "http://localhost:3000" }],
  });

  return cachedSpec;
}
