/**
 * Shared API-response types — the single source of truth for client/server API contracts.
 *
 * CLOSE-THE-CLASS (#52). The client consumes API responses; where it read them as `any` (or
 * re-declared local interfaces), field-name mismatches rendered as silent blanks/defaults and
 * shipped undetected — the F4 class (backups, safety-rails, uganda, system-pulse). Types defined
 * HERE are imported by BOTH the server (which returns them) and the client (`useQuery<T>`), so a
 * field-name divergence on either side becomes a COMPILE ERROR. Extend surface-by-surface;
 * new API queries should be typed against this file, not `any`.
 *
 * Provenance: types are MOVED here from server/lib (pure relocation — byte-identical shapes, no
 * runtime change). server/lib imports them back rather than re-declaring, so there is one
 * definition and no second copy to drift.
 */

/**
 * Wire shape of a server value after JSON serialization: `Date` columns arrive as ISO strings
 * (JSON has no Date type), recursively through arrays/objects. Use on the CLIENT when typing a
 * query against a Drizzle `$inferSelect` row — `useQuery<Jsonify<Row>[]>` — so timestamp fields
 * are typed as the strings actually received, not the server-side `Date`. Field names are
 * preserved, so the field-mismatch protection is unchanged; only `Date` → `string` is remapped.
 * The SERVER keeps the raw row type (real `Date` before serialization).
 */
export type Jsonify<T> = T extends Date
  ? string
  : T extends Array<infer U>
    ? Array<Jsonify<U>>
    : T extends object
      ? { [K in keyof T]: Jsonify<T[K]> }
      : T;

// ============ SAFETY RAILS (kill switch) — /api/safety/* ============

export type SafetyViolationType =
  | "REWARD_HACKING"
  | "LOGIC_LOOP"
  | "RATE_ANOMALY"
  | "DATA_EXFILTRATION"
  | "PRIVILEGE_ESCALATION"
  | "MODEL_DRIFT"
  | "CONSENSUS_FAILURE"
  | "TEMPORAL_ANOMALY";

export type SafetyRailStatus = "ARMED" | "TRIGGERED" | "MAINTENANCE" | "DISABLED";

/**
 * PUBLIC response shape for /api/safety/keys — deliberately OMITS `keyHash` (#53 security fix).
 * keyHash is the SHA-256 credential verifier of the agent's bearer key; it stays server-side (the
 * internal `AgentApiKeyRecord` in server/lib/safety-rails.ts holds it) and is stripped from the
 * response by getAgentKeys(). Because the client types its query against THIS shape, reading
 * `key.keyHash` is now a COMPILE ERROR — the exposure is structurally prevented, not just removed.
 */
export interface AgentApiKey {
  id: string;
  agentType: string;
  createdAt: Date;
  expiresAt: Date;
  isRevoked: boolean;
  revokedAt?: Date;
  revokedReason?: string;
}

export interface SafetyViolation {
  id: string;
  type: SafetyViolationType;
  severity: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL";
  agentId: string;
  description: string;
  evidence: Record<string, any>;
  detectedAt: Date;
  handledAt?: Date;
  action: "LOGGED" | "WARNING" | "THROTTLED" | "REVOKED" | "FULL_LOCKDOWN";
}

export interface SafetyRailConfig {
  maxApiCallsPerMinute: number;
  maxDecisionsPerHour: number;
  maxRejectionRatioThreshold: number;
  loopDetectionWindow: number;
  maxRepeatedActions: number;
  maxConfidenceDeviation: number;
  minConsensusThreshold: number;
  allowedOperatingHours: { start: number; end: number };
  maintenanceWindow: { start: number; end: number };
  criticalViolationsToLockdown: number;
  lockdownCooldownMinutes: number;
}

