/**
 * Mandu Contract Type Glue Generator
 * Contract에서 TypeScript 타입 글루 코드 생성
 */

import type { RouteSpec } from "../spec/schema";
import { GENERATED_RELATIVE_PATHS } from "../paths";

/**
 * Convert string to PascalCase
 */
function toPascalCase(str: string): string {
  return str
    .split(/[-_]/)
    .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
    .join("");
}

/**
 * Compute relative import path
 */
function computeRelativePath(fromDir: string, toPath: string): string {
  const fromParts = fromDir.replace(/\\/g, "/").split("/");
  const toParts = toPath.replace(/\\/g, "/").split("/");

  let commonLength = 0;
  while (
    commonLength < fromParts.length &&
    commonLength < toParts.length &&
    fromParts[commonLength] === toParts[commonLength]
  ) {
    commonLength++;
  }

  const upCount = fromParts.length - commonLength;
  const relativeParts = toParts.slice(commonLength);
  const ups = Array(upCount).fill("..");

  let result = [...ups, ...relativeParts].join("/");
  result = result.replace(/\.ts$/, "");

  return result;
}

/**
 * Generate type glue code for a route with contract
 *
 * @example
 * ```typescript
 * // generated/types/users.types.ts
 * import type { InferContract, InferQuery, InferBody, InferResponse } from "@mandujs/core";
 * import contract from "../../spec/contracts/users.contract";
 *
 * export type UsersContract = InferContract<typeof contract>;
 *
 * // Request types
 * export type UsersGetQuery = InferQuery<typeof contract, "GET">;
 * export type UsersPostBody = InferBody<typeof contract, "POST">;
 *
 * // Response types
 * export type UsersResponse200 = InferResponse<typeof contract, 200>;
 * export type UsersResponse201 = InferResponse<typeof contract, 201>;
 * ```
 */
export function generateContractTypeGlue(
  route: RouteSpec,
  typesDir: string = GENERATED_RELATIVE_PATHS.types
): string {
  if (!route.contractModule) {
    return "";
  }

  const pascalName = toPascalCase(route.id);
  const contractImportPath = computeRelativePath(typesDir, route.contractModule);

  return `// Generated by Mandu - DO NOT EDIT DIRECTLY
// Type glue for route: ${route.id}
// Contract: ${route.contractModule}

import type { InferContract, InferQuery, InferBody, InferParams, InferResponse } from "@mandujs/core";
import contract from "${contractImportPath}";

/**
 * Full contract type for ${route.id}
 */
export type ${pascalName}Contract = InferContract<typeof contract>;

// ============================================
// Request Types
// ============================================

/** GET query parameters */
export type ${pascalName}GetQuery = InferQuery<typeof contract, "GET">;

/** POST request body */
export type ${pascalName}PostBody = InferBody<typeof contract, "POST">;

/** PUT request body */
export type ${pascalName}PutBody = InferBody<typeof contract, "PUT">;

/** PATCH request body */
export type ${pascalName}PatchBody = InferBody<typeof contract, "PATCH">;

/** DELETE query parameters */
export type ${pascalName}DeleteQuery = InferQuery<typeof contract, "DELETE">;

/** Path parameters (if any) */
export type ${pascalName}Params = InferParams<typeof contract, "GET">;

// ============================================
// Response Types
// ============================================

/** 200 OK response */
export type ${pascalName}Response200 = InferResponse<typeof contract, 200>;

/** 201 Created response */
export type ${pascalName}Response201 = InferResponse<typeof contract, 201>;

/** 204 No Content response */
export type ${pascalName}Response204 = InferResponse<typeof contract, 204>;

/** 400 Bad Request response */
export type ${pascalName}Response400 = InferResponse<typeof contract, 400>;

/** 404 Not Found response */
export type ${pascalName}Response404 = InferResponse<typeof contract, 404>;

// Re-export contract for runtime use
export { contract };
`;
}

