// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.

use codec::{Decode, Encode};

// This code is taken from sp_core::crypto::DeriveJunction. The logic should be identical,
// though the API is tweaked a touch.

/// The length of the junction identifier. Note that this is also referred to as the
/// `CHAIN_CODE_LENGTH` in the context of Schnorrkel.
pub const JUNCTION_ID_LEN: usize = 32;

/// A since derivation junction description. It is the single parameter used when creating
/// a new secret key from an existing secret key and, in the case of `SoftRaw` and `SoftIndex`
/// a new public key from an existing public key.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Encode, Decode)]
pub enum DeriveJunction {
    /// Soft (vanilla) derivation. Public keys have a correspondent derivation.
    Soft([u8; JUNCTION_ID_LEN]),
    /// Hard ("hardened") derivation. Public keys do not have a correspondent derivation.
    Hard([u8; JUNCTION_ID_LEN]),
}

impl DeriveJunction {
    /// Consume self to return a soft derive junction with the same chain code.
    pub fn soften(self) -> Self {
        DeriveJunction::Soft(self.into_inner())
    }

    /// Consume self to return a hard derive junction with the same chain code.
    pub fn harden(self) -> Self {
        DeriveJunction::Hard(self.into_inner())
    }

    /// Create a new soft (vanilla) DeriveJunction from a given, encodable, value.
    ///
    /// If you need a hard junction, use `hard()`.
    pub fn soft<T: Encode>(index: T) -> Self {
        let mut cc: [u8; JUNCTION_ID_LEN] = Default::default();
        index.using_encoded(|data| {
            if data.len() > JUNCTION_ID_LEN {
                cc.copy_from_slice(&sp_crypto_hashing::blake2_256(data));
            } else {
                cc[0..data.len()].copy_from_slice(data);
            }
        });
        DeriveJunction::Soft(cc)
    }

    /// Create a new hard (hardened) DeriveJunction from a given, encodable, value.
    ///
    /// If you need a soft junction, use `soft()`.
    pub fn hard<T: Encode>(index: T) -> Self {
        Self::soft(index).harden()
    }

    /// Consume self to return the chain code.
    pub fn into_inner(self) -> [u8; JUNCTION_ID_LEN] {
        match self {
            DeriveJunction::Hard(c) | DeriveJunction::Soft(c) => c,
        }
    }

    /// Get a reference to the inner junction id.
    pub fn inner(&self) -> &[u8; JUNCTION_ID_LEN] {
        match self {
            DeriveJunction::Hard(c) | DeriveJunction::Soft(c) => c,
        }
    }

    /// Return `true` if the junction is soft.
    pub fn is_soft(&self) -> bool {
        matches!(*self, DeriveJunction::Soft(_))
    }

    /// Return `true` if the junction is hard.
    pub fn is_hard(&self) -> bool {
        matches!(*self, DeriveJunction::Hard(_))
    }
}

impl<T: AsRef<str>> From<T> for DeriveJunction {
    fn from(j: T) -> DeriveJunction {
        let j = j.as_ref();
        let (code, hard) = if let Some(stripped) = j.strip_prefix('/') {
            (stripped, true)
        } else {
            (j, false)
        };

        let res = if let Ok(n) = str::parse::<u64>(code) {
            // number
            DeriveJunction::soft(n)
        } else {
            // something else
            DeriveJunction::soft(code)
        };

        if hard { res.harden() } else { res }
    }
}