export interface SafetyRailState {
  status: SafetyRailStatus;
  lastCheck: Date;
  violationsToday: number;
  criticalViolationsToday: number;
  activeAgents: number;
  revokedKeys: number;
  lockdownActivatedAt?: Date;
  lockdownReason?: string;
  lockdownExpiresAt?: Date;
}

/** /api/safety/stats — the getViolationStats() return shape. */
export interface SafetyViolationStats {
  total: number;
  byType: Record<SafetyViolationType, number>;
  bySeverity: Record<string, number>;
  last24Hours: number;
}

// ============ BACKUP & RECOVERY — /api/backups/* ============
// Moved from server/lib/backup-service.ts (#52 Tier-1 rollout, one source of truth). The backups
// LIST returns `BackupRecord` rows (the drizzle row type in ./schema) — imported there, typed on
// both listBackups() and the client query so neither side can drift to `any`.

export interface BackupStats {
  totalBackups: number;
  lastBackupTime: Date | null;
  totalSizeBytes: number;
  averageSizeBytes: number;
  completedBackups: number;
  failedBackups: number;
  // NOTE: not currently provided by /api/backups/stats — the client renders "Not Set". Optional
  // so a future scheduler can populate it; documented so it doesn't read as a silent gap.
  nextScheduled?: string;
}

// A DOCUMENTED disaster-recovery plan — target objectives + intended strategies + planned schedule.
// rpo/rto/recoveryStrategies/testSchedule/status are hardcoded plan content (NOT measured/operational
// telemetry); lastTestDate is the latest backup completion (the client labels it honestly as such,
// NOT as a DR-test date). The client frames this whole tab as a documented plan, not operational state.
export interface RecoveryPlan {
  rpo: { value: number; unit: string; description: string };
  rto: { value: number; unit: string; description: string };
  recoveryStrategies: string[];
  testSchedule: string;
  lastTestDate: Date | null;
  status: string;
}

export interface RetentionCandidate {
  id: string;
  backupType: string;
  createdAt: Date | null;
  retentionDays: number;
  expiresAt: string;
  sizeBytes: number;
  fileOnDisk: boolean;
}

export interface RetentionStatus {
  policyDaysDefault: number;
  now: string;
  totalCompleted: number;
  live: number;
  expiredPendingPrune: number;
  reclaimableBytes: number;
  alreadyPruned: number;
  autoPruneEnabled: boolean;
  encryptionAtRest: { configured: boolean; algorithm: string; keySource: string | null; note: string };
  nextExpiryAt: string | null;
  candidates: RetentionCandidate[];
}

// ============ THREATS — /api/threats/stats ============
// The storage.getThreatsStats() return shape, surfaced on /system-pulse. Typed both-sides
// (storage returns it, the client useQuery reads it) so field/precision mismatches are compile
// errors. NOTE bioVaultScore is `number | null` (no identity-vault telemetry wired → null).
export interface ThreatsStats {
  totalNeutralized: number;
  criticalAnomalies: number;
  bioVaultScore: number | null;
  activeThreats: number;
}

// ============ UGANDA SOVEREIGNTY — /api/uganda/sovereignty/rules ============
// Moved from server/lib/uganda-compliance.ts (#52 Tier-1). Locks the e5233d9 reconcile (the client
// reads dataTypes[]/allowedJurisdictions[]/regulation/penaltyPercentage/isActive — the REAL fields);
// with useQuery<any[]> those reads were correct-but-unprotected. Now a divergence is a compile error.
export interface SovereigntyRule {
  id: string;
  name: string;
  dataTypes: string[];
  allowedJurisdictions: string[];
  regulation: string;
  penaltyPercentage: number;
  isActive: boolean;
}

