/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::time::SystemTime;

use superconsole::DrawMode;
use superconsole::Line;
use superconsole::Lines;

use crate::humanized::HumanizedBytes;
use crate::humanized::HumanizedBytesPerSecond;
use crate::two_snapshots::TwoSnapshots;

pub struct ReState {
    session_id: Option<String>,
    first_snapshot: Option<buck2_data::Snapshot>,
    two_snapshots: TwoSnapshots,
}

impl ReState {
    pub fn new() -> Self {
        Self {
            session_id: None,
            first_snapshot: None,
            two_snapshots: TwoSnapshots::default(),
        }
    }

    pub fn add_re_session(&mut self, session: &buck2_data::RemoteExecutionSessionCreated) {
        self.session_id = Some(session.session_id.clone());
    }

    pub fn update(&mut self, timestamp: SystemTime, snapshot: &buck2_data::Snapshot) {
        if self.first_snapshot.is_none() {
            self.first_snapshot = Some(snapshot.clone());
        }
        self.two_snapshots.update(timestamp, snapshot);
    }

    pub fn render_header(&self, draw_mode: DrawMode) -> Option<String> {
        let mut parts = Vec::new();

        if let (Some(first), Some((_, last))) = (&self.first_snapshot, &self.two_snapshots.last) {
            if last.re_upload_bytes > 0
                || last.re_download_bytes > 0
                || last.http_download_bytes > 0
            {
                let re_upload_bytes = last.re_upload_bytes.checked_sub(first.re_upload_bytes)?;
                let re_download_bytes = last
                    .re_download_bytes
                    .checked_sub(first.re_download_bytes)?;
                let http_download_bytes = last
                    .http_download_bytes
                    .checked_sub(first.http_download_bytes)?;

                let part = match draw_mode {
                    DrawMode::Normal => {
                        fn format_byte_per_second(bytes_per_second: u64) -> String {
                            if bytes_per_second == 0 {
                                " ".repeat(HumanizedBytesPerSecond::FIXED_WIDTH_WIDTH)
                            } else {
                                HumanizedBytesPerSecond::fixed_width(bytes_per_second).to_string()
                            }
                        }

                        let re_upload_bytes_per_second = self
                            .two_snapshots
                            .re_upload_bytes_per_second()
                            .unwrap_or_default();
                        let re_download_bytes_per_second = self
                            .two_snapshots
                            .re_download_bytes_per_second()
                            .unwrap_or_default();
                        let http_download_bytes_per_second = self
                            .two_snapshots
                            .http_download_bytes_per_second()
                            .unwrap_or_default();
                        format!(
                            "Up: {} {}  Down: {} {}",
                            HumanizedBytes::fixed_width(re_upload_bytes),
                            format_byte_per_second(re_upload_bytes_per_second),
                            HumanizedBytes::fixed_width(re_download_bytes + http_download_bytes),
                            format_byte_per_second(
                                re_download_bytes_per_second + http_download_bytes_per_second
                            ),
                        )
                    }
                    DrawMode::Final => {
                        format!(
                            "Up: {}  Down: {}",
                            HumanizedBytes::new(re_upload_bytes),
                            HumanizedBytes::new(re_download_bytes + http_download_bytes),
                        )
                    }
                };
                parts.push(part);
            }
        }

        if let Some(session_id) = self.session_id.as_ref() {
            parts.push(format!("({})", session_id.to_owned()));
        }

        if parts.is_empty() {
            return None;
        }

        Some(format!("Network: {}", parts.join("  ")))
    }

    fn render_detailed_item_no_progress_stats(
        &self,
        name: &str,
        stat: u64,
    ) -> anyhow::Result<Option<Line>> {
        let line = format!(
            "{name:<20}: \
            {stat:>5} bytes"
        );
        Ok(Some(Line::unstyled(&line)?))
    }

    fn render_detailed_items(
        &self,
        name: &str,
        started: u32,
        finished_successfully: u32,
        finished_with_error: u32,
    ) -> anyhow::Result<Option<Line>> {
        let in_progress = started
            .saturating_sub(finished_successfully)
            .saturating_sub(finished_with_error);
        if in_progress == 0 && finished_successfully == 0 && finished_with_error == 0 {
            return Ok(None);
        }
        let line = format!(
            "{name:<20}: \
            {in_progress:>5} in progress, \
            {finished_successfully:>5} success, \
            {finished_with_error:>5} error"
        );
        Ok(Some(Line::unstyled(&line)?))
    }

    fn render_detailed(&self) -> anyhow::Result<Vec<Line>> {
        let mut r = Vec::new();
        if let (Some(first), Some((_, last))) = (&self.first_snapshot, &self.two_snapshots.last) {
            r.extend(self.render_detailed_items(
                "re_uploads",
                last.re_uploads_started,
                last.re_uploads_finished_successfully,
                last.re_uploads_finished_with_error,
            )?);
            r.extend(self.render_detailed_items(
                "re_downloads",
                last.re_downloads_started,
                last.re_downloads_finished_successfully,
                last.re_downloads_finished_with_error,
            )?);
            r.extend(self.render_detailed_items(
                "re_action_cache",
                last.re_action_cache_started,
                last.re_action_cache_finished_successfully,
                last.re_action_cache_finished_with_error,
            )?);
            r.extend(self.render_detailed_items(
                "re_executes",
                last.re_executes_started,
                last.re_executes_finished_successfully,
                last.re_executes_finished_with_error,
            )?);
            r.extend(self.render_detailed_items(
                "re_materializes",
                last.re_materializes_started,
                last.re_materializes_finished_successfully,
                last.re_materializes_finished_with_error,
            )?);
            r.extend(self.render_detailed_items(
                "re_write_action_results",
                last.re_write_action_results_started,
                last.re_write_action_results_finished_successfully,
                last.re_write_action_results_finished_with_error,
            )?);
            r.extend(self.render_detailed_items(
                "re_get_digest_expirations",
                last.re_get_digest_expirations_started,
                last.re_get_digest_expirations_finished_successfully,
                last.re_get_digest_expirations_finished_with_error,
            )?);
            // TODO(raulgarcia4): Add some in-progress-stats for http metrics as well.
            r.extend(self.render_detailed_item_no_progress_stats(
                "http_download_bytes",
                last.http_download_bytes - first.http_download_bytes,
            )?);
        }
        Ok(r)
    }

    pub fn render(&self, detailed: bool, draw_mode: DrawMode) -> anyhow::Result<Lines> {
        let header = match self.render_header(draw_mode) {
            Some(header) => header,
            None => return Ok(Lines::new()),
        };
        let mut lines = vec![Line::unstyled(&header)?];
        if detailed {
            lines.extend(self.render_detailed()?);
        }
        Ok(Lines(lines))
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use buck2_wrapper_common::invocation_id::TraceId;

/// Information about the current command, such as session or build ids.
pub struct SessionInfo {
    pub trace_id: TraceId,
    pub test_session: Option<buck2_data::TestSessionInfo>,
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
use std::time::Instant;

use anyhow::Context as _;
use buck2_events::span::SpanId;
use buck2_events::BuckEvent;
use derivative::Derivative;
use derive_more::From;
use dupe::Dupe;
use linked_hash_map::LinkedHashMap;

use crate::what_ran::WhatRanRelevantAction;
use crate::what_ran::WhatRanState;

#[derive(Debug, thiserror::Error)]
enum SpanTrackerError<T: SpanTrackable> {
    #[error(
        "Tried to end a child (`{child:#?}`) that did not exist for its parent (`{parent:#?}`)."
    )]
    InvalidChildRemoval { child: T, parent: T },
    #[error("Tried to start an event not associated with a span: `{0:?}.")]
    NonSpanEvent(T),
}

#[derive(Debug, Clone)]
pub struct SpanInfo<T: SpanTrackable> {
    pub event: T,
    pub start: Instant,
}

#[derive(Debug, Clone)]
struct Span<T: SpanTrackable> {
    span_id: <T as SpanTrackable>::Id,
    info: SpanInfo<T>,
    children: LinkedHashMap<<T as SpanTrackable>::Id, ()>,
    /// Whether this span or any of its children are boring. Boring spans are showed after
    /// not-boring spans in the UI.
    boringness: usize,
}

impl<T: SpanTrackable + Dupe> Span<T> {
    /// NOTE: When adding a child, this node can become boring, but not parents of this node.
    fn add_child(&mut self, span_id: <T as SpanTrackable>::Id, boring: bool, roots: &mut Roots<T>) {
        self.children.insert(span_id, ());

        if boring {
            if self.boringness == 0 {
                roots.make_boring(self.span_id);
            }

            self.boringness += 1;
        }
    }

    fn remove_child(&mut self, child: Span<T>, roots: &mut Roots<T>) -> anyhow::Result<()> {
        let removed = self.children.remove(&child.span_id).is_some();

        if !removed {
            return Err(SpanTrackerError::InvalidChildRemoval {
                parent: self.info.event.clone(),
                child: child.info.event,
            }
            .into());
        }

        if child.boringness > 0 {
            if self.boringness == 1 {
                roots.make_not_boring(self.span_id)
            }

            self.boringness -= 1;
        }

        Ok(())
    }
}

#[derive(Derivative)]
#[derivative(Debug)]
pub struct SpanHandle<'a, T: SpanTrackable> {
    #[derivative(Debug = "ignore")]
    tracker: &'a SpanTracker<T>,
    span: &'a Span<T>,
}

