import type { ContractLike, ResolveContract } from "./contract-like.js";
import { resolveContract } from "./contract-like.js";
import type { AddedCtxFromHooks, Handler, RouteHook } from "./http.js";
import type {
  AnyUseCaseLike,
  AnyUseCaseRouteDef,
  UseCaseRouteDef,
  ValidatedRouteInputs,
} from "./use-case-route.js";

/**
 * Route registration that connects a contract to the handler implementing it.
 *
 * Most apps keep route definitions in `features/<feature>/routes.ts` and
 * compose them with `defineRoutes(...)`.
 */
export type HandlerRouteDef<
  Ctx,
  CLike extends ContractLike = ContractLike,
  Hooks extends readonly RouteHook<Ctx, object>[] = readonly RouteHook<
    Ctx,
    object
  >[],
> = {
  /**
   * Contract builder or plain contract config for this route.
   */
  contract: CLike;
  /**
   * Route-scoped hooks that run after group hooks and before the handler.
   */
  hooks?: Hooks;
  /**
   * Handler that implements the contract.
   */
  handle: Handler<Ctx & AddedCtxFromHooks<Hooks>, ResolveContract<CLike>>;
  useCase?: never;
  input?: never;
  status?: never;
};

/**
 * Route registration for one contract.
 *
 * Routes either bind the contract directly to a use case (`{ contract,
 * useCase }`) or implement a full handler (`{ contract, handle }`). The full
 * handler form is the escape hatch for response headers, streaming, native
 * `Response` values, and multi-status handling.
 */
export type RouteDef<
  Ctx,
  CLike extends ContractLike = ContractLike,
  Hooks extends readonly RouteHook<Ctx, object>[] = readonly RouteHook<
    Ctx,
    object
  >[],
> = HandlerRouteDef<Ctx, CLike, Hooks> | AnyUseCaseRouteDef<Ctx, CLike, Hooks>;

// biome-ignore lint/suspicious/noExplicitAny: route contract types are erased at collection boundaries
type AnyRouteDef = RouteDef<any, any>;

// biome-ignore lint/suspicious/noExplicitAny: route contract types are erased at collection boundaries
type PlainRouteDef<Ctx> = RouteDef<Ctx, any, readonly []>;

// biome-ignore lint/suspicious/noExplicitAny: route contract types are erased at collection boundaries
type AnyContractRouteDef<Ctx> = RouteDef<Ctx, any>;

const ROUTE_GROUP_KIND = "beignet.route-group";

/**
 * Named collection of related route registrations.
 *
 * Route groups colocate feature routes and can apply scoped route hooks to
 * every route in the group. `defineRoutes(...)` flattens them before server
 * registration while preserving those hooks.
 */
export type RouteGroup<
  Ctx,
  Routes extends readonly AnyRouteDef[] = readonly AnyRouteDef[],
> = {
  /**
   * Internal marker used by `defineRoutes(...)`.
   */
  kind: typeof ROUTE_GROUP_KIND;
  /**
   * Human-readable group name.
   */
  name: string;
  /**
   * Hooks applied to every route in this group.
   */
  hooks?: readonly RouteHook<Ctx, object>[];
  /**
   * Route definitions in this group.
   */
  routes: Routes;
};

type RouteInput<Ctx> =
  | AnyContractRouteDef<Ctx>
  | AnyRouteDef
  | RouteGroup<Ctx, readonly AnyRouteDef[]>;

type ContextualRouteInput<Ctx> =
  | PlainRouteDef<Ctx>
  | RouteGroup<Ctx, readonly AnyRouteDef[]>;

/**
 * App-bound builder for one route registration.
 */
