///|
fn build_eddsa_secret_key_packet(
  seed : Bytes,
  creation : Int32,
) -> @packet.SecretKeyPacket raise {
  let ed = @pki.ed25519_public_of_seed(seed.exact_view())
  // Public params: length-prefixed OID, MPI of the point (0x40 || key).
  let point = Bytes::from_array([0x40]).add(ed.public)
  let params : Array[Bytes] = [@pki.ed25519_oid(), point]
  let pub_packet : @packet.PublicKeyPacket = {
    version: 4,
    creation_time: creation,
    algo: @packet.EdDsaLegacy,
    params,
  }
  // Secret material: MPI(seed) + 2-octet checksum.
  let sw = @packet.Writer::new()
  sw.mpi(seed.exact_view())
  let mpi_bytes = sw.to_bytes()
  let w2 = @packet.Writer::new()
  w2.bytes(mpi_bytes.exact_view())
  w2.u16(@packet.session_key_checksum(mpi_bytes.exact_view()))
  @packet.SecretKeyPacket::{
    public: pub_packet,
    s2k_usage: 0,
    cipher_algo: 0,
    aead_algo: 0,
    s2k_spec: Bytes::new(0),
    iv: Bytes::new(0),
    secret_material: w2.to_bytes(),
    dummy: false,
  }
}

///|
/// Build a v6 Ed25519 secret key packet (RFC 9580 §5.5.2.3/§5.5.5.9):
/// native 32-octet public point, 32 raw octets of secret seed and no
/// trailing checksum.
fn build_ed25519_v6_secret_key_packet(
  seed : Bytes,
  creation : Int32,
) -> @packet.SecretKeyPacket raise {
  guard seed.length() == 32 else {
    @error.fail("api: Ed25519 seed must be 32 octets")
  }
  let ed = @pki.ed25519_public_of_seed(seed.exact_view())
  let params : Array[Bytes] = [ed.public]
  let pub_packet : @packet.PublicKeyPacket = {
    version: 6,
    creation_time: creation,
    algo: @packet.Ed25519,
    params,
  }
  @packet.SecretKeyPacket::{
    public: pub_packet,
    s2k_usage: 0,
    cipher_algo: 0,
    aead_algo: 0,
    s2k_spec: Bytes::new(0),
    iv: Bytes::new(0),
    secret_material: seed,
    dummy: false,
  }
}

///|
fn build_rsa_secret_key_packet(
  key : @pki.RsaPrivateKey,
  creation : Int32,
) -> @packet.SecretKeyPacket {
  // Public part: MPI(n), MPI(e).
  let params : Array[Bytes] = [
    @pki.i2osp(key.public.n, (key.public.n.bit_length() + 7) / 8),
    @pki.i2osp(key.public.e, (key.public.e.bit_length() + 7) / 8),
  ]
  let pub_packet : @packet.PublicKeyPacket = {
    version: 4,
    creation_time: creation,
    algo: @packet.RsaEncryptOrSign,
    params,
  }
  // Secret material: MPI(d) MPI(p) MPI(q) MPI(u) + 2-octet checksum.
  let sw = @packet.Writer::new()
  sw.mpi(@pki.i2osp(key.d, (key.d.bit_length() + 7) / 8).exact_view())
  sw.mpi(@pki.i2osp(key.p, (key.p.bit_length() + 7) / 8).exact_view())
  sw.mpi(@pki.i2osp(key.q, (key.q.bit_length() + 7) / 8).exact_view())
  sw.mpi(@pki.i2osp(key.u, (key.u.bit_length() + 7) / 8).exact_view())
  let mpi_bytes = sw.to_bytes()
  let w2 = @packet.Writer::new()
  w2.bytes(mpi_bytes.exact_view())
  w2.u16(@packet.session_key_checksum(mpi_bytes.exact_view()))
  @packet.SecretKeyPacket::{
    public: pub_packet,
    s2k_usage: 0,
    cipher_algo: 0,
    aead_algo: 0,
    s2k_spec: Bytes::new(0),
    iv: Bytes::new(0),
    secret_material: w2.to_bytes(),
    dummy: false,
  }
}

