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

export function generateApiHandler(route: RouteSpec): string {
  // contractModule + slotModule이 있으면 contract 검증 버전 생성
  if (route.contractModule && route.slotModule) {
    return generateApiHandlerWithContract(route);
  }

  // slotModule만 있으면 slot을 호출하는 버전 생성
  if (route.slotModule) {
    return generateApiHandlerWithSlot(route);
  }

  return `// Generated by Mandu - DO NOT EDIT DIRECTLY
// Route ID: ${route.id}
// Pattern: ${route.pattern}

import type { Request } from "bun";

export default function handler(req: Request, params: Record<string, string>): Response {
  return Response.json({
    status: "ok",
    routeId: "${route.id}",
    pattern: "${route.pattern}",
    timestamp: new Date().toISOString(),
  });
}
`;
}

export function generateApiHandlerWithSlot(route: RouteSpec): string {
  const slotImportPath = computeSlotImportPath(route.slotModule!, GENERATED_RELATIVE_PATHS.serverRoutes);

  return `// Generated by Mandu - DO NOT EDIT DIRECTLY
// Route ID: ${route.id}
// Pattern: ${route.pattern}
// Slot Module: ${route.slotModule}

import type { Request } from "bun";
import filling from "${slotImportPath}";

export default async function handler(
  req: Request,
  params: Record<string, string>
): Promise<Response> {
  return filling.handle(req, params);
}
`;
}

export function generateApiHandlerWithContract(route: RouteSpec): string {
  const slotImportPath = computeSlotImportPath(route.slotModule!, GENERATED_RELATIVE_PATHS.serverRoutes);
  const contractImportPath = computeSlotImportPath(route.contractModule!, GENERATED_RELATIVE_PATHS.serverRoutes);

  return `// Generated by Mandu - DO NOT EDIT DIRECTLY
// Route ID: ${route.id}
// Pattern: ${route.pattern}
// Contract Module: ${route.contractModule}
// Slot Module: ${route.slotModule}

import type { Request } from "bun";
import { ContractValidator, formatValidationErrors } from "@mandujs/core";
import contract from "${contractImportPath}";
import filling from "${slotImportPath}";

const validator = new ContractValidator(contract);
const isDev = process.env.NODE_ENV !== "production";

export default async function handler(
  req: Request,
  params: Record<string, string>
): Promise<Response> {
  const method = req.method;

  // 1. Request Validation
  const reqValidation = await validator.validateRequest(req, method, params);
  if (!reqValidation.success) {
    return Response.json(
      formatValidationErrors(reqValidation.errors!),
      { status: 400 }
    );
  }

  // 2. Execute Filling
  const response = await filling.handle(req, params, {
    routeId: "${route.id}",
    pattern: "${route.pattern}",
  });

  // 3. Response Validation (dev only)
  if (isDev && response.headers.get("content-type")?.includes("application/json")) {
    try {
      const cloned = response.clone();
      const body = await cloned.json();
      const resValidation = validator.validateResponse(body, response.status);
      if (!resValidation.success) {
        console.warn(
          "\\x1b[33m[Mandu] Contract violation in response:\\x1b[0m",
          "${route.id}",
          resValidation.errors
        );
      }
    } catch {
      // Ignore JSON parse errors for non-JSON responses
    }
  }

  return response;
}
`;
}

export function generateSlotLogic(route: RouteSpec): string {
  if (route.contractModule) {
    return generateSlotLogicWithContract(route);
  }

  return `// 🥟 Mandu Filling - ${route.id}
// Pattern: ${route.pattern}
// 이 파일에서 비즈니스 로직을 구현하세요.

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

export default Mandu.filling()
  // 📋 GET ${route.pattern}
  .get((ctx) => {
    return ctx.ok({
      message: "Hello from ${route.id}!",
      timestamp: new Date().toISOString(),
    });
  })

  // ➕ POST ${route.pattern}
  .post(async (ctx) => {
    const body = await ctx.body();
    return ctx.created({
      message: "Created!",
      received: body,
    });
  });

// 💡 사용 가능한 메서드:
// .get(ctx => ...)     - GET 요청 처리
// .post(ctx => ...)    - POST 요청 처리
// .put(ctx => ...)     - PUT 요청 처리
// .delete(ctx => ...)  - DELETE 요청 처리
// .guard(ctx => ...)   - 인증/검증 미들웨어
//
// 💡 Context (ctx) API:
// ctx.query            - Query parameters { name: 'value' }
// ctx.params           - Path parameters { id: '123' }
// ctx.body()           - Request body (자동 파싱)
// ctx.body(zodSchema)  - Body with validation
// ctx.headers          - Request headers
// ctx.ok(data)         - 200 OK
// ctx.created(data)    - 201 Created
// ctx.error(msg)       - 400 Bad Request
// ctx.notFound(msg)    - 404 Not Found
// ctx.set(key, value)  - Guard에서 Handler로 데이터 전달
// ctx.get(key)         - Guard에서 설정한 데이터 읽기
`;
}