export type RouteDefinitionBuilder<Ctx> = {
  <
    CLike extends ContractLike,
    UC extends AnyUseCaseLike,
    const Hooks extends readonly RouteHook<Ctx, object>[] = readonly [],
  >(
    route: UseCaseRouteDef<Ctx, CLike, UC, Hooks>,
  ): UseCaseRouteDef<Ctx, CLike, UC, Hooks>;
  <
    CLike extends ContractLike,
    const Hooks extends readonly RouteHook<Ctx, object>[] = readonly [],
  >(
    route: HandlerRouteDef<Ctx, CLike, Hooks>,
  ): HandlerRouteDef<Ctx, CLike, Hooks>;
};

/**
 * App-bound builder for a named route group.
 */
export type RouteGroupBuilder<Ctx> = {
  <
    const GroupHooks extends readonly RouteHook<Ctx, object>[] = readonly [],
    const R extends readonly PlainRouteDef<
      Ctx & AddedCtxFromHooks<GroupHooks>
    >[] = readonly PlainRouteDef<Ctx & AddedCtxFromHooks<GroupHooks>>[],
  >(group: {
    name: string;
    hooks?: GroupHooks;
    routes: R & ValidatedRouteInputs<Ctx & AddedCtxFromHooks<GroupHooks>, R>;
  }): RouteGroup<Ctx, R>;
  <
    const GroupHooks extends readonly RouteHook<Ctx, object>[] = readonly [],
    const R extends readonly AnyRouteDef[] = readonly AnyRouteDef[],
  >(group: {
    name: string;
    hooks?: GroupHooks;
    routes: R & ValidatedRouteInputs<Ctx & AddedCtxFromHooks<GroupHooks>, R>;
  }): RouteGroup<Ctx, R>;
};

/**
 * Route declaration builders bound to an application context type.
 */
export interface Routes<Ctx> {
  /** Define one route registration. */
  defineRoute: RouteDefinitionBuilder<Ctx>;
  /** Define a named group of related route registrations. */
  defineRouteGroup: RouteGroupBuilder<Ctx>;
}

type RoutesFromInput<Input> =
  Input extends RouteGroup<infer _Ctx, infer Routes>
    ? Routes
    : Input extends AnyRouteDef
      ? readonly [Input]
      : readonly [];

type FlattenRouteInputs<Inputs extends readonly unknown[]> =
  number extends Inputs["length"]
    ? readonly AnyRouteDef[]
    : Inputs extends readonly [infer First, ...infer Rest]
      ? readonly [...RoutesFromInput<First>, ...FlattenRouteInputs<Rest>]
      : readonly [];

type ContractsFromRouteList<
  // biome-ignore lint/suspicious/noExplicitAny: route contract types are erased at this level
  Routes extends readonly RouteDef<any, any>[],
> = {
  readonly [Index in keyof Routes]: Routes[Index] extends RouteDef<
    // biome-ignore lint/suspicious/noExplicitAny: route contract types are erased at this level
    any,
    infer CLike
  >
    ? ResolveContract<CLike>
    : never;
};

/**
 * Define one route registration with hook-aware handler typing.
 *
 * Direct route objects are still supported. Use this helper when route-scoped
 * hooks enrich `ctx` for a single handler and you want TypeScript to infer the
 * added fields.
 */
function createRouteBuilder<Ctx>(): RouteDefinitionBuilder<Ctx> {
  function define<
    CLike extends ContractLike,
    UC extends AnyUseCaseLike,
    const Hooks extends readonly RouteHook<Ctx, object>[] = readonly [],
  >(
    route: UseCaseRouteDef<Ctx, CLike, UC, Hooks>,
  ): UseCaseRouteDef<Ctx, CLike, UC, Hooks>;
  function define<
    CLike extends ContractLike,
    const Hooks extends readonly RouteHook<Ctx, object>[] = readonly [],
  >(
    route: HandlerRouteDef<Ctx, CLike, Hooks>,
  ): HandlerRouteDef<Ctx, CLike, Hooks>;
  function define(route: unknown): unknown {
    return route;
  }

  return define;
}