// ============ WAVE-8 EXAMINER SUITE — computed aggregates (#52 Tier-2) ============
// The 4 stats aggregates + scenarios + the raw-SQL i18n key list are computed API shapes (not DB
// rows). Defined here and RETURNED by their server functions in server/lib/pilot-readiness-extras-8.ts
// so a rename on either side is a compile error. Field names are the server's real keys (verified
// against getE2eStats/getMomoStats/getComplaintStats/getReplayStats). The 12 list endpoints type
// against their $inferSelect row types in @shared/schema-pilot-extras-8 instead.
export interface E2eScenario {
  code: string;
  name: string;
  stepCount: number;
}
export interface E2eStats {
  totalRuns: number;
  runsLast30d: number;
  passRatePct: number | null; // null = no runs in window; a rate over 0 runs is undefined, not 0%
  avgDurationMs: number | null; // null = no runs in window; an average over 0 runs is undefined, not 0ms
}
export interface MomoStats {
  totalTxns: number;
  completedCount: number;
  flaggedCount: number;
  totalVolumeUgx: number;
  mtnCount: number;
  airtelCount: number;
}
export interface ComplaintStats {
  total: number;
  open: number;
  resolved: number;
  escalated: number;
  slaBreached: number;
}
export interface ReplayStats {
  totalReplays: number;
  reproducibleCount: number;
  reproducibilityRatePct: number | null; // null = no replays; a rate over 0 replays is undefined, not 0%
}
// /api/i18n/keys/list is a raw db.execute SQL query — keys are snake_case (NOT Drizzle-mapped).
export interface TranslationKeyRow {
  translation_key: string;
  locale_count: number;
}

// ============ WAVE-9 REGULATOR SUITE — computed aggregate (#52 Tier-2) ============
// /api/public/status — the getPublicStatus() aggregate (server/lib/pilot-readiness-extras-9.ts).
// The client reads only `overall` + `components`; the server also returns the incident rows.
// getPublicStatus builds a `PublicStatus` literal (guarding these two field names server-side)
// then spreads the incident lists on, so both sides reference this shape for what the UI renders.
export interface StatusComponent {
  name: string;
  status: string;
}
export interface PublicStatus {
  overall: string;
  components: StatusComponent[];
}

// ============ COMMERCIAL compliance-claim contracts — /api/commercial/* (#52 Tier-2 Cycle 1) ============
// The highest-stakes /commercial-features panels: PDPO-breach, PCI-DSS, SOC 2, SLA. Each shape is
// defined here and RETURNED by its backing service function (pdpo-breach / pci-logging / soc2-evidence /
// sla-prediction — annotated there, verified sole caller is the route), so a field rename on either side
// is a compile error. NARROW views where the server type pulls heavy sub-types (SOC2 Evidence[], the
// full BreachNotification): the view carries only the client-consumed fields; the server guards them via
// a typed value (getSOC2Dashboard builds a `Soc2DashboardView` literal, then spreads the rest on).
export interface BreachStats {
  total: number;
  active: number;
  overdue: number;
  avgResolutionHours: number;
  byStatus: Record<string, number>;
  bySeverity: Record<string, number>;
}
// Client-consumed subset of BreachNotification (the full row carries server-only sub-types).
export interface BreachListItem {
  id: string;
  severity: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL";
  description: string;
  hoursRemaining: number;
}
export interface BreachesResponse {
  breaches: BreachListItem[];
  stats: BreachStats;
}
export interface PciComplianceScore {
  score: number;
  grade: string;
  assessed: boolean;
  note?: string;
  findings: { category: string; status: string; count: number }[];
}
// The /pci-logs endpoint also returns `logs`; the client reads only complianceScore.
export interface PciLogsResponse {
  complianceScore: PciComplianceScore;
}
// Narrow view of getSOC2Dashboard (the full return also carries evidence rows, upcoming tests, etc.).
export interface Soc2DashboardView {
  overallScore: number | null; // null = nothing assessed yet (0 controls with a real verdict); not "0% compliant"
  controlsStatus: { implemented: number; partial: number; notImplemented: number; notAssessed: number };
  // The self-attested caveat travels WITH the score as one payload unit — a client cannot render the
  // number without also having its qualifier (the disclosure-doesn't-travel fix, 2026-07-25).
  disclaimer: string;
}
export interface SlaMetrics {
  period: string;
  totalTickets: number;
  breachedTickets: number;
  atRiskTickets: number;
  avgResolutionTime: number | null; // null = no resolved tickets; an average over 0 is undefined, not 0
  slaComplianceRate: number | null; // null = no tickets in period; a rate over 0 tickets is undefined, not 100%
  byPriority: Record<string, { total: number; breached: number; avgTime: number }>;
  predictions: { accurate: number | null; total: number; accuracy: number | null };
}
// Client-consumed subset of { ticket: Ticket; prediction: SLAPrediction } (server types are wider).
export interface SlaAtRiskTicket {
  ticket: { id: string; title: string; priority: "P1" | "P2" | "P3" | "P4" };
  prediction: { breachProbability: number };
}
export interface SlaDashboardResponse {
  metrics: SlaMetrics;
  atRiskTickets: SlaAtRiskTicket[];
}