impl<'a, T: SpanTrackable> SpanHandle<'a, T> {
    pub fn info(&self) -> &SpanInfo<T> {
        &self.span.info
    }

    pub fn children<'b>(&'b self) -> impl ExactSizeIterator<Item = SpanHandle<'b, T>> + 'b
    where
        'a: 'b,
    {
        self.span.children.iter().map(move |c| {
            let span = self
                .tracker
                .all
                .get(c.0)
                .with_context(|| {
                    format!(
                        "Invariant violation: span `{:?}` references non-existent child `{}`",
                        self.span.info.event, c.0
                    )
                })
                .unwrap();

            SpanHandle {
                span,
                tracker: self.tracker,
            }
        })
    }
}

/// Tracking list of roots, partitioned in not-boring and boring roots. The former are yielded
/// first when iterating.
///
/// Invariants:
///
/// - A given Span is never in both roots and boring_roots.
#[derive(Clone)]
pub struct Roots<T: SpanTrackable> {
    roots: LinkedHashMap<<T as SpanTrackable>::Id, RootData>,
    boring_roots: LinkedHashMap<<T as SpanTrackable>::Id, RootData>,
    dice_counts: HashMap<&'static str, u64>,
}

#[derive(Clone)]
pub struct RootData {
    dice_key_type: Option<&'static str>,
}

impl RootData {
    pub fn new<T: SpanTrackable>(span: &T) -> Self {
        Self {
            dice_key_type: span.dice_key_type(),
        }
    }
}

impl<T: SpanTrackable> Default for Roots<T> {
    fn default() -> Self {
        Self {
            roots: Default::default(),
            boring_roots: Default::default(),
            dice_counts: Default::default(),
        }
    }
}

impl<T: SpanTrackable> Roots<T> {
    pub fn contains(&self, span_id: <T as SpanTrackable>::Id) -> bool {
        self.roots.contains_key(&span_id) || self.boring_roots.contains_key(&span_id)
    }

    /// Insert a span. This must be called once at most per Span ID.
    pub fn insert(&mut self, span_id: <T as SpanTrackable>::Id, boring: bool, data: RootData) {
        if let Some(dice_key_type) = data.dice_key_type {
            *self.dice_counts.entry(dice_key_type).or_default() += 1;
        }

        if boring {
            self.boring_roots.insert(span_id, data);
        } else {
            self.roots.insert(span_id, data);
        }
    }

    /// Remove a Span. It's OK if the span was never inserted.
    pub fn remove(&mut self, span_id: <T as SpanTrackable>::Id) -> Option<RootData> {
        let data = self
            .roots
            .remove(&span_id)
            .or_else(|| self.boring_roots.remove(&span_id));

        if let Some(dice_key_type) = data.as_ref().and_then(|d| d.dice_key_type) {
            // About this unwrap: the key type is never changed while the entry exists, so if we
            // delete an entry whose RootData has a key type, we must have increment it.
            match self.dice_counts.get_mut(dice_key_type) {
                Some(c) if *c > 0 => {
                    *c -= 1;
                }
                v => {
                    tracing::error!("Decrementing {} but it is was {:?}", dice_key_type, v);
                }
            }
        }

        data
    }

    /// If the span is currently not-boring, move it to the boring list.
    pub fn make_boring(&mut self, span_id: <T as SpanTrackable>::Id) {
        if let Some(root) = self.roots.remove(&span_id) {
            self.boring_roots.insert(span_id, root);
        }
    }

    /// If the span is currently boring, move it to the not-boring list.
    pub fn make_not_boring(&mut self, span_id: <T as SpanTrackable>::Id) {
        if let Some(root) = self.boring_roots.remove(&span_id) {
            self.roots.insert(span_id, root);
        }
    }

    pub fn len(&self) -> usize {
        self.roots.len() + self.boring_roots.len()
    }

    pub fn is_empty(&self) -> bool {
        self.roots.is_empty() && self.boring_roots.is_empty()
    }

    pub fn iter(&self) -> impl ExactSizeIterator<Item = &<T as SpanTrackable>::Id> {
        let size = self.roots.len() + self.boring_roots.len();

        // Chain does not implement ExactSizeIterator because it may overflow. We assume that our
        // roots don't overflow.
        ExactSizeIteratorWrapper {
            inner: self.roots.keys().chain(self.boring_roots.keys()),
            size,
        }
    }

    pub fn dice_counts(&self) -> &HashMap<&'static str, u64> {
        &self.dice_counts
    }
}

struct ExactSizeIteratorWrapper<T> {
    inner: T,
    size: usize,
}

impl<T> Iterator for ExactSizeIteratorWrapper<T>
where
    T: Iterator,
{
    type Item = <T as Iterator>::Item;

    fn next(&mut self) -> Option<Self::Item> {
        let val = self.inner.next();
        if val.is_some() {
            self.size -= 1;
        }
        val
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        (self.size, Some(self.size))
    }
}

impl<T> ExactSizeIterator for ExactSizeIteratorWrapper<T> where T: Iterator {}

/// SpanTracker tracks ongoing spans received via handle() (those are typically produced by
/// the Buck daemon). Internally, we keep track of:
///
/// - Ongoing spans that are roots. Those will be rendered on their own line in the console.
/// - All ongoing spans by id. This is used to access spans by id, such as when looking for
///   a parent span.
///
/// Internally, Spans also reference their children. It is required that SpanStart and SpanEnd
/// events for the same Span have the same parent. We guarantee that if a Span is referenced as a
/// child, it'll be found in the `all` map.
///
/// We also keep track of how many roots have ended.
#[derive(Clone)]
pub struct SpanTracker<T: SpanTrackable> {
    roots: Roots<T>,
    all: HashMap<<T as SpanTrackable>::Id, Span<T>>,
    roots_completed: usize,
}

impl<T: SpanTrackable + Dupe> SpanTracker<T> {
    pub fn new() -> Self {
        Self {
            roots: Default::default(),
            all: Default::default(),
            roots_completed: 0,
        }
    }

    pub fn start_at(&mut self, event: &T, at: Instant) -> anyhow::Result<()> {
        if !event.is_shown() {
            return Ok(());
        }

        let is_boring = event.is_boring();

        let span_id = event
            .span_id()
            .ok_or_else(|| SpanTrackerError::NonSpanEvent(event.dupe()))?;

        self.all.entry(span_id).or_insert_with(|| Span {
            span_id,
            info: SpanInfo {
                event: event.dupe(),
                start: at,
            },
            boringness: if is_boring { 1 } else { 0 },
            children: LinkedHashMap::new(),
        });

        let parent = event.parent_id().and_then(|id| self.all.get_mut(&id));

        match parent {
            Some(parent) => {
                parent.add_child(span_id, is_boring, &mut self.roots);
            }
            None => {
                self.roots.insert(span_id, is_boring, RootData::new(event));
            }
        };

        Ok(())
    }

    fn end(&mut self, event: &T) -> anyhow::Result<()> {
        let span_id = event
            .span_id()
            .ok_or_else(|| SpanTrackerError::NonSpanEvent(event.dupe()))?;

        // NOTE: We temporarily violate the invariant that all `roots` are referenced in `all`, but
        // since we have a `&mut self`, we just need to be careful not to rely on this invariant
        // in this function.
        let removed = match self.all.remove(&span_id) {
            Some(removed) => removed,
            None => {
                return Ok(());
            }
        };

        // This event might not be a root, but we need to maintain the invariant that nothing can
        // be in `roots` if it's not in `all` so we still have to clear it. Besides, we need to
        // find out if it was indeed a root to track roots_completed.
        if self.roots.remove(span_id).is_some() {
            self.roots_completed += 1;
        } else {
            let parent = event.parent_id().and_then(|id| self.all.get_mut(&id));
            match parent {
                Some(parent) => {
                    parent.remove_child(removed, &mut self.roots)?;
                }
                None => {
                    // Likely a bug: if this node was shown (meaning it was in `all`), but didn't
                    // have a parent or its parent wasn't shown, then it should have been a root.
                }
            };
        }

        Ok(())
    }

    pub fn iter_roots<'a>(&'a self) -> impl ExactSizeIterator<Item = SpanHandle<'a, T>> + 'a {
        self.roots.iter().map(move |s| {
            // NOTE: This unwrap is safe because we guarantee that `roots` only references spans
            // that exist in `all`.
            let span = self
                .all
                .get(s)
                .expect("Root cannot be registered and missing from all()");

            SpanHandle {
                span,
                tracker: self,
            }
        })
    }

    pub fn roots_completed(&self) -> usize {
        self.roots_completed
    }

    /// Return false if span_tracker has been used.
    pub fn is_unused(&self) -> bool {
        self.roots.is_empty() && self.roots_completed == 0
    }

    pub fn roots_ongoing(&self) -> usize {
        self.roots.len()
    }

    pub fn roots(&self) -> &Roots<T> {
        &self.roots
    }
}