/**
 * Define and flatten route registrations with strong type inference.
 *
 * Pass route definitions and route groups here before `createServer(...)`.
 * Group entries are flattened so downstream tooling receives one route list.
 *
 * @example
 * ```ts
 * const routes = defineRoutes<AppContext>([
 *   { contract: listPosts, useCase: listPostsUseCase },
 * ]);
 * ```
 */
export function defineRoutes<
  Ctx,
  const R extends
    readonly ContextualRouteInput<Ctx>[] = readonly ContextualRouteInput<Ctx>[],
>(routes: R & ValidatedRouteInputs<Ctx, R>): FlattenRouteInputs<R>;
export function defineRoutes<
  Ctx,
  const R extends readonly RouteInput<Ctx>[] = readonly RouteInput<Ctx>[],
>(routes: R & ValidatedRouteInputs<Ctx, R>): FlattenRouteInputs<R>;
export function defineRoutes<
  Ctx,
  const R extends readonly RouteInput<Ctx>[] = readonly RouteInput<Ctx>[],
>(routes: R): FlattenRouteInputs<R> {
  const flattened: RouteDef<Ctx>[] = [];

  for (const route of routes) {
    if (isRouteGroup(route)) {
      for (const groupRoute of route.routes) {
        flattened.push({
          ...groupRoute,
          hooks: [...(route.hooks ?? []), ...(groupRoute.hooks ?? [])],
        });
      }
    } else {
      flattened.push(route);
    }
  }

  return flattened as unknown as FlattenRouteInputs<R>;
}

/**
 * Extract contract configs from a route list.
 *
 * Use this to drive clients, OpenAPI, and docs from the same route list passed
 * to `createServer(...)`.
 */
export function contractsFromRoutes<
  // biome-ignore lint/suspicious/noExplicitAny: route contract types are erased at this level
  const R extends readonly RouteDef<any, any>[],
>(routes: R): ContractsFromRouteList<R> {
  return routes.map((route) =>
    resolveContract(route.contract),
  ) as unknown as ContractsFromRouteList<R>;
}

/**
 * Define a named group of related route registrations.
 *
 * Route groups are flattened by defineRoutes, so createServer still receives
 * a regular route list while app code can keep feature route wiring and scoped
 * hooks colocated.
 *
 * @example
 * ```ts
 * const { defineRouteGroup } = createRoutes<AppContext>();
 * const todoRoutes = defineRouteGroup({
 *   name: "todos",
 *   hooks: [auth.optional()],
 *   routes: [
 *     { contract: listTodos, useCase: listTodosUseCase },
 *   ]
 * });
 * ```
 */
function createRouteGroupBuilder<Ctx>(): RouteGroupBuilder<Ctx> {
  const createGroup = <
    const GroupHooks extends readonly RouteHook<Ctx, object>[] = readonly [],
    const GroupRoutes extends readonly AnyRouteDef[] = readonly AnyRouteDef[],
  >(input: {
    name: string;
    hooks?: GroupHooks;
    routes: GroupRoutes;
  }): RouteGroup<Ctx, GroupRoutes> => ({
    kind: ROUTE_GROUP_KIND,
    name: input.name,
    hooks: input.hooks,
    routes: input.routes,
  });

  return createGroup as RouteGroupBuilder<Ctx>;
}

/**
 * Create route declaration builders bound to an application context type.
 *
 * Call this once in `lib/routes.ts`, then import the app-bound builders from
 * feature route files.
 *
 * @example
 * ```ts
 * export const { defineRoute, defineRouteGroup } =
 *   createRoutes<AppContext>();
 * ```
 */
export function createRoutes<Ctx>(): Routes<Ctx> {
  return {
    defineRoute: createRouteBuilder<Ctx>(),
    defineRouteGroup: createRouteGroupBuilder<Ctx>(),
  };
}

function isRouteGroup<Ctx>(route: RouteInput<Ctx>): route is RouteGroup<Ctx> {
  return (
    typeof route === "object" &&
    route !== null &&
    "kind" in route &&
    route.kind === ROUTE_GROUP_KIND
  );
}
