/**
 * Custom Zod validation middleware for Hono routes.
 *
 * Provides typed request validation for JSON bodies, query params,
 * and path params using Zod schemas. Returns 400 with structured
 * error details on validation failure.
 */

import type { Context, MiddlewareHandler } from "hono";
import type { z } from "zod";

/** Structured validation error response shape. */
export interface ValidationErrorResponse {
  error: "Validation failed";
  details: Array<{ path: string; message: string }>;
}

/**
 * Format Zod issues into a flat list of { path, message } objects.
 * Works with both Zod v3 and v4 issue formats.
 */
function formatZodIssues(error: z.ZodError): ValidationErrorResponse["details"] {
  return error.issues.map((issue) => ({
    path: issue.path.map(String).join(".") || "(root)",
    message: issue.message,
  }));
}

/**
 * Validate the JSON request body against a Zod schema.
 *
 * On success, stores the parsed data on `c.set("validatedBody", data)`.
 * On failure, returns 400 with `{ error: "Validation failed", details: [...] }`.
 *
 * Usage:
 * ```ts
 * const schema = z.object({ name: z.string().min(1) });
 * api.post("/items", validateBody(schema), async (c) => {
 *   const body = c.get("validatedBody") as z.infer<typeof schema>;
 *   // body is typed and validated
 * });
 * ```
 */
export function validateBody<T extends z.ZodType>(schema: T): MiddlewareHandler {
  return async (c: Context, next) => {
    let raw: unknown;
    try {
      raw = await c.req.json();
    } catch {
      return c.json(
        { error: "Validation failed", details: [{ path: "(root)", message: "Invalid JSON body" }] } satisfies ValidationErrorResponse,
        400,
      );
    }
    const result = schema.safeParse(raw);
    if (!result.success) {
      return c.json(
        { error: "Validation failed", details: formatZodIssues(result.error) } satisfies ValidationErrorResponse,
        400,
      );
    }
    c.set("validatedBody", result.data);
    return next();
  };
}

/**
 * Validate query parameters against a Zod schema.
 *
 * Extracts query params as a plain object and validates.
 * On success, stores parsed data on `c.set("validatedQuery", data)`.
 * On failure, returns 400 with structured error details.
 */
export function validateQuery<T extends z.ZodType>(schema: T): MiddlewareHandler {
  return async (c: Context, next) => {
    const raw = Object.fromEntries(new URL(c.req.url).searchParams.entries());
    const result = schema.safeParse(raw);
    if (!result.success) {
      return c.json(
        { error: "Validation failed", details: formatZodIssues(result.error) } satisfies ValidationErrorResponse,
        400,
      );
    }
    c.set("validatedQuery", result.data);
    return next();
  };
}

/**
 * Parse and validate a single path parameter inline.
 *
 * Returns `{ success: true, data }` or produces a 400 response.
 * Useful for validating `:id` and similar route params.
 */
export function parseParam<T extends z.ZodType>(
  c: Context,
  paramName: string,
  schema: T,
): { success: true; data: z.infer<T> } | { success: false; response: Response } {
  const raw = c.req.param(paramName);
  const result = schema.safeParse(raw);
  if (!result.success) {
    return {
      success: false,
      response: c.json(
        { error: "Validation failed", details: formatZodIssues(result.error) } satisfies ValidationErrorResponse,
        400,
      ),
    };
  }
  return { success: true, data: result.data as z.infer<T> };
}

/**
 * Parse and validate a JSON body inline (without middleware).
 *
 * Returns `{ success: true, data }` or produces a 400 response.
 * Useful for routes that need conditional validation or have
 * existing complex handler logic that doesn't suit middleware.
 */
export async function parseBody<T extends z.ZodType>(
  c: Context,
  schema: T,
): Promise<{ success: true; data: z.infer<T> } | { success: false; response: Response }> {
  let raw: unknown;
  let hasBody = true;
  try {
    raw = await c.req.json();
  } catch {
    hasBody = false;
  }

  if (!hasBody) {
    // When no JSON body is present, try validating undefined.
    // Schemas with .optional().default({}) will succeed; others produce
    // a clear 400 error (e.g., "expected object, received undefined").
    const fallback = schema.safeParse(undefined);
    if (fallback.success) {
      return { success: true, data: fallback.data as z.infer<T> };
    }
    return {
      success: false,
      response: c.json(
        { error: "Validation failed", details: [{ path: "(root)", message: "Invalid JSON body" }] } satisfies ValidationErrorResponse,
        400,
      ),
    };
  }

  const result = schema.safeParse(raw);
  if (!result.success) {
    return {
      success: false,
      response: c.json(
        { error: "Validation failed", details: formatZodIssues(result.error) } satisfies ValidationErrorResponse,
        400,
      ),
    };
  }
  return { success: true, data: result.data as z.infer<T> };
}