export function generateSlotLogicWithContract(route: RouteSpec): string {
  const contractImportPath = computeSlotImportPath(
    route.contractModule!,
    pathDirname(route.slotModule ?? "spec/slots")
  );

  return `// 🥟 Mandu Filling - ${route.id}
// Pattern: ${route.pattern}
// Contract Module: ${route.contractModule}
// 이 파일에서 비즈니스 로직을 구현하세요.

import { Mandu } from "@mandujs/core";
import contract from "${contractImportPath}";

export default Mandu.filling()
  // 📋 GET ${route.pattern}
  .get(async (ctx) => {
    const input = await ctx.input(contract, "GET", ctx.params);
    // TODO: 계약의 응답 코드에 맞게 status를 조정하세요
    return ctx.output(contract, 200, {
      message: "Hello from ${route.id}!",
      input,
      timestamp: new Date().toISOString(),
    });
  })

  // ➕ POST ${route.pattern}
  .post(async (ctx) => {
    const input = await ctx.input(contract, "POST", ctx.params);
    // TODO: 계약의 응답 코드에 맞게 status를 조정하세요
    return ctx.output(contract, 201, {
      message: "Created!",
      input,
      timestamp: new Date().toISOString(),
    });
  });

// 💡 Contract 기반 사용법:
// ctx.input(contract, "GET")  - Contract로 요청 검증 + 정규화
// ctx.output(contract, 200, data) - Contract로 응답 검증
// ctx.okContract(contract, data)  - 200 OK (Contract 검증)
// ctx.createdContract(contract, data) - 201 Created (Contract 검증)
`;
}

function computeSlotImportPath(slotModule: string, fromDir: string): string {
  // slotModule: "spec/slots/users.slot.ts"
  // fromDir: ".mandu/generated/server/routes"
  // result: "../../../../spec/slots/users.slot"

  const slotParts = slotModule.replace(/\\/g, "/").split("/");
  const fromParts = fromDir.replace(/\\/g, "/").split("/");

  // Find common prefix length
  let commonLength = 0;
  while (
    commonLength < slotParts.length &&
    commonLength < fromParts.length &&
    slotParts[commonLength] === fromParts[commonLength]
  ) {
    commonLength++;
  }

  // Calculate relative path
  const upCount = fromParts.length - commonLength;
  const relativeParts = slotParts.slice(commonLength);
  const ups = Array(upCount).fill("..");

  let result = [...ups, ...relativeParts].join("/");

  // Remove .ts extension
  result = result.replace(/\.ts$/, "");

  return result;
}

export function generatePageComponent(route: RouteSpec): string {
  // Island-First: clientModule이 있으면 Island render를 SSR에서 직접 사용
  if (route.clientModule) {
    return generatePageComponentWithIsland(route);
  }

  const hasCompilerOwnedBoundaries = (route.boundaries?.length ?? 0) > 0;

  if (needsHydration(route) && !hasCompilerOwnedBoundaries) {
    throw new Error(
      `[${route.id}] Route has hydration strategy "${route.hydration?.strategy}" but no clientModule. ` +
      "Refusing to generate a placeholder page because it would disagree with runtime hydration state.",
    );
  }

  // slotModule이 있으면 PageHandler 형식으로 생성 (filling 포함)
  if (route.slotModule) {
    return generatePageHandlerWithSlot(route);
  }

  if (route.kind === "page" && route.componentModule) {
    return generatePageComponentFromModule(route);
  }

  if (needsHydration(route)) {
    throw new Error(
      `[${route.id}] Route has hydration strategy "${route.hydration?.strategy}" and compiler-owned boundaries, ` +
      "but no componentModule. Refusing to generate a placeholder page because it would disagree with runtime hydration state.",
    );
  }

  const pageName = toPascalCase(route.id);

  // Legacy fallback for malformed historical manifests that lack componentModule.
  return `// Generated by Mandu - DO NOT EDIT DIRECTLY
// Route ID: ${route.id}
// Pattern: ${route.pattern}

import React from "react";

interface Props {
  params: Record<string, string>;
  loaderData?: unknown;
}

export default function ${pageName}Page({ params }: Props): React.ReactElement {
  return React.createElement("div", null,
    React.createElement("h1", null, "${pageName} Page"),
    React.createElement("p", null, "Route ID: ${route.id}"),
    React.createElement("p", null, "Pattern: ${route.pattern}")
  );
}
`;
}