pub trait SpanTrackable: std::fmt::Debug + Send + Sync + 'static {
    type Id: std::fmt::Display + std::fmt::Debug + std::hash::Hash + Eq + PartialEq + Copy;

    fn span_id(&self) -> Option<Self::Id>;

    fn parent_id(&self) -> Option<Self::Id>;

    /// Determine whether this Span should be rendered. If this span's parent isn't shown (if any
    /// exists), then it'll be rendered as a root. Otherwise, it'll be rendered as a child.
    fn is_shown(&self) -> bool;

    fn is_boring(&self) -> bool;

    /// Report the DICE key type that contains this span. We use this to be able to tell how many
    /// spans we currently are reporting that map to a given DICE key type. The key types here
    /// should match the type we receive in the DiceStateSnapshot.
    fn dice_key_type(&self) -> Option<&'static str>;
}

impl SpanTrackable for BuckEvent {
    type Id = SpanId;

    fn span_id(&self) -> Option<Self::Id> {
        BuckEvent::span_id(self)
    }

    fn parent_id(&self) -> Option<Self::Id> {
        BuckEvent::parent_id(self)
    }

    fn is_shown(&self) -> bool {
        is_span_shown(self)
    }

    fn is_boring(&self) -> bool {
        use buck2_data::span_start_event::Data;

        match self.span_start_event().and_then(|span| span.data.as_ref()) {
            Some(Data::ExecutorStage(data)) => {
                use buck2_data::executor_stage_start::Stage;

                match data.stage.as_ref() {
                    Some(Stage::Local(stage)) => {
                        use buck2_data::local_stage::Stage;

                        match stage.stage.as_ref() {
                            Some(Stage::Queued(..) | Stage::AcquireLocalResource(..)) => true,
                            _ => false,
                        }
                    }
                    Some(Stage::Re(stage)) => {
                        use buck2_data::re_stage::Stage;

                        match stage.stage.as_ref() {
                            Some(Stage::Queue(..)) => true,
                            _ => false,
                        }
                    }
                    _ => false,
                }
            }
            Some(Data::BxlDiceInvocation(..)) => true,
            _ => false,
        }
    }

    fn dice_key_type(&self) -> Option<&'static str> {
        use buck2_data::span_start_event::Data;

        match self.span_start_event().and_then(|span| span.data.as_ref()) {
            Some(Data::ActionExecution(..)) => Some("BuildKey"),
            Some(Data::Analysis(..)) => Some("AnalysisKey"),
            _ => None,
        }
    }
}

impl<T: SpanTrackable> SpanTrackable for Arc<T> {
    type Id = <T as SpanTrackable>::Id;

    fn span_id(&self) -> Option<Self::Id> {
        SpanTrackable::span_id(self.as_ref())
    }

    fn parent_id(&self) -> Option<Self::Id> {
        SpanTrackable::parent_id(self.as_ref())
    }

    fn is_shown(&self) -> bool {
        SpanTrackable::is_shown(self.as_ref())
    }

    fn is_boring(&self) -> bool {
        SpanTrackable::is_boring(self.as_ref())
    }

    fn dice_key_type(&self) -> Option<&'static str> {
        SpanTrackable::dice_key_type(self.as_ref())
    }
}

pub fn is_span_shown(event: &BuckEvent) -> bool {
    use buck2_data::span_start_event::Data;

    match event.span_start_event().and_then(|span| span.data.as_ref()) {
        Some(
            Data::Command(..)
            | Data::CommandCritical(..)
            | Data::Materialization(..)
            | Data::DiceCriticalSection(..)
            | Data::BxlEnsureArtifacts(..),
        ) => false,
        Some(
            Data::ActionExecution(..)
            | Data::FinalMaterialization(..)
            | Data::Analysis(..)
            | Data::Load(..)
            | Data::LoadPackage(..)
            | Data::TestDiscovery(..)
            | Data::TestStart(..)
            | Data::FileWatcher(..)
            | Data::SharedTask(..)
            | Data::CreateOutputSymlinks(..)
            | Data::InstallEventInfo(..)
            | Data::DiceStateUpdate(..)
            | Data::Fake(..)
            | Data::AnalysisStage(..)
            | Data::ExecutorStage(..)
            | Data::MatchDepFiles(..)
            | Data::CacheUpload(..)
            | Data::DiceBlockConcurrentCommand(..)
            | Data::DiceSynchronizeSection(..)
            | Data::DiceCleanup(..)
            | Data::ExclusiveCommandWait(..)
            | Data::DeferredPreparationStage(..)
            | Data::DynamicLambda(..)
            | Data::BxlExecution(..)
            | Data::BxlDiceInvocation(..)
            | Data::ReUpload(..)
            | Data::ConnectToInstaller(..)
            | Data::LocalResources(..)
            | Data::ReleaseLocalResources(..),
        ) => true,
        None => false,
    }
}

pub type BuckEventSpanTracker = SpanTracker<Arc<BuckEvent>>;
pub type BuckEventSpanHandle<'a> = SpanHandle<'a, Arc<BuckEvent>>;
pub type BuckEventSpanInfo = SpanInfo<Arc<BuckEvent>>;

impl BuckEventSpanTracker {
    pub fn handle_event(
        &mut self,
        receive_time: Instant,
        event: &Arc<BuckEvent>,
    ) -> anyhow::Result<()> {
        if let Some(_start) = event.span_start_event() {
            self.start_at(event, receive_time)?;
        } else if let Some(_end) = event.span_end_event() {
            self.end(event)?;
        }
        Ok(())
    }
}

impl WhatRanState<OptionalSpanId> for SpanTracker<Arc<BuckEvent>> {
    fn get(&self, span_id: OptionalSpanId) -> Option<WhatRanRelevantAction<'_>> {
        let span_id = span_id.0?;

        self.all
            .get(&span_id)
            .map(|e| e.info.event.data())
            .and_then(WhatRanRelevantAction::from_buck_data)
    }
}

/// A wrapper type to make calls to emit_event_if_relevant more convenient, since parent_id is
/// `Option<SpanId>` on BuckEvent.
#[derive(From, Copy, Clone, Dupe)]
pub struct OptionalSpanId(Option<SpanId>);

impl fmt::Display for OptionalSpanId {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        if let Some(this) = self.0 {
            write!(formatter, "{}", this)
        } else {
            write!(formatter, "(none)")
        }
    }
}

#[cfg(test)]
mod test {
    use std::sync::atomic::AtomicI64;
    use std::sync::atomic::Ordering;

    use assert_matches::assert_matches;

    use super::*;

    #[derive(Copy, Clone, Dupe, Eq, PartialEq, Debug)]
    struct TestSpan {
        span_id: i64,
        parent_id: Option<i64>,
        boring: bool,
        dice_key_type: Option<&'static str>,
    }

    impl SpanTrackable for TestSpan {
        type Id = i64;

        fn span_id(&self) -> Option<Self::Id> {
            Some(self.span_id)
        }

        fn parent_id(&self) -> Option<Self::Id> {
            self.parent_id
        }

        fn is_shown(&self) -> bool {
            true
        }

        fn is_boring(&self) -> bool {
            self.boring
        }

