/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

#[cfg(any(test, feature = "for-tests"))]
use quickcheck_arbitrary_derive::Arbitrary;
use serde::Deserialize;
use serde::Serialize;
use type_macros::auto_wire;
use types::HgId;
use types::Key;
use types::RepoPathBuf;

use crate::ServerError;

#[auto_wire]
#[derive(Clone, Default, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct BlameRequest {
    // The commit+path pairs we are blaming.
    #[id(0)]
    pub files: Vec<Key>,
}

#[auto_wire]
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct BlameResult {
    #[id(0)]
    pub file: Key,
    #[id(1)]
    #[no_default]
    pub data: Result<BlameData, ServerError>,
}

#[auto_wire]
#[derive(Clone, Default, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct BlameData {
    #[id(0)]
    pub line_ranges: Vec<BlameLineRange>,
    #[id(1)]
    pub commits: Vec<HgId>,
    #[id(2)]
    pub paths: Vec<RepoPathBuf>,
}

// BlameLineRange represents a sequence of consecutive lines that originated
// from the same commit.
#[auto_wire]
#[derive(Clone, Default, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct BlameLineRange {
    // First line of this range (zero indexed).
    #[id(0)]
    pub line_offset: u32,
    // Number of lines in this range.
    #[id(1)]
    pub line_count: u32,
    // What commit added this line (index into BlameData.commits)
    #[id(2)]
    pub commit_index: u32,
    // Path this line was originally added to  (index into BlameData.paths)
    #[id(3)]
    pub path_index: u32,
    // First line of this range in origin commit (zero indexed).
    #[id(4)]
    pub origin_line_offset: u32,
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

#[cfg(any(test, feature = "for-tests"))]
use quickcheck_arbitrary_derive::Arbitrary;
use serde_derive::Deserialize;
use serde_derive::Serialize;
use type_macros::auto_wire;
use types::hgid::HgId;

use crate::land::PushVar;

#[auto_wire]
#[derive(Clone, Default, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct BookmarkRequest {
    #[id(0)]
    pub bookmarks: Vec<String>,
}

#[auto_wire]
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct BookmarkEntry {
    #[id(1)]
    pub bookmark: String,
    #[id(2)]
    pub hgid: Option<HgId>,
}

#[auto_wire]
#[derive(Clone, Default, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct SetBookmarkRequest {
    #[id(0)]
    pub bookmark: String,

    #[id(1)]
    pub to: Option<HgId>,

    #[id(2)]
    pub from: Option<HgId>,

    #[id(4)]
    pub pushvars: Vec<PushVar>,
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::iter;
use std::num::NonZeroU64;

use anyhow::Result;
use bytes::Bytes;
use dag_types::Location;
#[cfg(any(test, feature = "for-tests"))]
use quickcheck::Arbitrary;
#[cfg(any(test, feature = "for-tests"))]
use quickcheck::Gen;
#[cfg(any(test, feature = "for-tests"))]
use quickcheck_arbitrary_derive::Arbitrary;
use serde_derive::Deserialize;
use serde_derive::Serialize;
use type_macros::auto_wire;
use types::hgid::HgId;
use types::Parents;
use types::RepoPathBuf;

use crate::BonsaiChangesetId;
use crate::CommitId;
use crate::CommitIdScheme;
use crate::FileType;
use crate::ServerError;
use crate::UploadToken;

/// Given a graph location, return `count` hashes following first parent links.
///
/// Example:
/// 0 - a - b - c
/// In this example our initial commit is `0`, then we have `a` the first commit, `b` second,
/// `c` third.
/// {
///   location: {
///     descendant: c,
///     distance: 1,
///   }
///   count: 2,
/// }
/// => [b, a]
#[auto_wire]
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[derive(Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct CommitLocationToHashRequest {
    #[id(1)]
    pub location: Location<HgId>,
    #[id(2)]
    pub count: u64,
}

#[auto_wire]
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[derive(Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct CommitLocationToHashResponse {
    #[id(1)]
    pub location: Location<HgId>,
    #[id(2)]
    pub count: u64,
    #[id(3)]
    pub hgids: Vec<HgId>,
}

#[auto_wire]
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[derive(Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct CommitLocationToHashRequestBatch {
    #[id(1)]
    pub requests: Vec<CommitLocationToHashRequest>,
}

#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[derive(Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct CommitHashToLocationRequestBatch {
    pub master_heads: Vec<HgId>,
    pub hgids: Vec<HgId>,
    pub unfiltered: Option<bool>,
}

#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Serialize)] // used to convert to Python
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct CommitHashToLocationResponse {
    pub hgid: HgId,
    pub result: Result<Option<Location<HgId>>, ServerError>,
}

#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Serialize)]
pub struct CommitKnownResponse {
    pub hgid: HgId,
    /// `Ok(true)`: The server verified that `hgid` is known.
    /// `Ok(false)`: The server does not known `hgid`.
    /// `Err`: The server cannot check `hgid`.
    pub known: Result<bool, ServerError>,
}

#[auto_wire]
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct CommitGraphEntry {
    #[id(1)]
    pub hgid: HgId,
    #[id(2)]
    pub parents: Vec<HgId>,
    #[id(3)]
    pub is_draft: Option<bool>, // server may be able to return phases
}

#[auto_wire]
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[derive(Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct CommitGraphRequest {
    #[id(1)]
    pub common: Vec<HgId>,
    #[id(2)]
    pub heads: Vec<HgId>,
}

#[auto_wire]
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct CommitGraphSegmentsEntry {
    #[id(1)]
    pub head: HgId,
    #[id(2)]
    pub base: HgId,
    #[id(3)]
    pub length: u64,
    #[id(4)]
    pub parents: Vec<CommitGraphSegmentParent>,
}

#[auto_wire]
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct CommitGraphSegmentParent {
    #[id(1)]
    pub hgid: HgId,
    #[id(2)]
    pub location: Option<Location<HgId>>,
}

#[auto_wire]
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[derive(Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct CommitGraphSegmentsRequest {
    #[id(1)]
    pub common: Vec<HgId>,
    #[id(2)]
    pub heads: Vec<HgId>,
}

/// The list of Mercurial commit identifiers for which we want the commit data to be returned.
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[derive(Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct CommitRevlogDataRequest {
    pub hgids: Vec<HgId>,
}

/// A mercurial commit entry as it was serialized in the revlog.
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[derive(Serialize, Deserialize)]
pub struct CommitRevlogData {
    #[serde(with = "types::serde_with::hgid::bytes")]
    pub hgid: HgId,
    pub revlog_data: Bytes,
}

impl CommitRevlogData {
    pub fn new(hgid: HgId, revlog_data: Bytes) -> Self {
        Self { hgid, revlog_data }
    }
}

/// Request commit hashes that fall in the [low..high] range.
// Note: limit is implied to be 10.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[derive(Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub enum CommitHashLookupRequest {
    InclusiveRange(HgId, HgId),
}

/// make a hash lookup request from a hash prefix for the range of hashes that
/// could match the hash prefix
/// ex: the range 3f54ab22d87423966e0000000000000000000000:3f54ab22d87423966effffffffffffffffffffff
///     matches the prefix 3f54ab22d87423966e
pub fn make_hash_lookup_request(prefix: String) -> Result<CommitHashLookupRequest, anyhow::Error> {
    let suffix_len = HgId::hex_len() - prefix.len();
    let low_hex = prefix
        .clone()
        .chars()
        .chain(iter::repeat('0').take(suffix_len))
        .collect::<String>();
    let low_id = HgId::from_hex(low_hex.as_bytes())?;
    let high_hex = prefix
        .chars()
        .chain(iter::repeat('f').take(suffix_len))
        .collect::<String>();
    let high_id = HgId::from_hex(high_hex.as_bytes())?;
    Ok(CommitHashLookupRequest::InclusiveRange(low_id, high_id))
}

/// Commit hashes that are known to the server in the described range.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[derive(Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct CommitHashLookupResponse {
    pub request: CommitHashLookupRequest,
    pub hgids: Vec<HgId>,
}

#[auto_wire]
#[derive(Clone, Default, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct Extra {
    #[id(1)]
    pub key: Vec<u8>,
    #[id(2)]
    pub value: Vec<u8>,
}

#[auto_wire]
#[derive(Clone, Default, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct HgChangesetContent {
    #[id(1)]
    pub parents: Parents,
    #[id(2)]
    pub manifestid: HgId,
    #[id(3)]
    pub user: Vec<u8>,
    #[id(4)]
    pub time: i64,
    #[id(5)]
    pub tz: i32,
    #[id(6)]
    pub extras: Vec<Extra>,
    #[id(7)]
    pub files: Vec<RepoPathBuf>,
    #[id(8)]
    pub message: Vec<u8>,
}

#[auto_wire]
#[derive(Clone, Default, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct UploadHgChangeset {
    #[id(1)]
    pub node_id: HgId,
    #[id(2)]
    pub changeset_content: HgChangesetContent,
}

#[auto_wire]
#[derive(Clone, Default, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct HgMutationEntryContent {
    #[id(1)]
    pub successor: HgId,
    #[id(2)]
    pub predecessors: Vec<HgId>,
    #[id(3)]
    pub split: Vec<HgId>,
    #[id(4)]
    pub op: String,
    #[id(5)]
    pub user: Vec<u8>,
    #[id(6)]
    pub time: i64,
    #[id(7)]
    pub tz: i32,
    #[id(8)]
    pub extras: Vec<Extra>,
}

#[auto_wire]
#[derive(Clone, Default, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct UploadHgChangesetsRequest {
    /// list of changesets to upload, changesets must be sorted topologically (use dag.sort)
    #[id(1)]
    pub changesets: Vec<UploadHgChangeset>,
    /// list of mutation entries for the uploading changesets
    #[id(2)]
    pub mutations: Vec<HgMutationEntryContent>,
}

#[auto_wire]
#[derive(Clone, Default, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct BonsaiExtra {
    #[id(1)]
    pub key: String,
    #[id(2)]
    pub value: Vec<u8>,
}

#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
pub enum BonsaiFileChange {
    Change {
        /// Token proving the file was uploaded, and containing its content id and size
        upload_token: UploadToken,
        file_type: FileType,
    },
    Deletion,
    UntrackedChange {
        upload_token: UploadToken,
        file_type: FileType,
    },
    UntrackedDeletion,
}

#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct BonsaiChangesetContent {
    pub hg_parents: Parents,
    pub author: String,
    pub time: i64,
    pub tz: i32,
    pub extra: Vec<BonsaiExtra>,
    pub file_changes: Vec<(RepoPathBuf, BonsaiFileChange)>,
    pub message: String,
    pub is_snapshot: bool,
}

#[auto_wire]
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct UploadBonsaiChangesetRequest {
    /// changeset to upload
    #[id(1)]
    pub changeset: BonsaiChangesetContent,
}

#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
pub struct SnapshotRawFiles {
    /// Absolute root of the repository, where all files are
    /// relative to. Can be different from cwd.
    pub root: RepoPathBuf,
    /// Tracked files modified in local changes
    pub modified: Vec<(RepoPathBuf, FileType)>,
    /// Files added with "hg add"
    pub added: Vec<(RepoPathBuf, FileType)>,
    /// Files that are not tracked but are in the local changes
    pub untracked: Vec<(RepoPathBuf, FileType)>,
    /// Files removed with "hg rm"
    pub removed: Vec<RepoPathBuf>,
    /// Files that are not in the local changes but were tracked
    pub missing: Vec<RepoPathBuf>,
}

#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
pub struct SnapshotRawData {
    pub hg_parents: Parents,
    pub files: SnapshotRawFiles,
    pub time: i64,
    pub tz: i32,
    pub author: String,
}

#[auto_wire]
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct FetchSnapshotRequest {
    #[id(1)]
    pub cs_id: BonsaiChangesetId,
}

#[auto_wire]
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct FetchSnapshotResponse {
    #[id(1)]
    pub hg_parents: Parents,
    #[id(2)]
    pub file_changes: Vec<(RepoPathBuf, BonsaiFileChange)>,
    #[id(3)]
    pub author: String,
    #[id(4)]
    pub time: i64,
    #[id(5)]
    pub tz: i32,
    #[id(6)]
    pub bubble_id: Option<NonZeroU64>,
    #[id(7)]
    pub labels: Vec<String>,
}

#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
#[auto_wire]
pub struct AlterSnapshotRequest {
    #[id(1)]
    pub cs_id: BonsaiChangesetId,
    #[id(2)]
    pub labels_to_add: Vec<String>,
    #[id(3)]
    pub labels_to_remove: Vec<String>,
}

#[auto_wire]
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct AlterSnapshotResponse {
    #[id(1)]
    pub current_labels: Vec<String>,
}

#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
pub struct UploadSnapshotResponse {
    pub changeset_token: UploadToken,
    pub bubble_id: NonZeroU64,
}

#[auto_wire]
#[derive(Clone, Default, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct EphemeralPrepareRequest {
    #[id(1)]
    pub custom_duration_secs: Option<u64>,
    #[id(2)]
    pub labels: Option<Vec<String>>,
}

#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct EphemeralPrepareResponse {
    pub bubble_id: NonZeroU64,
}

