export const API_VERSION = "v1" as const;
export const CREDIT_PRICE_CENTS = 500 as const;
export const BILLING_CURRENCY = "USD" as const;

type JsonPrimitive = string | number | boolean | null;
export type JsonValue = JsonPrimitive | JsonValue[] | { readonly [key: string]: JsonValue };

export class ContractValidationError extends Error {
  readonly issues: readonly string[];
  constructor(issues: readonly string[]) {
    super(`Invalid API contract: ${issues.join("; ")}`);
    this.name = "ContractValidationError";
    this.issues = issues;
  }
}

export interface SafeParseResult<T> { success: boolean; data?: T; error?: ContractValidationError }
export interface ContractSchema<T> {
  parse(value: unknown): T;
  safeParse(value: unknown): SafeParseResult<T>;
}

function schema<T>(validate: (value: unknown, path: string) => T): ContractSchema<T> {
  return {
    parse(value) { return validate(value, "$" ); },
    safeParse(value) {
      try { return { success: true, data: validate(value, "$") }; }
      catch (error) {
        return { success: false, error: error instanceof ContractValidationError ? error : new ContractValidationError(["validation failed"]) };
      }
    },
  };
}

function fail(path: string, message: string): never { throw new ContractValidationError([`${path}: ${message}`]); }
function object(value: unknown, path: string, keys: readonly string[]): Record<string, unknown> {
  if (typeof value !== "object" || value === null || Array.isArray(value)) fail(path, "expected object");
  const record = value as Record<string, unknown>;
  const extra = Object.keys(record).filter((key) => !keys.includes(key));
  if (extra.length) fail(path, `unexpected field(s): ${extra.join(", ")}`);
  return record;
}
function string(value: unknown, path: string, options: { min?: number; max?: number; pattern?: RegExp } = {}): string {
  if (typeof value !== "string") fail(path, "expected string");
  if (value.length < (options.min ?? 0) || value.length > (options.max ?? Infinity)) fail(path, "invalid length");
  if (options.pattern && !options.pattern.test(value)) fail(path, "invalid format");
  return value;
}
function integer(value: unknown, path: string, min = 0, max = Number.MAX_SAFE_INTEGER): number {
  if (!Number.isSafeInteger(value) || (value as number) < min || (value as number) > max) fail(path, "expected safe integer");
  return value as number;
}
function boolean(value: unknown, path: string): boolean { if (typeof value !== "boolean") fail(path, "expected boolean"); return value; }
function literal<T extends string>(value: unknown, path: string, values: readonly T[]): T {
  if (typeof value !== "string" || !values.includes(value as T)) fail(path, `expected ${values.join(" | ")}`);
  return value as T;
}
function optional<T>(value: unknown, path: string, parse: (v: unknown, p: string) => T): T | undefined { return value === undefined ? undefined : parse(value, path); }
function array<T>(value: unknown, path: string, parse: (v: unknown, p: string) => T): T[] {
  if (!Array.isArray(value)) fail(path, "expected array");
  return value.map((item, index) => parse(item, `${path}[${index}]`));
}
const id = (v: unknown, p: string) => string(v, p, { min: 1, max: 128, pattern: /^[A-Za-z0-9_-]+$/ });
const isoDate = (v: unknown, p: string) => { const result = string(v, p, { min: 20, max: 35 }); if (!Number.isFinite(Date.parse(result))) fail(p, "expected ISO date-time"); return result; };
const email = (v: unknown, p: string) => string(v, p, { min: 3, max: 254, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ });
const password = (v: unknown, p: string) => string(v, p, { min: 8, max: 1024 });

export type ApiErrorCode = "AUTH_REQUIRED" | "INVALID_CREDENTIALS" | "FORBIDDEN" | "NOT_FOUND" | "INVALID_REQUEST" | "UPLOAD_REJECTED" | "INSUFFICIENT_CREDITS" | "CONFLICT" | "RATE_LIMITED" | "INTERNAL_ERROR";
export interface ApiErrorResponse { version: typeof API_VERSION; error: { code: ApiErrorCode; message: string; retryable: boolean } }
export const apiErrorResponseSchema = schema<ApiErrorResponse>((v,p) => { const o=object(v,p,["version","error"]); const e=object(o.error,`${p}.error`,["code","message","retryable"]); return { version: literal(o.version,`${p}.version`,[API_VERSION]), error: { code: literal(e.code,`${p}.error.code`,["AUTH_REQUIRED","INVALID_CREDENTIALS","FORBIDDEN","NOT_FOUND","INVALID_REQUEST","UPLOAD_REJECTED","INSUFFICIENT_CREDITS","CONFLICT","RATE_LIMITED","INTERNAL_ERROR"]), message: string(e.message,`${p}.error.message`,{min:1,max:300}), retryable: boolean(e.retryable,`${p}.error.retryable`) } }; });

