/**
 * Synor Compiler SDK
 *
 * Smart contract compilation, optimization, and ABI generation.
 */

import {
  CompilerConfig,
  CompilerError,
  CompilationRequest,
  CompilationResult,
  ContractAbi,
  ContractAnalysis,
  ContractMetadata,
  FunctionAbi,
  OptimizationLevel,
  SourceMap,
  StripOptions,
  ValidationResult,
  DEFAULT_COMPILER_CONFIG,
  DEFAULT_STRIP_OPTIONS,
} from './types';

export * from './types';

/* ============================================================================
 * Main Client
 * ============================================================================ */

/**
 * Synor Compiler SDK Client
 *
 * Smart contract compilation, optimization, and ABI generation.
 *
 * @example
 * ```typescript
 * const compiler = new SynorCompiler({ apiKey: 'your-api-key' });
 *
 * // Compile WASM bytecode
 * const result = await compiler.compile(wasmBytes, {
 *   optimizationLevel: OptimizationLevel.Size,
 * });
 *
 * console.log(`Optimized size: ${result.optimizedSize} bytes`);
 * console.log(`Size reduction: ${result.sizeReduction.toFixed(1)}%`);
 * ```
 */
export class SynorCompiler {
  private readonly config: Required<
    Omit<CompilerConfig, 'stripOptions'> & { stripOptions: StripOptions }
  >;
  private closed = false;

  /** Contracts sub-client */
  public readonly contracts: ContractsClient;
  /** ABI sub-client */
  public readonly abi: AbiClient;
  /** Analysis sub-client */
  public readonly analysis: AnalysisClient;
  /** Validation sub-client */
  public readonly validation: ValidationClient;

  constructor(config: CompilerConfig) {
    this.config = {
      ...DEFAULT_COMPILER_CONFIG,
      ...config,
      endpoint: config.endpoint ?? DEFAULT_COMPILER_CONFIG.endpoint!,
      timeout: config.timeout ?? DEFAULT_COMPILER_CONFIG.timeout!,
      retries: config.retries ?? DEFAULT_COMPILER_CONFIG.retries!,
      debug: config.debug ?? DEFAULT_COMPILER_CONFIG.debug!,
      optimizationLevel: config.optimizationLevel ?? DEFAULT_COMPILER_CONFIG.optimizationLevel!,
      useWasmOpt: config.useWasmOpt ?? DEFAULT_COMPILER_CONFIG.useWasmOpt!,
      validate: config.validate ?? DEFAULT_COMPILER_CONFIG.validate!,
      extractMetadata: config.extractMetadata ?? DEFAULT_COMPILER_CONFIG.extractMetadata!,
      generateAbi: config.generateAbi ?? DEFAULT_COMPILER_CONFIG.generateAbi!,
      maxContractSize: config.maxContractSize ?? 256 * 1024,
      stripOptions: {
        ...DEFAULT_STRIP_OPTIONS,
        ...config.stripOptions,
      },
    };

    this.contracts = new ContractsClient(this);
    this.abi = new AbiClient(this);
    this.analysis = new AnalysisClient(this);
    this.validation = new ValidationClient(this);
  }

  /** Get the default optimization level */
  get defaultOptimizationLevel(): OptimizationLevel {
    return this.config.optimizationLevel;
  }