#[cfg(any(test, feature = "for-tests"))]
impl Arbitrary for BonsaiFileChange {
    fn arbitrary(g: &mut Gen) -> Self {
        match u64::arbitrary(g) % 100 {
            0..=49 => Self::Change {
                upload_token: Arbitrary::arbitrary(g),
                file_type: Arbitrary::arbitrary(g),
            },
            50..=79 => Self::Deletion,
            80..=94 => Self::UntrackedChange {
                upload_token: Arbitrary::arbitrary(g),
                file_type: Arbitrary::arbitrary(g),
            },
            95..=99 => Self::UntrackedDeletion,
            _ => unreachable!(),
        }
    }
}

#[auto_wire]
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct CommitMutationsRequest {
    #[id(1)]
    pub commits: Vec<HgId>,
}

#[auto_wire]
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct CommitMutationsResponse {
    #[id(1)]
    pub mutation: HgMutationEntryContent,
}

#[auto_wire]
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct CommitTranslateIdRequest {
    #[id(1)]
    pub commits: Vec<CommitId>,
    #[id(2)]
    pub scheme: CommitIdScheme,
    #[id(3)]
    pub from_repo: Option<String>,
    #[id(4)]
    pub to_repo: Option<String>,
}

#[auto_wire]
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct CommitTranslateIdResponse {
    #[id(1)]
    pub commit: CommitId,
    #[id(2)]
    pub translated: CommitId,
}

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

    fn make_range(start: &[u8], end: &[u8]) -> Result<CommitHashLookupRequest> {
        let start_id = HgId::from_hex(start)?;
        let end_id = HgId::from_hex(end)?;
        Ok(CommitHashLookupRequest::InclusiveRange(start_id, end_id))
    }

    #[test]
    fn test_make_hash_lookup_request() -> Result<()> {
        let short_hash = String::from("3f54ab22d87423966e");
        let prefix_req = make_hash_lookup_request(short_hash)?;
        let expected_req = make_range(
            b"3f54ab22d87423966e0000000000000000000000",
            b"3f54ab22d87423966effffffffffffffffffffff",
        )?;
        assert_eq!(prefix_req, expected_req);

        let empty_hash = String::new();
        let empty_req = make_hash_lookup_request(empty_hash)?;
        let expected_empty_req = make_range(
            b"0000000000000000000000000000000000000000",
            b"ffffffffffffffffffffffffffffffffffffffff",
        )?;
        assert_eq!(empty_req, expected_empty_req);

        let full_hash = String::from("3f54ab22d87423966e27374e6c0f8112a269999b");
        let full_req = make_hash_lookup_request(full_hash)?;
        let expected_full_req = make_range(
            b"3f54ab22d87423966e27374e6c0f8112a269999b",
            b"3f54ab22d87423966e27374e6c0f8112a269999b",
        )?;
        assert_eq!(full_req, expected_full_req);

        Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

#[cfg(any(test, feature = "for-tests"))]
use quickcheck_arbitrary_derive::Arbitrary;
use serde_derive::Deserialize;
use serde_derive::Serialize;
use type_macros::auto_wire;
use types::HgId;

#[auto_wire]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub enum CommitIdScheme {
    #[id(1)]
    Hg,
    #[id(2)]
    Bonsai,
    #[id(3)]
    Globalrev,
    #[id(4)]
    GitSha1,
}

impl Default for CommitIdScheme {
    fn default() -> Self {
        Self::Hg
    }
}

sized_hash!(GitSha1, 20);
blake2_hash!(BonsaiChangesetId);

#[auto_wire]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub enum CommitId {
    #[id(1)]
    Hg(HgId),
    #[id(2)]
    Bonsai(BonsaiChangesetId),
    #[id(3)]
    Globalrev(u64),
    #[id(4)]
    GitSha1(GitSha1),
}

impl Default for CommitId {
    fn default() -> CommitId {
        CommitId::Hg(HgId::null_id().clone())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use serde::Deserialize;
use serde::Serialize;
use thiserror::Error;

#[derive(Clone, Debug, Error, Eq, PartialEq)]
#[derive(Serialize, Deserialize)] // used to convert to Python
#[cfg_attr(
    any(test, feature = "for-tests"),
    derive(quickcheck_arbitrary_derive::Arbitrary)
)]
#[error("server error (code {code}): {message}")]
/// Common error structure between Mononoke and Mercurial.
/// The `message` field is self explanatory, a natural language description of the issue that was
/// encountered.
/// The `code` field represents a numeric identifier of the type of issue that was encountered. In
/// most situations the code will be `0`, meaning that there is nothing special about the error.
/// Non-zero codes are used for situations where the client wants to take a specific action (when
/// the client needs to handle that error).
///
/// Error code list:
/// ---------------
/// 1: SegmentedChangelogMismatchedHeads
///    Fatal inconsistency between client and server. The client will want to reclone in this
///    situation.
/// 2: HexError
///    Failed to convert hex to binary hash.
pub struct ServerError {
    pub message: String,
    pub code: u64,
}

impl ServerError {
    pub fn new<M: Into<String>>(m: M, code: u64) -> Self {
        Self {
            message: m.into(),
            code,
        }
    }

    pub fn generic<M: Into<String>>(m: M) -> Self {
        Self::new(m, 0)
    }
}

