// Synor ZK SDK for JavaScript/TypeScript
//
// Zero-Knowledge proof systems for ZK-Rollups and privacy.

import {
  ZkConfig,
  ZkError,
  DEFAULT_CONFIG,
  ProofSystem,
  CircuitType,
  Proof,
  VerificationKey,
  ProvingKey,
  CircuitConfig,
  ProofRequest,
  VerificationResult,
  Batch,
  RollupStats,
  Transfer,
  Deposit,
  Withdrawal,
  AccountState,
  MerkleProof,
  TrustedSetup,
  CeremonyContribution,
} from './types';

export * from './types';

/**
 * Synor ZK SDK Client
 *
 * Provides zero-knowledge proof generation and verification for ZK-Rollups.
 *
 * @example
 * ```typescript
 * const zk = new SynorZk({ apiKey: 'your-api-key' });
 *
 * // Generate a proof
 * const proof = await zk.proofs.generate({
 *   circuitType: CircuitType.Transfer,
 *   publicInputs: [...],
 *   privateInputs: [...],
 * });
 *
 * // Verify the proof
 * const result = await zk.proofs.verify(proof);
 * console.log('Valid:', result.valid);
 * ```
 */
export class SynorZk {
  private readonly config: Required<ZkConfig>;
  private closed = false;

  public readonly proofs: ProofsClient;
  public readonly circuits: CircuitsClient;
  public readonly rollup: RollupClient;
  public readonly state: StateClient;
  public readonly ceremony: CeremonyClient;

  constructor(config: ZkConfig) {
    this.config = {
      ...DEFAULT_CONFIG,
      ...config,
    } as Required<ZkConfig>;

    this.proofs = new ProofsClient(this);
    this.circuits = new CircuitsClient(this);
    this.rollup = new RollupClient(this);
    this.state = new StateClient(this);
    this.ceremony = new CeremonyClient(this);
  }

  /**
   * Get the default proof system
   */
  get defaultProofSystem(): ProofSystem {
    return this.config.defaultProofSystem;
  }

  /**
   * Health check
   */
  async healthCheck(): Promise<boolean> {
    try {
      const result = await this.get<{ status: string }>('/health');
      return result.status === 'healthy';
    } catch {
      return false;
    }
  }

  /**
   * Get service info
   */
  async getInfo(): Promise<Record<string, unknown>> {
    return this.get('/info');
  }

  /**
   * Close the client
   */
  close(): void {
    this.closed = true;
  }

  /**
   * Check if client is closed
   */
  get isClosed(): boolean {
    return this.closed;
  }

  // Internal HTTP methods
  async get<T>(path: string): Promise<T> {
    return this.request<T>('GET', path);
  }

  async post<T>(path: string, body?: unknown): Promise<T> {
    return this.request<T>('POST', path, body);
  }

  async delete<T>(path: string): Promise<T> {
    return this.request<T>('DELETE', path);
  }