export function generatePageComponentFromModule(route: RouteSpec): string {
  const pageName = toPascalCase(route.id);
  const pageImportPath = computeSlotImportPath(route.componentModule!, GENERATED_RELATIVE_PATHS.webRoutes);

  return `// Generated by Mandu - DO NOT EDIT DIRECTLY
// Route ID: ${route.id}
// Pattern: ${route.pattern}
// Page Module: ${route.componentModule}

import React from "react";
import pageModule from "${pageImportPath}";

interface Props {
  params: Record<string, string>;
  loaderData?: unknown;
}

export default function ${pageName}Page(props: Props): React.ReactElement {
  return React.createElement(pageModule as React.ComponentType<Props>, props);
}
`;
}

/**
 * Island-First Rendering: SSR이 island의 render 함수를 직접 사용
 * - clientModule의 definition.setup() + definition.render() 호출
 * - SSR과 클라이언트가 동일한 렌더링 로직 사용 → 불일치 구조적 방지
 * - slotModule 유무에 따라 두 가지 변형 생성
 */
export function generatePageComponentWithIsland(route: RouteSpec): string {
  const pageName = toPascalCase(route.id);
  const clientImportPath = computeSlotImportPath(route.clientModule!, GENERATED_RELATIVE_PATHS.webRoutes);
  const pageImportPath = route.kind === "page" && route.componentModule
    ? computeSlotImportPath(route.componentModule, GENERATED_RELATIVE_PATHS.webRoutes)
    : null;
  const pageModuleComment = route.kind === "page" && route.componentModule
    ? `// Page Module: ${route.componentModule}\n`
    : "";
  const shouldImportPageModule = !!pageImportPath &&
    normalizeRouteModulePath(route.componentModule) !== normalizeRouteModulePath(route.clientModule);
  const pageImport = shouldImportPageModule ? `import pageModule from "${pageImportPath}";\n` : "";
  const pageRenderTarget = shouldImportPageModule ? "pageModule" : "islandModule";
  const renderHelper = generateClientBackedPageRenderHelper(pageRenderTarget, route.id);

  // clientModule + slotModule → PageRegistration 형식
  if (route.slotModule) {
    const slotImportPath = computeSlotImportPath(route.slotModule!, GENERATED_RELATIVE_PATHS.webRoutes);

    return `// Generated by Mandu - DO NOT EDIT DIRECTLY
// Island-First Rendering + Slot Module
// Route ID: ${route.id}
// Pattern: ${route.pattern}
// Client Module: ${route.clientModule}
// Slot Module: ${route.slotModule}
${pageModuleComment}

import React from "react";
import filling from "${slotImportPath}";
import islandModule from "${clientImportPath}";
${pageImport}

interface Props {
  params: Record<string, string>;
  loaderData?: unknown;
}

${renderHelper}

function ${pageName}Page({ params, loaderData }: Props): React.ReactElement {
  const serverData = (loaderData || {}) as Record<string, unknown>;
  return renderClientBackedPage({ params, loaderData }, serverData);
}

// PageRegistration 형식으로 export (server.ts의 registerPageHandler용)
export default {
  component: ${pageName}Page,
  filling: filling,
};
`;
  }

  // clientModule만 (slotModule 없음) → default export 컴포넌트
  return `// Generated by Mandu - DO NOT EDIT DIRECTLY
// Island-First Rendering: SSR이 island render 직접 사용
// Route ID: ${route.id}
// Pattern: ${route.pattern}
// Client Module: ${route.clientModule}
${pageModuleComment}

import React from "react";
import islandModule from "${clientImportPath}";
${pageImport}

interface Props {
  params: Record<string, string>;
  loaderData?: unknown;
}

${renderHelper}

export default function ${pageName}Page({ params, loaderData }: Props): React.ReactElement {
  const serverData = (loaderData || {}) as Record<string, unknown>;
  return renderClientBackedPage({ params, loaderData }, serverData);
}
`;
}

