import { CareInfo, IdentificationResult, Language, PlantHealthCheck } from '../../types'; export type PlanId = 'free' | 'pro'; export type BillingProvider = 'mock' | 'revenuecat' | 'stripe'; export type PurchaseProductId = 'monthly_pro' | 'yearly_pro' | 'topup_small' | 'topup_medium' | 'topup_large'; export type SimulatedWebhookEvent = | 'entitlement_granted' | 'entitlement_revoked' | 'topup_granted' | 'credits_depleted'; export interface BackendDatabaseEntry extends IdentificationResult { imageUri: string; imageStatus?: 'ok' | 'missing' | 'invalid'; categories: string[]; } export interface CreditState { monthlyAllowance: number; usedThisCycle: number; topupBalance: number; available: number; cycleStartedAt: string; cycleEndsAt: string; } export interface EntitlementState { plan: PlanId; provider: BillingProvider; status: 'active' | 'inactive'; renewsAt: string | null; } export interface BillingSummary { entitlement: EntitlementState; credits: CreditState; availableProducts: PurchaseProductId[]; } export interface ScanPlantRequest { userId: string; idempotencyKey: string; imageUri: string; language: Language; } export interface ScanPlantResponse { result: IdentificationResult; creditsCharged: number; modelPath: string[]; modelUsed?: string | null; modelFallbackCount?: number; billing: BillingSummary; } export interface SemanticSearchRequest { userId: string; idempotencyKey: string; query: string; language: Language; } export interface SemanticSearchResponse { status: 'success' | 'no_results'; results: BackendDatabaseEntry[]; creditsCharged: number; billing: BillingSummary; } export interface HealthCheckRequest { userId: string; idempotencyKey: string; imageUri: string; language: Language; plantContext?: { name: string; botanicalName: string; careInfo: CareInfo; description?: string; }; } export interface HealthCheckResponse { healthCheck: PlantHealthCheck; creditsCharged: number; modelUsed?: string | null; modelFallbackCount?: number; billing: BillingSummary; } export interface ServiceHealthResponse { ok: boolean; uptimeSec: number; timestamp: string; openAiConfigured: boolean; dbReady?: boolean; dbPath?: string; stripeConfigured?: boolean; scanModel?: string; healthModel?: string; } export interface SimulatePurchaseRequest { userId: string; idempotencyKey: string; productId: PurchaseProductId; } export interface SimulatePurchaseResponse { appliedProduct: PurchaseProductId; billing: BillingSummary; } export interface SimulateWebhookRequest { userId: string; idempotencyKey: string; event: SimulatedWebhookEvent; payload?: { credits?: number; }; } export interface SimulateWebhookResponse { event: SimulatedWebhookEvent; billing: BillingSummary; } export type BackendErrorCode = | 'INSUFFICIENT_CREDITS' | 'UNAUTHORIZED' | 'TIMEOUT' | 'PROVIDER_ERROR' | 'BAD_REQUEST'; export class BackendApiError extends Error { public readonly code: BackendErrorCode; public readonly status: number; public readonly metadata?: Record; constructor( code: BackendErrorCode, message: string, status = 500, metadata?: Record, ) { super(message); this.name = 'BackendApiError'; this.code = code; this.status = status; this.metadata = metadata; } } export const isBackendApiError = (error: unknown): error is BackendApiError => { return error instanceof BackendApiError; };