// ============ WAVE-10 SANDBOX SUITE — computed aggregate (#52 Tier-2) ============
// /api/wave10/consent/verify-chain — verifyConsentChain() (pilot-readiness-extras-10.ts).
// The 14 list endpoints type against their $inferSelect rows in @shared/schema-pilot-extras-10.
export interface ConsentChainVerification {
  valid: boolean;
  brokenAt?: string;
  reason?: string;
  total: number;
}

// ============ WAVE-11 PILOT-READINESS SUITE — computed aggregates + enriched view (#52 Tier-2) ============
// The 11 list endpoints type against their $inferSelect rows in @shared/schema-pilot-extras-11.
// These are the 4 summary aggregates + the ENRICHED key-inventory item (row + computed age/urgency).
// Server functions (pilot-readiness-extras-11.ts) reference these — the summaries via a typed literal
// then spread the rest; keys/list + keys/summary annotate directly. Narrow = the client-consumed slice.
export interface CanarySummary { avgScore7d: number; activeAlerts: number; }
export interface SlaSummary { openCount: number; criticalOpen: number; }
export interface KeyInventorySummary { total: number; overdue: number; dueSoon: number; postQuantum: number; }
export interface Wave11Score { compositeScore: number; grade: string; }
// listKeyInventory() returns each key_inventory row PLUS computed ageDays/daysToDue/urgency.
export interface KeyInventoryView {
  id: string;
  keyId: string;
  keyType: string;
  algorithm: string;
  bitLength: number;
  storageLocation: string;
  ageDays: number;
  daysToDue: number;
  urgency: "ok" | "due-soon" | "overdue";
  postQuantum: number;
}

// ============ UGANDA COMPLIANCE HUB — /api/uganda/* (#52 Tier-2, floor-trace) ============
// The whole page was built without the server shapes → systematic field-hedging over honest data
// (the Tier-3 passes already removed the fabrications; empty stores are honest-empty). These are the
// REAL response shapes; the 5 row interfaces are RELOCATED here (uganda-compliance.ts imports them
// back) so client + server share one definition and a field rename is a compile error.