impl From<types::hash::HexError> for ServerError {
    fn from(e: types::hash::HexError) -> Self {
        Self::new(e.to_string(), 2)
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use bytes::Bytes;
#[cfg(any(test, feature = "for-tests"))]
use quickcheck::Arbitrary;
#[cfg(any(test, feature = "for-tests"))]
use quickcheck_arbitrary_derive::Arbitrary;
use revisionstore_types::Metadata;
use serde_derive::Deserialize;
use serde_derive::Serialize;
use thiserror::Error;
use type_macros::auto_wire;
use types::hgid::HgId;
use types::key::Key;
use types::parents::Parents;

use crate::Blake3;
use crate::ContentId;
use crate::InvalidHgId;
use crate::ServerError;
use crate::Sha1;
use crate::Sha256;
use crate::UploadToken;

/// Tombstone string that replaces the content of redacted files.
/// TODO(T48685378): Handle redacted content in a less hacky way.
const REDACTED_TOMBSTONE: &str = "PoUOK1GkdH6Xtx5j9WKYew3dZXspyfkahcNkhV6MJ4rhyNICTvX0nxmbCImFoT0oH\
      AF9ivWGaC6ByswQZUgf1nlyxcDcahHknJS15Vl9Lvc4NokYhMg0mV1rapq1a4bhNo\
      UI9EWTBiAkYmkadkO3YQXV0TAjyhUQWxxLVskjOwiiFPdL1l1pdYYCLTE3CpgOoxQ\
      V3EPVxGUPh1FGfk7F9Myv22qN1sUPSNN4h3IFfm2NNPRFgWPDsqAcaQ7BUSKa\n";

#[derive(Debug, Error)]
pub enum FileError {
    #[error(transparent)]
    Corrupt(InvalidHgId),
    /// File entry was redacted by the server. The received content
    /// did not validate but matches the known tombstone content for
    /// redacted data.
    #[error("Content for {0} is redacted")]
    Redacted(Key, Bytes),
    #[error("Can't validate filenode hash for {0} because it is an LFS pointer")]
    Lfs(Key, Bytes),
    #[error("Content for {0} is unavailable")]
    MissingContent(Key),
}

impl FileError {
    /// Get the data anyway, despite the error.
    pub fn data(&self) -> Bytes {
        use FileError::*;
        match self {
            Corrupt(InvalidHgId { data, .. }) => data,
            Redacted(_, data) => data,
            Lfs(_, data) => data,
            MissingContent(_) => panic!("no content attribute available for FileEntry"),
        }
        .clone()
    }
}

/// File "aux data", requires an additional mononoke blobstore lookup. See mononoke_types::ContentMetadataV2.
#[auto_wire]
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct FileAuxData {
    #[id(0)]
    pub total_size: u64,
    #[id(1)]
    pub content_id: ContentId,
    #[id(2)]
    pub sha1: Sha1,
    #[id(3)]
    pub sha256: Sha256,
    #[id(4)]
    pub seeded_blake3: Option<Blake3>,
}

/// File content
#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
pub struct FileContent {
    pub hg_file_blob: Bytes,
    pub metadata: Metadata,
}

impl FileContent {
    /// Get this entry's data. Checks data integrity but allows hash mismatches
    /// if the content is redacted or contains an LFS pointer.
    pub fn data(&self, key: &Key, parents: Parents) -> Result<Bytes, FileError> {
        use FileError::*;
        self.data_checked(key, parents).or_else(|e| match e {
            Corrupt(_) => Err(e),
            Redacted(..) => Ok(e.data()),
            Lfs(..) => Ok(e.data()),
            MissingContent(_) => Err(e),
        })
    }

    /// Get this entry's data after verifying the hgid hash.
    ///
    /// This method will return an error if the computed hash doesn't match
    /// the provided hash, regardless of the reason. Such mismatches are
    /// sometimes expected (e.g., for redacted files or LFS pointers), so most
    /// application logic should call `FileEntry::data` instead, which allows
    /// these exceptions. `FileEntry::data_checked` should only be used when
    /// strict filenode validation is required.
    pub fn data_checked(&self, key: &Key, parents: Parents) -> Result<Bytes, FileError> {
        // TODO(meyer): Clean this up, make LFS Pointers and redaction strongly typed all the way from here through scmstore
        let data = &self.hg_file_blob;

        // TODO(T48685378): Handle redacted content in a less hacky way.
        if data.len() == REDACTED_TOMBSTONE.len() && data == REDACTED_TOMBSTONE {
            return Err(FileError::Redacted(key.clone(), data.clone()));
        }

        // We can't check the hash of an LFS blob since it is computed using the
        // full file content, but the file entry only contains the LFS pointer.
        if self.metadata().is_lfs() {
            return Err(FileError::Lfs(key.clone(), data.clone()));
        }

        let computed = HgId::from_content(&data, parents);
        if computed != key.hgid {
            let err = InvalidHgId {
                expected: key.hgid,
                computed,
                parents,
                data: data.clone(),
            };

            return Err(FileError::Corrupt(err));
        }

        Ok(data.clone())
    }

    /// Get this entry's data without verifying the hgid hash.
    pub fn data_unchecked(&self) -> &Bytes {
        &self.hg_file_blob
    }

    /// Get this entry's metadata.
    pub fn metadata(&self) -> &Metadata {
        &self.metadata
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct FileResponse {
    pub key: Key,
    pub result: Result<FileEntry, ServerError>,
}

/// Structure representing source control file content on the wire.
/// Includes the information required to add the data to a mutable store,
/// along with the parents for hash validation.
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct FileEntry {
    pub key: Key,
    pub parents: Parents,
    pub content: Option<FileContent>,
    pub aux_data: Option<FileAuxData>,
}

impl FileEntry {
    pub fn new(key: Key, parents: Parents) -> Self {
        Self {
            key,
            parents,
            ..Default::default()
        }
    }

    pub fn with_content(mut self, content: FileContent) -> Self {
        self.content = Some(content);
        self
    }

    pub fn with_aux_data(mut self, aux_data: FileAuxData) -> Self {
        self.aux_data = Some(aux_data);
        self
    }

    pub fn key(&self) -> &Key {
        &self.key
    }

    pub fn parents(&self) -> &Parents {
        &self.parents
    }

    pub fn aux_data(&self) -> Option<&FileAuxData> {
        self.aux_data.as_ref()
    }

    pub fn content(&self) -> Option<&FileContent> {
        self.content.as_ref()
    }

    pub fn data(&self) -> Result<Bytes, FileError> {
        self.content
            .as_ref()
            .ok_or_else(|| FileError::MissingContent(self.key().clone()))?
            .data(self.key(), *self.parents())
    }

    pub fn metadata(&self) -> Result<&Metadata, FileError> {
        Ok(self
            .content
            .as_ref()
            .ok_or_else(|| FileError::MissingContent(self.key().clone()))?
            .metadata())
    }
}

#[auto_wire]
#[derive(Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct FileAttributes {
    #[id(0)]
    pub content: bool,
    #[id(1)]
    pub aux_data: bool,
}

#[auto_wire]
#[derive(Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct FileSpec {
    #[id(0)]
    pub key: Key,
    #[id(1)]
    pub attrs: FileAttributes,
}

#[auto_wire]
#[derive(Clone, Default, Debug, Eq, PartialEq, Serialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct FileRequest {
    // TODO(meyer): Deprecate keys field
    #[id(0)]
    pub keys: Vec<Key>,
    #[id(1)]
    pub reqs: Vec<FileSpec>,
}

#[cfg(any(test, feature = "for-tests"))]
impl Arbitrary for FileContent {
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
        let bytes: Vec<u8> = Arbitrary::arbitrary(g);
        Self {
            hg_file_blob: Bytes::from(bytes),
            metadata: Arbitrary::arbitrary(g),
        }
    }
}

#[auto_wire]
#[derive(Clone, Default, Debug, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct HgFilenodeData {
    #[id(0)]
    pub node_id: HgId,
    #[id(1)]
    pub parents: Parents,
    #[id(2)]
    pub file_content_upload_token: UploadToken,
    #[id(3)]
    pub metadata: Vec<u8>,
}

#[auto_wire]
#[derive(Clone, Default, Debug, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct UploadHgFilenodeRequest {
    #[id(0)]
    pub data: HgFilenodeData,
}

#[auto_wire]
#[derive(Clone, Default, Serialize, Deserialize, Debug, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct UploadTokensResponse {
    #[id(2)]
    pub token: UploadToken,
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

macro_rules! sized_hash {
    ($name: ident, $size: literal) => {
        paste::paste! {
            pub type $name = ::types::hash::AbstractHashType<[< $name TypeInfo >], $size>;

            pub struct [< $name TypeInfo >];

            impl ::types::hash::HashTypeInfo for [< $name TypeInfo >] {
                const HASH_TYPE_NAME: &'static str = stringify!($name);
            }
        }
    };
}

macro_rules! blake2_hash {
    ($name: ident) => {
        sized_hash!($name, 32);
    };
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use quickcheck_arbitrary_derive::Arbitrary;
use serde_derive::Deserialize;
use serde_derive::Serialize;
use types::hgid::HgId;
use types::key::Key;
use types::nodeinfo::NodeInfo;
use types::parents::Parents;
use types::path::RepoPathBuf;

/// Structure containing the fields corresponding to the client's
/// in-memory representation of a history entry. Useful for adding
/// new entries to client-side data structures.
#[derive(
    Clone,
    Debug,
    Default,
    Eq,
    Hash,
    Ord,
    PartialEq,
    PartialOrd,
    Serialize,
    Deserialize
)]
pub struct HistoryEntry {
    pub key: Key,
    pub nodeinfo: NodeInfo,
}

impl HistoryEntry {
    /// A WireHistoryEntry doesn't contain enough information to construct
    /// a HistoryEntry because it doesn't contain the path of the file or
    /// directory to which the entry refers. As such, the path needs to be
    /// supplied by the caller in order to perform the conversion.
    pub fn from_wire(entry: WireHistoryEntry, path: RepoPathBuf) -> Self {
        // If this entry represents a copied file, use the original path as the path of
        // the p1 key instead of the current entry's path.
        let p1_path = entry.copyfrom.unwrap_or_else(|| path.clone());
        let parents = match entry.parents {
            Parents::None => Default::default(),
            Parents::One(p1) => {
                let p1 = Key::new(p1_path, p1);
                // If there is no p2, its hgid hash is null and its path is empty.
                let p2 = Key::default();
                [p1, p2]
            }
            Parents::Two(p1, p2) => {
                let p1 = Key::new(p1_path, p1);
                // If there is a p2, its path must match the current entry's path.
                let p2 = Key::new(path.clone(), p2);
                [p1, p2]
            }
        };

        Self {
            key: Key::new(path, entry.node),
            nodeinfo: NodeInfo {
                parents,
                linknode: entry.linknode,
            },
        }
    }
}

/// History entry structure containing fields corresponding to
/// a single history record in Mercurial's loose file format.
/// This format contains less information than a HistoryEntry
/// (namely, it doesn't contain the path of the file), and has
/// less redundancy, making it more suitable as a compact
/// representation of a history entry for data exchange between
/// the client and server.
#[derive(
    Clone,
    Debug,
    Default,
    Eq,
    Hash,
    Ord,
    PartialEq,
    PartialOrd,
    Serialize,
    Deserialize
)]
pub struct WireHistoryEntry {
    pub node: HgId,
    pub parents: Parents,
    pub linknode: HgId,
    pub copyfrom: Option<RepoPathBuf>,
}