  /** Check service health */
  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;
  }

  /**
   * Compile WASM bytecode
   *
   * @param wasm - WASM bytecode as Uint8Array or base64 string
   * @param options - Compilation options
   * @returns Compilation result
   */
  async compile(
    wasm: Uint8Array | string,
    options?: Partial<CompilationRequest>
  ): Promise<CompilationResult> {
    const wasmBase64 = typeof wasm === 'string' ? wasm : this.toBase64(wasm);

    return this.post<CompilationResult>('/compile', {
      wasm: wasmBase64,
      optimization_level: options?.optimizationLevel ?? this.config.optimizationLevel,
      strip_options: options?.stripOptions ?? this.config.stripOptions,
      use_wasm_opt: options?.useWasmOpt ?? this.config.useWasmOpt,
      validate: options?.validate ?? this.config.validate,
      extract_metadata: options?.extractMetadata ?? this.config.extractMetadata,
      generate_abi: options?.generateAbi ?? this.config.generateAbi,
    });
  }

  /** Internal GET request */
  async get<T>(path: string): Promise<T> {
    this.checkClosed();
    const response = await this.request('GET', path);
    return this.handleResponse<T>(response);
  }

  /** Internal POST request */
  async post<T>(path: string, body?: unknown): Promise<T> {
    this.checkClosed();
    const response = await this.request('POST', path, body);
    return this.handleResponse<T>(response);
  }

  private async request(method: string, path: string, body?: unknown): Promise<Response> {
    const url = `${this.config.endpoint}${path}`;
    const headers: Record<string, string> = {
      Authorization: `Bearer ${this.config.apiKey}`,
      'Content-Type': 'application/json',
      'X-SDK-Version': 'js/0.1.0',
    };

    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);

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

  private async handleResponse<T>(response: Response): Promise<T> {
    const text = await response.text();

    if (!response.ok) {
      let error: { message?: string; code?: string } = {};
      try {
        error = JSON.parse(text);
      } catch {
        error = { message: text || `HTTP ${response.status}` };
      }
      throw new CompilerError(
        error.message ?? `HTTP ${response.status}`,
        error.code,
        response.status
      );
    }

    return JSON.parse(text, this.reviver) as T;
  }

  private reviver(_key: string, value: unknown): unknown {
    if (typeof value === 'string') {
      // Convert snake_case to camelCase for keys handled during JSON parse
      return value;
    }
    if (value && typeof value === 'object' && !Array.isArray(value)) {
      const converted: Record<string, unknown> = {};
      for (const [k, v] of Object.entries(value)) {
        const camelKey = k.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
        converted[camelKey] = v;
      }
      return converted;
    }
    return value;
  }

  private checkClosed(): void {
    if (this.closed) {
      throw new CompilerError('Client has been closed', 'CLIENT_CLOSED');
    }
  }

  private toBase64(data: Uint8Array): string {
    if (typeof Buffer !== 'undefined') {
      return Buffer.from(data).toString('base64');
    }
    return btoa(String.fromCharCode(...data));
  }
}

/* ============================================================================
 * Contracts Sub-Client
 * ============================================================================ */

/** Contracts sub-client for contract compilation */
export class ContractsClient {
  constructor(private readonly compiler: SynorCompiler) {}

  /**
   * Compile WASM bytecode with default settings
   */
  async compile(
    wasm: Uint8Array | string,
    options?: Partial<CompilationRequest>
  ): Promise<CompilationResult> {
    return this.compiler.compile(wasm, options);
  }

  /**
   * Compile with development settings (fast, minimal optimization)
   */
  async compileDev(wasm: Uint8Array | string): Promise<CompilationResult> {
    return this.compiler.compile(wasm, {
      optimizationLevel: OptimizationLevel.None,
      useWasmOpt: false,
      validate: true,
    });
  }

  /**
   * Compile with production settings (aggressive optimization)
   */
  async compileProduction(wasm: Uint8Array | string): Promise<CompilationResult> {
    return this.compiler.compile(wasm, {
      optimizationLevel: OptimizationLevel.Aggressive,
      useWasmOpt: true,
      validate: true,
      extractMetadata: true,
      generateAbi: true,
    });
  }

  /**
   * Get a compiled contract by ID
   */
  async get(contractId: string): Promise<CompilationResult> {
    return this.compiler.get<CompilationResult>(`/contracts/${contractId}`);
  }

  /**
   * List compiled contracts
   */
  async list(limit?: number, offset?: number): Promise<CompilationResult[]> {
    const params = new URLSearchParams();
    if (limit !== undefined) params.set('limit', limit.toString());
    if (offset !== undefined) params.set('offset', offset.toString());
    const query = params.toString() ? `?${params}` : '';

    const result = await this.compiler.get<{ contracts: CompilationResult[] }>(`/contracts${query}`);
    return result.contracts ?? [];
  }

