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 const API_ERROR_MESSAGES = {
  AUTH_REQUIRED: "Authentication required",
  INVALID_CREDENTIALS: "Invalid credentials",
  FORBIDDEN: "Forbidden",
  NOT_FOUND: "Resource not found",
  INVALID_REQUEST: "Invalid request",
  UPLOAD_REJECTED: "Upload rejected",
  INSUFFICIENT_CREDITS: "Insufficient credits",
  CONFLICT: "Request conflict",
  RATE_LIMITED: "Too many requests",
  INTERNAL_ERROR: "Internal server error",
} as const satisfies Record<ApiErrorCode, string>;
export interface ApiErrorResponse { version: typeof API_VERSION; error: { code: ApiErrorCode; message: (typeof API_ERROR_MESSAGES)[ApiErrorCode]; 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"]); const 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"]); const message=literal(e.message,`${p}.error.message`,[API_ERROR_MESSAGES[code]]); return { version: literal(o.version,`${p}.version`,[API_VERSION]), error: { code, message, 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";
interface ConversionBase { id:string; originalFilename:string; acceptedAt:string; sourceExpiresAt:string }
export interface PendingConversion extends ConversionBase { status:"queued"|"processing"; downloadable:false; completedAt?:never; zipExpiresAt?:never; failureMessage?:never }
export interface CompletedConversion extends ConversionBase { status:"completed"; completedAt:string; zipExpiresAt:string; downloadable:boolean; failureMessage?:never }
export interface FailedConversion extends ConversionBase { status:"failed"; downloadable:false; failureMessage:string; completedAt?:never; zipExpiresAt?:never }
export type Conversion = PendingConversion | CompletedConversion | FailedConversion;
function conversion(v:unknown,p:string):Conversion {
  const o=object(v,p,["id","originalFilename","status","acceptedAt","sourceExpiresAt","completedAt","zipExpiresAt","downloadable","failureMessage"]);
  const base:ConversionBase={id:id(o.id,`${p}.id`),originalFilename:string(o.originalFilename,`${p}.originalFilename`,{min:1,max:255}),acceptedAt:isoDate(o.acceptedAt,`${p}.acceptedAt`),sourceExpiresAt:isoDate(o.sourceExpiresAt,`${p}.sourceExpiresAt`)};
  const status=literal(o.status,`${p}.status`,["queued","processing","completed","failed"]);
  if(status==="queued"||status==="processing") {
    if(o.completedAt!==undefined||o.zipExpiresAt!==undefined||o.failureMessage!==undefined) fail(p,`${status} conversion contains terminal fields`);
    if(o.downloadable!==false) fail(`${p}.downloadable`,`expected false`);
    return {...base,status,downloadable:false};
  }
  if(status==="completed") {
    if(o.failureMessage!==undefined) fail(`${p}.failureMessage`,`not allowed for completed conversion`);
    return {...base,status,completedAt:isoDate(o.completedAt,`${p}.completedAt`),zipExpiresAt:isoDate(o.zipExpiresAt,`${p}.zipExpiresAt`),downloadable:boolean(o.downloadable,`${p}.downloadable`)};
  }
  if(o.completedAt!==undefined||o.zipExpiresAt!==undefined) fail(p,"failed conversion contains completion fields");
  if(o.downloadable!==false) fail(`${p}.downloadable`,`expected false`);
  return {...base,status,downloadable:false,failureMessage:string(o.failureMessage,`${p}.failureMessage`,{min:1,max:300})};
}
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 DownloadRequest {version:typeof API_VERSION;conversionId:string}
export const downloadRequestSchema=schema<DownloadRequest>((v,p)=>{const o=object(v,p,["version","conversionId"]);return{version:literal(o.version,`${p}.version`,[API_VERSION]),conversionId:id(o.conversionId,`${p}.conversionId`)}});
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"] as const));if(cursor)r.cursor=cursor;if(limit!==undefined)r.limit=limit;if(status)r.status=status;return r});
export type AdminConversion = 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"]);if(!Number.isSafeInteger(o.deltaCredits))fail(`${p}.deltaCredits`,`expected signed safe integer`);const delta=o.deltaCredits as number;if(delta===0||Math.abs(delta)>100000)fail(`${p}.deltaCredits`,`expected non-zero integer between -100000 and 100000`);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, DownloadRequestSchema=downloadRequestSchema, DownloadResponseSchema=downloadResponseSchema;
export const AdminListRequestSchema=adminListRequestSchema, AdminListResponseSchema=adminListResponseSchema, AdminRetryRequestSchema=adminRetryRequestSchema, AdminCreditAdjustmentRequestSchema=adminCreditAdjustmentRequestSchema, AdminActionResponseSchema=adminActionResponseSchema, ApiErrorResponseSchema=apiErrorResponseSchema;