impl From<HistoryEntry> for WireHistoryEntry {
    fn from(entry: HistoryEntry) -> Self {
        let [p1, p2] = entry.nodeinfo.parents;
        // If the p1's path differs from the entry's path, this means the file
        // was copied, so populate the copyfrom path with the p1 path.
        let copyfrom = if !p1.hgid.is_null() && !p1.path.is_empty() && p1.path != entry.key.path {
            Some(p1.path)
        } else {
            None
        };

        Self {
            node: entry.key.hgid,
            parents: Parents::new(p1.hgid, p2.hgid),
            linknode: entry.nodeinfo.linknode,
            copyfrom,
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct HistoryRequest {
    pub keys: Vec<Key>,
    pub length: Option<u32>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct HistoryResponse {
    pub chunks: Vec<HistoryResponseChunk>,
}

impl HistoryResponse {
    pub fn new(chunks: impl IntoIterator<Item = HistoryResponseChunk>) -> Self {
        Self::from_iter(chunks)
    }
}

impl FromIterator<HistoryResponseChunk> for HistoryResponse {
    fn from_iter<I: IntoIterator<Item = HistoryResponseChunk>>(chunks: I) -> Self {
        Self {
            chunks: chunks.into_iter().collect(),
        }
    }
}

impl IntoIterator for HistoryResponse {
    type Item = HistoryEntry;
    type IntoIter = Box<dyn Iterator<Item = HistoryEntry> + Send + 'static>;

    fn into_iter(self) -> Self::IntoIter {
        Box::new(self.chunks.into_iter().flatten())
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct HistoryResponseChunk {
    pub path: RepoPathBuf,
    pub entries: Vec<WireHistoryEntry>,
}

impl HistoryResponseChunk {
    pub fn new(path: RepoPathBuf, entries: impl IntoIterator<Item = WireHistoryEntry>) -> Self {
        Self {
            path,
            entries: entries.into_iter().collect(),
        }
    }
}

impl IntoIterator for HistoryResponseChunk {
    type Item = HistoryEntry;
    type IntoIter = Box<dyn Iterator<Item = HistoryEntry> + Send + 'static>;

    fn into_iter(self) -> Self::IntoIter {
        let path = self.path;
        let iter = self
            .entries
            .into_iter()
            .map(move |entry| HistoryEntry::from_wire(entry, path.clone()));

        Box::new(iter)
    }
}

#[cfg(any(test, feature = "for-tests"))]
use quickcheck::Arbitrary;

#[cfg(any(test, feature = "for-tests"))]
impl Arbitrary for HistoryEntry {
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
        let key = Key::arbitrary(g);
        let mut nodeinfo = NodeInfo::arbitrary(g);

        // If this entry has a p2, then that p2's path must match
        // this entry's Key path. In the case of copies, Mercurial
        // always puts the copied from path in the p1 Key's path,
        // so p2's path must always match the current entry's path
        // unless p2 is null.
        if !nodeinfo.parents[1].hgid.is_null() {
            nodeinfo.parents[1].path = key.path.clone();
        }

        // If p1's key contains a null hgid hash or an empty path,
        // the other field must also be null/empty, since it doesn't
        // make sense to have a file path with a null hash or an empty
        // path with a non-null hash.
        //
        // Likewise, if p1 is null, then p2 must also be null.
        if nodeinfo.parents[0].path.is_empty() || nodeinfo.parents[0].hgid.is_null() {
            nodeinfo.parents[0] = Key::default();
            nodeinfo.parents[1] = Key::default();
        }

        Self { key, nodeinfo }
    }
}

#[cfg(any(test, feature = "for-tests"))]
impl Arbitrary for WireHistoryEntry {
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
        // It doesn't make sense to have a non-None copyfrom containing
        // an empty name, so set copyfrom to None in such cases.
        let mut copyfrom = <Option<RepoPathBuf>>::arbitrary(g).filter(|name| !name.is_empty());
        let parents = Parents::arbitrary(g);

        // It is not possible to have a copy without a p1, so if there is no p1,
        // set copyfrom to None.
        if parents.p1().is_none() {
            copyfrom = None;
        }

        Self {
            node: HgId::arbitrary(g),
            parents,
            linknode: HgId::arbitrary(g),
            copyfrom,
        }
    }
}

#[cfg(test)]
mod tests {
    use quickcheck_macros::quickcheck;

    use super::*;

    #[quickcheck]
    fn history_entry_roundtrip(entry: HistoryEntry) -> bool {
        let path = entry.key.path.clone();
        let wire = WireHistoryEntry::from(entry.clone());
        let roundtrip = HistoryEntry::from_wire(wire, path);
        entry == roundtrip
    }

    #[quickcheck]
    fn wire_entry_roundtrip(wire: WireHistoryEntry, path: RepoPathBuf) -> bool {
        let entry = HistoryEntry::from_wire(wire.clone(), path);
        let roundtrip = WireHistoryEntry::from(entry);
        wire == roundtrip
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::collections::HashMap;

#[cfg(any(test, feature = "for-tests"))]
use quickcheck_arbitrary_derive::Arbitrary;
use serde_derive::Deserialize;
use serde_derive::Serialize;
use type_macros::auto_wire;
use types::HgId;

#[auto_wire]
#[derive(Clone, Default, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct PushVar {
    #[id(0)]
    pub key: String,

    #[id(1)]
    pub value: String,
}

#[auto_wire]
#[derive(Clone, Default, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct LandStackRequest {
    #[id(0)]
    pub bookmark: String,

    #[id(1)]
    pub head: HgId,

    #[id(2)]
    pub base: HgId,

    #[id(4)]
    pub pushvars: Vec<PushVar>,
}

#[auto_wire]
#[derive(Clone, Default, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct LandStackResponse {
    #[id(0)]
    pub new_head: HgId,

    #[id(1)]
    pub old_to_new_hgids: HashMap<HgId, HgId>,
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

//! Types shared between the EdenAPI client and server.
//!
//! This crate exists primarily to provide a lightweight place to
//! put types that need to be used by both the client and server.
//! Types that are exclusive used by either the client or server
//! SHOULD NOT be added to this crate.
//!
//! Given that the client and server are each part of different
//! projects (Mercurial and Mononoke, respectively) which have
//! different build processes, putting shared types in their own
//! crate decreases the likelihood of build failures caused by
//! dependencies with complex or esoteric build requirements.
//!
//! Most of the types in this crate are used for data interchange
//! between the client and server. As such, CHANGES TO THE THESE
//! TYPES MAY CAUSE VERSION SKEW, so any changes should proceed
//! with caution.

#[macro_use]
pub mod hash;

pub mod anyid;
pub mod batch;
pub mod blame;
pub mod bookmark;
pub mod commit;
pub mod commitid;
pub mod errors;
pub mod file;
pub mod history;
pub mod land;
pub mod metadata;
pub mod token;
pub mod tree;
pub mod wire;

use bytes::Bytes;
// re-export CloneData
pub use dag_types::CloneData;
pub use dag_types::FlatSegment;
pub use dag_types::Location as CommitLocation;
pub use dag_types::PreparedFlatSegments;
#[cfg(any(test, feature = "for-tests"))]
use quickcheck_arbitrary_derive::Arbitrary;
use thiserror::Error;
pub use types::hgid::HgId;
pub use types::key::Key;
pub use types::nodeinfo::NodeInfo;
pub use types::parents::Parents;
pub use types::path::RepoPathBuf;

pub use crate::anyid::AnyId;
pub use crate::anyid::LookupRequest;
pub use crate::anyid::LookupResponse;
pub use crate::anyid::LookupResult;
pub use crate::batch::Batch;
pub use crate::blame::BlameData;
pub use crate::blame::BlameLineRange;
pub use crate::blame::BlameRequest;
pub use crate::blame::BlameResult;
pub use crate::bookmark::BookmarkEntry;
pub use crate::bookmark::BookmarkRequest;
pub use crate::bookmark::SetBookmarkRequest;
pub use crate::commit::make_hash_lookup_request;
pub use crate::commit::AlterSnapshotRequest;
pub use crate::commit::AlterSnapshotResponse;
pub use crate::commit::BonsaiChangesetContent;
pub use crate::commit::BonsaiFileChange;
pub use crate::commit::CommitGraphEntry;
pub use crate::commit::CommitGraphRequest;
pub use crate::commit::CommitGraphSegmentParent;
pub use crate::commit::CommitGraphSegmentsEntry;
pub use crate::commit::CommitGraphSegmentsRequest;
pub use crate::commit::CommitHashLookupRequest;
pub use crate::commit::CommitHashLookupResponse;
pub use crate::commit::CommitHashToLocationRequestBatch;
pub use crate::commit::CommitHashToLocationResponse;
pub use crate::commit::CommitKnownResponse;
pub use crate::commit::CommitLocationToHashRequest;
pub use crate::commit::CommitLocationToHashRequestBatch;
pub use crate::commit::CommitLocationToHashResponse;
pub use crate::commit::CommitMutationsRequest;
pub use crate::commit::CommitMutationsResponse;
pub use crate::commit::CommitRevlogData;
pub use crate::commit::CommitRevlogDataRequest;
pub use crate::commit::CommitTranslateIdRequest;
pub use crate::commit::CommitTranslateIdResponse;
pub use crate::commit::EphemeralPrepareRequest;
pub use crate::commit::EphemeralPrepareResponse;
pub use crate::commit::Extra;
pub use crate::commit::FetchSnapshotRequest;
pub use crate::commit::FetchSnapshotResponse;
pub use crate::commit::HgChangesetContent;
pub use crate::commit::HgMutationEntryContent;
pub use crate::commit::SnapshotRawData;
pub use crate::commit::SnapshotRawFiles;
pub use crate::commit::UploadBonsaiChangesetRequest;
pub use crate::commit::UploadHgChangeset;
pub use crate::commit::UploadHgChangesetsRequest;
pub use crate::commit::UploadSnapshotResponse;
pub use crate::commitid::BonsaiChangesetId;
pub use crate::commitid::CommitId;
pub use crate::commitid::CommitIdScheme;
pub use crate::commitid::GitSha1;
pub use crate::errors::ServerError;
pub use crate::file::FileAttributes;
pub use crate::file::FileAuxData;
pub use crate::file::FileContent;
pub use crate::file::FileEntry;
pub use crate::file::FileError;
pub use crate::file::FileRequest;
pub use crate::file::FileResponse;
pub use crate::file::FileSpec;
pub use crate::file::HgFilenodeData;
pub use crate::file::UploadHgFilenodeRequest;
pub use crate::file::UploadTokensResponse;
pub use crate::history::HistoryEntry;
pub use crate::history::HistoryRequest;
pub use crate::history::HistoryResponse;
pub use crate::history::HistoryResponseChunk;
pub use crate::history::WireHistoryEntry;
pub use crate::land::LandStackRequest;
pub use crate::land::LandStackResponse;
pub use crate::land::PushVar;
pub use crate::metadata::AnyFileContentId;
pub use crate::metadata::Blake3;
pub use crate::metadata::ContentId;
pub use crate::metadata::DirectoryMetadata;
pub use crate::metadata::DirectoryMetadataRequest;
pub use crate::metadata::FileMetadata;
pub use crate::metadata::FileMetadataRequest;
pub use crate::metadata::FileType;
pub use crate::metadata::FsnodeId;
pub use crate::metadata::Sha1;
pub use crate::metadata::Sha256;
pub use crate::token::FileContentTokenMetadata;
pub use crate::token::IndexableId;
pub use crate::token::UploadToken;
pub use crate::token::UploadTokenData;
pub use crate::token::UploadTokenMetadata;
pub use crate::token::UploadTokenSignature;
pub use crate::tree::TreeAttributes;
pub use crate::tree::TreeChildDirectoryEntry;
pub use crate::tree::TreeChildEntry;
pub use crate::tree::TreeChildFileEntry;
pub use crate::tree::TreeEntry;
pub use crate::tree::TreeError;
pub use crate::tree::TreeRequest;
pub use crate::tree::UploadTreeEntry;
pub use crate::tree::UploadTreeRequest;
pub use crate::tree::UploadTreeResponse;
pub use crate::wire::ToApi;
pub use crate::wire::ToWire;
pub use crate::wire::WireToApiConversionError;

#[derive(Debug, Error)]
#[error("Invalid hash: {expected} (expected) != {computed} (computed)")]
pub struct InvalidHgId {
    expected: HgId,
    computed: HgId,
    data: Bytes,
    parents: Parents,
}

#[derive(Clone, Debug, Error, Eq, PartialEq)]
#[error("Error fetching key {key:?}: {err}")]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct EdenApiServerError {
    pub err: EdenApiServerErrorKind,
    pub key: Option<Key>,
}

impl EdenApiServerError {
    pub fn new(err: impl std::fmt::Debug) -> EdenApiServerError {
        EdenApiServerError {
            err: EdenApiServerErrorKind::OpaqueError(format!("{:?}", err)),
            key: None,
        }
    }

    pub fn with_key(key: Key, err: impl std::fmt::Debug) -> EdenApiServerError {
        EdenApiServerError {
            err: EdenApiServerErrorKind::OpaqueError(format!("{:?}", err)),
            key: Some(key),
        }
    }

    pub fn with_path(path: RepoPathBuf, err: impl std::fmt::Debug) -> EdenApiServerError {
        EdenApiServerError {
            err: EdenApiServerErrorKind::OpaqueError(format!("{:?}", err)),
            key: Some(Key {
                path,
                hgid: *HgId::null_id(),
            }),
        }
    }

    pub fn with_hgid(hgid: HgId, err: impl std::fmt::Debug) -> EdenApiServerError {
        EdenApiServerError {
            err: EdenApiServerErrorKind::OpaqueError(format!("{:?}", err)),
            key: Some(Key {
                hgid,
                path: RepoPathBuf::new(),
            }),
        }
    }
}

#[derive(Clone, Debug, Error, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub enum EdenApiServerErrorKind {
    #[error("EdenAPI server returned an error with message: {0}")]
    OpaqueError(String),
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::fmt;
use std::str::FromStr;

#[cfg(any(test, feature = "for-tests"))]
use quickcheck_arbitrary_derive::Arbitrary;
use serde_derive::Deserialize;
use serde_derive::Serialize;
use type_macros::auto_wire;

use crate::ServerError;

/// Directory entry metadata
#[auto_wire]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct DirectoryMetadata {
    #[id(0)]
    pub fsnode_id: Option<FsnodeId>,
    #[id(1)]
    pub simple_format_sha1: Option<Sha1>,
    #[id(2)]
    pub simple_format_sha256: Option<Sha256>,
    #[id(3)]
    pub child_files_count: Option<u64>,
    #[id(4)]
    pub child_files_total_size: Option<u64>,
    #[id(5)]
    pub child_dirs_count: Option<u64>,
    #[id(6)]
    pub descendant_files_count: Option<u64>,
    #[id(7)]
    pub descendant_files_total_size: Option<u64>,
}

#[auto_wire]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct DirectoryMetadataRequest {
    #[id(0)]
    pub with_fsnode_id: bool,
    #[id(1)]
    pub with_simple_format_sha1: bool,
    #[id(2)]
    pub with_simple_format_sha256: bool,
    #[id(3)]
    pub with_child_files_count: bool,
    #[id(4)]
    pub with_child_files_total_size: bool,
    #[id(5)]
    pub with_child_dirs_count: bool,
    #[id(6)]
    pub with_descendant_files_count: bool,
    #[id(7)]
    pub with_descendant_files_total_size: bool,
}

/// File entry metadata
#[auto_wire]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct FileMetadata {
    #[id(0)]
    pub revisionstore_flags: Option<u64>,
    #[id(1)]
    pub content_id: Option<ContentId>,
    #[id(2)]
    pub file_type: Option<FileType>,
    #[id(3)]
    pub size: Option<u64>,
    #[id(4)]
    pub content_sha1: Option<Sha1>,
    #[id(5)]
    pub content_sha256: Option<Sha256>,
    #[id(6)]
    pub content_seeded_blake3: Option<Blake3>,
}