export interface RegisterRequest { version: typeof API_VERSION; email: string; password: string }
export const registerRequestSchema = schema<RegisterRequest>((v,p)=>{const o=object(v,p,["version","email","password"]);return{version:literal(o.version,`${p}.version`,[API_VERSION]),email:email(o.email,`${p}.email`),password:password(o.password,`${p}.password`)}});
export interface LoginRequest extends RegisterRequest {}
export const loginRequestSchema = registerRequestSchema;
export interface SessionResponse { version: typeof API_VERSION; user: { id: string; email: string; isAdmin: boolean }; csrfToken: string; creditBalance: number; creditDeficit: number }
export const sessionResponseSchema = schema<SessionResponse>((v,p)=>{const o=object(v,p,["version","user","csrfToken","creditBalance","creditDeficit"]),u=object(o.user,`${p}.user`,["id","email","isAdmin"]);return{version:literal(o.version,`${p}.version`,[API_VERSION]),user:{id:id(u.id,`${p}.user.id`),email:email(u.email,`${p}.user.email`),isAdmin:boolean(u.isAdmin,`${p}.user.isAdmin`)},csrfToken:string(o.csrfToken,`${p}.csrfToken`,{min:16,max:512}),creditBalance:integer(o.creditBalance,`${p}.creditBalance`),creditDeficit:integer(o.creditDeficit,`${p}.creditDeficit`)}});
export interface LogoutResponse { version: typeof API_VERSION; loggedOut: true }
export const logoutResponseSchema = schema<LogoutResponse>((v,p)=>{const o=object(v,p,["version","loggedOut"]);if(o.loggedOut!==true)fail(`${p}.loggedOut`,`expected true`);return{version:literal(o.version,`${p}.version`,[API_VERSION]),loggedOut:true}});