/**
 * Generate contract template for a route
 *
 * @example
 * ```typescript
 * // spec/contracts/users.contract.ts
 * import { z } from "zod";
 * import { Mandu } from "@mandujs/core";
 *
 * export default Mandu.contract({
 *   description: "Users API",
 *   tags: ["users"],
 *   request: {
 *     GET: { query: z.object({ page: z.coerce.number().default(1) }) },
 *     POST: { body: z.object({ name: z.string() }) },
 *   },
 *   response: {
 *     200: z.object({ data: z.array(z.unknown()) }),
 *     201: z.object({ data: z.unknown() }),
 *     400: z.object({ error: z.string() }),
 *   },
 * });
 * ```
 */
export function generateContractTemplate(route: RouteSpec): string {
  const methods = route.methods || ["GET"];
  const hasGet = methods.includes("GET");
  const hasPost = methods.includes("POST");
  const hasPut = methods.includes("PUT");
  const hasPatch = methods.includes("PATCH");
  const hasDelete = methods.includes("DELETE");

  const requestParts: string[] = [];

  if (hasGet) {
    requestParts.push(`    GET: {
      query: z.object({
        page: z.coerce.number().int().min(1).default(1),
        limit: z.coerce.number().int().min(1).max(100).default(10),
      }),
    }`);
  }

  if (hasPost) {
    requestParts.push(`    POST: {
      body: z.object({
        // TODO: Define your request body schema
        name: z.string().min(1),
      }),
    }`);
  }

  if (hasPut) {
    requestParts.push(`    PUT: {
      body: z.object({
        // TODO: Define your request body schema
        name: z.string().min(1),
      }),
    }`);
  }

  if (hasPatch) {
    requestParts.push(`    PATCH: {
      body: z.object({
        // TODO: Define your partial update schema
        name: z.string().min(1).optional(),
      }),
    }`);
  }

  if (hasDelete) {
    requestParts.push(`    DELETE: {
      // Usually no body for DELETE
    }`);
  }

  return `// 📜 Mandu Contract - ${route.id}
// Pattern: ${route.pattern}
// 이 파일에서 API 스키마를 정의하세요.

import { z } from "zod";
import { Mandu } from "@mandujs/core";

// ============================================
// 🥟 Schema Definitions
// ============================================

// TODO: Define your data schemas here
// const ItemSchema = z.object({
//   id: z.string().uuid(),
//   name: z.string(),
//   createdAt: z.string().datetime(),
// });

// ============================================
// 📜 Contract Definition
// ============================================

export default Mandu.contract({
  description: "${toPascalCase(route.id)} API",
  tags: ["${route.id}"],

  request: {
${requestParts.join(",\n\n")}
  },

  response: {
    200: z.object({
      data: z.unknown(),
      // TODO: Define your success response schema
    }),
    ${hasPost || hasPut ? `201: z.object({
      data: z.unknown(),
      // TODO: Define your created response schema
    }),
    ` : ""}400: z.object({
      error: z.string(),
      details: z.array(z.object({
        type: z.string(),
        issues: z.array(z.object({
          path: z.string(),
          message: z.string(),
        })),
      })).optional(),
    }),
    404: z.object({
      error: z.string(),
    }),
  },
});

// 💡 Contract 사용법:
// 1. 위의 스키마를 실제 데이터 구조에 맞게 정의하세요
// 2. mandu generate를 실행하면 타입이 자동으로 Slot에 연결됩니다
// 3. OpenAPI 문서가 자동으로 생성됩니다
`;
}

/**
 * Generate index file that exports all contract types
 */
export function generateContractTypesIndex(routeIds: string[]): string {
  const exports = routeIds.map((id) => {
    const fileName = `${id}.types`;
    return `export * from "./${fileName}";`;
  });

  return `// Generated by Mandu - DO NOT EDIT DIRECTLY
// Contract type exports

${exports.join("\n")}
`;
}