        fn dice_key_type(&self) -> Option<&'static str> {
            self.dice_key_type
        }
    }

    impl TestSpan {
        fn new() -> Self {
            static CURR: AtomicI64 = AtomicI64::new(0);

            Self {
                span_id: CURR.fetch_add(1, Ordering::Relaxed),
                parent_id: None,
                boring: false,
                dice_key_type: None,
            }
        }

        fn parent(mut self, parent: TestSpan) -> Self {
            self.parent_id = Some(parent.span_id);
            self
        }

        fn boring(mut self) -> Self {
            self.boring = true;
            self
        }

        fn dice_key_type(mut self, dice_key_type: &'static str) -> Self {
            self.dice_key_type = Some(dice_key_type);
            self
        }
    }

    #[test]
    fn test_boring_via_self() -> anyhow::Result<()> {
        let t0 = Instant::now();

        let boring = TestSpan::new().boring();
        let not_boring = TestSpan::new();

        let mut tracker = SpanTracker::new();
        tracker.start_at(&boring, t0)?;
        tracker.start_at(&not_boring, t0)?;

        let mut iter = tracker.iter_roots();
        {
            assert_matches!(iter.next(), Some(hdl) => {
                assert_eq!(hdl.info().event, not_boring);
            });
            assert_matches!(iter.next(), Some(hdl) => {
                assert_eq!(hdl.info().event, boring);
            });
        }

        Ok(())
    }

    #[test]
    fn test_boring_via_child() -> anyhow::Result<()> {
        let t0 = Instant::now();

        let parent = TestSpan::new();
        let child = TestSpan::new().parent(parent).boring();

        let other = TestSpan::new();
        let other2 = TestSpan::new();

        let mut tracker = SpanTracker::new();
        tracker.start_at(&parent, t0)?;

        {
            let mut iter = tracker.iter_roots();
            assert_matches!(iter.next(), Some(hdl) => {
                assert_eq!(hdl.info().event, parent);
            });
        }

        tracker.start_at(&other, t0)?;

        {
            let mut iter = tracker.iter_roots();
            assert_matches!(iter.next(), Some(hdl) => {
                assert_eq!(hdl.info().event, parent);
            });
            assert_matches!(iter.next(), Some(hdl) => {
                assert_eq!(hdl.info().event, other);
            });
        }

        tracker.start_at(&child, t0)?;
        {
            let mut iter = tracker.iter_roots();
            assert_matches!(iter.next(), Some(hdl) => {
                assert_eq!(hdl.info().event, other);
            });
            assert_matches!(iter.next(), Some(hdl) => {
                assert_eq!(hdl.info().event, parent);
            });
        }

        tracker.end(&child)?;
        tracker.start_at(&other2, t0)?;
        {
            let mut iter = tracker.iter_roots();
            assert_matches!(iter.next(), Some(hdl) => {
                assert_eq!(hdl.info().event, other);
            });
            assert_matches!(iter.next(), Some(hdl) => {
                assert_eq!(hdl.info().event, parent);
            });
            assert_matches!(iter.next(), Some(hdl) => {
                assert_eq!(hdl.info().event, other2);
            });
        }

        Ok(())
    }

    #[test]
    fn test_iter_roots_len() -> anyhow::Result<()> {
        let t0 = Instant::now();

        let e1 = TestSpan::new();
        let e2 = TestSpan::new().boring();
        let e3 = TestSpan::new();

        let mut tracker = SpanTracker::new();
        tracker.start_at(&e1, t0)?;
        tracker.start_at(&e2, t0)?;
        tracker.start_at(&e3, t0)?;

        {
            let mut iter = tracker.iter_roots();
            assert_eq!(iter.len(), 3);

            iter.next();
            assert_eq!(iter.len(), 2);

            iter.next();
            assert_eq!(iter.len(), 1);

            iter.next();
            assert_eq!(iter.len(), 0);

            iter.next();
            assert_eq!(iter.len(), 0);
        }

        Ok(())
    }

    #[test]
    fn test_dice_counts() -> anyhow::Result<()> {
        let t0 = Instant::now();

        let foo = TestSpan::new().dice_key_type("foo");
        let bar = TestSpan::new().dice_key_type("bar");

        let mut tracker = SpanTracker::new();
        tracker.start_at(&foo, t0)?;
        tracker.start_at(&bar, t0)?;

        assert_eq!(tracker.roots.dice_counts["foo"], 1);
        assert_eq!(tracker.roots.dice_counts["bar"], 1);

        tracker.end(&foo)?;
        assert_eq!(tracker.roots.dice_counts["foo"], 0);
        assert_eq!(tracker.roots.dice_counts["bar"], 1);

        tracker.end(&bar)?;
        assert_eq!(tracker.roots.dice_counts["foo"], 0);
        assert_eq!(tracker.roots.dice_counts["bar"], 0);

        Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::collections::BTreeSet;
use std::time::SystemTime;

use buck2_data::DebugAdapterSnapshot;

#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct StoppedEval {
    pub description: String,
    pub stopped_at: String,
}

#[derive(Debug)]
pub struct StarlarkDebuggerState {
    pub debugger_attached: bool,
    pub this_stopped_evals: BTreeSet<StoppedEval>,
    pub other_stopped_evals: BTreeSet<StoppedEval>,
}

impl StarlarkDebuggerState {
    pub fn new() -> Self {
        Self {
            debugger_attached: false,
            this_stopped_evals: BTreeSet::new(),
            other_stopped_evals: BTreeSet::new(),
        }
    }

