use base64::Engine;
use git::rpc::proto as p;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;

fn oid_val(oid: Option<p::ObjectId>) -> String {
    oid.map(|o| o.value).unwrap_or_default()
}

fn oid_opt(oid: Option<p::ObjectId>) -> Option<String> {
    oid.map(|o| o.value)
}

fn oid_vec(oids: Vec<p::ObjectId>) -> Vec<String> {
    oids.into_iter().map(|o| o.value).collect()
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct ObjectIdDto {
    pub value: String,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct BranchListItemDto {
    pub name: String,
    pub oid: String,
    pub is_head: bool,
    pub is_remote: bool,
    pub is_current: bool,
    pub upstream: Option<String>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct BranchListResponseDto {
    pub branches: Vec<BranchListItemDto>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct BranchInfoResponseDto {
    pub branch: Option<BranchListItemDto>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct BranchSummaryDto {
    pub local_count: u64,
    pub remote_count: u64,
    pub all_count: u64,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct BranchSummaryResponseDto {
    pub summary: Option<BranchSummaryDto>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct BranchHeadResponseDto {
    pub head_name: String,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct BranchAheadBehindResponseDto {
    pub ahead: u64,
    pub behind: u64,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct BranchUpstreamResponseDto {
    pub upstream_name: String,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct CommitSignatureDto {
    pub name: String,
    pub email: String,
    pub time_secs: i64,
    pub offset_minutes: i32,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct CommitMetaDto {
    pub oid: String,
    pub message: String,
    pub summary: String,
    pub author: Option<CommitSignatureDto>,
    pub committer: Option<CommitSignatureDto>,
    pub tree_id: Option<String>,
    pub parent_ids: Vec<String>,
    pub encoding: Option<String>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct CommitInfoResponseDto {
    pub commit: Option<CommitMetaDto>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct CommitHistoryResponseDto {
    pub commits: Vec<CommitMetaDto>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct CommitSummaryDto {
    pub head: Option<CommitMetaDto>,
    pub count: u64,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct CommitSummaryResponseDto {
    pub summary: Option<CommitSummaryDto>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct CommitRefInfoDto {
    pub name: String,
    pub target: String,
    pub is_remote: bool,
    pub is_tag: bool,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct CommitRefsResponseDto {
    pub refs: Vec<CommitRefInfoDto>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct CommitPrefixResponseDto {
    pub oid: Option<String>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct CommitExistsResponseDto {
    pub exists: bool,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct CherryPickResponseDto {
    pub oid: Option<String>,
}

#[derive(Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum TreeKindDto {
    Blob,
    Tree,
    LfsPointer,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct TreeEntryDto {
    pub name: String,
    pub oid: String,
    pub kind: TreeKindDto,
    pub filemode: u32,
    pub is_binary: bool,
    pub is_lfs: bool,
    pub last_commit_message: Option<String>,
    pub last_commit_time: Option<String>,
    pub last_commit_author_name: Option<String>,
    pub last_commit_author_email: Option<String>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct TreeEntriesResponseDto {
    pub entries: Vec<TreeEntryDto>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct TreeEntryByPathResponseDto {
    pub entry: Option<TreeEntryDto>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct TreeInfoDto {
    pub oid: String,
    pub entry_count: u64,
    pub is_empty: bool,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct ResolveTreeResponseDto {
    pub info: Option<TreeInfoDto>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct BlobLoadResponseDto {
    pub blob: String,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct BlobSizeResponseDto {
    pub size: u64,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct BlobExistsResponseDto {
    pub exists: bool,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct BlobIsBinaryResponseDto {
    pub is_binary: bool,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct BlobUploadResponseDto {
    pub id: Option<String>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct TagItemDto {
    pub name: String,
    pub oid: String,
    pub target: String,
    pub is_annotated: bool,
    pub message: Option<String>,
    pub tagger: Option<String>,
    pub tagger_email: Option<String>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct TagListResponseDto {
    pub tags: Vec<TagItemDto>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct TagInfoResponseDto {
    pub tag: Option<TagItemDto>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct TagSummaryDto {
    pub total_count: u64,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct TagSummaryResponseDto {
    pub summary: Option<TagSummaryDto>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct TagInitResponseDto {
    pub oid: Option<String>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct TagUpdateMessageResponseDto {
    pub oid: Option<String>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct CommitBlameHunkDto {
    pub commit_oid: Option<String>,
    pub final_start_line: u32,
    pub final_lines: u32,
    pub orig_start_line: u32,
    pub orig_lines: u32,
    pub boundary: bool,
    pub orig_path: Option<String>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct CommitBlameLineDto {
    pub commit_oid: Option<String>,
    pub line_no: u32,
    pub content: String,
    pub orig_path: Option<String>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct BlameFileResponseDto {
    pub hunks: Vec<CommitBlameHunkDto>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct BlameLinesResponseDto {
    pub lines: Vec<CommitBlameLineDto>,
}

#[derive(Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum DiffDeltaStatusDto {
    Unmodified,
    Added,
    Deleted,
    Modified,
    Renamed,
    Copied,
    Typechange,
    Conflicted,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct DiffFileDto {
    pub oid: Option<String>,
    pub path: Option<String>,
    pub size: u64,
    pub is_binary: bool,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct DiffHunkDto {
    pub old_start: u32,
    pub old_lines: u32,
    pub new_start: u32,
    pub new_lines: u32,
    pub header: String,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct DiffLineDto {
    pub content: String,
    pub origin: String,
    pub old_lineno: Option<u32>,
    pub new_lineno: Option<u32>,
    pub num_lines: u32,
    pub content_offset: i64,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct DiffDeltaDto {
    pub status: i32,
    pub old_file: Option<DiffFileDto>,
    pub new_file: Option<DiffFileDto>,
    pub nfiles: u32,
    pub hunks: Vec<DiffHunkDto>,
    pub lines: Vec<DiffLineDto>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct DiffStatsDto {
    pub files_changed: u64,
    pub insertions: u64,
    pub deletions: u64,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct DiffResultDto {
    pub stats: Option<DiffStatsDto>,
    pub deltas: Vec<DiffDeltaDto>,
}

#[derive(Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum SideBySideChangeTypeDto {
    Unchanged,
    Added,
    Removed,
    Modified,
    Empty,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct SideBySideLineDto {
    pub left_line_no: Option<u32>,
    pub right_line_no: Option<u32>,
    pub left_content: String,
    pub right_content: String,
    pub change_type: i32,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct SideBySideFileDto {
    pub path: String,
    pub additions: u64,
    pub deletions: u64,
    pub is_binary: bool,
    pub is_rename: bool,
    pub lines: Vec<SideBySideLineDto>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct SideBySideDiffResultDto {
    pub files: Vec<SideBySideFileDto>,
    pub total_additions: u64,
    pub total_deletions: u64,
}

impl From<p::ObjectId> for ObjectIdDto {
    fn from(o: p::ObjectId) -> Self {
        ObjectIdDto { value: o.value }
    }
}

impl From<p::BranchListItem> for BranchListItemDto {
    fn from(b: p::BranchListItem) -> Self {
        BranchListItemDto {
            name: b.name,
            oid: oid_val(b.oid),
            is_head: b.is_head,
            is_remote: b.is_remote,
            is_current: b.is_current,
            upstream: b.upstream,
        }
    }
}

impl From<p::BranchListResponse> for BranchListResponseDto {
    fn from(r: p::BranchListResponse) -> Self {
        BranchListResponseDto {
            branches: r.branches.into_iter().map(Into::into).collect(),
        }
    }
}

impl From<p::BranchInfoResponse> for BranchInfoResponseDto {
    fn from(r: p::BranchInfoResponse) -> Self {
        BranchInfoResponseDto {
            branch: r.branch.map(Into::into),
        }
    }
}

impl From<p::BranchSummary> for BranchSummaryDto {
    fn from(s: p::BranchSummary) -> Self {
        BranchSummaryDto {
            local_count: s.local_count,
            remote_count: s.remote_count,
            all_count: s.all_count,
        }
    }
}

impl From<p::BranchSummaryResponse> for BranchSummaryResponseDto {
    fn from(r: p::BranchSummaryResponse) -> Self {
        BranchSummaryResponseDto {
            summary: r.summary.map(Into::into),
        }
    }
}

impl From<p::BranchHeadResponse> for BranchHeadResponseDto {
    fn from(r: p::BranchHeadResponse) -> Self {
        BranchHeadResponseDto {
            head_name: r.head_name,
        }
    }
}

impl From<p::BranchAheadBehindResponse> for BranchAheadBehindResponseDto {
    fn from(r: p::BranchAheadBehindResponse) -> Self {
        BranchAheadBehindResponseDto {
            ahead: r.ahead,
            behind: r.behind,
        }
    }
}

impl From<p::BranchUpstreamResponse> for BranchUpstreamResponseDto {
    fn from(r: p::BranchUpstreamResponse) -> Self {
        BranchUpstreamResponseDto {
            upstream_name: r.upstream_name,
        }
    }
}

impl From<p::CommitSignature> for CommitSignatureDto {
    fn from(s: p::CommitSignature) -> Self {
        CommitSignatureDto {
            name: s.name,
            email: s.email,
            time_secs: s.time_secs,
            offset_minutes: s.offset_minutes,
        }
    }
}

impl From<p::CommitMeta> for CommitMetaDto {
    fn from(c: p::CommitMeta) -> Self {
        CommitMetaDto {
            oid: oid_val(c.oid),
            message: c.message,
            summary: c.summary,
            author: c.author.map(Into::into),
            committer: c.committer.map(Into::into),
            tree_id: oid_opt(c.tree_id),
            parent_ids: oid_vec(c.parent_ids),
            encoding: c.encoding,
        }
    }
}

impl From<p::CommitInfoResponse> for CommitInfoResponseDto {
    fn from(r: p::CommitInfoResponse) -> Self {
        CommitInfoResponseDto {
            commit: r.commit.map(Into::into),
        }
    }
}

impl From<p::CommitHistoryResponse> for CommitHistoryResponseDto {
    fn from(r: p::CommitHistoryResponse) -> Self {
        CommitHistoryResponseDto {
            commits: r.commits.into_iter().map(Into::into).collect(),
        }
    }
}

impl From<p::CommitSummary> for CommitSummaryDto {
    fn from(s: p::CommitSummary) -> Self {
        CommitSummaryDto {
            head: s.head.map(Into::into),
            count: s.count,
        }
    }
}

impl From<p::CommitSummaryResponse> for CommitSummaryResponseDto {
    fn from(r: p::CommitSummaryResponse) -> Self {
        CommitSummaryResponseDto {
            summary: r.summary.map(Into::into),
        }
    }
}

impl From<p::CommitRefInfo> for CommitRefInfoDto {
    fn from(r: p::CommitRefInfo) -> Self {
        CommitRefInfoDto {
            name: r.name,
            target: oid_val(r.target),
            is_remote: r.is_remote,
            is_tag: r.is_tag,
        }
    }
}

impl From<p::CommitRefsResponse> for CommitRefsResponseDto {
    fn from(r: p::CommitRefsResponse) -> Self {
        CommitRefsResponseDto {
            refs: r.refs.into_iter().map(Into::into).collect(),
        }
    }
}

impl From<p::CommitPrefixResponse> for CommitPrefixResponseDto {
    fn from(r: p::CommitPrefixResponse) -> Self {
        CommitPrefixResponseDto {
            oid: oid_opt(r.oid),
        }
    }
}

impl From<p::CommitExistsResponse> for CommitExistsResponseDto {
    fn from(r: p::CommitExistsResponse) -> Self {
        CommitExistsResponseDto { exists: r.exists }
    }
}

impl From<p::CherryPickResponse> for CherryPickResponseDto {
    fn from(r: p::CherryPickResponse) -> Self {
        CherryPickResponseDto {
            oid: oid_opt(r.oid),
        }
    }
}

impl From<p::CherryPickSequenceResponse> for CherryPickResponseDto {
    fn from(r: p::CherryPickSequenceResponse) -> Self {
        CherryPickResponseDto {
            oid: oid_opt(r.oid),
        }
    }
}

fn tree_kind_from_proto(kind: i32) -> TreeKindDto {
    match kind {
        0 => TreeKindDto::Blob,
        1 => TreeKindDto::Tree,
        2 => TreeKindDto::LfsPointer,
        _ => TreeKindDto::Blob,
    }
}

impl From<p::TreeEntry> for TreeEntryDto {
    fn from(e: p::TreeEntry) -> Self {
        fn opt(s: String) -> Option<String> {
            if s.is_empty() { None } else { Some(s) }
        }
        TreeEntryDto {
            name: e.name,
            oid: oid_val(e.oid),
            kind: tree_kind_from_proto(e.kind),
            filemode: e.filemode,
            is_binary: e.is_binary,
            is_lfs: e.is_lfs,
            last_commit_message: opt(e.last_commit_message),
            last_commit_time: opt(e.last_commit_time),
            last_commit_author_name: opt(e.last_commit_author_name),
            last_commit_author_email: opt(e.last_commit_author_email),
        }
    }
}

impl From<p::TreeEntriesResponse> for TreeEntriesResponseDto {
    fn from(r: p::TreeEntriesResponse) -> Self {
        TreeEntriesResponseDto {
            entries: r.entries.into_iter().map(Into::into).collect(),
        }
    }
}

impl From<p::TreeEntryByPathResponse> for TreeEntryByPathResponseDto {
    fn from(r: p::TreeEntryByPathResponse) -> Self {
        TreeEntryByPathResponseDto {
            entry: r.entry.map(Into::into),
        }
    }
}

impl From<p::TreeEntryByPathFromCommitResponse> for TreeEntryByPathResponseDto {
    fn from(r: p::TreeEntryByPathFromCommitResponse) -> Self {
        TreeEntryByPathResponseDto {
            entry: r.entry.map(Into::into),
        }
    }
}

impl From<p::TreeInfo> for TreeInfoDto {
    fn from(i: p::TreeInfo) -> Self {
        TreeInfoDto {
            oid: oid_val(i.oid),
            entry_count: i.entry_count,
            is_empty: i.is_empty,
        }
    }
}

impl From<p::ResolveTreeResponse> for ResolveTreeResponseDto {
    fn from(r: p::ResolveTreeResponse) -> Self {
        ResolveTreeResponseDto {
            info: r.info.map(Into::into),
        }
    }
}

impl From<p::BlobLoadResponse> for BlobLoadResponseDto {
    fn from(r: p::BlobLoadResponse) -> Self {
        BlobLoadResponseDto {
            blob: base64::engine::general_purpose::STANDARD.encode(&r.blob),
        }
    }
}

impl From<p::BlobSizeResponse> for BlobSizeResponseDto {
    fn from(r: p::BlobSizeResponse) -> Self {
        BlobSizeResponseDto { size: r.size }
    }
}

impl From<p::BlobExistsResponse> for BlobExistsResponseDto {
    fn from(r: p::BlobExistsResponse) -> Self {
        BlobExistsResponseDto { exists: r.exists }
    }
}

impl From<p::BlobIsBinaryResponse> for BlobIsBinaryResponseDto {
    fn from(r: p::BlobIsBinaryResponse) -> Self {
        BlobIsBinaryResponseDto {
            is_binary: r.is_binary,
        }
    }
}

impl From<p::BlobUploadResponse> for BlobUploadResponseDto {
    fn from(r: p::BlobUploadResponse) -> Self {
        BlobUploadResponseDto { id: oid_opt(r.id) }
    }
}

impl From<p::TagItem> for TagItemDto {
    fn from(t: p::TagItem) -> Self {
        TagItemDto {
            name: t.name,
            oid: oid_val(t.oid),
            target: oid_val(t.target),
            is_annotated: t.is_annotated,
            message: t.message,
            tagger: t.tagger,
            tagger_email: t.tagger_email,
        }
    }
}

impl From<p::TagListResponse> for TagListResponseDto {
    fn from(r: p::TagListResponse) -> Self {
        TagListResponseDto {
            tags: r.tags.into_iter().map(Into::into).collect(),
        }
    }
}

impl From<p::TagInfoResponse> for TagInfoResponseDto {
    fn from(r: p::TagInfoResponse) -> Self {
        TagInfoResponseDto {
            tag: r.tag.map(Into::into),
        }
    }
}

impl From<p::TagSummary> for TagSummaryDto {
    fn from(s: p::TagSummary) -> Self {
        TagSummaryDto {
            total_count: s.total_count,
        }
    }
}

impl From<p::TagSummaryResponse> for TagSummaryResponseDto {
    fn from(r: p::TagSummaryResponse) -> Self {
        TagSummaryResponseDto {
            summary: r.summary.map(Into::into),
        }
    }
}

impl From<p::TagInitResponse> for TagInitResponseDto {
    fn from(r: p::TagInitResponse) -> Self {
        TagInitResponseDto {
            oid: oid_opt(r.oid),
        }
    }
}

impl From<p::TagUpdateMessageResponse> for TagUpdateMessageResponseDto {
    fn from(r: p::TagUpdateMessageResponse) -> Self {
        TagUpdateMessageResponseDto {
            oid: oid_opt(r.oid),
        }
    }
}

impl From<p::CommitBlameHunk> for CommitBlameHunkDto {
    fn from(h: p::CommitBlameHunk) -> Self {
        CommitBlameHunkDto {
            commit_oid: oid_opt(h.commit_oid),
            final_start_line: h.final_start_line,
            final_lines: h.final_lines,
            orig_start_line: h.orig_start_line,
            orig_lines: h.orig_lines,
            boundary: h.boundary,
            orig_path: h.orig_path,
        }
    }
}

impl From<p::CommitBlameLine> for CommitBlameLineDto {
    fn from(l: p::CommitBlameLine) -> Self {
        CommitBlameLineDto {
            commit_oid: oid_opt(l.commit_oid),
            line_no: l.line_no,
            content: l.content,
            orig_path: l.orig_path,
        }
    }
}

impl From<p::BlameFileResponse> for BlameFileResponseDto {
    fn from(r: p::BlameFileResponse) -> Self {
        BlameFileResponseDto {
            hunks: r.hunks.into_iter().map(Into::into).collect(),
        }
    }
}

impl From<p::BlameHunkResponse> for BlameFileResponseDto {
    fn from(r: p::BlameHunkResponse) -> Self {
        BlameFileResponseDto {
            hunks: r.hunks.into_iter().map(Into::into).collect(),
        }
    }
}

impl From<p::BlameLinesResponse> for BlameLinesResponseDto {
    fn from(r: p::BlameLinesResponse) -> Self {
        BlameLinesResponseDto {
            lines: r.lines.into_iter().map(Into::into).collect(),
        }
    }
}

impl From<p::DiffDeltaStatus> for DiffDeltaStatusDto {
    fn from(s: p::DiffDeltaStatus) -> Self {
        match s {
            p::DiffDeltaStatus::Unmodified => DiffDeltaStatusDto::Unmodified,
            p::DiffDeltaStatus::Added => DiffDeltaStatusDto::Added,
            p::DiffDeltaStatus::Deleted => DiffDeltaStatusDto::Deleted,
            p::DiffDeltaStatus::Modified => DiffDeltaStatusDto::Modified,
            p::DiffDeltaStatus::Renamed => DiffDeltaStatusDto::Renamed,
            p::DiffDeltaStatus::Copied => DiffDeltaStatusDto::Copied,
            p::DiffDeltaStatus::Typechange => DiffDeltaStatusDto::Typechange,
            p::DiffDeltaStatus::Conflicted => DiffDeltaStatusDto::Conflicted,
        }
    }
}

impl From<p::DiffFile> for DiffFileDto {
    fn from(f: p::DiffFile) -> Self {
        DiffFileDto {
            oid: oid_opt(f.oid),
            path: f.path,
            size: f.size,
            is_binary: f.is_binary,
        }
    }
}

impl From<p::DiffHunk> for DiffHunkDto {
    fn from(h: p::DiffHunk) -> Self {
        DiffHunkDto {
            old_start: h.old_start,
            old_lines: h.old_lines,
            new_start: h.new_start,
            new_lines: h.new_lines,
            header: h.header,
        }
    }
}

impl From<p::DiffLine> for DiffLineDto {
    fn from(l: p::DiffLine) -> Self {
        DiffLineDto {
            content: l.content,
            origin: l.origin,
            old_lineno: l.old_lineno,
            new_lineno: l.new_lineno,
            num_lines: l.num_lines,
            content_offset: l.content_offset,
        }
    }
}

impl From<p::DiffDelta> for DiffDeltaDto {
    fn from(d: p::DiffDelta) -> Self {
        DiffDeltaDto {
            status: d.status,
            old_file: d.old_file.map(Into::into),
            new_file: d.new_file.map(Into::into),
            nfiles: d.nfiles,
            hunks: d.hunks.into_iter().map(Into::into).collect(),
            lines: d.lines.into_iter().map(Into::into).collect(),
        }
    }
}

impl From<p::DiffStats> for DiffStatsDto {
    fn from(s: p::DiffStats) -> Self {
        DiffStatsDto {
            files_changed: s.files_changed,
            insertions: s.insertions,
            deletions: s.deletions,
        }
    }
}

impl From<p::DiffResult> for DiffResultDto {
    fn from(r: p::DiffResult) -> Self {
        DiffResultDto {
            stats: r.stats.map(Into::into),
            deltas: r.deltas.into_iter().map(Into::into).collect(),
        }
    }
}

impl From<p::SideBySideChangeType> for SideBySideChangeTypeDto {
    fn from(t: p::SideBySideChangeType) -> Self {
        match t {
            p::SideBySideChangeType::Unchanged => {
                SideBySideChangeTypeDto::Unchanged
            }
            p::SideBySideChangeType::Added => SideBySideChangeTypeDto::Added,
            p::SideBySideChangeType::Removed => {
                SideBySideChangeTypeDto::Removed
            }
            p::SideBySideChangeType::Modified => {
                SideBySideChangeTypeDto::Modified
            }
            p::SideBySideChangeType::Empty => SideBySideChangeTypeDto::Empty,
        }
    }
}

impl From<p::SideBySideLine> for SideBySideLineDto {
    fn from(l: p::SideBySideLine) -> Self {
        SideBySideLineDto {
            left_line_no: l.left_line_no,
            right_line_no: l.right_line_no,
            left_content: l.left_content,
            right_content: l.right_content,
            change_type: l.change_type,
        }
    }
}

impl From<p::SideBySideFile> for SideBySideFileDto {
    fn from(f: p::SideBySideFile) -> Self {
        SideBySideFileDto {
            path: f.path,
            additions: f.additions,
            deletions: f.deletions,
            is_binary: f.is_binary,
            is_rename: f.is_rename,
            lines: f.lines.into_iter().map(Into::into).collect(),
        }
    }
}

impl From<p::SideBySideDiffResult> for SideBySideDiffResultDto {
    fn from(r: p::SideBySideDiffResult) -> Self {
        SideBySideDiffResultDto {
            files: r.files.into_iter().map(Into::into).collect(),
            total_additions: r.total_additions,
            total_deletions: r.total_deletions,
        }
    }
}