  /**
   * Get optimized bytecode for a contract
   */
  async getCode(contractId: string): Promise<Uint8Array> {
    const result = await this.compiler.get<{ code: string }>(`/contracts/${contractId}/code`);
    return this.fromBase64(result.code);
  }

  private fromBase64(data: string): Uint8Array {
    if (typeof Buffer !== 'undefined') {
      return new Uint8Array(Buffer.from(data, 'base64'));
    }
    return new Uint8Array(
      atob(data)
        .split('')
        .map((c) => c.charCodeAt(0))
    );
  }
}

/* ============================================================================
 * ABI Sub-Client
 * ============================================================================ */

/** ABI sub-client for ABI generation and encoding */
export class AbiClient {
  constructor(private readonly compiler: SynorCompiler) {}

  /**
   * Extract ABI from WASM bytecode
   */
  async extract(wasm: Uint8Array | string): Promise<ContractAbi> {
    const wasmBase64 = typeof wasm === 'string' ? wasm : this.toBase64(wasm);
    return this.compiler.post<ContractAbi>('/abi/extract', { wasm: wasmBase64 });
  }

  /**
   * Get ABI for a compiled contract
   */
  async get(contractId: string): Promise<ContractAbi> {
    return this.compiler.get<ContractAbi>(`/contracts/${contractId}/abi`);
  }

  /**
   * Encode function call
   */
  async encodeCall(functionAbi: FunctionAbi, args: unknown[]): Promise<string> {
    const result = await this.compiler.post<{ data: string }>('/abi/encode', {
      function: functionAbi,
      args,
    });
    return result.data;
  }

  /**
   * Decode function result
   */
  async decodeResult(functionAbi: FunctionAbi, data: string): Promise<unknown[]> {
    const result = await this.compiler.post<{ values: unknown[] }>('/abi/decode', {
      function: functionAbi,
      data,
    });
    return result.values;
  }

  /**
   * Compute function selector
   */
  computeSelector(functionName: string): string {
    // Use blake3 hash and take first 4 bytes
    // This is a simplified version - actual implementation would use proper hashing
    let hash = 0;
    for (let i = 0; i < functionName.length; i++) {
      const char = functionName.charCodeAt(i);
      hash = (hash << 5) - hash + char;
      hash = hash & hash;
    }
    const bytes = new Uint8Array(4);
    bytes[0] = (hash >> 24) & 0xff;
    bytes[1] = (hash >> 16) & 0xff;
    bytes[2] = (hash >> 8) & 0xff;
    bytes[3] = hash & 0xff;
    return Array.from(bytes)
      .map((b) => b.toString(16).padStart(2, '0'))
      .join('');
  }

  /**
   * Generate TypeScript types from ABI
   */
  async generateTypes(abi: ContractAbi): Promise<string> {
    const result = await this.compiler.post<{ typescript: string }>('/abi/generate-types', {
      abi,
      language: 'typescript',
    });
    return result.typescript;
  }

  private toBase64(data: Uint8Array): string {
    if (typeof Buffer !== 'undefined') {
      return Buffer.from(data).toString('base64');
    }
    return btoa(String.fromCharCode(...data));
  }
}

/* ============================================================================
 * Analysis Sub-Client
 * ============================================================================ */

/** Analysis sub-client for contract analysis */
export class AnalysisClient {
  constructor(private readonly compiler: SynorCompiler) {}

  /**
   * Analyze WASM bytecode
   */
  async analyze(wasm: Uint8Array | string): Promise<ContractAnalysis> {
    const wasmBase64 = typeof wasm === 'string' ? wasm : this.toBase64(wasm);
    return this.compiler.post<ContractAnalysis>('/analysis', { wasm: wasmBase64 });
  }