#[auto_wire]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct FileMetadataRequest {
    #[id(0)]
    pub with_revisionstore_flags: bool,
    #[id(1)]
    pub with_content_id: bool,
    #[id(2)]
    pub with_file_type: bool,
    #[id(3)]
    pub with_size: bool,
    #[id(4)]
    pub with_content_sha1: bool,
    #[id(5)]
    pub with_content_sha256: bool,
    #[id(6)]
    pub with_content_seeded_blake3: bool,
}

sized_hash!(Sha1, 20);
sized_hash!(Sha256, 32);
sized_hash!(Blake3, 32);
blake2_hash!(ContentId);
blake2_hash!(FsnodeId);

#[auto_wire]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub enum FileType {
    #[id(1)]
    Regular,
    #[id(2)]
    Executable,
    #[id(3)]
    Symlink,
}

impl Default for FileType {
    fn default() -> Self {
        Self::Regular
    }
}

#[auto_wire]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub enum AnyFileContentId {
    #[id(1)]
    ContentId(ContentId),
    #[id(2)]
    Sha1(Sha1),
    #[id(3)]
    Sha256(Sha256),
    #[id(4)]
    SeededBlake3(Blake3),
}

impl Default for AnyFileContentId {
    fn default() -> Self {
        AnyFileContentId::ContentId(ContentId::default())
    }
}

impl FromStr for AnyFileContentId {
    type Err = ServerError;

    fn from_str(s: &str) -> Result<AnyFileContentId, Self::Err> {
        let v: Vec<&str> = s.split('/').collect();
        if v.len() != 2 {
            return Err(Self::Err::generic(
                "AnyFileContentId parsing failure: format is 'idtype/id'",
            ));
        }
        let idtype = v[0];
        let id = v[1];
        let any_file_content_id = match idtype {
            "content_id" => AnyFileContentId::ContentId(ContentId::from_str(id)?),
            "sha1" => AnyFileContentId::Sha1(Sha1::from_str(id)?),
            "sha256" => AnyFileContentId::Sha256(Sha256::from_str(id)?),
            "seeded_blake3" => AnyFileContentId::SeededBlake3(Blake3::from_str(id)?),
            _ => {
                return Err(Self::Err::generic(
                    "AnyFileContentId parsing failure: supported id types are: 'content_id', 'sha1', 'sha256' and 'seeded_blake3'",
                ));
            }
        };
        Ok(any_file_content_id)
    }
}

impl fmt::Display for AnyFileContentId {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            AnyFileContentId::ContentId(id) => write!(f, "{}", id),
            AnyFileContentId::Sha1(id) => write!(f, "{}", id),
            AnyFileContentId::Sha256(id) => write!(f, "{}", id),
            AnyFileContentId::SeededBlake3(id) => write!(f, "{}", id),
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::num::NonZeroU64;

#[cfg(any(test, feature = "for-tests"))]
use quickcheck::Arbitrary;
#[cfg(any(test, feature = "for-tests"))]
use quickcheck_arbitrary_derive::Arbitrary;
use serde_derive::Deserialize;
use serde_derive::Serialize;
use type_macros::auto_wire;

use crate::AnyId;

#[auto_wire]
/// Token metadata for file content token type.
#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct FileContentTokenMetadata {
    #[id(1)]
    pub content_size: u64,
}

/// Token metadata. Could be different for different token types.
/// A signed token guarantee the metadata has been verified.
#[auto_wire]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub enum UploadTokenMetadata {
    #[id(1)]
    FileContentTokenMetadata(FileContentTokenMetadata),
}

impl Default for UploadTokenMetadata {
    fn default() -> Self {
        Self::FileContentTokenMetadata(Default::default())
    }
}

impl From<FileContentTokenMetadata> for UploadTokenMetadata {
    fn from(fctm: FileContentTokenMetadata) -> Self {
        Self::FileContentTokenMetadata(fctm)
    }
}

#[auto_wire]
#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct UploadTokenData {
    #[id(1)]
    pub id: AnyId,
    #[id(3)]
    pub bubble_id: Option<NonZeroU64>,
    #[id(2)]
    pub metadata: Option<UploadTokenMetadata>,
    // TODO: add other data (like expiration time).
}

#[auto_wire]
#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct UploadTokenSignature {
    #[id(1)]
    pub signature: Vec<u8>,
}

/// Uniquely identifies an id an upload token can refer to.
/// Can be used as a key in maps/sets.
#[auto_wire]
#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct IndexableId {
    #[id(1)]
    pub id: AnyId,
    #[id(2)]
    pub bubble_id: Option<NonZeroU64>,
}

#[auto_wire]
#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct UploadToken {
    #[id(1)]
    pub data: UploadTokenData,
    #[id(2)]
    pub signature: UploadTokenSignature,
}

impl UploadToken {
    pub fn new_fake_token(id: AnyId, bubble_id: Option<NonZeroU64>) -> Self {
        Self {
            data: UploadTokenData {
                id,
                bubble_id,
                metadata: None,
            },
            signature: UploadTokenSignature {
                signature: "faketokensignature".into(),
            },
        }
    }

    pub fn new_fake_token_with_metadata(
        id: AnyId,
        bubble_id: Option<NonZeroU64>,
        metadata: UploadTokenMetadata,
    ) -> Self {
        Self {
            data: UploadTokenData {
                id,
                bubble_id,
                metadata: Some(metadata),
            },
            signature: UploadTokenSignature {
                signature: "faketokensignature".into(),
            },
        }
    }
    // TODO: implement secure signed tokens

    pub fn indexable_id(&self) -> IndexableId {
        IndexableId {
            id: self.data.id.clone(),
            bubble_id: self.data.bubble_id,
        }
    }
}

