use brk_error::Result;
use brk_types::{CapitalSentimentPhase, StoredBool, StoredI8, StoredU8, Version};
use vecdb::{Database, ReadableCloneableVec, UnaryTransform};

use super::Vecs;
use crate::{
    indexes,
    internal::{DailyMappings, DailyMetric, LazyDailyMetric},
};

const VERSION: Version = Version::new(5);

struct CodeToPhase;

impl UnaryTransform<StoredU8, Option<CapitalSentimentPhase>> for CodeToPhase {
    #[inline]
    fn apply(code: StoredU8) -> Option<CapitalSentimentPhase> {
        if *code == 0 {
            None
        } else {
            Some(
                CapitalSentimentPhase::from_code(*code)
                    .expect("persisted Capital Sentiment phase code must be valid"),
            )
        }
    }
}

struct PhaseToScore;

impl UnaryTransform<Option<CapitalSentimentPhase>, Option<StoredI8>> for PhaseToScore {
    #[inline]
    fn apply(phase: Option<CapitalSentimentPhase>) -> Option<StoredI8> {
        phase.map(|phase| StoredI8::new(phase.score()))
    }
}

struct IsLongToIsShort;

impl UnaryTransform<StoredBool, StoredBool> for IsLongToIsShort {
    #[inline]
    fn apply(is_long: StoredBool) -> StoredBool {
        StoredBool::from(is_long.is_false())
    }
}

impl Vecs {
    pub(crate) fn forced_import(
        db: &Database,
        parent_version: Version,
        indexes: &indexes::Vecs,
    ) -> Result<Self> {
        let version = parent_version + VERSION;
        let mappings = DailyMappings::new(indexes);

        let phase_code =
            DailyMetric::forced_import(db, "capital_sentiment_phase_code", version, &mappings)?;
        let is_long = DailyMetric::<StoredBool>::forced_import(
            db,
            "capital_sentiment_is_long",
            version,
            &mappings,
        )?;
        let is_short = LazyDailyMetric::from_source::<IsLongToIsShort>(
            "capital_sentiment_is_short",
            version,
            is_long.day1.read_only_boxed_clone(),
            &mappings,
        );
        let phase = LazyDailyMetric::from_source::<CodeToPhase>(
            "capital_sentiment_phase",
            version,
            phase_code.day1.read_only_boxed_clone(),
            &mappings,
        );
        let score = LazyDailyMetric::from_source::<PhaseToScore>(
            "capital_sentiment_score",
            version,
            phase.day1.read_only_boxed_clone(),
            &mappings,
        );

        Ok(Self {
            phase_code,
            is_long,
            is_short,
            phase,
            score,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn short_is_the_lazy_complement_of_long() {
        assert!(IsLongToIsShort::apply(StoredBool::FALSE).is_true());
        assert!(IsLongToIsShort::apply(StoredBool::TRUE).is_false());
    }
}
