extern crate alloc;

pub use beancount_account::Acc;
pub use beancount_account::Account;
pub use beancount_account::Seg;
pub use beancount_account::Segment;
pub use beancount_amount::Amount;
pub use beancount_commodity::Commodity;
pub use beancount_cost::CostBasis;
pub use beancount_cost::CostSpec;
pub use beancount_metadata::common_keys;
pub use beancount_metadata::Link;
pub use beancount_metadata::LinkSet;
pub use beancount_metadata::MetadataKey;
pub use beancount_metadata::MetadataKy;
pub use beancount_metadata::MetadataMap;
pub use beancount_metadata::MetadataValue;
pub use beancount_price_spec::PriceSpec;
use delegate::delegate;

pub use crate::account::template::Template as AccountTemplate;
pub use crate::balance::Balance;
pub use crate::price::Price;
pub use crate::transaction::Flag;
pub use crate::transaction::Posting;
pub use crate::transaction::Transaction;

use core::fmt;
use core::fmt::Display;
use core::fmt::Formatter;

use enum_kinds::EnumKind;
use time::Date;
use time::OffsetDateTime;

mod account;
mod balance;
mod price;
mod transaction;

#[derive(Clone, Debug, EnumKind)]
#[enum_kind(DirectiveKind, derive(Hash, Ord, PartialOrd))]
pub enum Directive {
    Balance(Balance),
    Close(Close),
    Open(Open),
    Price(Price),
    Transaction(Transaction),
}

impl Directive {
    pub fn add_meta(
        &mut self,
        key: impl Into<MetadataKey>,
        value: impl Into<MetadataValue>,
    ) -> &mut Self {
        match self {
            Self::Balance(inner) => {
                inner.add_meta(key, value);
            }
            Self::Close(inner) => {
                inner.add_meta(key, value);
            }
            Self::Open(inner) => {
                inner.add_meta(key, value);
            }
            Self::Price(inner) => {
                inner.add_meta(key, value);
            }
            Self::Transaction(inner) => {
                inner.add_meta(key, value);
            }
        }

        self
    }
}

impl Directive {
    #[must_use]
    pub const fn date(&self) -> Date {
        match self {
            Self::Balance(inner) => inner.date,
            Self::Close(inner) => inner.date,
            Self::Open(inner) => inner.date,
            Self::Price(inner) => inner.date,
            Self::Transaction(inner) => inner.date,
        }
    }

    /// Returns `true` if the directive is [`Balance`].
    ///
    /// [`Balance`]: Directive::Balance
    #[must_use]
    pub const fn is_balance(&self) -> bool {
        matches!(self, Self::Balance(..))
    }

    #[must_use]
    pub fn kind(&self) -> DirectiveKind {
        DirectiveKind::from(self)
    }

    #[must_use]
    pub fn main_account(&self) -> Option<&Acc> {
        match self {
            Self::Balance(inner) => Some(&inner.account),
            Self::Close(inner) => Some(&inner.account),
            Self::Open(inner) => Some(&inner.account),
            Self::Price(_) => None,
            Self::Transaction(inner) => inner.main_account(),
        }
    }

    #[must_use]
    pub fn timestamp(&self) -> Option<OffsetDateTime> {
        match self {
            Self::Balance(inner) => inner.timestamp(),
            Self::Close(inner) => inner.timestamp(),
            Self::Open(inner) => inner.timestamp(),
            Self::Price(_) => None, // TODO
            Self::Transaction(inner) => inner.timestamp(),
        }
    }
}

impl Display for Directive {
    delegate! {
        to match self {
            Self::Balance(inner) => inner,
            Self::Close(inner) => inner,
            Self::Open(inner) => inner,
            Self::Price(inner) => inner,
            Self::Transaction(inner) => inner,
        } {
            fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result;
        }
    }
}

impl From<Balance> for Directive {
    fn from(inner: Balance) -> Self {
        Self::Balance(inner)
    }
}

impl From<Close> for Directive {
    fn from(inner: Close) -> Self {
        Self::Close(inner)
    }
}

impl From<Open> for Directive {
    fn from(inner: Open) -> Self {
        Self::Open(inner)
    }
}

impl From<Price> for Directive {
    fn from(inner: Price) -> Self {
        Self::Price(inner)
    }
}

impl From<Transaction> for Directive {
    fn from(inner: Transaction) -> Self {
        Self::Transaction(inner)
    }
}