#[cfg(any(test, feature = "for-tests"))]
impl Arbitrary for UploadTokenData {
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
        Self {
            id: Arbitrary::arbitrary(g),
            bubble_id: Arbitrary::arbitrary(g),
            metadata: None,
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use bytes::Bytes;
#[cfg(any(test, feature = "for-tests"))]
use quickcheck::Arbitrary;
#[cfg(any(test, feature = "for-tests"))]
use quickcheck_arbitrary_derive::Arbitrary;
use serde_derive::Deserialize;
use serde_derive::Serialize;
use thiserror::Error;
use type_macros::auto_wire;
use types::hgid::HgId;
use types::key::Key;
use types::parents::Parents;

use crate::DirectoryMetadata;
use crate::EdenApiServerError;
use crate::FileMetadata;
use crate::InvalidHgId;
use crate::UploadToken;

#[derive(Debug, Error)]
pub enum TreeError {
    #[error(transparent)]
    Corrupt(InvalidHgId),
    /// If this entry represents a root node (i.e., has an empty path), it
    /// may be a hybrid tree manifest which has the content of a root tree
    /// manifest node, but the hash of the corresponding flat manifest. This
    /// situation should only occur for manifests created in "hybrid mode"
    /// (i.e., during a transition from flat manifests to tree manifests).
    #[error("Detected possible hybrid manifest: {0}")]
    MaybeHybridManifest(#[source] InvalidHgId),

    #[error("TreeEntry missing field '{0}'")]
    MissingField(&'static str),
}

impl TreeError {
    /// Get the data anyway, despite the error.
    pub fn data(&self) -> Option<Bytes> {
        use TreeError::*;
        match self {
            Corrupt(InvalidHgId { data, .. }) => Some(data),
            MaybeHybridManifest(InvalidHgId { data, .. }) => Some(data),
            _ => None,
        }
        .cloned()
    }
}

/// Structure representing source control tree entry on the wire.
/// Includes the information required to add the data to a mutable store,
/// along with the parents for hash validation.
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
pub struct TreeEntry {
    pub key: Key,
    pub data: Option<Bytes>,
    pub parents: Option<Parents>,
    #[serde(skip)]
    pub children: Option<Vec<Result<TreeChildEntry, EdenApiServerError>>>,
}

impl TreeEntry {
    pub fn new(key: Key, data: Bytes, parents: Parents) -> Self {
        Self {
            key,
            data: Some(data),
            parents: Some(parents),
            children: None,
        }
    }

    pub fn with_children<'a>(
        &'a mut self,
        children: Option<Vec<Result<TreeChildEntry, EdenApiServerError>>>,
    ) -> &'a mut Self {
        self.children = children;
        self
    }

    pub fn key(&self) -> &Key {
        &self.key
    }

    /// Get this entry's data. Checks data integrity but allows hash mismatches
    /// if this is a suspected hybrid manifest.
    pub fn data(&self) -> Result<Bytes, TreeError> {
        use TreeError::*;
        self.data_checked().or_else(|e| match e {
            MaybeHybridManifest(_) => Ok(e
                .data()
                .expect("TreeError::MaybeHybridManifest should always carry underlying data")),
            _ => Err(e),
        })
    }

    /// Get this entry's data after verifying the hgid hash.
    pub fn data_checked(&self) -> Result<Bytes, TreeError> {
        if let Some(data) = self.data.as_ref() {
            if let Some(parents) = self.parents {
                let computed = HgId::from_content(&data, parents);
                if computed != self.key.hgid {
                    let err = InvalidHgId {
                        expected: self.key.hgid,
                        computed,
                        data: data.clone(),
                        parents,
                    };

                    return Err(if self.key.path.is_empty() {
                        TreeError::MaybeHybridManifest(err)
                    } else {
                        TreeError::Corrupt(err)
                    });
                }

                Ok(data.clone())
            } else {
                Err(TreeError::MissingField("parents"))
            }
        } else {
            Err(TreeError::MissingField("data"))
        }
    }

    /// Get this entry's data without verifying the hgid hash.
    pub fn data_unchecked(&self) -> Option<Bytes> {
        self.data.clone()
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub enum TreeChildEntry {
    File(TreeChildFileEntry),
    Directory(TreeChildDirectoryEntry),
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct TreeChildFileEntry {
    pub key: Key,
    pub file_metadata: Option<FileMetadata>,
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct TreeChildDirectoryEntry {
    pub key: Key,
    pub directory_metadata: Option<DirectoryMetadata>,
}

impl TreeChildEntry {
    pub fn new_file_entry(key: Key, metadata: FileMetadata) -> Self {
        TreeChildEntry::File(TreeChildFileEntry {
            key,
            file_metadata: Some(metadata),
        })
    }

    pub fn new_directory_entry(key: Key) -> Self {
        TreeChildEntry::Directory(TreeChildDirectoryEntry {
            key,
            ..Default::default()
        })
    }
}

#[cfg(any(test, feature = "for-tests"))]
impl Arbitrary for TreeEntry {
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
        let bytes: Option<Vec<u8>> = Arbitrary::arbitrary(g);
        Self {
            key: Arbitrary::arbitrary(g),
            data: bytes.map(|b| Bytes::from(b)),
            parents: Arbitrary::arbitrary(g),
            // Recursive TreeEntry in children causes stack overflow in QuickCheck
            children: None,
        }
    }
}

#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct TreeRequest {
    pub keys: Vec<Key>,
    pub attributes: TreeAttributes,
}

#[derive(Copy, Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct TreeAttributes {
    #[serde(default = "get_true")]
    pub manifest_blob: bool,
    #[serde(default = "get_true")]
    pub parents: bool,
    #[serde(default = "get_true")]
    pub child_metadata: bool,
}

fn get_true() -> bool {
    true
}

impl TreeAttributes {
    pub fn all() -> Self {
        TreeAttributes {
            manifest_blob: true,
            parents: true,
            child_metadata: true,
        }
    }
}

impl Default for TreeAttributes {
    fn default() -> Self {
        TreeAttributes {
            manifest_blob: true,
            parents: true,
            child_metadata: false,
        }
    }
}

#[auto_wire]
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct UploadTreeEntry {
    #[id(0)]
    pub node_id: HgId,
    #[id(1)]
    pub data: Vec<u8>,
    #[id(2)]
    pub parents: Parents,
}

#[auto_wire]
#[derive(Clone, Default, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct UploadTreeRequest {
    #[id(0)]
    pub entry: UploadTreeEntry,
}

#[auto_wire]
#[derive(Clone, Default, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct UploadTreeResponse {
    #[id(1)]
    pub token: UploadToken,
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

pub use crate::anyid::WireAnyId;
pub use crate::anyid::WireLookupRequest;
pub use crate::anyid::WireLookupResponse;
pub use crate::anyid::WireLookupResult;
use crate::commitid::BonsaiChangesetId;
use crate::commitid::GitSha1;
use crate::wire::ToApi;
use crate::wire::ToWire;

wire_hash! {
    wire => WireBonsaiChangesetId,
    api  => BonsaiChangesetId,
    size => 32,
}

wire_hash! {
    wire => WireGitSha1,
    api  => GitSha1,
    size => 20,
}

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

    auto_wire_tests!(
        WireAnyId,
        WireLookupRequest,
        WireLookupResponse,
        WireLookupResult,
    );
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

#[cfg(any(test, feature = "for-tests"))]
use quickcheck::Arbitrary;
use serde::Deserialize;
use serde::Serialize;

use crate::wire::ToApi;
use crate::wire::ToWire;
use crate::wire::WireToApiConversionError;
use crate::Batch;

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct WireBatch<T> {
    #[serde(rename = "1")]
    pub batch: Vec<T>,
}

impl<T> ToWire for Batch<T>
where
    T: ToWire,
{
    type Wire = WireBatch<T::Wire>;

    fn to_wire(self) -> Self::Wire {
        Self::Wire {
            batch: self.batch.to_wire(),
        }
    }
}

impl<T> ToApi for WireBatch<T>
where
    T: ToApi,
{
    type Api = Batch<T::Api>;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        let batch = self.batch.to_api().map_err(|e| e.into())?;
        Ok(Batch { batch })
    }
}

#[cfg(any(test, feature = "for-tests"))]
impl<T> quickcheck::Arbitrary for WireBatch<T>
where
    T: quickcheck::Arbitrary,
{
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
        WireBatch {
            batch: Arbitrary::arbitrary(g),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::wire::tests::auto_wire_tests;
    use crate::wire::WireHgId;

    type WireHgIdBatch = WireBatch<WireHgId>;

    auto_wire_tests!(WireHgIdBatch);
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

pub use crate::bookmark::WireBookmarkEntry;
pub use crate::bookmark::WireBookmarkRequest;
pub use crate::bookmark::WireSetBookmarkRequest;

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

    auto_wire_tests!(
        WireBookmarkRequest,
        WireBookmarkEntry,
        WireSetBookmarkRequest
    );
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::collections::BTreeMap;

#[cfg(any(test, feature = "for-tests"))]
use quickcheck::Arbitrary;
use serde_derive::Deserialize;
use serde_derive::Serialize;
use types::HgId;

use crate::wire::ToApi;
use crate::wire::ToWire;
use crate::wire::WireDagId;
use crate::wire::WireHgId;
use crate::wire::WireToApiConversionError;
use crate::CloneData;
use crate::FlatSegment;
use crate::PreparedFlatSegments;

// Only when an id has more than one parent it is sent over the wire.
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct WireCloneData {
    #[serde(rename = "2")]
    pub flat_segments: Vec<WireFlatSegment>,
    #[serde(rename = "3")]
    pub idmap: Vec<WireIdMapEntry>,
}

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct WireFlatSegment {
    #[serde(rename = "1")]
    pub low: WireDagId,
    #[serde(rename = "2")]
    pub high: WireDagId,
    #[serde(rename = "3")]
    pub parents: Vec<WireDagId>,
}

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct WireIdMapEntry {
    #[serde(rename = "1")]
    pub dag_id: WireDagId,
    #[serde(rename = "2")]
    pub hg_id: WireHgId,
}

impl ToWire for CloneData<HgId> {
    type Wire = WireCloneData;

    fn to_wire(self) -> Self::Wire {
        let mut idmap: Vec<_> = self.idmap.into_iter().collect();
        // Let's sort the array to produce consistent format
        idmap.sort();
        let idmap = idmap
            .into_iter()
            .map(|(k, v)| WireIdMapEntry {
                dag_id: k.to_wire(),
                hg_id: v.to_wire(),
            })
            .collect();
        WireCloneData {
            flat_segments: self
                .flat_segments
                .segments
                .into_iter()
                .collect::<Vec<_>>()
                .to_wire(),
            idmap,
        }
    }
}

impl ToApi for WireCloneData {
    type Api = CloneData<HgId>;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        let mut idmap = BTreeMap::new();
        for wie in self.idmap {
            idmap.insert(wie.dag_id.to_api()?, wie.hg_id.to_api()?);
        }
        let flat_segments = PreparedFlatSegments {
            segments: self.flat_segments.to_api()?.into_iter().collect(),
        };
        Ok(CloneData {
            flat_segments,
            idmap,
        })
    }
}

impl ToWire for FlatSegment {
    type Wire = WireFlatSegment;

    fn to_wire(self) -> Self::Wire {
        WireFlatSegment {
            low: self.low.to_wire(),
            high: self.high.to_wire(),
            parents: self.parents.to_wire(),
        }
    }
}

impl ToApi for WireFlatSegment {
    type Api = FlatSegment;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        Ok(FlatSegment {
            low: self.low.to_api()?,
            high: self.high.to_api()?,
            parents: self.parents.to_api()?,
        })
    }
}

#[cfg(any(test, feature = "for-tests"))]
impl Arbitrary for WireFlatSegment {
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
        FlatSegment::arbitrary(g).to_wire()
    }
}

#[cfg(any(test, feature = "for-tests"))]
impl Arbitrary for WireCloneData {
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
        CloneData::arbitrary(g).to_wire()
    }
}

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

    auto_wire_tests!(WireCloneData, WireFlatSegment);
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::num::NonZeroU64;

use dag_types::Location;
#[cfg(any(test, feature = "for-tests"))]
use quickcheck::Arbitrary;
use serde_derive::Deserialize;
use serde_derive::Serialize;
use types::HgId;