    pub fn update(
        &mut self,
        _event_time: SystemTime,
        event: &DebugAdapterSnapshot,
    ) -> anyhow::Result<()> {
        self.debugger_attached = true;
        self.this_stopped_evals.clear();
        self.other_stopped_evals.clear();

        for (handle, evals) in &event.current_handles {
            for stopped in &evals.stopped_evals {
                let v = StoppedEval {
                    description: stopped.description.clone(),
                    stopped_at: stopped.stopped_at.clone(),
                };
                if *handle == event.this_handle {
                    self.this_stopped_evals.insert(v);
                } else {
                    self.other_stopped_evals.insert(v);
                }
            }
        }
        Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use buck2_test_api::data::TestStatus;

#[derive(Default)]
pub struct TestState {
    pub discovered: u64,
    pub pass: u64,
    pub fail: u64,
    pub fatal: u64,
    pub timeout: u64,
    pub skipped: u64,
    pub omitted: u64,
    pub retry: u64,
    pub unknown: u64,
    pub listing_success: u64,
    pub listing_failed: u64,
}

impl TestState {
    pub(crate) fn update(&mut self, result: &buck2_data::TestResult) -> anyhow::Result<()> {
        let status = TestStatus::try_from(result.status)?;
        let counter = match status {
            TestStatus::PASS => &mut self.pass,
            TestStatus::FAIL => &mut self.fail,
            TestStatus::FATAL => &mut self.fatal,
            TestStatus::SKIP => &mut self.skipped,
            TestStatus::OMITTED => &mut self.omitted,
            TestStatus::TIMEOUT => &mut self.timeout,
            TestStatus::UNKNOWN => &mut self.unknown,
            TestStatus::RERUN => &mut self.retry,
            TestStatus::LISTING_SUCCESS => &mut self.listing_success,
            TestStatus::LISTING_FAILED => &mut self.listing_failed,
        };
        *counter += 1;

        Ok(())
    }

    pub fn not_executed(&self) -> u64 {
        self.skipped + self.omitted
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::mem;
use std::time::Duration;
use std::time::SystemTime;

#[derive(Default)]
pub struct TwoSnapshots {
    pub penultimate: Option<(SystemTime, buck2_data::Snapshot)>,
    pub last: Option<(SystemTime, buck2_data::Snapshot)>,
}

impl TwoSnapshots {
    pub fn update(&mut self, timestamp: SystemTime, snapshot: &buck2_data::Snapshot) {
        self.penultimate = mem::replace(&mut self.last, Some((timestamp, snapshot.clone())));
    }

    fn non_zero_duration(&self) -> Option<Duration> {
        let (penultimate_time, _) = self.penultimate.as_ref()?;
        let (last_time, _) = self.last.as_ref()?;
        let duration = last_time.duration_since(*penultimate_time).ok()?;
        if duration.is_zero() {
            return None;
        }
        Some(duration)
    }

    /// User + system CPU time between two snapshots in percents.
    pub fn cpu_percents(&self) -> Option<u32> {
        let (_, penultimate_snapshot) = self.penultimate.as_ref()?;
        let (_, last_snapshot) = self.last.as_ref()?;
        let duration = self.non_zero_duration()?;
        let last_cpu_us = last_snapshot.buck2_user_cpu_us + last_snapshot.buck2_system_cpu_us;
        let penultimate_cpu_us =
            penultimate_snapshot.buck2_user_cpu_us + penultimate_snapshot.buck2_system_cpu_us;
        if penultimate_cpu_us > 0 && last_cpu_us >= penultimate_cpu_us {
            let cpu_us = last_cpu_us - penultimate_cpu_us;
            Some((cpu_us * 100 / (duration.as_micros() as u64)) as u32)
        } else {
            None
        }
    }

    /// Measure bytes-per-second rate between two snapshots for some field.
    fn bytes_per_second(&self, field: impl Fn(&buck2_data::Snapshot) -> u64) -> Option<u64> {
        let (_, penultimate_snapshot) = self.penultimate.as_ref()?;
        let (_, last_snapshot) = self.last.as_ref()?;
        let duration = self.non_zero_duration()?;
        let last_bytes = field(last_snapshot);
        let penultimate_bytes = field(penultimate_snapshot);
        let bytes = last_bytes.checked_sub(penultimate_bytes)?;
        Some(bytes * 1_000_000 / duration.as_micros() as u64)
    }

    pub fn re_download_bytes_per_second(&self) -> Option<u64> {
        self.bytes_per_second(|snapshot| snapshot.re_download_bytes)
    }

    pub fn re_upload_bytes_per_second(&self) -> Option<u64> {
        self.bytes_per_second(|snapshot| snapshot.re_upload_bytes)
    }

    pub fn http_download_bytes_per_second(&self) -> Option<u64> {
        self.bytes_per_second(|snapshot| snapshot.http_download_bytes)
    }
}

#[cfg(test)]
mod tests {
    use std::ops::Add;
    use std::time::Duration;
    use std::time::SystemTime;

    use super::TwoSnapshots;

    #[test]
    fn test_cpu_percents() {
        let t0 = SystemTime::UNIX_EPOCH.add(Duration::from_secs(100000));

        let mut two_snapshots = TwoSnapshots::default();
        assert_eq!(None, two_snapshots.cpu_percents());
        two_snapshots.update(
            t0,
            &buck2_data::Snapshot {
                buck2_user_cpu_us: 100,
                buck2_system_cpu_us: 200,
                ..Default::default()
            },
        );
        assert_eq!(None, two_snapshots.cpu_percents());
        two_snapshots.update(
            t0.add(Duration::from_secs(2)),
            &buck2_data::Snapshot {
                buck2_user_cpu_us: 6_000_100,
                buck2_system_cpu_us: 8_000_200,
                ..Default::default()
            },
        );
        // 2 seconds real time, 14 seconds user + system time, so 700% CPU.
        assert_eq!(Some(700), two_snapshots.cpu_percents());
    }

    #[test]
    fn test_bytes_per_second() {
        let t0 = SystemTime::UNIX_EPOCH.add(Duration::from_secs(100000));

        let mut two_snapshots = TwoSnapshots::default();
        assert_eq!(None, two_snapshots.re_download_bytes_per_second());
        two_snapshots.update(
            t0,
            &buck2_data::Snapshot {
                re_download_bytes: 100,
                ..Default::default()
            },
        );
        assert_eq!(None, two_snapshots.re_download_bytes_per_second());
        two_snapshots.update(
            t0.add(Duration::from_secs(2)),
            &buck2_data::Snapshot {
                re_download_bytes: 6100,
                ..Default::default()
            },
        );
        assert_eq!(Some(3000), two_snapshots.re_download_bytes_per_second());
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use buck2_data::buck_event;
use buck2_data::InstantEvent;
use buck2_data::SpanEndEvent;
use buck2_data::SpanStartEvent;
use buck2_events::BuckEvent;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum VisitorError {
    #[error("Sent an event missing one or more fields: `{0:?}`")]
    MissingField(BuckEvent),
    #[error("Sent an unexpected Record event: `{0:?}`")]
    UnexpectedRecord(BuckEvent),
}

/// Just a simple structure that makes it easier to deal with BuckEvent rather than
/// needing to deal with the unpacking of optional fields yourself.
pub enum UnpackedBuckEvent<'a> {
    SpanStart(
        &'a BuckEvent,
        &'a SpanStartEvent,
        &'a buck2_data::span_start_event::Data,
    ),
    SpanEnd(
        &'a BuckEvent,
        &'a SpanEndEvent,
        &'a buck2_data::span_end_event::Data,
    ),
    Instant(
        &'a BuckEvent,
        &'a InstantEvent,
        &'a buck2_data::instant_event::Data,
    ),
}

pub fn unpack_event(event: &BuckEvent) -> anyhow::Result<UnpackedBuckEvent> {
    match &event.data() {
        buck_event::Data::SpanStart(v) => Ok(UnpackedBuckEvent::SpanStart(
            event,
            v,
            v.data
                .as_ref()
                .ok_or_else(|| VisitorError::MissingField(event.clone()))?,
        )),
        buck_event::Data::SpanEnd(v) => Ok(UnpackedBuckEvent::SpanEnd(
            event,
            v,
            v.data
                .as_ref()
                .ok_or_else(|| VisitorError::MissingField(event.clone()))?,
        )),
        buck_event::Data::Instant(v) => Ok(UnpackedBuckEvent::Instant(
            event,
            v,
            v.data
                .as_ref()
                .ok_or_else(|| VisitorError::MissingField((*event).clone()))?,
        )),
        buck_event::Data::Record(_) => Err(VisitorError::UnexpectedRecord(event.clone()).into()),
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use dupe::Dupe;

/// The logging verbosity to use in our various consoles.
///
/// Each level is a superset of the previous ones.
/// Accessor methods should be used to determine
/// specific requested functionality.
#[derive(Debug, Copy, Clone, Dupe, PartialEq, Eq, PartialOrd, Ord)]
pub enum Verbosity {
    /// Print as few messages as possible.
    Quiet,
    /// The default verbosity. Error messages are printed, but extra details may not be.
    Default,
    /// Print more things, but still a manageable and potentially useful amount of output.
    Verbose,
    /// Print even more things, this is likely not very useful.
    AllCommands,
    /// Print too much output, this is likely not useful.
    AllStderr,
}

impl Verbosity {
    pub fn try_from_cli(value: &str) -> anyhow::Result<Self> {
        Ok(match value.parse::<i64>()? {
            i if i <= 0 => Self::Quiet,
            1 => Self::Default,
            2 => Self::Verbose,
            3 => Self::AllCommands,
            _ => Self::AllStderr,
        })
    }

    /// If a verbosity setting is triggered at a particular level, does this verbosity trigger it
    fn at(self, required: Verbosity) -> bool {
        self >= required
    }

    /// Whether stderr should be printed to users for successful commands by default.
    pub fn print_success_stderr(self) -> bool {
        self.at(Self::AllStderr)
    }

    /// Whether the full command for failed actions should be printed. Otherwise, a truncated command is printed.
    pub fn print_failure_full_command(self) -> bool {
        self.at(Self::Verbose)
    }

    /// Whether to print all commands that are being executed
    pub fn print_all_commands(self) -> bool {
        self.at(Self::AllCommands)
    }

    /// Whether we should show all actions (in consoles where that is relevant).
    pub fn print_all_actions(self) -> bool {
        self.at(Self::Verbose)
    }

    /// Whether we should print periodic status messages
    pub fn print_status(self) -> bool {
        self.at(Self::Default)
    }

    /// Whether we should print I/O stats and so on when outputting status of what we're waiting
    /// on. Note that we'll still print them if we hit a condition where we think they are
    /// relevant, such as zero open spans.
    pub fn always_print_stats_in_status(self) -> bool {
        self.at(Self::Verbose)
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::borrow::Cow;
use std::fmt;

use buck2_data::re_platform::Property;
use dupe::Dupe;
use superconsole::Line;
use superconsole::Lines;
use superconsole::SuperConsole;

use crate::display;
use crate::display::TargetDisplayOptions;

/// Options controlling what WhatRan produces.
#[derive(Debug, Default, clap::Parser)]
pub struct WhatRanOptions {
    #[clap(long)]
    pub emit_cache_queries: bool,
    #[clap(long)]
    pub skip_cache_hits: bool,
    #[clap(long)]
    pub skip_remote_executions: bool,
    #[clap(long)]
    pub skip_local_executions: bool,
}

/// An action that makes sense to use to contextualize a command we ran.
#[derive(Copy, Clone, Dupe)]
pub enum WhatRanRelevantAction<'a> {
    ActionExecution(&'a buck2_data::ActionExecutionStart),
    TestDiscovery(&'a buck2_data::TestDiscoveryStart),
    TestRun(&'a buck2_data::TestRunStart),
}

impl<'a> WhatRanRelevantAction<'a> {
    /// Extract a relevant action from an event's data, if we can find one.
    pub fn from_buck_data(data: &'a buck2_data::buck_event::Data) -> Option<Self> {
        match data {
            buck2_data::buck_event::Data::SpanStart(span) => match &span.data {
                Some(buck2_data::span_start_event::Data::ActionExecution(action)) => {
                    Some(Self::ActionExecution(action))
                }
                Some(buck2_data::span_start_event::Data::TestDiscovery(suite)) => {
                    Some(Self::TestDiscovery(suite))
                }
                Some(buck2_data::span_start_event::Data::TestStart(test)) => {
                    Some(Self::TestRun(test))
                }
                _ => None,
            },
            _ => None,
        }
    }
}

pub struct WhatRanOutputCommand<'a> {
    reason: &'a str,
    identity: &'a str,
    repro: CommandReproducer<'a>,
    extra: Option<WhatRanOutputCommandExtra<'a>>,
}

impl WhatRanOutputCommand<'_> {
    pub fn reason(&self) -> &str {
        self.reason
    }
    pub fn identity(&self) -> &str {
        self.identity
    }
    pub fn repro(&self) -> CommandReproducer<'_> {
        self.repro
    }
    pub fn extra(&self) -> Option<WhatRanOutputCommandExtra<'_>> {
        self.extra
    }
}

#[derive(Clone, Copy, Dupe)]
pub enum WhatRanOutputCommandExtra<'a> {
    TestCases(&'a [String]),
}

/// Output to log commands that ran. The expectation is that we can use this to print out events.
pub trait WhatRanOutputWriter {
    fn emit_command(&mut self, command: WhatRanOutputCommand<'_>) -> anyhow::Result<()>;
}

/// Storage provided for events. The expectations is that any previously event that would qualify
/// as a WhatRanRelevantAction was captured in this and will be returned.
pub trait WhatRanState<T> {
    fn get(&self, span_id: T) -> Option<WhatRanRelevantAction<'_>>;
}

/// Presented with an event and its containing span, emit it to the output if it's relevant. The
/// state is used to associate the parent with something meaningful. This does not take the parent
/// directly because *most* events are *not* relevant so we save the lookup in that case.
pub fn emit_event_if_relevant<T: fmt::Display + Copy>(
    parent_span_id: T,
    data: &buck2_data::buck_event::Data,
    state: &impl WhatRanState<T>,
    output: &mut impl WhatRanOutputWriter,
    options: &WhatRanOptions,
) -> anyhow::Result<()> {
    if let Some(repro) = CommandReproducer::from_buck_data(data, options) {
        emit(parent_span_id, repro, state, output)?;
    }

    Ok(())
}

/// Find and format the parent span (if any), then emit the relevant command.
fn emit<T: fmt::Display + Copy>(
    parent_span_id: T,
    repro: CommandReproducer<'_>,
    state: &impl WhatRanState<T>,
    output: &mut impl WhatRanOutputWriter,
) -> anyhow::Result<()> {
    emit_reproducer(state.get(parent_span_id), repro, output)
}

pub fn emit_reproducer(
    action: Option<WhatRanRelevantAction<'_>>,
    repro: CommandReproducer<'_>,
    output: &mut impl WhatRanOutputWriter,
) -> anyhow::Result<()> {
    let (reason, identity, extra) = match action {
        Some(WhatRanRelevantAction::ActionExecution(action)) => (
            "build",
            Cow::Owned(display::display_action_identity(
                action.key.as_ref(),
                action.name.as_ref(),
                TargetDisplayOptions::for_log(),
            )?),
            None,
        ),
        Some(WhatRanRelevantAction::TestDiscovery(test)) => (
            "test.discovery",
            Cow::Borrowed(test.suite_name.as_str()),
            None,
        ),
        Some(WhatRanRelevantAction::TestRun(test)) => match test.suite.as_ref() {
            Some(suite) => (
                "test.run",
                Cow::Borrowed(suite.suite_name.as_str()),
                Some(WhatRanOutputCommandExtra::TestCases(&suite.test_names)),
            ),
            None => ("test.run", Cow::Borrowed("unknown test suite"), None),
        },
        None => ("unknown", Cow::Borrowed("unknown action"), None),
    };

    output.emit_command(WhatRanOutputCommand {
        reason,
        identity: &identity,
        repro,
        extra,
    })?;

    Ok(())
}

/// The reproduction details for this command.
#[derive(Clone, Copy, Dupe)]
pub enum CommandReproducer<'a> {
    CacheQuery(&'a buck2_data::CacheQuery),
    CacheHit(&'a buck2_data::CacheHit),
    ReExecute(&'a buck2_data::ReExecute),
    LocalExecute(&'a buck2_data::LocalExecute),
    WorkerExecute(&'a buck2_data::WorkerExecute),
}

impl<'a> CommandReproducer<'a> {
    pub fn executor(&self) -> String {
        match self {
            Self::CacheQuery(..) => "cache_query".to_owned(),
            Self::CacheHit(..) => "cache".to_owned(),
            Self::ReExecute(execute) => executor_with_platform(execute),
            Self::LocalExecute(..) => "local".to_owned(),
            Self::WorkerExecute(..) => "worker".to_owned(),
        }
    }

    /// Human-readable representation of this repro instruction
    pub fn as_human_readable(&self) -> HumanReadableCommandReproducer<'a> {
        HumanReadableCommandReproducer { command: *self }
    }

    pub fn from_buck_data(
        data: &'a buck2_data::buck_event::Data,
        options: &WhatRanOptions,
    ) -> Option<Self> {
        match data {
            buck2_data::buck_event::Data::SpanStart(span) => match &span.data {
                Some(buck2_data::span_start_event::Data::ExecutorStage(executor_stage)) => {
                    match &executor_stage.stage {
                        Some(buck2_data::executor_stage_start::Stage::CacheQuery(cache_hit))
                            if options.emit_cache_queries =>
                        {
                            return Some(CommandReproducer::CacheQuery(cache_hit));
                        }
                        Some(buck2_data::executor_stage_start::Stage::CacheHit(cache_hit))
                            if !options.skip_cache_hits =>
                        {
                            return Some(CommandReproducer::CacheHit(cache_hit));
                        }
                        Some(buck2_data::executor_stage_start::Stage::Re(re_stage))
                            if !options.skip_remote_executions =>
                        {
                            match &re_stage.stage {
                                Some(buck2_data::re_stage::Stage::Execute(execute)) => {
                                    return Some(CommandReproducer::ReExecute(execute));
                                }
                                _ => {}
                            }
                        }
                        Some(buck2_data::executor_stage_start::Stage::Local(local_stage)) => {
                            if !options.skip_local_executions {
                                match &local_stage.stage {
                                    Some(buck2_data::local_stage::Stage::Execute(
                                        local_execute,
                                    )) => {
                                        return Some(CommandReproducer::LocalExecute(
                                            local_execute,
                                        ));
                                    }
                                    Some(buck2_data::local_stage::Stage::WorkerExecute(
                                        worker_execute,
                                    )) => {
                                        return Some(CommandReproducer::WorkerExecute(
                                            worker_execute,
                                        ));
                                    }
                                    _ => {}
                                }
                            }
                        }
                        _ => {}
                    }
                }
                _ => {}
            },
            _ => {}
        };

        None
    }
}

/// A wrapper type to output CommandReproducer as a human readable string.
pub struct HumanReadableCommandReproducer<'a> {
    command: CommandReproducer<'a>,
}

impl<'a> fmt::Display for HumanReadableCommandReproducer<'a> {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        match &self.command {
            CommandReproducer::CacheQuery(re_action_cache) => {
                write!(formatter, "{}", re_action_cache.action_digest)
            }
            CommandReproducer::CacheHit(re_action_cache) => {
                write!(formatter, "{}", re_action_cache.action_digest)
            }
            CommandReproducer::ReExecute(re_action_cache) => {
                write!(formatter, "{}", re_action_cache.action_digest)
            }
            CommandReproducer::LocalExecute(local_execute) => {
                if let Some(command) = &local_execute.command {
                    write!(formatter, "{}", command_to_string(command))
                } else {
                    Ok(())
                }
            }
            CommandReproducer::WorkerExecute(worker_execute) => {
                if let Some(command) = &worker_execute.command {
                    write!(
                        formatter,
                        "{}",
                        worker_command_as_fallback_to_string(command)
                    )
                } else {
                    Ok(())
                }
            }
        }
    }
}

pub struct Command<'a> {
    env: &'a Vec<buck2_data::EnvironmentEntry>,
    argv: &'a Vec<String>,
}

impl<'a> From<&'a buck2_data::LocalCommand> for Command<'a> {
    fn from(command: &'a buck2_data::LocalCommand) -> Self {
        Command {
            env: &command.env,
            argv: &command.argv,
        }
    }
}

impl<'a> From<&'a buck2_data::WorkerCommand> for Command<'a> {
    fn from(command: &'a buck2_data::WorkerCommand) -> Self {
        Command {
            env: &command.env,
            argv: &command.argv,
        }
    }
}

impl<'a> From<&'a buck2_data::WorkerInitCommand> for Command<'a> {
    fn from(command: &'a buck2_data::WorkerInitCommand) -> Self {
        Command {
            env: &command.env,
            argv: &command.argv,
        }
    }
}

pub fn worker_command_as_fallback_to_string(command: &buck2_data::WorkerCommand) -> String {
    let mut argv = command.fallback_exe.to_vec();
    argv.extend(command.argv.to_vec());
    command_to_string(Command {
        env: &command.env,
        argv: &argv,
    })
}

pub fn command_to_string<'a>(command: impl Into<Command<'a>>) -> String {
    let command = command.into();
    let mut cmd = vec![];

    if !command.env.is_empty() {
        cmd.push(Cow::Borrowed("env"));
        cmd.push(Cow::Borrowed("--"));
        for entry in command.env.iter() {
            cmd.push(Cow::Owned(format!("{}={}", entry.key, entry.value)))
        }
    }

    for arg in command.argv.iter() {
        cmd.push(Cow::Borrowed(arg));
    }

    shlex::join(cmd.iter().map(|e| e.as_ref()))
}

impl WhatRanOutputWriter for SuperConsole {
    fn emit_command(&mut self, command: WhatRanOutputCommand<'_>) -> anyhow::Result<()> {
        // TODO: Change this API to just produce a String.
        let msg = WhatRanCommandConsoleFormat {
            reason: command.reason(),
            identity: command.identity(),
            repro: command.repro(),
        }
        .to_string();
        self.emit(Lines(vec![Line::sanitized(&msg)]));
        Ok(())
    }
}

/// A consistent format for printing that we are about to run an action.
pub struct WhatRanCommandConsoleFormat<'a, 'b> {
    pub reason: &'a str,
    pub identity: &'a str,
    pub repro: CommandReproducer<'b>,
}

impl<'a, 'b> fmt::Display for WhatRanCommandConsoleFormat<'a, 'b> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "Running action: {} ({}), {} executor: {}",
            self.identity,
            self.reason,
            self.repro.executor(),
            self.repro.as_human_readable()
        )
    }
}

fn executor_with_platform(execute: &buck2_data::ReExecute) -> String {
    if let Some(platform) = &execute.platform {
        let platform = platform
            .properties
            .iter()
            .map(|Property { name, value }| format!("{}={}", name, value))
            .collect::<Vec<String>>()
            .join(",");
        format!("re({})", platform)
    } else {
        "re".to_owned()
    }
}

#[cfg(test)]
mod tests {
    use buck2_data::re_platform::Property;
    use buck2_data::ReExecute;
    use buck2_data::RePlatform;