  private async request<T>(method: string, path: string, body?: unknown): Promise<T> {
    if (this.closed) {
      throw new ZkError('Client has been closed', 'CLIENT_CLOSED');
    }

    const url = `${this.config.endpoint}${path}`;
    const headers: Record<string, string> = {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${this.config.apiKey}`,
      'X-SDK-Version': 'js/0.1.0',
    };

    let lastError: Error | null = null;

    for (let attempt = 0; attempt <= this.config.retries; attempt++) {
      try {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);

        const response = await fetch(url, {
          method,
          headers,
          body: body ? JSON.stringify(body) : undefined,
          signal: controller.signal,
        });

        clearTimeout(timeoutId);

        if (!response.ok) {
          const error = await response.json().catch(() => ({}));
          throw new ZkError(
            error.message || `HTTP ${response.status}`,
            error.code,
            response.status
          );
        }

        return await response.json();
      } catch (error) {
        lastError = error as Error;
        if (error instanceof ZkError) throw error;
        if (attempt < this.config.retries) {
          await new Promise((r) => setTimeout(r, 100 * Math.pow(2, attempt)));
        }
      }
    }

    throw new ZkError(lastError?.message || 'Request failed', 'NETWORK_ERROR');
  }
}

/**
 * Proofs sub-client
 */
export class ProofsClient {
  constructor(private readonly zk: SynorZk) {}

  /**
   * Generate a zero-knowledge proof
   */
  async generate(request: ProofRequest): Promise<Proof> {
    return this.zk.post('/proofs/generate', {
      circuit_type: request.circuitType,
      public_inputs: request.publicInputs,
      private_inputs: request.privateInputs,
      system: request.system || this.zk.defaultProofSystem,
    });
  }

  /**
   * Verify a proof
   */
  async verify(proof: Proof): Promise<VerificationResult> {
    return this.zk.post('/proofs/verify', {
      proof_id: proof.id,
      data: proof.data,
      public_inputs: proof.publicInputs,
      system: proof.system,
      circuit_type: proof.circuitType,
    });
  }

  /**
   * Get a proof by ID
   */
  async get(proofId: string): Promise<Proof> {
    return this.zk.get(`/proofs/${proofId}`);
  }

  /**
   * List recent proofs
   */
  async list(options?: { limit?: number; offset?: number }): Promise<Proof[]> {
    const params = new URLSearchParams();
    if (options?.limit) params.set('limit', options.limit.toString());
    if (options?.offset) params.set('offset', options.offset.toString());
    const query = params.toString();
    const result = await this.zk.get<{ proofs: Proof[] }>(`/proofs${query ? `?${query}` : ''}`);
    return result.proofs || [];
  }

  /**
   * Serialize a proof to bytes
   */
  async serialize(proof: Proof): Promise<Uint8Array> {
    const result = await this.zk.post<{ data: string }>('/proofs/serialize', {
      proof_id: proof.id,
    });
    return Uint8Array.from(atob(result.data), (c) => c.charCodeAt(0));
  }

  /**
   * Deserialize bytes to a proof
   */
  async deserialize(data: Uint8Array, system: ProofSystem): Promise<Proof> {
    const base64 = btoa(String.fromCharCode(...data));
    return this.zk.post('/proofs/deserialize', {
      data: base64,
      system,
    });
  }
}

/**
 * Circuits sub-client
 */
export class CircuitsClient {
  constructor(private readonly zk: SynorZk) {}

  /**
   * Get verification key for a circuit
   */
  async getVerificationKey(circuitType: CircuitType, system?: ProofSystem): Promise<VerificationKey> {
    return this.zk.get(`/circuits/${circuitType}/vk?system=${system || this.zk.defaultProofSystem}`);
  }

  /**
   * Get proving key for a circuit
   */
  async getProvingKey(circuitType: CircuitType, system?: ProofSystem): Promise<ProvingKey> {
    return this.zk.get(`/circuits/${circuitType}/pk?system=${system || this.zk.defaultProofSystem}`);
  }

  /**
   * List available circuits
   */
  async list(): Promise<CircuitConfig[]> {
    const result = await this.zk.get<{ circuits: CircuitConfig[] }>('/circuits');
    return result.circuits || [];
  }

  /**
   * Get circuit configuration
   */
  async getConfig(circuitType: CircuitType): Promise<CircuitConfig> {
    return this.zk.get(`/circuits/${circuitType}/config`);
  }

  /**
   * Compile a custom circuit
   */
  async compile(config: CircuitConfig): Promise<{ circuitId: string }> {
    return this.zk.post('/circuits/compile', config);
  }
}

/**
 * Rollup sub-client
 */
export class RollupClient {
  constructor(private readonly zk: SynorZk) {}

  /**
   * Get rollup statistics
   */
  async getStats(): Promise<RollupStats> {
    return this.zk.get('/rollup/stats');
  }

  /**
   * Get current batch
   */
  async getCurrentBatch(): Promise<Batch> {
    return this.zk.get('/rollup/batch/current');
  }

  /**
   * Get batch by number
   */
  async getBatch(batchNumber: number): Promise<Batch> {
    return this.zk.get(`/rollup/batch/${batchNumber}`);
  }

  /**
   * Submit a transfer
   */
  async submitTransfer(transfer: Transfer): Promise<{ txId: string }> {
    return this.zk.post('/rollup/transfer', transfer);
  }

  /**
   * Submit a deposit
   */
  async submitDeposit(deposit: Deposit): Promise<{ txId: string }> {
    return this.zk.post('/rollup/deposit', deposit);
  }

  /**
   * Submit a withdrawal
   */
  async submitWithdrawal(withdrawal: Withdrawal): Promise<{ txId: string }> {
    return this.zk.post('/rollup/withdraw', withdrawal);
  }

  /**
   * Finalize current batch (generates proof and submits to L1)
   */
  async finalizeBatch(): Promise<Batch> {
    return this.zk.post('/rollup/batch/finalize', {});
  }

  /**
   * Get pending transactions
   */
  async getPendingTransactions(): Promise<Transfer[]> {
    const result = await this.zk.get<{ transactions: Transfer[] }>('/rollup/pending');
    return result.transactions || [];
  }
}

/**
 * State sub-client
 */
export class StateClient {
  constructor(private readonly zk: SynorZk) {}

  /**
   * Get current state root
   */
  async getRoot(): Promise<string> {
    const result = await this.zk.get<{ root: string }>('/state/root');
    return result.root;
  }

  /**
   * Get account state
   */
  async getAccount(address: string): Promise<AccountState> {
    return this.zk.get(`/state/account/${address}`);
  }

  /**
   * Get merkle proof for account inclusion
   */
  async getMerkleProof(address: string): Promise<MerkleProof> {
    return this.zk.get(`/state/proof/${address}`);
  }

  /**
   * Verify merkle proof
   */
  async verifyMerkleProof(proof: MerkleProof): Promise<boolean> {
    const result = await this.zk.post<{ valid: boolean }>('/state/proof/verify', proof);
    return result.valid;
  }

  /**
   * Get state at specific batch
   */
  async getStateAtBatch(batchNumber: number): Promise<{ root: string; accountCount: number }> {
    return this.zk.get(`/state/batch/${batchNumber}`);
  }
}

/**
 * Ceremony sub-client (trusted setup)
 */
export class CeremonyClient {
  constructor(private readonly zk: SynorZk) {}

  /**
   * Get ceremony status
   */
  async getStatus(circuitType: CircuitType, system?: ProofSystem): Promise<TrustedSetup> {
    return this.zk.get(`/ceremony/${circuitType}?system=${system || this.zk.defaultProofSystem}`);
  }

  /**
   * Contribute to ceremony
   */
  async contribute(
    circuitType: CircuitType,
    entropy: Uint8Array,
    system?: ProofSystem
  ): Promise<CeremonyContribution> {
    const entropyBase64 = btoa(String.fromCharCode(...entropy));
    return this.zk.post('/ceremony/contribute', {
      circuit_type: circuitType,
      entropy: entropyBase64,
      system: system || this.zk.defaultProofSystem,
    });
  }

  /**
   * Verify a contribution
   */
  async verifyContribution(
    circuitType: CircuitType,
    contributionHash: string
  ): Promise<{ valid: boolean }> {
    return this.zk.post('/ceremony/verify', {
      circuit_type: circuitType,
      contribution_hash: contributionHash,
    });
  }

  /**
   * List contributions
   */
  async listContributions(circuitType: CircuitType): Promise<CeremonyContribution[]> {
    const result = await this.zk.get<{ contributions: CeremonyContribution[] }>(
      `/ceremony/${circuitType}/contributions`
    );
    return result.contributions || [];
  }
}

// Default export
export default SynorZk;