use crate::commit::BonsaiChangesetContent;
use crate::commit::BonsaiFileChange;
use crate::commit::CommitHashLookupRequest;
use crate::commit::CommitHashLookupResponse;
use crate::commit::CommitHashToLocationRequestBatch;
use crate::commit::CommitHashToLocationResponse;
use crate::commit::EphemeralPrepareResponse;
pub use crate::commit::WireBonsaiExtra;
pub use crate::commit::WireCommitGraphEntry;
pub use crate::commit::WireCommitGraphRequest;
pub use crate::commit::WireCommitLocationToHashRequest;
pub use crate::commit::WireCommitLocationToHashRequestBatch;
pub use crate::commit::WireCommitLocationToHashResponse;
pub use crate::commit::WireCommitMutationsRequest;
pub use crate::commit::WireCommitMutationsResponse;
pub use crate::commit::WireEphemeralPrepareRequest;
pub use crate::commit::WireExtra;
pub use crate::commit::WireFetchSnapshotRequest;
pub use crate::commit::WireFetchSnapshotResponse;
pub use crate::commit::WireHgChangesetContent;
pub use crate::commit::WireHgMutationEntryContent;
pub use crate::commit::WireUploadBonsaiChangesetRequest;
pub use crate::commit::WireUploadHgChangeset;
pub use crate::commit::WireUploadHgChangesetsRequest;
use crate::wire::is_default;
use crate::wire::ToApi;
use crate::wire::ToWire;
use crate::wire::WireFileType;
use crate::wire::WireHgId;
use crate::wire::WireParents;
use crate::wire::WireRepoPathBuf;
use crate::wire::WireResult;
use crate::wire::WireToApiConversionError;
use crate::wire::WireUploadToken;

#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct WireCommitLocation {
    #[serde(rename = "1")]
    pub descendant: WireHgId,
    #[serde(rename = "2")]
    pub distance: u64,
}

impl ToWire for Location<HgId> {
    type Wire = WireCommitLocation;

    fn to_wire(self) -> Self::Wire {
        Self::Wire {
            descendant: self.descendant.to_wire(),
            distance: self.distance,
        }
    }
}

impl ToApi for WireCommitLocation {
    type Api = Location<HgId>;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        let api = Self::Api {
            descendant: self.descendant.to_api()?,
            distance: self.distance,
        };
        Ok(api)
    }
}

#[cfg(any(test, feature = "for-tests"))]
impl Arbitrary for WireCommitLocation {
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
        Location::arbitrary(g).to_wire()
    }
}

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct WireCommitHashToLocationRequestBatch {
    #[serde(rename = "1")]
    pub client_head: Option<WireHgId>,
    #[serde(rename = "2")]
    pub hgids: Vec<WireHgId>,
    #[serde(rename = "3", default)]
    pub master_heads: Vec<WireHgId>,
    #[serde(rename = "4")]
    pub unfiltered: Option<bool>,
}

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct WireCommitHashToLocationResponse {
    #[serde(rename = "1")]
    pub hgid: WireHgId,
    #[serde(rename = "2")]
    pub location: Option<WireCommitLocation>,
    #[serde(rename = "3")]
    pub result: Option<WireResult<Option<WireCommitLocation>>>,
}

impl ToWire for CommitHashToLocationRequestBatch {
    type Wire = WireCommitHashToLocationRequestBatch;

    fn to_wire(self) -> Self::Wire {
        let client_head = self.master_heads.get(0).copied().to_wire();
        Self::Wire {
            client_head,
            hgids: self.hgids.to_wire(),
            master_heads: self.master_heads.to_wire(),
            unfiltered: self.unfiltered,
        }
    }
}

impl ToApi for WireCommitHashToLocationRequestBatch {
    type Api = CommitHashToLocationRequestBatch;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        let mut master_heads = self.master_heads.to_api()?;
        if master_heads.is_empty() {
            let client_head = self.client_head.to_api()?;
            if let Some(head) = client_head {
                master_heads = vec![head];
            }
        }
        let api = Self::Api {
            master_heads,
            hgids: self.hgids.to_api()?,
            unfiltered: self.unfiltered,
        };
        Ok(api)
    }
}

#[cfg(any(test, feature = "for-tests"))]
impl Arbitrary for WireCommitHashToLocationRequestBatch {
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
        CommitHashToLocationRequestBatch::arbitrary(g).to_wire()
    }
}

impl ToWire for CommitHashToLocationResponse {
    type Wire = WireCommitHashToLocationResponse;

    fn to_wire(self) -> Self::Wire {
        let location = match self.result {
            Ok(Some(x)) => Some(x.to_wire()),
            _ => None,
        };
        Self::Wire {
            hgid: self.hgid.to_wire(),
            location,
            result: Some(self.result.to_wire()),
        }
    }
}

impl ToApi for WireCommitHashToLocationResponse {
    type Api = CommitHashToLocationResponse;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        let result = match self.result {
            Some(x) => x.to_api()?,
            None => match self.location {
                None => Ok(None),
                Some(l) => Ok(Some(l.to_api()?)),
            },
        };
        let api = Self::Api {
            hgid: self.hgid.to_api()?,
            result,
        };
        Ok(api)
    }
}

#[cfg(any(test, feature = "for-tests"))]
impl Arbitrary for WireCommitHashToLocationResponse {
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
        CommitHashToLocationResponse::arbitrary(g).to_wire()
    }
}

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct WireCommitHashLookupRequest {
    #[serde(rename = "1", default, skip_serializing_if = "is_default")]
    pub inclusive_range: Option<(WireHgId, WireHgId)>,
}

impl ToWire for CommitHashLookupRequest {
    type Wire = WireCommitHashLookupRequest;

    fn to_wire(self) -> Self::Wire {
        use crate::CommitHashLookupRequest::*;
        match self {
            InclusiveRange(low, high) => WireCommitHashLookupRequest {
                inclusive_range: Some((low.to_wire(), high.to_wire())),
            },
        }
    }
}

impl ToApi for WireCommitHashLookupRequest {
    type Api = CommitHashLookupRequest;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        let ir =
            self.inclusive_range
                .ok_or(WireToApiConversionError::CannotPopulateRequiredField(
                    "inclusive_range",
                ))?;
        let api = CommitHashLookupRequest::InclusiveRange(ir.0.to_api()?, ir.1.to_api()?);
        Ok(api)
    }
}

#[cfg(any(test, feature = "for-tests"))]
impl Arbitrary for WireCommitHashLookupRequest {
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
        CommitHashLookupRequest::arbitrary(g).to_wire()
    }
}

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct WireCommitHashLookupResponse {
    #[serde(rename = "1")]
    pub request: Option<WireCommitHashLookupRequest>,
    #[serde(rename = "2", default, skip_serializing_if = "is_default")]
    pub hgids: Option<Vec<WireHgId>>,
}

impl ToWire for CommitHashLookupResponse {
    type Wire = WireCommitHashLookupResponse;

    fn to_wire(self) -> Self::Wire {
        Self::Wire {
            request: Some(self.request.to_wire()),
            hgids: Some(self.hgids.to_wire()),
        }
    }
}

impl ToApi for WireCommitHashLookupResponse {
    type Api = CommitHashLookupResponse;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        let request = self
            .request
            .ok_or(WireToApiConversionError::CannotPopulateRequiredField(
                "request",
            ))?
            .to_api()?;
        let hgids = self
            .hgids
            .ok_or(WireToApiConversionError::CannotPopulateRequiredField(
                "hgids",
            ))?
            .to_api()?;
        let api = Self::Api { request, hgids };
        Ok(api)
    }
}

#[cfg(any(test, feature = "for-tests"))]
impl Arbitrary for WireCommitHashLookupResponse {
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
        CommitHashLookupResponse::arbitrary(g).to_wire()
    }
}

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub enum WireBonsaiFileChange {
    #[serde(rename = "1")]
    Change(WireUploadToken, WireFileType),

    #[serde(rename = "2")]
    UntrackedChange(WireUploadToken, WireFileType),

    #[serde(rename = "3")]
    UntrackedDeletion,

    #[serde(rename = "4")]
    Deletion,

    #[serde(other, rename = "0")]
    Unknown,
}

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct WireSnapshotState {}

#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct WireBonsaiChangesetContent {
    #[serde(rename = "1")]
    pub hg_parents: WireParents,

    #[serde(rename = "2")]
    pub author: String,

    #[serde(rename = "3")]
    pub time: i64,

    #[serde(rename = "4")]
    pub tz: i32,

    #[serde(rename = "5")]
    pub extra: Vec<WireBonsaiExtra>,

    #[serde(rename = "6")]
    pub file_changes: Vec<(WireRepoPathBuf, WireBonsaiFileChange)>,

    #[serde(rename = "7")]
    pub message: String,

    #[serde(rename = "8")]
    pub snapshot_state: Option<WireSnapshotState>,
}

impl ToWire for BonsaiFileChange {
    type Wire = WireBonsaiFileChange;
    fn to_wire(self) -> Self::Wire {
        match self {
            Self::Change {
                upload_token,
                file_type,
            } => WireBonsaiFileChange::Change(upload_token.to_wire(), file_type.to_wire()),
            Self::UntrackedChange {
                upload_token,
                file_type,
            } => WireBonsaiFileChange::UntrackedChange(upload_token.to_wire(), file_type.to_wire()),
            Self::UntrackedDeletion => WireBonsaiFileChange::UntrackedDeletion,
            Self::Deletion => WireBonsaiFileChange::Deletion,
        }
    }
}

impl ToWire for BonsaiChangesetContent {
    type Wire = WireBonsaiChangesetContent;

    fn to_wire(self) -> Self::Wire {
        WireBonsaiChangesetContent {
            hg_parents: self.hg_parents.to_wire(),
            author: self.author,
            time: self.time,
            tz: self.tz,
            extra: self.extra.to_wire(),
            file_changes: self
                .file_changes
                .into_iter()
                .map(|(a, b)| (a.to_wire(), b.to_wire()))
                .collect(),
            message: self.message,
            snapshot_state: self.is_snapshot.then(|| WireSnapshotState {}),
        }
    }
}

impl ToApi for WireBonsaiFileChange {
    type Api = BonsaiFileChange;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        match self {
            Self::Change(upload_token, file_type) => Ok(BonsaiFileChange::Change {
                upload_token: upload_token.to_api()?,
                file_type: file_type.to_api()?,
            }),
            Self::UntrackedChange(upload_token, file_type) => {
                Ok(BonsaiFileChange::UntrackedChange {
                    upload_token: upload_token.to_api()?,
                    file_type: file_type.to_api()?,
                })
            }
            Self::UntrackedDeletion => Ok(BonsaiFileChange::UntrackedDeletion),
            Self::Deletion => Ok(BonsaiFileChange::Deletion),
            Self::Unknown => Err(WireToApiConversionError::UnrecognizedEnumVariant(
                "WireBonsaiFileChange",
            )),
        }
    }
}