  /**
   * Get analysis for a compiled contract
   */
  async get(contractId: string): Promise<ContractAnalysis> {
    return this.compiler.get<ContractAnalysis>(`/contracts/${contractId}/analysis`);
  }

  /**
   * Extract metadata from WASM bytecode
   */
  async extractMetadata(wasm: Uint8Array | string): Promise<ContractMetadata> {
    const wasmBase64 = typeof wasm === 'string' ? wasm : this.toBase64(wasm);
    return this.compiler.post<ContractMetadata>('/analysis/metadata', { wasm: wasmBase64 });
  }

  /**
   * Extract source map from WASM bytecode
   */
  async extractSourceMap(wasm: Uint8Array | string): Promise<SourceMap | null> {
    const wasmBase64 = typeof wasm === 'string' ? wasm : this.toBase64(wasm);
    const result = await this.compiler.post<{ sourceMap: SourceMap | null }>(
      '/analysis/source-map',
      {
        wasm: wasmBase64,
      }
    );
    return result.sourceMap;
  }

  /**
   * Estimate gas for contract deployment
   */
  async estimateDeployGas(wasm: Uint8Array | string): Promise<number> {
    const wasmBase64 = typeof wasm === 'string' ? wasm : this.toBase64(wasm);
    const result = await this.compiler.post<{ gas: number }>('/analysis/estimate-gas', {
      wasm: wasmBase64,
    });
    return result.gas;
  }

  /**
   * Get security analysis
   */
  async securityScan(wasm: Uint8Array | string): Promise<ContractAnalysis['security']> {
    const wasmBase64 = typeof wasm === 'string' ? wasm : this.toBase64(wasm);
    const result = await this.compiler.post<{ security: ContractAnalysis['security'] }>(
      '/analysis/security',
      {
        wasm: wasmBase64,
      }
    );
    return result.security;
  }

  private toBase64(data: Uint8Array): string {
    if (typeof Buffer !== 'undefined') {
      return Buffer.from(data).toString('base64');
    }
    return btoa(String.fromCharCode(...data));
  }
}

/* ============================================================================
 * Validation Sub-Client
 * ============================================================================ */

/** Validation sub-client for WASM validation */
export class ValidationClient {
  constructor(private readonly compiler: SynorCompiler) {}

  /**
   * Validate WASM bytecode against VM requirements
   */
  async validate(wasm: Uint8Array | string): Promise<ValidationResult> {
    const wasmBase64 = typeof wasm === 'string' ? wasm : this.toBase64(wasm);
    return this.compiler.post<ValidationResult>('/validate', { wasm: wasmBase64 });
  }

  /**
   * Check if WASM is valid
   */
  async isValid(wasm: Uint8Array | string): Promise<boolean> {
    const result = await this.validate(wasm);
    return result.valid;
  }

  /**
   * Get validation errors
   */
  async getErrors(wasm: Uint8Array | string): Promise<string[]> {
    const result = await this.validate(wasm);
    return result.errors.map((e) => e.message);
  }

  /**
   * Validate contract exports
   */
  async validateExports(wasm: Uint8Array | string, requiredExports: string[]): Promise<boolean> {
    const wasmBase64 = typeof wasm === 'string' ? wasm : this.toBase64(wasm);
    const result = await this.compiler.post<{ valid: boolean }>('/validate/exports', {
      wasm: wasmBase64,
      required_exports: requiredExports,
    });
    return result.valid;
  }

  /**
   * Validate memory limits
   */
  async validateMemory(wasm: Uint8Array | string, maxPages: number): Promise<boolean> {
    const wasmBase64 = typeof wasm === 'string' ? wasm : this.toBase64(wasm);
    const result = await this.compiler.post<{ valid: boolean }>('/validate/memory', {
      wasm: wasmBase64,
      max_pages: maxPages,
    });
    return result.valid;
  }

  private toBase64(data: Uint8Array): string {
    if (typeof Buffer !== 'undefined') {
      return Buffer.from(data).toString('base64');
    }
    return btoa(String.fromCharCode(...data));
  }
}