    use super::*;

    #[test]
    fn test_executor_with_platform() {
        let execute = ReExecute {
            action_digest: "placeholder".to_owned(),
            platform: Some(RePlatform {
                properties: vec![
                    Property {
                        name: "platform".to_owned(),
                        value: "linux-remote-execution".to_owned(),
                    },
                    Property {
                        name: "name1".to_owned(),
                        value: "value1".to_owned(),
                    },
                ],
            }),
            action_key: None,
        };
        let result = executor_with_platform(&execute);
        assert_eq!(
            result,
            "re(platform=linux-remote-execution,name1=value1)".to_owned()
        );
    }

    #[test]
    fn test_executor_with_platform_no_platform() {
        let execute = buck2_data::ReExecute::default();
        let result = executor_with_platform(&execute);
        assert_eq!(result, "re".to_owned());
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::sync::Arc;

use anyhow::Context;
use buck2_core::directory::DirectoryEntry;
use buck2_core::fs::fs_util;
use buck2_core::fs::project::ProjectRoot;
use buck2_core::fs::project_rel_path::ProjectRelativePath;
use dupe::Dupe;

use crate::artifact_value::ArtifactValue;
use crate::digest_config::DigestConfig;
use crate::directory::extract_artifact_value;
use crate::directory::insert_artifact;
use crate::directory::insert_entry;
use crate::directory::new_symlink;
use crate::directory::relativize_directory;
use crate::directory::ActionDirectoryBuilder;
use crate::directory::ActionDirectoryEntry;
use crate::directory::ActionDirectoryMember;
use crate::directory::ActionSharedDirectory;
use crate::directory::INTERNER;

pub struct ArtifactValueBuilder<'a> {
    /// Only used to relativize paths; no disk operations performed!
    project_fs: &'a ProjectRoot,
    builder: ActionDirectoryBuilder,
    digest_config: DigestConfig,
}

impl<'a> ArtifactValueBuilder<'a> {
    pub fn new(project_fs: &'a ProjectRoot, digest_config: DigestConfig) -> Self {
        Self {
            project_fs,
            builder: ActionDirectoryBuilder::empty(),
            digest_config,
        }
    }