impl ToApi for WireBonsaiChangesetContent {
    type Api = BonsaiChangesetContent;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        Ok(BonsaiChangesetContent {
            hg_parents: self.hg_parents.to_api()?,
            author: self.author,
            time: self.time,
            tz: self.tz,
            extra: self.extra.to_api()?,
            file_changes: self
                .file_changes
                .into_iter()
                .map(|(a, b)| Ok((a.to_api()?, b.to_api()?)))
                .collect::<Result<_, Self::Error>>()?,
            message: self.message,
            is_snapshot: self.snapshot_state.is_some(),
        })
    }
}

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct WireEphemeralPrepareResponse {
    #[serde(rename = "1")]
    pub bubble_id: Option<NonZeroU64>,
}

impl ToWire for EphemeralPrepareResponse {
    type Wire = WireEphemeralPrepareResponse;

    fn to_wire(self) -> Self::Wire {
        Self::Wire {
            bubble_id: Some(self.bubble_id),
        }
    }
}

impl ToApi for WireEphemeralPrepareResponse {
    type Api = EphemeralPrepareResponse;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        Ok(Self::Api {
            bubble_id: self.bubble_id.ok_or(
                WireToApiConversionError::CannotPopulateRequiredField("bubble_id"),
            )?,
        })
    }
}

#[cfg(any(test, feature = "for-tests"))]
impl Arbitrary for WireEphemeralPrepareResponse {
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
        EphemeralPrepareResponse::arbitrary(g).to_wire()
    }
}

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

    auto_wire_tests!(
        WireCommitLocation,
        WireCommitLocationToHashRequest,
        WireCommitLocationToHashResponse,
        WireCommitLocationToHashRequestBatch,
        WireCommitHashToLocationRequestBatch,
        WireCommitHashToLocationResponse,
        WireCommitHashLookupRequest,
        WireCommitHashLookupResponse,
        WireEphemeralPrepareRequest,
        WireEphemeralPrepareResponse,
        WireCommitGraphRequest,
        WireUploadHgChangeset,
        WireUploadHgChangesetsRequest,
        WireFetchSnapshotRequest,
        WireFetchSnapshotResponse,
        WireCommitMutationsRequest,
        WireCommitMutationsResponse,
    );
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use serde::Deserialize;
use serde::Serialize;

use crate::wire::ToApi;
use crate::wire::ToWire;
use crate::wire::WireToApiConversionError;
use crate::ServerError;

pub type WireResult<T> = Result<T, WireError>;

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct WireError {
    #[serde(rename = "1")]
    pub message: Option<String>,
    #[serde(rename = "2")]
    pub code: Option<u64>,
}

impl WireError {
    pub fn new<M: Into<String>>(m: M, code: u64) -> Self {
        Self {
            message: Some(m.into()),
            code: Some(code),
        }
    }
}

impl ToWire for ServerError {
    type Wire = WireError;

    fn to_wire(self) -> Self::Wire {
        WireError {
            message: Some(self.message),
            code: Some(self.code),
        }
    }
}

impl ToApi for WireError {
    type Api = ServerError;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        let message = self
            .message
            .ok_or_else(|| WireToApiConversionError::CannotPopulateRequiredField("message"))?;
        let code = self.code.unwrap_or(0);
        Ok(ServerError::new(message, code))
    }
}

impl<T, E> ToWire for Result<T, E>
where
    T: ToWire,
    E: ToWire,
{
    type Wire = Result<<T as ToWire>::Wire, <E as ToWire>::Wire>;

    fn to_wire(self) -> Self::Wire {
        self.map(|x| x.to_wire()).map_err(|e| e.to_wire())
    }
}

impl<T, E> ToApi for Result<T, E>
where
    T: ToApi,
    E: ToApi,
{
    type Api = Result<<T as ToApi>::Api, <E as ToApi>::Api>;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        match self {
            Ok(x) => match x.to_api() {
                Ok(y) => Ok(Ok(y)),
                Err(te) => Err(te.into()),
            },
            Err(e) => match e.to_api() {
                Ok(ae) => Ok(Err(ae)),
                Err(ee) => Err(ee.into()),
            },
        }
    }
}

#[cfg(any(test, feature = "for-tests"))]
impl quickcheck::Arbitrary for WireError {
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
        WireError::new(String::arbitrary(g), u64::arbitrary(g))
    }
}

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

    auto_wire_tests!(WireError);
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use bytes::Bytes;
#[cfg(any(test, feature = "for-tests"))]
use quickcheck::Arbitrary;
use serde_derive::Deserialize;
use serde_derive::Serialize;

use crate::file::FileContent;
use crate::file::FileEntry;
use crate::file::FileResponse;
pub use crate::file::WireFileAttributes;
pub use crate::file::WireFileAuxData;
pub use crate::file::WireFileRequest;
pub use crate::file::WireFileSpec;
pub use crate::file::WireHgFilenodeData;
pub use crate::file::WireUploadHgFilenodeRequest;
pub use crate::file::WireUploadTokensResponse;
use crate::wire::is_default;
use crate::wire::ToApi;
use crate::wire::ToWire;
use crate::wire::WireKey;
use crate::wire::WireParents;
use crate::wire::WireResult;
use crate::wire::WireRevisionstoreMetadata;
use crate::wire::WireToApiConversionError;

#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
pub struct WireFileEntry {
    #[serde(rename = "0", default, skip_serializing_if = "is_default")]
    key: WireKey,

    #[serde(rename = "1", default, skip_serializing_if = "is_default")]
    data: Option<Bytes>,

    #[serde(rename = "2", default, skip_serializing_if = "is_default")]
    parents: WireParents,

    #[serde(rename = "3", default, skip_serializing_if = "is_default")]
    metadata: Option<WireRevisionstoreMetadata>,

    #[serde(rename = "4", default, skip_serializing_if = "is_default")]
    aux_data: Option<WireFileAuxData>,
}

impl ToWire for FileEntry {
    type Wire = WireFileEntry;

    fn to_wire(self) -> Self::Wire {
        let (data, metadata) = self
            .content
            .map_or((None, None), |c| (Some(c.hg_file_blob), Some(c.metadata)));
        WireFileEntry {
            key: self.key.to_wire(),
            parents: self.parents.to_wire(),
            data,
            metadata: metadata.to_wire(),
            aux_data: self.aux_data.to_wire(),
        }
    }
}

impl ToApi for WireFileEntry {
    type Api = FileEntry;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        let content = if let Some(hg_file_blob) = self.data {
            Some(FileContent {
                hg_file_blob,
                metadata: self
                    .metadata
                    .ok_or(WireToApiConversionError::CannotPopulateRequiredField(
                        "content.metadata",
                    ))?
                    .to_api()?,
            })
        } else {
            None
        };
        Ok(FileEntry {
            key: self.key.to_api()?,
            // if content is present, metadata must be also
            content,

            aux_data: self.aux_data.to_api()?,
            parents: self.parents.to_api()?,
        })
    }
}

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct WireFileResponse {
    #[serde(rename = "0")]
    pub key: Option<WireKey>,
    #[serde(rename = "1")]
    pub result: Option<WireResult<WireFileEntry>>,
}

impl ToWire for FileResponse {
    type Wire = WireFileResponse;

    fn to_wire(self) -> Self::Wire {
        Self::Wire {
            key: Some(self.key.to_wire()),
            result: Some(self.result.to_wire()),
        }
    }
}

impl ToApi for WireFileResponse {
    type Api = FileResponse;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        Ok(Self::Api {
            key: match self.key {
                Some(key) => key.to_api()?,
                None => return Err(WireToApiConversionError::CannotPopulateRequiredField("key")),
            },
            result: match self.result {
                Some(result) => result.to_api()?,
                None => {
                    return Err(WireToApiConversionError::CannotPopulateRequiredField(
                        "result",
                    ));
                }
            },
        })
    }
}

#[cfg(any(test, feature = "for-tests"))]
impl Arbitrary for WireFileEntry {
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
        let bytes: Option<Vec<u8>> = Arbitrary::arbitrary(g);
        Self {
            key: Arbitrary::arbitrary(g),
            data: bytes.map(Bytes::from),
            parents: Arbitrary::arbitrary(g),
            metadata: Arbitrary::arbitrary(g),
            aux_data: Arbitrary::arbitrary(g),
        }
    }
}

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

    auto_wire_tests!(
        WireFileRequest,
        WireFileEntry,
        WireUploadHgFilenodeRequest,
        WireUploadTokensResponse
    );
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

macro_rules! wire_hash {
    {
        wire => $wire: ident,
        api  => $api: ident,
        size => $size: literal,
    } => {

        #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
        pub struct $wire([u8; $wire::len()]);

        impl $wire {
            pub const fn len() -> usize {
                $size
            }
        }

        impl std::fmt::Debug for $wire {
            fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
                write!(fmt, "{}({:?})", stringify!($wire), types::sha::to_hex(&self.0))
            }
        }

        impl ToWire for $api {
            type Wire = $wire;

            fn to_wire(self) -> Self::Wire {
                $wire(self.into())
            }
        }

        impl ToApi for $wire {
            type Api = $api;
            type Error = std::convert::Infallible;

            fn to_api(self) -> Result<Self::Api, Self::Error> {
                Ok($api::from(self.0))
            }
        }

        impl serde::Serialize for $wire {
            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
            where
                S: serde::Serializer,
            {
                serializer.serialize_bytes(&self.0)
            }
        }

        impl<'de> serde::Deserialize<'de> for $wire {
            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
            where
                D: serde::Deserializer<'de>,
            {
                let bytes: serde_bytes::ByteBuf = serde_bytes::deserialize(deserializer)?;
                let bytes = bytes.as_ref();

                if bytes.len() == Self::len() {
                    let mut ary = [0u8; Self::len()];
                    ary.copy_from_slice(&bytes);
                    Ok($wire(ary))
                } else {
                    Err(<D::Error as serde::de::Error>::custom($crate::wire::TryFromBytesError {
                        expected_len: Self::len(),
                        found_len: bytes.len(),
                    }))
                }
            }
        }

        #[cfg(any(test, feature = "for-tests"))]
        impl quickcheck::Arbitrary for $wire {
            fn arbitrary(g: &mut quickcheck::Gen) -> Self {
                $api::arbitrary(g).to_wire()
            }
        }
    };

    // fallback when not comma terminated
    {
        wire => $name: ident,
        api  => $api: ident,
        size => $size: literal
    } => {wire_hash! {
        wire => $name,
        api  => $api,
        size => $size,
    }}
}