export interface CreateCheckoutRequest { version: typeof API_VERSION; quantity: number }
export const createCheckoutRequestSchema=schema<CreateCheckoutRequest>((v,p)=>{const o=object(v,p,["version","quantity"]);return{version:literal(o.version,`${p}.version`,[API_VERSION]),quantity:integer(o.quantity,`${p}.quantity`,1,1000)}});
export interface CreateCheckoutResponse { version: typeof API_VERSION; checkoutId: string; checkoutUrl: string; quantity: number; unitPriceCents: 500; totalCents: number; currency: "USD" }
export const createCheckoutResponseSchema=schema<CreateCheckoutResponse>((v,p)=>{const o=object(v,p,["version","checkoutId","checkoutUrl","quantity","unitPriceCents","totalCents","currency"]);const quantity=integer(o.quantity,`${p}.quantity`,1,1000),unit=integer(o.unitPriceCents,`${p}.unitPriceCents`,500,500),total=integer(o.totalCents,`${p}.totalCents`);if(total!==quantity*unit)fail(`${p}.totalCents`,`must equal quantity × unitPriceCents`);return{version:literal(o.version,`${p}.version`,[API_VERSION]),checkoutId:id(o.checkoutId,`${p}.checkoutId`),checkoutUrl:string(o.checkoutUrl,`${p}.checkoutUrl`,{min:8,max:2048,pattern:/^https:\/\//}),quantity,unitPriceCents:500,totalCents:total,currency:literal(o.currency,`${p}.currency`,[BILLING_CURRENCY])}});
export interface BillingSummaryResponse { version: typeof API_VERSION; creditBalance: number; creditDeficit: number; unitPriceCents: 500; currency: "USD" }
export const billingSummaryResponseSchema=schema<BillingSummaryResponse>((v,p)=>{const o=object(v,p,["version","creditBalance","creditDeficit","unitPriceCents","currency"]);return{version:literal(o.version,`${p}.version`,[API_VERSION]),creditBalance:integer(o.creditBalance,`${p}.creditBalance`),creditDeficit:integer(o.creditDeficit,`${p}.creditDeficit`),unitPriceCents:integer(o.unitPriceCents,`${p}.unitPriceCents`,500,500) as 500,currency:literal(o.currency,`${p}.currency`,[BILLING_CURRENCY])}});

export interface AcceptUploadRequest { version: typeof API_VERSION; uploadId: string; originalFilename: string; contentType: "application/pdf"; sizeBytes: number; idempotencyKey: string }
export const acceptUploadRequestSchema=schema<AcceptUploadRequest>((v,p)=>{const o=object(v,p,["version","uploadId","originalFilename","contentType","sizeBytes","idempotencyKey"]);return{version:literal(o.version,`${p}.version`,[API_VERSION]),uploadId:id(o.uploadId,`${p}.uploadId`),originalFilename:string(o.originalFilename,`${p}.originalFilename`,{min:1,max:255}),contentType:literal(o.contentType,`${p}.contentType`,["application/pdf"]),sizeBytes:integer(o.sizeBytes,`${p}.sizeBytes`,1),idempotencyKey:string(o.idempotencyKey,`${p}.idempotencyKey`,{min:16,max:128,pattern:/^[A-Za-z0-9_-]+$/})}});
export type ConversionStatus="queued"|"processing"|"completed"|"failed";
export interface Conversion { id:string; originalFilename:string; status:ConversionStatus; acceptedAt:string; sourceExpiresAt:string; completedAt?:string; zipExpiresAt?:string; downloadable:boolean; failureMessage?:string }
function conversion(v:unknown,p:string):Conversion {const o=object(v,p,["id","originalFilename","status","acceptedAt","sourceExpiresAt","completedAt","zipExpiresAt","downloadable","failureMessage"]);const status=literal(o.status,`${p}.status`,["queued","processing","completed","failed"]);const result:Conversion={id:id(o.id,`${p}.id`),originalFilename:string(o.originalFilename,`${p}.originalFilename`,{min:1,max:255}),status,acceptedAt:isoDate(o.acceptedAt,`${p}.acceptedAt`),sourceExpiresAt:isoDate(o.sourceExpiresAt,`${p}.sourceExpiresAt`),downloadable:boolean(o.downloadable,`${p}.downloadable`)};const completedAt=optional(o.completedAt,`${p}.completedAt`,isoDate),zipExpiresAt=optional(o.zipExpiresAt,`${p}.zipExpiresAt`,isoDate),failureMessage=optional(o.failureMessage,`${p}.failureMessage`,(x,q)=>string(x,q,{min:1,max:300}));if(completedAt)result.completedAt=completedAt;if(zipExpiresAt)result.zipExpiresAt=zipExpiresAt;if(failureMessage)result.failureMessage=failureMessage;return result;}
export interface AcceptUploadResponse { version:typeof API_VERSION; conversion:Conversion; creditBalance:number; accepted:boolean }
export const acceptUploadResponseSchema=schema<AcceptUploadResponse>((v,p)=>{const o=object(v,p,["version","conversion","creditBalance","accepted"]);if(o.accepted!==true)fail(`${p}.accepted`,`expected true`);return{version:literal(o.version,`${p}.version`,[API_VERSION]),conversion:conversion(o.conversion,`${p}.conversion`),creditBalance:integer(o.creditBalance,`${p}.creditBalance`),accepted:true}});
export interface ConversionResponse {version:typeof API_VERSION;conversion:Conversion}
export const conversionResponseSchema=schema<ConversionResponse>((v,p)=>{const o=object(v,p,["version","conversion"]);return{version:literal(o.version,`${p}.version`,[API_VERSION]),conversion:conversion(o.conversion,`${p}.conversion`)}});
export interface LibraryRequest {version:typeof API_VERSION;cursor?:string;limit?:number}
export const libraryRequestSchema=schema<LibraryRequest>((v,p)=>{const o=object(v,p,["version","cursor","limit"]);const r:LibraryRequest={version:literal(o.version,`${p}.version`,[API_VERSION])},cursor=optional(o.cursor,`${p}.cursor`,id),limit=optional(o.limit,`${p}.limit`,(x,q)=>integer(x,q,1,100));if(cursor)r.cursor=cursor;if(limit!==undefined)r.limit=limit;return r});
export interface LibraryResponse {version:typeof API_VERSION;items:Conversion[];nextCursor?:string}
export const libraryResponseSchema=schema<LibraryResponse>((v,p)=>{const o=object(v,p,["version","items","nextCursor"]);const r:LibraryResponse={version:literal(o.version,`${p}.version`,[API_VERSION]),items:array(o.items,`${p}.items`,conversion)},next=optional(o.nextCursor,`${p}.nextCursor`,id);if(next)r.nextCursor=next;return r});
export interface DownloadResponse {version:typeof API_VERSION;downloadUrl:string;expiresAt:string;filename:string}
export const downloadResponseSchema=schema<DownloadResponse>((v,p)=>{const o=object(v,p,["version","downloadUrl","expiresAt","filename"]);return{version:literal(o.version,`${p}.version`,[API_VERSION]),downloadUrl:string(o.downloadUrl,`${p}.downloadUrl`,{min:8,max:4096,pattern:/^https:\/\//}),expiresAt:isoDate(o.expiresAt,`${p}.expiresAt`),filename:string(o.filename,`${p}.filename`,{min:5,max:255,pattern:/\.zip$/i})}});

export interface AdminListRequest {version:typeof API_VERSION;cursor?:string;limit?:number;status?:ConversionStatus}
export const adminListRequestSchema=schema<AdminListRequest>((v,p)=>{const o=object(v,p,["version","cursor","limit","status"]);const r:AdminListRequest={version:literal(o.version,`${p}.version`,[API_VERSION])},cursor=optional(o.cursor,`${p}.cursor`,id),limit=optional(o.limit,`${p}.limit`,(x,q)=>integer(x,q,1,100)),status=optional(o.status,`${p}.status`,(x,q)=>literal(x,q,["queued","processing","completed","failed"]));if(cursor)r.cursor=cursor;if(limit!==undefined)r.limit=limit;if(status)r.status=status;return r});
export interface AdminConversion extends Conversion {userId:string;attemptCount:number}
function adminConversion(v:unknown,p:string):AdminConversion {const o=object(v,p,["id","originalFilename","status","acceptedAt","sourceExpiresAt","completedAt","zipExpiresAt","downloadable","failureMessage","userId","attemptCount"]);const base=conversion(Object.fromEntries(Object.entries(o).filter(([k])=>!['userId','attemptCount'].includes(k))),p);return{...base,userId:id(o.userId,`${p}.userId`),attemptCount:integer(o.attemptCount,`${p}.attemptCount`)}}
export interface AdminListResponse {version:typeof API_VERSION;items:AdminConversion[];nextCursor?:string}
export const adminListResponseSchema=schema<AdminListResponse>((v,p)=>{const o=object(v,p,["version","items","nextCursor"]);const r:AdminListResponse={version:literal(o.version,`${p}.version`,[API_VERSION]),items:array(o.items,`${p}.items`,adminConversion)},next=optional(o.nextCursor,`${p}.nextCursor`,id);if(next)r.nextCursor=next;return r});
export interface AdminRetryRequest {version:typeof API_VERSION;conversionId:string;reason:string;idempotencyKey:string}
export const adminRetryRequestSchema=schema<AdminRetryRequest>((v,p)=>{const o=object(v,p,["version","conversionId","reason","idempotencyKey"]);return{version:literal(o.version,`${p}.version`,[API_VERSION]),conversionId:id(o.conversionId,`${p}.conversionId`),reason:string(o.reason,`${p}.reason`,{min:3,max:500}),idempotencyKey:string(o.idempotencyKey,`${p}.idempotencyKey`,{min:16,max:128,pattern:/^[A-Za-z0-9_-]+$/})}});
export interface AdminCreditAdjustmentRequest {version:typeof API_VERSION;userId:string;deltaCredits:number;reason:string;idempotencyKey:string}
export const adminCreditAdjustmentRequestSchema=schema<AdminCreditAdjustmentRequest>((v,p)=>{const o=object(v,p,["version","userId","deltaCredits","reason","idempotencyKey"]);const delta=integer(Math.abs(o.deltaCredits as number),`${p}.deltaCredits`,1,100000)*(Number(o.deltaCredits)<0?-1:1);return{version:literal(o.version,`${p}.version`,[API_VERSION]),userId:id(o.userId,`${p}.userId`),deltaCredits:delta,reason:string(o.reason,`${p}.reason`,{min:3,max:500}),idempotencyKey:string(o.idempotencyKey,`${p}.idempotencyKey`,{min:16,max:128,pattern:/^[A-Za-z0-9_-]+$/})}});
export interface AdminActionResponse {version:typeof API_VERSION;accepted:true;operationId:string}
export const adminActionResponseSchema=schema<AdminActionResponse>((v,p)=>{const o=object(v,p,["version","accepted","operationId"]);if(o.accepted!==true)fail(`${p}.accepted`,`expected true`);return{version:literal(o.version,`${p}.version`,[API_VERSION]),accepted:true,operationId:id(o.operationId,`${p}.operationId`)}});

// Stable PascalCase aliases make schema discovery consistent with the exported types.
export const RegisterRequestSchema=registerRequestSchema, LoginRequestSchema=loginRequestSchema, SessionResponseSchema=sessionResponseSchema, LogoutResponseSchema=logoutResponseSchema;
export const CreateCheckoutRequestSchema=createCheckoutRequestSchema, CreateCheckoutResponseSchema=createCheckoutResponseSchema, BillingSummaryResponseSchema=billingSummaryResponseSchema;
export const AcceptUploadRequestSchema=acceptUploadRequestSchema, AcceptUploadResponseSchema=acceptUploadResponseSchema, ConversionResponseSchema=conversionResponseSchema;
export const LibraryRequestSchema=libraryRequestSchema, LibraryResponseSchema=libraryResponseSchema, DownloadResponseSchema=downloadResponseSchema;
export const AdminListRequestSchema=adminListRequestSchema, AdminListResponseSchema=adminListResponseSchema, AdminRetryRequestSchema=adminRetryRequestSchema, AdminCreditAdjustmentRequestSchema=adminCreditAdjustmentRequestSchema, AdminActionResponseSchema=adminActionResponseSchema, ApiErrorResponseSchema=apiErrorResponseSchema;