// getComplianceScore() — the client renders `overall` with `assessedPercentage` coverage; `runId`
// null ⇒ no run yet ⇒ NOT_ASSESSED (never "0%", which would read as "failed everything").
export interface BouComplianceScore {
  overall: number;
  assessedPercentage: number;
  total: number;
  assessed: number;
  passed: number;
  failed: number;
  notAssessed: number;
  runId: string | null;
}
// getControls() — client-consumed subset of BoUControlMapping (controlName/complianceStatus/controlId).
export interface BouControlView {
  controlId: string;
  controlName: string;
  category: string;
  complianceStatus: "COMPLIANT" | "PARTIAL" | "NON_COMPLIANT" | "NOT_APPLICABLE" | "NOT_ASSESSED";
}
export interface DataResidencyCheck {
  id: string;
  dataType: "PII" | "FINANCIAL" | "BIOMETRIC" | "TELEMETRY";
  sourceLocation: string;
  destinationLocation: string;
  isCompliant: boolean;
  regulatoryReference: string;
  timestamp: Date;
  // Detection verdict, NOT an enforcement action: checkDataTransfer is a monitor
  // (only reachable via POST /api/uganda/sovereignty/check) and routeData — the
  // path that would actually block egress — is unwired. So this records whether a
  // transfer WOULD comply, never that the platform prevented one. (Was
  // "ALLOWED"/"BLOCKED"/"FLAGGED" — "BLOCKED" over-claimed prevention the platform
  // does not perform; FLAGGED was never written.)
  action: "COMPLIANT" | "NON_COMPLIANT";
  details: string;
}
// Sovereignty-lock data-residency infrastructure. Single-sourced here (#52) so the
// server writer and the client reader share one shape — a field rename breaks both at
// compile. NOTE: over the wire the Date fields serialize to ISO strings; the client
// reads them via `new Date(...)`, which accepts either. `location` is nested (region
// lives at `location.region`, NOT a flat `region`).
export interface SovereigntyNode {
  nodeId: string;
  name: string;
  type: "PRIMARY" | "SECONDARY" | "EDGE";
  location: {
    city: string;
    region: string;
    coordinates: { lat: number; lng: number };
  };
  ipRange: string;
  status: "ACTIVE" | "STANDBY" | "MAINTENANCE";
  dataCategories: ("PII" | "FINANCIAL" | "AUDIT" | "TELEMETRY")[];
  lastHealthCheck: Date;
}
export interface DataRoutingDecision {
  id: string;
  dataType: "PII" | "FINANCIAL" | "AUDIT" | "TELEMETRY";
  sourceIP: string;
  destinationIP: string;
  destinationNode: string;
  isWithinUganda: boolean;
  decision: "ROUTED" | "BLOCKED" | "QUARANTINED";
  sovereigntyViolation: boolean;
  timestamp: Date;
}
export interface USSDSession {
  sessionId: string;
  msisdn: string;
  sessionCode: string;
  startTime: Date;
  lastActivity: Date;
  expiresAt: Date;
  locationId: string;
  pinEntrySpeed: number;
  isActive: boolean;
  riskScore: number;
  simSwapAlert: boolean;
}
export interface SimSwapCheck {
  id: string;
  msisdn: string;
  timestamp: Date;
  previousLocationId: string;
  currentLocationId: string;
  previousPinSpeed: number;
  currentPinSpeed: number;
  isAnomalous: boolean;
  riskFactors: string[];
  action: "ALLOWED" | "BLOCKED" | "MFA_REQUIRED";
}
export interface BereaKuGuardAlert {
  id: string;
  type: "PHISHING" | "SOCIAL_ENGINEERING" | "SIM_SWAP" | "ACCOUNT_TAKEOVER" | "MONEY_MULE";
  riskLevel: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL";
  affectedMsisdn: string;
  description: string;
  educationalContent: string;
  suggestedAction: string;
  timestamp: Date;
  resolved: boolean;
}
export interface BereaTip { topic: string; tip: string; luganda: string; }
export interface EdgeNode {
  nodeId: string;
  name: string;
  location: string;
  region: "CENTRAL" | "EASTERN" | "WESTERN" | "NORTHERN";
  coordinates: { lat: number; lng: number };
  status: "ONLINE" | "OFFLINE" | "DEGRADED" | "SYNCING";
  lastSync: Date;
  cachedTrustScores: number;
  localVelocityLimit: number;
  connectionType: "FIBER" | "SATELLITE" | "MOBILE_4G" | "MICROWAVE";
  isMainBrain: boolean;
}
export interface EdgeStats {
  totalNodes: number;
  onlineNodes: number;
  offlineNodes: number;
  totalCachedScores: number;
  pendingSync: number;
}