///|
/// Generate a fresh transferable key: RSA primary (certify+sign) with a
/// User ID self-certification plus an RSA encryption subkey.
pub fn generate_key(user_id : String, bits~ : Int32 = 3072) -> PrivateKey raise {
  // Int truncation of the Int64 wall clock is wire-safe (Writer::u32 emits
  // the two's-complement low 32 bits, which is the correct u32 epoch past
  // 2038) but stays negative in memory; only date FORMATTING from this
  // value would be wrong (F09, documented limitation).
  let now = @primitives.unix_now().to_int()
  let sub_bits = if bits >= 4096 { 3072 } else { bits }
  let primary_rsa = @pki.generate_rsa_key(bits~)
  let primary = build_rsa_secret_key_packet(primary_rsa, now)
  let self_sig = make_self_signature(primary, primary.public, user_id, now)
  let sub_rsa = @pki.generate_rsa_key(bits=sub_bits)
  let subkey = build_rsa_secret_key_packet(sub_rsa, now)
  let binding = make_binding_signature(
    primary,
    primary.public,
    subkey.public,
    now,
  )
  PrivateKey::{
    primary,
    user_ids: [UserIdBinding::{ user_id, self_signature: self_sig, }],
    direct_signature: None,
    subkeys: [SecretSubkeyCert::{ secret: subkey, binding, revocation: None, }],
    revocations: [],
  }
}

///|
/// Generate a transferable key with an Ed25519 primary (EdDSA legacy,
/// signing/certification) and a Curve25519Legacy ECDH encryption
/// subkey — the gopenpgp "Default" profile shape. EdDSA signatures
/// require the digest itself as the Ed25519 message (RFC 9580 §5.2.3).
pub fn generate_ed25519_key(user_id : String) -> PrivateKey raise {
  let now = @primitives.unix_now().to_int()
  let seed = @primitives.random_bytes(32)
  let primary = build_eddsa_secret_key_packet(seed, now)
  let self_sig = make_self_signature(primary, primary.public, user_id, now)
  let subkey = build_ecdh_secret_key_packet(now)
  let binding = make_binding_signature(
    primary,
    primary.public,
    subkey.public,
    now,
  )
  PrivateKey::{
    primary,
    user_ids: [UserIdBinding::{ user_id, self_signature: self_sig, }],
    direct_signature: None,
    subkeys: [SecretSubkeyCert::{ secret: subkey, binding, revocation: None, }],
    revocations: [],
  }
}

///|
/// Build a v6 X25519 encryption secret subkey packet (RFC 9580 §5.5.5.7):
/// the public parameter is the 32-octet native public key and the secret
/// material the 32-octet native secret key, both raw (v6 native
/// algorithms carry no MPI prefix and no checksum).
fn build_v6_x25519_secret_key_packet(
  creation : Int32,
) -> @packet.SecretKeyPacket raise {
  let x = @pki.x25519_generate()
  let pub_packet : @packet.PublicKeyPacket = {
    version: 6,
    creation_time: creation,
    algo: @packet.X25519,
    params: [x.public],
  }
  @packet.SecretKeyPacket::{
    public: pub_packet,
    s2k_usage: 0,
    cipher_algo: 0,
    aead_algo: 0,
    s2k_spec: Bytes::new(0),
    iv: Bytes::new(0),
    secret_material: x.scalar,
    dummy: false,
  }
}

///|
/// Generate a v6 Ed25519 signing key with an X25519 encryption subkey
/// (RFC 9580 §5.5.5.7/§5.5.5.9) — the shape of the Appendix A.3/A.4
/// sample certificate: a v6 primary key with a User ID self-certification,
/// the Direct Key signature the RFC requires before a v6 key may be used,
/// and a v6 X25519 subkey with a v6 binding signature.
pub fn generate_ed25519_v6_key(user_id : String) -> PrivateKey raise {
  let now = @primitives.unix_now().to_int()
  let seed = @primitives.random_bytes(32)
  let primary = build_ed25519_v6_secret_key_packet(seed, now)
  let self_sig = make_self_signature(primary, primary.public, user_id, now)
  let direct = make_direct_key_signature(primary, primary.public, now)
  let subkey = build_v6_x25519_secret_key_packet(now)
  let binding = make_binding_signature(
    primary,
    primary.public,
    subkey.public,
    now,
  )
  PrivateKey::{
    primary,
    user_ids: [UserIdBinding::{ user_id, self_signature: self_sig, }],
    direct_signature: Some(direct),
    subkeys: [SecretSubkeyCert::{ secret: subkey, binding, revocation: None, }],
    revocations: [],
  }
}