/**
 * automation/core/http-validation.ts
 *
 * Express 路由的请求体 zod 校验工具。
 *
 * 设计：
 *   - 不引入 middleware 链（路由层面已经有 try/catch）。
 *   - 提供 `parseBody(req, schema)`：成功返回 typed data，失败抛 ApiValidationError。
 *   - ApiValidationError 携带 statusCode=400 与 zod issues，由 server.ts 的 sendApiError 统一回 JSON。
 *
 * 使用：
 *   const Body = z.object({ batch_id: z.string().min(1), material_ids: z.array(z.string()).optional() });
 *   try {
 *     const { batch_id, material_ids } = parseBody(req, Body);
 *     ...
 *   } catch (err) {
 *     sendApiError(res, err);
 *   }
 */

import type { Request } from "express";
import type { z } from "zod";

export interface ApiValidationIssue {
  /** zod 路径，如 ["materials", 0, "quantity"] */
  path: (string | number)[];
  message: string;
}

export class ApiValidationError extends Error {
  /** HTTP 响应状态码，由 sendApiError 读出后写入响应 */
  readonly statusCode = 400;
  /** 全部失败 issue，由前端展示或日志使用 */
  readonly issues: ApiValidationIssue[];

  constructor(message: string, issues: ApiValidationIssue[]) {
    super(message);
    this.name = "ApiValidationError";
    this.issues = issues;
  }
}

export function isApiValidationError(error: unknown): error is ApiValidationError {
  return error instanceof ApiValidationError;
}

function summarizeIssues(issues: z.ZodIssue[]): string {
  const paths = issues
    .map((issue) => issue.path.join(".") || "(root)")
    .filter((path, index, arr) => arr.indexOf(path) === index);
  return paths.join(", ");
}

/**
 * 用 zod schema 校验 `req.body`。失败时抛 `ApiValidationError`。
 * 成功返回 schema 推断出的强类型数据。
 */
export function parseBody<TSchema extends z.ZodTypeAny>(
  req: Request,
  schema: TSchema,
): z.infer<TSchema> {
  const result = schema.safeParse(req.body);
  if (!result.success) {
    const issues: ApiValidationIssue[] = result.error.issues.map((issue) => ({
      path: [...issue.path],
      message: issue.message,
    }));
    throw new ApiValidationError(
      `请求体校验失败: ${summarizeIssues(result.error.issues)}`,
      issues,
    );
  }
  return result.data;
}
