/**
 * Errors emitted by `@shade/recovery`. All of them subclass the same base
 * so consumers can catch any recovery-related failure in one block, then
 * branch on the concrete type for messaging.
 */
export class RecoveryError extends Error {
  constructor(message: string, options?: ErrorOptions) {
    super(message, options);
    this.name = 'RecoveryError';
  }
}

/**
 * The new-device flow timed out while waiting for the threshold number
 * of guardians to respond. Retryable — the caller can ask the user to
 * nudge guardians offline and retry.
 */
export class RecoveryTimeoutError extends RecoveryError {
  constructor(
    public readonly received: number,
    public readonly threshold: number,
  ) {
    super(
      `Recovery timed out: received ${received} guardian responses, need ${threshold}`,
    );
    this.name = 'RecoveryTimeoutError';
  }
}

/**
 * One or more guardians explicitly declined the recovery request. Listed
 * in `declines` (guardian addresses). The new-device flow keeps running
 * with the remaining guardians; this error fires only when too many
 * decline to ever reach the threshold.
 */
export class RecoveryDeclinedError extends RecoveryError {
  constructor(
    public readonly declines: ReadonlyArray<string>,
    public readonly threshold: number,
    public readonly remaining: number,
  ) {
    super(
      `Recovery aborted: ${declines.length} guardian(s) declined and ${remaining} are left, ` +
        `which is below the threshold of ${threshold}`,
    );
    this.name = 'RecoveryDeclinedError';
  }
}

/**
 * The reconstructed `recoveryKey` did not authenticate the encrypted
 * `shareSecret` (AEAD tag mismatch). Most likely cause: a guardian
 * supplied a forged share, OR the user supplied the wrong original
 * address. Treat as adversarial — abort and notify the user, do not
 * retry with the same shares.
 */
export class RecoveryReconstructionError extends RecoveryError {
  constructor(message: string, options?: ErrorOptions) {
    super(message, options);
    this.name = 'RecoveryReconstructionError';
  }
}

/**
 * The guardian-side approve callback returned `false` (or threw),
 * indicating the user did not match the OOB safety number. The peer
 * receives a `share-decline` envelope; this error is thrown locally on
 * the new device when its requestRecovery() detects too many declines.
 */
export class RecoveryGuardianRejectedError extends RecoveryError {
  constructor(
    public readonly guardianAddress: string,
    public readonly reason: string,
  ) {
    super(`Guardian ${guardianAddress} rejected the recovery request: ${reason}`);
    this.name = 'RecoveryGuardianRejectedError';
  }
}

/**
 * A protocol envelope arrived that could not be parsed (malformed JSON,
 * missing required field, unknown version, etc.). Always treat as a bug
 * or as malicious input — never silently ignore.
 */
export class RecoveryProtocolError extends RecoveryError {
  constructor(message: string, options?: ErrorOptions) {
    super(message, options);
    this.name = 'RecoveryProtocolError';
  }
}