/**
 * slotModule이 있는 Page Route용 Handler 생성
 * - component와 filling을 함께 export
 * - server.ts에서 filling.executeLoader() 호출 가능
 */
export function generatePageHandlerWithSlot(route: RouteSpec): string {
  const pageName = toPascalCase(route.id);
  const slotImportPath = computeSlotImportPath(route.slotModule!, GENERATED_RELATIVE_PATHS.serverRoutes);
  const pageImportPath = route.kind === "page" && route.componentModule
    ? computeSlotImportPath(route.componentModule, GENERATED_RELATIVE_PATHS.serverRoutes)
    : null;
  const pageImport = pageImportPath ? `import pageModule from "${pageImportPath}";\n` : "";
  const pageRender = pageImportPath
    ? `  return React.createElement(pageModule as React.ComponentType<Props>, { params, loaderData });`
    : `  return React.createElement("div", null,
    React.createElement("h1", null, "${pageName} Page"),
    React.createElement("p", null, "Route ID: ${route.id}"),
    React.createElement("p", null, "Pattern: ${route.pattern}"),
    loaderData ? React.createElement("pre", null, JSON.stringify(loaderData, null, 2)) : null
  );`;

  return `// Generated by Mandu - DO NOT EDIT DIRECTLY
// Route ID: ${route.id}
// Pattern: ${route.pattern}
// Slot Module: ${route.slotModule}

import React from "react";
import filling from "${slotImportPath}";
${pageImport}

interface Props {
  params: Record<string, string>;
  loaderData?: unknown;
}

function ${pageName}Page({ params, loaderData }: Props): React.ReactElement {
${pageRender}
}

// PageRegistration 형식으로 export (server.ts의 registerPageHandler용)
export default {
  component: ${pageName}Page,
  filling: filling,
};
`;
}

function generateClientBackedPageRenderHelper(pageRenderTarget: string, routeId: string): string {
  return `type ManduIslandModule = {
  __mandu_island: true;
  definition: {
    setup: (serverData: Record<string, unknown>) => unknown;
    render: (props: unknown) => React.ReactNode;
  };
};

function isManduIslandModule(value: unknown): value is ManduIslandModule {
  const candidate = value as Partial<ManduIslandModule> | null;
  return !!(
    candidate &&
    typeof candidate === "object" &&
    candidate.__mandu_island === true &&
    typeof candidate.definition?.setup === "function" &&
    typeof candidate.definition?.render === "function"
  );
}

function renderClientBackedPage(props: Props, serverData: Record<string, unknown>): React.ReactElement {
  if (isManduIslandModule(islandModule)) {
    const setupResult = islandModule.definition.setup(serverData);
    return islandModule.definition.render(setupResult) as React.ReactElement;
  }

  if (typeof ${pageRenderTarget} === "function") {
    return React.createElement(${pageRenderTarget} as React.ComponentType<Props>, props);
  }

  if (typeof islandModule === "function") {
    return React.createElement(islandModule as React.ComponentType<Record<string, unknown>>, serverData);
  }

  if (React.isValidElement(${pageRenderTarget})) {
    return ${pageRenderTarget} as React.ReactElement;
  }

  if (React.isValidElement(islandModule)) {
    return islandModule as React.ReactElement;
  }

  throw new Error("[Mandu] Route ${routeId} clientModule must export a Mandu island or React component.");
}`;
}

/**
 * Convert string to PascalCase (handles kebab-case, snake_case)
 * "todo-page" → "TodoPage"
 * "user_profile" → "UserProfile"
 */
function toPascalCase(str: string): string {
  return str
    .split(/[-_]/)
    .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
    .join("");
}

function pathDirname(filePath: string): string {
  return filePath.replace(/\\/g, "/").split("/").slice(0, -1).join("/");
}

function normalizeRouteModulePath(filePath: string | undefined): string {
  return (filePath ?? "").replace(/\\/g, "/").replace(/^\.\//, "");
}