    pub fn add_entry(
        &mut self,
        path: &ProjectRelativePath,
        entry: ActionDirectoryEntry<ActionDirectoryBuilder>,
    ) -> anyhow::Result<()> {
        insert_entry(&mut self.builder, path, entry)
    }

    /// Inserts an input to the tree, which will be required when following
    /// symlinks to calculate the `deps` of the `ArtifactValue`.
    pub fn add_input_value(
        &mut self,
        path: &ProjectRelativePath,
        value: &ArtifactValue,
    ) -> anyhow::Result<()> {
        insert_artifact(&mut self.builder, path, value)
    }

    /// Takes an input `src_value`, adds it to the builder at `src`. Then
    /// creates a symlink to `src`, adds it to the builder at `dest` and
    /// returns it.
    pub fn add_symlinked(
        &mut self,
        src_value: &ArtifactValue,
        src: &ProjectRelativePath,
        dest: &ProjectRelativePath,
    ) -> anyhow::Result<()> {
        insert_artifact(&mut self.builder, src, src_value)?;
        let entry = DirectoryEntry::Leaf(new_symlink(self.project_fs.relative_path(src, dest))?);
        self.builder.insert(dest, entry)?;
        Ok(())
    }

    /// Takes an input `src_value`, adds it to the builder at `src`. Then
    /// creates a copy of `src_value`'s entry relativized as if it had been
    /// copied from `src` to `dest`, adds it to the builder at `dest` and
    /// returns it.
    pub fn add_copied(
        &mut self,
        src_value: &ArtifactValue,
        src: &ProjectRelativePath,
        dest: &ProjectRelativePath,
    ) -> anyhow::Result<ActionDirectoryEntry<ActionSharedDirectory>> {
        insert_artifact(&mut self.builder, src, src_value)?;

        let entry = match src_value.entry() {
            DirectoryEntry::Dir(directory) => {
                let mut builder = directory.dupe().into_builder();
                relativize_directory(&mut builder, src, dest)?;
                DirectoryEntry::Dir(
                    builder.fingerprint(self.digest_config.as_directory_serializer()),
                )
            }
            DirectoryEntry::Leaf(ActionDirectoryMember::Symlink(s)) => {
                // TODO: This seems like it normally shouldn't need to be normalizing anything.
                let reldest = self
                    .project_fs
                    .relative_path(src.parent().context("Symlink has no dir parent")?, dest);
                // RelativePathBuf converts platform specific path separators.
                let reldest = fs_util::relative_path_from_system(&reldest)?;
                let s = s.relativized(reldest);
                DirectoryEntry::Leaf(ActionDirectoryMember::Symlink(Arc::new(s)))
            }
            DirectoryEntry::Leaf(ActionDirectoryMember::ExternalSymlink(s)) => {
                DirectoryEntry::Leaf(ActionDirectoryMember::ExternalSymlink(
                    s.with_full_target()?,
                ))
            }
            DirectoryEntry::Leaf(ActionDirectoryMember::File(f)) => {
                DirectoryEntry::Leaf(ActionDirectoryMember::File(f.dupe()))
            }
        };

        let entry = entry.map_dir(|d| d.shared(&*INTERNER));

        self.builder
            .insert(dest, entry.dupe().map_dir(|d| d.into_builder()))?;

        Ok(entry)
    }

