use amplify::confinement::{LargeOrdMap, LargeVec, SmallVec};
use bp::Outpoint;
use rgb::{
    AssignmentType, AttachId, ContractState, FungibleOutput, MediaType, RevealedAttach,
    RevealedData, SealWitness,
};
use strict_encoding::FieldName;
use strict_types::typify::TypedVal;
use strict_types::{decode, StrictVal};
use crate::interface::IfaceImpl;
use crate::LIB_NAME_RGB_STD;
#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
#[display(doc_comments)]
pub enum ContractError {
    FieldNameUnknown(FieldName),
    #[from]
    #[display(inner)]
    Reify(decode::Error),
}
#[derive(Clone, Eq, PartialEq, Debug, Display, From)]
#[display(inner)]
pub enum TypedState {
    #[display("")]
    Void,
    #[from]
    Amount(u64),
    #[from]
    Data(RevealedData),
    #[from]
    Attachment(AttachedState),
}
#[derive(Clone, Eq, PartialEq, Debug, Display)]
#[display("{id}:{media_type}")]
pub struct AttachedState {
    pub id: AttachId,
    pub media_type: MediaType,
}
impl From<RevealedAttach> for AttachedState {
    fn from(attach: RevealedAttach) -> Self {
        AttachedState {
            id: attach.id,
            media_type: attach.media_type,
        }
    }
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct FungibleAllocation {
    pub owner: Outpoint,
    pub witness: SealWitness,
    pub value: u64,
}
impl From<FungibleOutput> for FungibleAllocation {
    fn from(out: FungibleOutput) -> Self { Self::from(&out) }
}
impl From<&FungibleOutput> for FungibleAllocation {
    fn from(out: &FungibleOutput) -> Self {
        FungibleAllocation {
            owner: out.seal,
            witness: out.witness,
            value: out.state.value.as_u64(),
        }
    }
}
#[derive(Clone, Eq, PartialEq, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB_STD)]
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct ContractIface {
    pub state: ContractState,
    pub iface: IfaceImpl,
}
impl ContractIface {
    pub fn global(&self, name: impl Into<FieldName>) -> Result<SmallVec<StrictVal>, ContractError> {
        let name = name.into();
        let type_system = &self.state.schema.type_system;
        let type_id = self
            .iface
            .global_type(&name)
            .ok_or(ContractError::FieldNameUnknown(name))?;
        let type_schema = self
            .state
            .schema
            .global_types
            .get(&type_id)
            .expect("schema doesn't match interface");
        let state = unsafe { self.state.global_unchecked(type_id) };
        let state = state
            .into_iter()
            .map(|revealed| {
                type_system
                    .strict_deserialize_type(type_schema.sem_id, revealed.as_ref())
                    .map(TypedVal::unbox)
            })
            .take(type_schema.max_items as usize)
            .collect::<Result<Vec<_>, _>>()?;
        Ok(SmallVec::try_from_iter(state).expect("same or smaller collection size"))
    }
    pub fn fungible(
        &self,
        name: impl Into<FieldName>,
    ) -> Result<LargeVec<FungibleAllocation>, ContractError> {
        let name = name.into();
        let type_id = self
            .iface
            .assignments_type(&name)
            .ok_or(ContractError::FieldNameUnknown(name))?;
        let state = self
            .state
            .fungibles()
            .iter()
            .filter(|outp| outp.opout.ty == type_id)
            .map(FungibleAllocation::from);
        Ok(LargeVec::try_from_iter(state).expect("same or smaller collection size"))
    }
    pub fn outpoint(
        &self,
        _outpoint: Outpoint,
    ) -> LargeOrdMap<AssignmentType, LargeVec<TypedState>> {
        todo!()
    }
}