    /// Builds the `ArtifactValue`. Since `self.builder` is rooted at the
    /// project root, `output` must be passed to specify the path of the value
    /// being built.
    pub fn build(&self, output: &ProjectRelativePath) -> anyhow::Result<ArtifactValue> {
        match extract_artifact_value(&self.builder, output, self.digest_config)? {
            Some(v) => Ok(v),
            None => {
                tracing::debug!("Extracting {} produces empty directory!", output);
                Ok(ArtifactValue::dir(self.digest_config.empty_directory()))
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use buck2_core::fs::project::ProjectRootTemp;

    use super::*;
    use crate::directory::Symlink;

    fn path<'a>(s: &'a str) -> &'a ProjectRelativePath {
        ProjectRelativePath::new(s).unwrap()
    }

    fn get_symlink(s: &str) -> Arc<Symlink> {
        Arc::new(Symlink::new(s.into()))
    }

    fn get_symlink_artifact_value(s: &str) -> ArtifactValue {
        let symlink = DirectoryEntry::Leaf(ActionDirectoryMember::Symlink(get_symlink(s)));
        ArtifactValue::new(symlink, None)
    }

    #[test]
    fn copy_relativized_symlink() -> anyhow::Result<()> {
        // /
        // |-d1/
        // | |-d2/
        // | | |-d3/
        // | | |  |-d4/
        // | | |  | |-link -> ../../../d6/target
        // | |-d5/
        // | | |-new_link
        // |-d6/
        // | |-target

        let entry = {
            let fs = ProjectRootTemp::new().unwrap();
            let mut builder = ArtifactValueBuilder::new(fs.path(), DigestConfig::testing_default());
            builder.add_copied(
                &get_symlink_artifact_value("../../../d6/target"),
                path("d1/d2/d3/d4/link"),
                path("d1/d5/new_link"),
            )?
        };

        let new_symlink = match entry.as_ref() {
            DirectoryEntry::Leaf(ActionDirectoryMember::Symlink(s)) => s,
            _ => panic!("Symlink type is expected!"),
        };

        assert_eq!(
            new_symlink,
            &get_symlink("../d6/target"),
            "Symlinks are different"
        );

        Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::sync::Arc;

use allocative::Allocative;
use buck2_common::external_symlink::ExternalSymlink;
use buck2_common::file_ops::FileDigest;
use buck2_common::file_ops::FileMetadata;
use dupe::Dupe;

use crate::directory::ActionDirectoryEntry;
use crate::directory::ActionDirectoryMember;
use crate::directory::ActionSharedDirectory;

/// `ArtifactValue` stores enough information about an artifact such that, if
/// it's in the CAS, we don't have to read anything from disk. In summary:
/// - for files, that's the digest and whether it's executable;
/// - for symlinks, that's its target (which we'd read with `fs::read_link`);
/// - for directories, that's the whole file tree.
///
/// However, when we have symlinks, we also must make the artifacts they point
/// to available. Therefore, when this represents a symlink, or a directory
/// with symlinks pointing outside such directory, we must also store the value
/// of the artifacts pointed to by those symlinks. That's the `deps` attribute.
#[derive(Clone, Debug, Dupe, PartialEq, Eq, Allocative)]
pub struct ArtifactValue {
    /// The information about the artifact i.e. digest + is_executable if this
    /// is a file, the file tree if this is a directory, and so on.
    entry: ActionDirectoryEntry<ActionSharedDirectory>,
    /// A tree with all other artifacts which this value depends on. Unlike
    /// `entry` above, which is rooted at this artifact's path, `deps` is
    /// always rooted at the project root.
    deps: Option<ActionSharedDirectory>,
}

impl From<ActionDirectoryEntry<ActionSharedDirectory>> for ArtifactValue {
    fn from(entry: ActionDirectoryEntry<ActionSharedDirectory>) -> Self {
        Self::new(entry, None)
    }
}

impl ArtifactValue {
    pub fn new(
        entry: ActionDirectoryEntry<ActionSharedDirectory>,
        deps: Option<ActionSharedDirectory>,
    ) -> Self {
        Self { entry, deps }
    }

    pub fn file(meta: FileMetadata) -> Self {
        Self {
            entry: ActionDirectoryEntry::Leaf(ActionDirectoryMember::File(meta)),
            deps: None,
        }
    }

    pub fn dir(dir: ActionSharedDirectory) -> Self {
        Self {
            entry: ActionDirectoryEntry::Dir(dir),
            deps: None,
        }
    }

    pub fn is_dir(&self) -> bool {
        matches!(self.entry, ActionDirectoryEntry::Dir(_))
    }

    pub fn external_symlink(symlink: Arc<ExternalSymlink>) -> Self {
        Self {
            entry: ActionDirectoryEntry::Leaf(ActionDirectoryMember::ExternalSymlink(symlink)),
            deps: None,
        }
    }

    pub fn entry(&self) -> &ActionDirectoryEntry<ActionSharedDirectory> {
        &self.entry
    }

    pub fn deps(&self) -> Option<&ActionSharedDirectory> {
        self.deps.as_ref()
    }

    pub fn digest(&self) -> Option<&FileDigest> {
        match &self.entry {
            ActionDirectoryEntry::Dir(d) => Some(d.fingerprint().data()),
            ActionDirectoryEntry::Leaf(ActionDirectoryMember::File(f)) => Some(f.digest.data()),
            ActionDirectoryEntry::Leaf(ActionDirectoryMember::Symlink(..)) => None,
            ActionDirectoryEntry::Leaf(ActionDirectoryMember::ExternalSymlink(..)) => None,
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::fmt;

use buck2_common::cas_digest::CasDigest;
use buck2_common::cas_digest::CasDigestParseError;
use buck2_common::cas_digest::DigestAlgorithm;
use buck2_common::cas_digest::TrackedCasDigest;
use remote_execution::Digest;
use remote_execution::TDigest;
use thiserror::Error;

use crate::digest_config::DigestConfig;

#[derive(Error, Debug)]
pub enum DigestConversionError {
    #[error("Error parsing digest: `{}`", digest)]
    ParseError {
        digest: String,

        #[source]
        error: CasDigestParseError,
    },
}

pub trait CasDigestFromReExt: Sized {
    fn from_re_with_algo(
        x: &TDigest,
        digest_config: DigestConfig,
    ) -> Result<(Self, DigestAlgorithm), DigestConversionError>;

    /// Parse a digest and return the algorithm it used
    fn from_grpc_with_algo(
        x: &Digest,
        digest_config: DigestConfig,
    ) -> Result<(Self, DigestAlgorithm), DigestConversionError>;

    fn from_re(x: &TDigest, digest_config: DigestConfig) -> Result<Self, DigestConversionError> {
        Self::from_re_with_algo(x, digest_config).map(|(d, _a)| d)
    }

    fn from_grpc(x: &Digest, digest_config: DigestConfig) -> Result<Self, DigestConversionError> {
        Self::from_grpc_with_algo(x, digest_config).map(|(d, _a)| d)
    }
}

pub trait CasDigestToReExt {
    fn to_re(&self) -> TDigest;
    fn to_grpc(&self) -> Digest;
}

impl<Kind> CasDigestFromReExt for CasDigest<Kind> {
    fn from_re_with_algo(
        digest: &TDigest,
        digest_config: DigestConfig,
    ) -> Result<(Self, DigestAlgorithm), DigestConversionError> {
        let (ret, algo) =
            Self::parse_digest_without_size(&digest.hash, digest_config.cas_digest_config())
                .map_err(|error| DigestConversionError::ParseError {
                    digest: digest.to_string(),
                    error,
                })?;
        Ok((Self::new(ret, digest.size_in_bytes as u64), algo))
    }

    fn from_grpc_with_algo(
        digest: &Digest,
        digest_config: DigestConfig,
    ) -> Result<(Self, DigestAlgorithm), DigestConversionError> {
        let (ret, algo) =
            Self::parse_digest_without_size(&digest.hash, digest_config.cas_digest_config())
                .map_err(|error| DigestConversionError::ParseError {
                    digest: format!("{}:{}", digest.hash, digest.size_bytes),
                    error,
                })?;

        Ok((Self::new(ret, digest.size_bytes as u64), algo))
    }
}

pub trait CasDigestConversionResultExt {
    fn as_display(&self) -> &dyn fmt::Display;
}

impl<Kind> CasDigestConversionResultExt for Result<CasDigest<Kind>, DigestConversionError> {
    fn as_display(&self) -> &dyn fmt::Display {
        match self {
            Self::Ok(ref v) => v as _,
            Self::Err(DigestConversionError::ParseError { ref digest, .. }) => digest as _,
        }
    }
}

impl<Kind> CasDigestToReExt for TrackedCasDigest<Kind> {
    fn to_re(&self) -> TDigest {
        self.data().to_re()
    }

    fn to_grpc(&self) -> Digest {
        self.data().to_grpc()
    }
}

impl<Kind> CasDigestToReExt for CasDigest<Kind> {
    fn to_re(&self) -> TDigest {
        TDigest {
            hash: self.raw_digest().to_string(),
            size_in_bytes: self.size() as i64,
            ..Default::default()
        }
    }

    fn to_grpc(&self) -> Digest {
        Digest {
            hash: self.raw_digest().to_string(),
            size_bytes: self.size() as i64,
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use allocative::Allocative;
use buck2_common::cas_digest::CasDigestConfig;
use buck2_common::cas_digest::CasDigestConfigError;
use buck2_common::cas_digest::DigestAlgorithm;
use buck2_common::file_ops::FileMetadata;
use derivative::Derivative;
use dice::DiceData;
use dice::DiceDataBuilder;
use dupe::Dupe;
use once_cell::sync::Lazy;
use ref_cast::RefCast;

use crate::directory::ActionDirectoryBuilder;
use crate::directory::ActionSharedDirectory;
use crate::directory::ReDirectorySerializer;
use crate::directory::INTERNER;

/// This configuration describes how to interpret digests received from a RE backend.
#[derive(Copy, Clone, Dupe, Debug, Allocative, Hash, Eq, PartialEq)]
pub struct DigestConfig {
    inner: &'static DigestConfigInner,
}

impl DigestConfig {
    pub fn testing_default() -> Self {
        static COMPAT: Lazy<DigestConfigInner> =
            Lazy::new(|| DigestConfigInner::new(CasDigestConfig::testing_default()));

        Self { inner: &COMPAT }
    }

    /// We just Box::leak this since we create one per daemon and as a result just use
    /// CasDigestConfig as a pointer.
    pub fn leak_new(
        algorithms: Vec<DigestAlgorithm>,
        preferred_source_algorithm: Option<DigestAlgorithm>,
    ) -> Result<Self, CasDigestConfigError> {
        let inner = Box::leak(Box::new(DigestConfigInner::new(CasDigestConfig::leak_new(
            algorithms,
            preferred_source_algorithm,
        )?)));
        Ok(Self { inner })
    }

    pub fn cas_digest_config(&self) -> CasDigestConfig {
        self.inner.cas_digest_config
    }

    pub fn empty_file(&self) -> FileMetadata {
        // TODO: This should be a field on the DigestConfig, obviously.
        FileMetadata::empty(self.cas_digest_config())
    }

    pub fn as_directory_serializer(&self) -> &ReDirectorySerializer {
        ReDirectorySerializer::ref_cast(&self.inner.cas_digest_config)
    }

    pub fn empty_directory(&self) -> ActionSharedDirectory {
        self.inner.empty_directory.dupe()
    }
}

#[derive(Debug, Allocative, Derivative, Eq)]
#[derivative(Hash, PartialEq)]
struct DigestConfigInner {
    cas_digest_config: CasDigestConfig,
    #[derivative(PartialEq = "ignore", Hash = "ignore")]
    empty_directory: ActionSharedDirectory,
}

impl DigestConfigInner {
    fn new(cas_digest_config: CasDigestConfig) -> Self {
        let empty_directory = ActionDirectoryBuilder::empty()
            .fingerprint(&ReDirectorySerializer { cas_digest_config })
            .shared(&*INTERNER);

        Self {
            cas_digest_config,
            empty_directory,
        }
    }
}

pub trait HasDigestConfig {
    fn get_digest_config(&self) -> DigestConfig;
}

pub trait SetDigestConfig {
    fn set_digest_config(&mut self, digest_config: DigestConfig);
}

impl HasDigestConfig for DiceData {
    fn get_digest_config(&self) -> DigestConfig {
        self.get::<DigestConfig>()
            .expect("digest config should be set")
            .dupe()
    }
}

impl SetDigestConfig for DiceDataBuilder {
    fn set_digest_config(&mut self, digest_config: DigestConfig) {
        self.set(digest_config)
    }
}