use std::{borrow::Cow, cell::Cell, cmp, path::Path};

use anyhow::Result;
use bytesize::ByteSize;
use crossterm::event::Event;
use gnostr_asyncgit::{
    hash,
    sync::{self, diff::DiffLinePosition, RepoPathRef},
    //DiffLine, DiffLineType, FileDiff,
    DiffLine,
    DiffLineType,
    FileDiff,
};
use ratatui::{
    layout::Rect,
    symbols,
    text::{Line, Span},
    widgets::{Block, Borders, Paragraph},
    Frame,
};

use super::{
    utils::{scroll_horizontal::HorizontalScroll, scroll_vertical::VerticalScroll},
    CommandBlocking, Direction, DrawableComponent, HorizontalScrollType, ScrollType,
};
use crate::{
    app::Environment,
    components::{CommandInfo, Component, EventState},
    keys::{key_match, SharedKeyConfig},
    options::SharedOptions,
    queue::{Action, InternalEvent, NeedsUpdate, Queue, ResetItem},
    string_utils::{tabs_to_spaces, trim_offset},
    strings,
    try_or_popup,
    ui::style::SharedTheme,
    //utils::parse_private_key,
};

use nostr_sdk_0_37_0::prelude::*;
use sha2::{Digest, Sha256};

#[derive(Default)]
struct Current {
    path: String,
    is_stage: bool,
    hash: u64,
}

#[derive(Clone, Copy)]
enum Selection {
    Single(usize),
    Multiple(usize, usize),
}

impl Selection {
    const fn get_start(&self) -> usize {
        match self {
            Self::Single(start) | Self::Multiple(start, _) => *start,
        }
    }

    const fn get_end(&self) -> usize {
        match self {
            Self::Single(end) | Self::Multiple(_, end) => *end,
        }
    }

    fn get_top(&self) -> usize {
        match self {
            Self::Single(start) => *start,
            Self::Multiple(start, end) => cmp::min(*start, *end),
        }
    }

    fn get_bottom(&self) -> usize {
        match self {
            Self::Single(start) => *start,
            Self::Multiple(start, end) => cmp::max(*start, *end),
        }
    }

    fn modify(&mut self, direction: Direction, max: usize) {
        let start = self.get_start();
        let old_end = self.get_end();

        *self = match direction {
            Direction::Up => Self::Multiple(start, old_end.saturating_sub(1)),

            Direction::Down => Self::Multiple(start, cmp::min(old_end + 1, max)),
        };
    }

    fn contains(&self, index: usize) -> bool {
        match self {
            Self::Single(start) => index == *start,
            Self::Multiple(start, end) => {
                if start <= end {
                    *start <= index && index <= *end
                } else {
                    *end <= index && index <= *start
                }
            }
        }
    }
}

/// DiffComponent
pub struct DiffComponent {
    repo: RepoPathRef,
    diff: Option<FileDiff>,
    longest_line: usize,
    pending: bool,
    selection: Selection,
    selected_hunk: Option<usize>,
    current_size: Cell<(u16, u16)>,
    focused: bool,
    current: Current,
    vertical_scroll: VerticalScroll,
    horizontal_scroll: HorizontalScroll,
    queue: Queue,
    theme: SharedTheme,
    key_config: SharedKeyConfig,
    is_immutable: bool,
    options: SharedOptions,
}

impl DiffComponent {
		/// new
        pub fn new(env: &Environment, is_immutable: bool) -> Self {
        Self {
            focused: false,
            queue: env.queue.clone(),
            current: Current::default(),
            pending: false,
            selected_hunk: None,
            diff: None,
            longest_line: 0,
            current_size: Cell::new((0, 0)),
            selection: Selection::Single(0),
            vertical_scroll: VerticalScroll::new(),
            horizontal_scroll: HorizontalScroll::new(),
            theme: env.theme.clone(),
            key_config: env.key_config.clone(),
            is_immutable,
            repo: env.repo.clone(),
            options: env.options.clone(),
        }
    }
                fn can_scroll(&self) -> bool {
        self.diff.as_ref().is_some_and(|diff| diff.lines > 1)
    }
				/// current
        pub fn current(&self) -> (String, bool) {
        (self.current.path.clone(), self.current.is_stage)
    }
		/// clear
        pub fn clear(&mut self, pending: bool) {
        self.current = Current::default();
        self.diff = None;
        self.longest_line = 0;
        self.vertical_scroll.reset();
        self.horizontal_scroll.reset();
        self.selection = Selection::Single(0);
        self.selected_hunk = None;
        self.pending = pending;
    }
		/// update
        pub fn update(&mut self, path: String, is_stage: bool, diff: FileDiff) {
        self.pending = false;

        //hash
        let hash = hash(&diff);

        if self.current.hash != hash {
            let reset_selection = self.current.path != path;

            self.current = Current {
                path,
                is_stage,
                hash,
            };

            self.diff = Some(diff);

            self.longest_line = self
                .diff
                .iter()
                .flat_map(|diff| diff.hunks.iter())
                .flat_map(|hunk| hunk.lines.iter())
                .map(|line| {
                    let converted_content = tabs_to_spaces(line.content.as_ref().to_string());

                    converted_content.len()
                })
                .max()
                .map_or(0, |len| {
                    // Each hunk uses a 1-character wide vertical bar
                    // to its left to indicate selection.
                    len + 1
                });

            if reset_selection {
                self.vertical_scroll.reset();
                self.selection = Selection::Single(0);
                self.update_selection(0);
            } else {
                let old_selection = match self.selection {
                    Selection::Single(line) => line,
                    Selection::Multiple(start, _) => start,
                };
                self.update_selection(old_selection);
            }
        }
    }

    fn move_selection(&mut self, move_type: ScrollType) {
        if let Some(diff) = &self.diff {
            let max = diff.lines.saturating_sub(1);

            let new_start = match move_type {
                ScrollType::Down => self.selection.get_bottom().saturating_add(1),
                ScrollType::Up => self.selection.get_top().saturating_sub(1),
                ScrollType::Home => 0,
                ScrollType::End => max,
                ScrollType::PageDown => self
                    .selection
                    .get_bottom()
                    .saturating_add(self.current_size.get().1.saturating_sub(1) as usize),
                ScrollType::PageUp => self
                    .selection
                    .get_top()
                    .saturating_sub(self.current_size.get().1.saturating_sub(1) as usize),
            };

            self.update_selection(new_start);
        }
    }

    fn update_selection(&mut self, new_start: usize) {
        if let Some(diff) = &self.diff {
            let max = diff.lines.saturating_sub(1);
            let new_start = cmp::min(max, new_start);
            self.selection = Selection::Single(new_start);
            self.selected_hunk = Self::find_selected_hunk(diff, new_start);
        }
    }

    fn lines_count(&self) -> usize {
        self.diff.as_ref().map_or(0, |diff| diff.lines)
    }

    fn max_scroll_right(&self) -> usize {
        self.longest_line
            .saturating_sub(self.current_size.get().0.into())
    }

    fn modify_selection(&mut self, direction: Direction) {
        if self.diff.is_some() {
            self.selection.modify(direction, self.lines_count());
        }
    }

    fn copy_selection(&self) {
        if let Some(diff) = &self.diff {
            let lines_to_copy: Vec<&str> = diff
                .hunks
                .iter()
                .flat_map(|hunk| hunk.lines.iter())
                .enumerate()
                .filter_map(|(i, line)| {
                    if self.selection.contains(i) {
                        Some(line.content.trim_matches(|c| c == '\n' || c == '\r'))
                    } else {
                        None
                    }
                })
                .collect();

            try_or_popup!(
                self,
                "copy to clipboard error:",
                crate::clipboard::copy_string(&lines_to_copy.join("\n"))
            );
        }
    }

    fn find_selected_hunk(diff: &FileDiff, line_selected: usize) -> Option<usize> {
        let mut line_cursor = 0_usize;
        for (i, hunk) in diff.hunks.iter().enumerate() {
            let hunk_len = hunk.lines.len();
            let hunk_min = line_cursor;
            let hunk_max = line_cursor + hunk_len;

            let hunk_selected = hunk_min <= line_selected && hunk_max > line_selected;

            if hunk_selected {
                return Some(i);
            }

            line_cursor += hunk_len;
        }

        None
    }

    ///fn get_text(&self, width: u16, height: u16) -> Vec<Line>
    fn get_text(&self, width: u16, height: u16) -> Vec<Line<'_>> {
        if let Some(diff) = &self.diff {
            return if diff.hunks.is_empty() {
                self.get_text_binary(diff)
            } else {
                let mut res: Vec<Line> = Vec::new();

                let min = self.vertical_scroll.get_top();
                let max = min + height as usize;

                let mut line_cursor = 0_usize;
                let mut lines_added = 0_usize;

                for (i, hunk) in diff.hunks.iter().enumerate() {
                    let hunk_selected = self.focused() && (self.selected_hunk == Some(i));

                    if lines_added >= height as usize {
                        break;
                    }

                    let hunk_len = hunk.lines.len();
                    let hunk_min = line_cursor;
                    let hunk_max = line_cursor + hunk_len;

                    if Self::hunk_visible(hunk_min, hunk_max, min, max) {
                        for (i, line) in hunk.lines.iter().enumerate() {
                            if line_cursor >= min && line_cursor <= max {
                                res.push(Self::get_line_to_add(
                                    width,
                                    line,
                                    self.focused() && self.selection.contains(line_cursor),
                                    hunk_selected,
                                    i == hunk_len - 1,
                                    &self.theme,
                                    self.horizontal_scroll.get_right(),
                                ));
                                lines_added += 1;
                            }

                            line_cursor += 1;
                        }
                    } else {
                        line_cursor += hunk_len;
                    }
                }

                res
            };
        }

        vec![]
    }

    fn get_text_binary(&self, diff: &FileDiff) -> Vec<Line<'_>> {
        let is_positive = diff.size_delta >= 0;
        let delta_byte_size = ByteSize::b(diff.size_delta.unsigned_abs());
        let sign = if is_positive { "+" } else { "-" };
        vec![Line::from(vec![
            Span::raw(Cow::from("size: ")),
            Span::styled(
                Cow::from(format!("{}", ByteSize::b(diff.sizes.0))),
                self.theme.text(false, false),
            ),
            Span::raw(Cow::from(" -> ")),
            Span::styled(
                Cow::from(format!("{}", ByteSize::b(diff.sizes.1))),
                self.theme.text(false, false),
            ),
            Span::raw(Cow::from(" (")),
            Span::styled(
                Cow::from(format!("{sign}{delta_byte_size:}")),
                self.theme.diff_line(
                    if is_positive {
                        DiffLineType::Add
                    } else {
                        DiffLineType::Delete
                    },
                    false,
                ),
            ),
            Span::raw(Cow::from(")")),
        ])]
    }

    ///fn get_line_to_add<'a>
    fn get_line_to_add<'a>(
        width: u16,
        line: &'a DiffLine,
        selected: bool,
        selected_hunk: bool,
        end_of_hunk: bool,
        theme: &SharedTheme,
        scrolled_right: usize,
    ) -> Line<'a> {
        let style = theme.diff_hunk_marker(selected_hunk);

        let is_content_line = matches!(line.line_type, DiffLineType::None);

        let left_side_of_line = if end_of_hunk {
            Span::styled(Cow::from(symbols::line::BOTTOM_LEFT), style)
        } else {
            match line.line_type {
                DiffLineType::Header => Span::styled(Cow::from(symbols::line::TOP_LEFT), style),
                _ => Span::styled(Cow::from(symbols::line::VERTICAL), style),
            }
        };

        let content = if !is_content_line && line.content.as_ref().is_empty() {
            theme.line_break()
        } else {
            tabs_to_spaces(line.content.as_ref().to_string())
        };
        let content = trim_offset(&content, scrolled_right);

        let mut hasher = Sha256::new();
        hasher.update(content);
        let result = hasher.finalize();

        let keys = Keys::parse(format!("{:x}", result)).unwrap();

        let filled = if selected {
            // selected line
            format!(
                "  {} {content:w$}",
                keys.public_key().to_string().clone(),
                w = (width + 2) as usize
            )
        } else {
            // weird eof missing eol line
            format!("  {} {content}", keys.public_key())
        };

        Line::from(vec![
            left_side_of_line,
            Span::styled(Cow::from(filled), theme.diff_line(line.line_type, selected)),
        ])
    }

    const fn hunk_visible(hunk_min: usize, hunk_max: usize, min: usize, max: usize) -> bool {
        // full overlap
        if hunk_min <= min && hunk_max >= max {
            return true;
        }

        // partly overlap
        if (hunk_min >= min && hunk_min <= max) || (hunk_max >= min && hunk_max <= max) {
            return true;
        }

        false
    }

    #[allow(clippy::needless_pass_by_ref_mut)]
    fn unstage_hunk(&mut self) -> Result<()> {
        if let Some(diff) = &self.diff {
            if let Some(hunk) = self.selected_hunk {
                let hash = diff.hunks[hunk].header_hash;
                sync::unstage_hunk(
                    &self.repo.borrow(),
                    &self.current.path,
                    hash,
                    Some(self.options.borrow().diff_options()),
                )?;
                self.queue_update();
            }
        }

        Ok(())
    }

    #[allow(clippy::needless_pass_by_ref_mut)]
    fn stage_hunk(&mut self) -> Result<()> {
        if let Some(diff) = &self.diff {
            if let Some(hunk) = self.selected_hunk {
                if diff.untracked {
                    sync::stage_add_file(&self.repo.borrow(), Path::new(&self.current.path))?;
                } else {
                    #[allow(clippy::needless_pass_by_ref_mut)]
                    let hash = diff.hunks[hunk].header_hash;
                    sync::stage_hunk(
                        &self.repo.borrow(),
                        &self.current.path,
                        hash,
                        Some(self.options.borrow().diff_options()),
                    )?;
                }

                self.queue_update();
            }
        }

        Ok(())
    }

    fn queue_update(&self) {
        self.queue.push(InternalEvent::Update(NeedsUpdate::ALL));
    }

    fn reset_hunk(&self) {
        if let Some(diff) = &self.diff {
            if let Some(hunk) = self.selected_hunk {
                let hash = diff.hunks[hunk].header_hash;

                self.queue
                    .push(InternalEvent::ConfirmAction(Action::ResetHunk(
                        self.current.path.clone(),
                        hash,
                    )));
            }
        }
    }

    fn reset_lines(&self) {
        self.queue
            .push(InternalEvent::ConfirmAction(Action::ResetLines(
                self.current.path.clone(),
                self.selected_lines(),
            )));
    }

    fn stage_lines(&self) {
        if let Some(diff) = &self.diff {
            //TODO: support untracked files as well
            if !diff.untracked {
                let selected_lines = self.selected_lines();

                try_or_popup!(
                    self,
                    "(un)stage lines:",
                    sync::stage_lines(
                        &self.repo.borrow(),
                        &self.current.path,
                        self.is_stage(),
                        &selected_lines,
                    )
                );

                self.queue_update();
            }
        }
    }

    fn selected_lines(&self) -> Vec<DiffLinePosition> {
        self.diff
            .as_ref()
            .map(|diff| {
                diff.hunks
                    .iter()
                    .flat_map(|hunk| hunk.lines.iter())
                    .enumerate()
                    .filter_map(|(i, line)| {
                        let is_add_or_delete = line.line_type == DiffLineType::Add
                            || line.line_type == DiffLineType::Delete;
                        if self.selection.contains(i) && is_add_or_delete {
                            Some(line.position)
                        } else {
                            None
                        }
                    })
                    .collect()
            })
            .unwrap_or_default()
    }

    fn reset_untracked(&self) {
        self.queue
            .push(InternalEvent::ConfirmAction(Action::Reset(ResetItem {
                path: self.current.path.clone(),
            })));
    }

    fn stage_unstage_hunk(&mut self) -> Result<()> {
        if self.current.is_stage {
            self.unstage_hunk()?;
        } else {
            self.stage_hunk()?;
        }

        Ok(())
    }

    fn calc_hunk_move_target(&self, direction: isize) -> Option<usize> {
        let diff = self.diff.as_ref()?;
        if diff.hunks.is_empty() {
            return None;
        }
        let max = diff.hunks.len() - 1;
        let target_index = self.selected_hunk.map_or(0, |i| {
            let target = if direction >= 0 {
                i.saturating_add(direction.unsigned_abs())
            } else {
                i.saturating_sub(direction.unsigned_abs())
            };
            std::cmp::min(max, target)
        });
        Some(target_index)
    }

    fn diff_hunk_move_up_down(&mut self, direction: isize) {
        let Some(diff) = &self.diff else { return };
        let hunk_index = self.calc_hunk_move_target(direction);
        // return if selected_hunk not change
        if self.selected_hunk == hunk_index {
            return;
        }
        if let Some(hunk_index) = hunk_index {
            let line_index = diff
                .hunks
                .iter()
                .take(hunk_index)
                .fold(0, |sum, hunk| sum + hunk.lines.len());
            let hunk = &diff.hunks[hunk_index];
            self.selection = Selection::Single(line_index);
            self.selected_hunk = Some(hunk_index);
            self.vertical_scroll.move_area_to_visible(
                self.current_size.get().1 as usize,
                line_index,
                line_index.saturating_add(hunk.lines.len()),
            );
        }
    }

    const fn is_stage(&self) -> bool {
        self.current.is_stage
    }
}

impl DrawableComponent for DiffComponent {
    //                     Frame     Rect
    fn draw(&self, f: &mut Frame, r: Rect) -> Result<()> {
        self.current_size
            .set((r.width.saturating_sub(2), r.height.saturating_sub(2)));

        let current_width = self.current_size.get().0;
        let current_height = self.current_size.get().1; // / 2;

        self.vertical_scroll.update(
            self.selection.get_end(),
            self.lines_count(),
            usize::from(current_height),
        );

        self.horizontal_scroll
            .update_no_selection(self.longest_line, current_width.into());

        let title = format!(
            "components/diff.rs title_diff:{} current.path:{}",
            //strings::title_diff(&self.key_config),
            strings::title_diff(&self.key_config),
            self.current.path
        );

        let txt = if self.pending {
            vec![Line::from(vec![Span::styled(
                Cow::from(strings::loading_text(&self.key_config)),
                self.theme.text(false, false),
            )])]
        } else {
            self.get_text(r.width, current_height)
        };

        //split r
        f.render_widget(
            Paragraph::new(txt).block(
                Block::default()
                    .title(Span::styled(
                        title.as_str(),
                        self.theme.title(self.focused()),
                    ))
                    .borders(Borders::ALL)
                    .border_style(self.theme.block(self.focused())),
            ),
            r,
        );

        if self.focused() {
            self.vertical_scroll.draw(f, r, &self.theme);

            if self.max_scroll_right() > 0 {
                self.horizontal_scroll.draw(f, r, &self.theme);
            }
        }

        Ok(())
    }
}

impl Component for DiffComponent {
    //chunk navigation
    //TODO generate private_key for each chunk
    fn commands(&self, out: &mut Vec<CommandInfo>, _force_all: bool) -> CommandBlocking {
        out.push(CommandInfo::new(
            strings::commands::scroll(&self.key_config),
            self.can_scroll(),
            self.focused(),
        ));
        out.push(CommandInfo::new(
            strings::commands::diff_hunk_next(&self.key_config),
            self.calc_hunk_move_target(1) != self.selected_hunk,
            self.focused(),
        ));
        out.push(CommandInfo::new(
            strings::commands::diff_hunk_prev(&self.key_config),
            self.calc_hunk_move_target(-1) != self.selected_hunk,
            self.focused(),
        ));
        out.push(
            CommandInfo::new(
                strings::commands::diff_home_end(&self.key_config),
                self.can_scroll(),
                self.focused(),
            )
            .hidden(),
        );

        if !self.is_immutable {
            out.push(CommandInfo::new(
                strings::commands::diff_hunk_remove(&self.key_config),
                self.selected_hunk.is_some(),
                self.focused() && self.is_stage(),
            ));
            out.push(CommandInfo::new(
                strings::commands::diff_hunk_add(&self.key_config),
                self.selected_hunk.is_some(),
                self.focused() && !self.is_stage(),
            ));
            out.push(CommandInfo::new(
                strings::commands::diff_hunk_revert(&self.key_config),
                self.selected_hunk.is_some(),
                self.focused() && !self.is_stage(),
            ));
            out.push(CommandInfo::new(
                strings::commands::diff_lines_revert(&self.key_config),
                //TODO: only if any modifications are selected
                true,
                self.focused() && !self.is_stage(),
            ));
            out.push(CommandInfo::new(
                strings::commands::diff_lines_stage(&self.key_config),
                //TODO: only if any modifications are selected
                true,
                self.focused() && !self.is_stage(),
            ));
            out.push(CommandInfo::new(
                strings::commands::diff_lines_unstage(&self.key_config),
                //TODO: only if any modifications are selected
                true,
                self.focused() && self.is_stage(),
            ));
        }

        out.push(CommandInfo::new(
            strings::commands::copy(&self.key_config),
            true,
            self.focused(),
        ));

        CommandBlocking::PassingOn
    }

    #[allow(clippy::cognitive_complexity, clippy::too_many_lines)]
    fn event(&mut self, ev: &Event) -> Result<EventState> {
        if self.focused() {
            if let Event::Key(e) = ev {
                return if key_match(e, self.key_config.keys.move_down) {
                    self.move_selection(ScrollType::Down);
                    Ok(EventState::Consumed)
                } else if key_match(e, self.key_config.keys.shift_down) {
                    self.modify_selection(Direction::Down);
                    Ok(EventState::Consumed)
                } else if key_match(e, self.key_config.keys.shift_up) {
                    self.modify_selection(Direction::Up);
                    Ok(EventState::Consumed)
                } else if key_match(e, self.key_config.keys.end) {
                    self.move_selection(ScrollType::End);
                    Ok(EventState::Consumed)
                } else if key_match(e, self.key_config.keys.home) {
                    self.move_selection(ScrollType::Home);
                    Ok(EventState::Consumed)
                } else if key_match(e, self.key_config.keys.move_up) {
                    self.move_selection(ScrollType::Up);
                    Ok(EventState::Consumed)
                } else if key_match(e, self.key_config.keys.page_up) {
                    self.move_selection(ScrollType::PageUp);
                    Ok(EventState::Consumed)
                } else if key_match(e, self.key_config.keys.page_down) {
                    self.move_selection(ScrollType::PageDown);
                    Ok(EventState::Consumed)
                } else if key_match(e, self.key_config.keys.move_right) {
                    self.horizontal_scroll
                        .move_right(HorizontalScrollType::Right);
                    Ok(EventState::Consumed)
                } else if key_match(e, self.key_config.keys.move_left) {
                    self.horizontal_scroll
                        .move_right(HorizontalScrollType::Left);
                    Ok(EventState::Consumed)
                } else if key_match(e, self.key_config.keys.diff_hunk_next) {
                    self.diff_hunk_move_up_down(1);
                    Ok(EventState::Consumed)
                } else if key_match(e, self.key_config.keys.diff_hunk_prev) {
                    self.diff_hunk_move_up_down(-1);
                    Ok(EventState::Consumed)
                } else if key_match(e, self.key_config.keys.stage_unstage_item)
                    && !self.is_immutable
                {
                    try_or_popup!(self, "hunk error:", self.stage_unstage_hunk());

                    Ok(EventState::Consumed)
                } else if key_match(e, self.key_config.keys.status_reset_item)
                    && !self.is_immutable
                    && !self.is_stage()
                {
                    if let Some(diff) = &self.diff {
                        if diff.untracked {
                            self.reset_untracked();
                        } else {
                            self.reset_hunk();
                        }
                    }
                    Ok(EventState::Consumed)
                } else if key_match(e, self.key_config.keys.diff_stage_lines) && !self.is_immutable
                {
                    self.stage_lines();
                    Ok(EventState::Consumed)
                } else if key_match(e, self.key_config.keys.diff_reset_lines)
                    && !self.is_immutable
                    && !self.is_stage()
                {
                    if let Some(diff) = &self.diff {
                        //TODO: reset untracked lines
                        if !diff.untracked {
                            self.reset_lines();
                        }
                    }
                    Ok(EventState::Consumed)
                } else if key_match(e, self.key_config.keys.copy) {
                    self.copy_selection();
                    Ok(EventState::Consumed)
                } else {
                    Ok(EventState::NotConsumed)
                };
            }
        }

        Ok(EventState::NotConsumed)
    }

    fn focused(&self) -> bool {
        self.focused
    }
    fn focus(&mut self, focus: bool) {
        self.focused = focus;
    }
}

#[cfg(test)]
mod tests {
    use std::{io::Write, rc::Rc};

    use tempfile::NamedTempFile;

    use super::*;
    use crate::ui::style::Theme;

    #[test]
    fn test_line_break() {
        let diff_line = DiffLine {
            content: "".into(),
            line_type: DiffLineType::Add,
            position: Default::default(),
        };

        {
            let default_theme = Rc::new(Theme::default());

            assert_eq!(
                DiffComponent::get_line_to_add(
                    4,
                    &diff_line,
                    false,
                    false,
                    false,
                    &default_theme,
                    0
                )
                .spans
                .last()
                .unwrap(),
                &Span::styled(
                    Cow::from(
                        "  7efbb4a7ddaf15f111924574b3074bef589ad3bc24ce39ce1e1ff27447101b8a ¶"
                    ),
                    default_theme.diff_line(diff_line.line_type, false)
                )
            );
        }

        {
            let mut file = NamedTempFile::new().unwrap();

            writeln!(
                file,
                r#"
(
	line_break: Some("+")
)
"#
            )
            .unwrap();

            let theme = Rc::new(Theme::init(&file.path().to_path_buf()));

            assert_eq!(
                DiffComponent::get_line_to_add(4, &diff_line, false, false, false, &theme, 0)
                    .spans
                    .last()
                    .unwrap(),
                &Span::styled(
                    Cow::from(
                        "  72ccf8e478a1fbdbac1aa7a62ac016f53d6a3dad6bb3726d94ca713149d1abe2 +"
                    ),
                    theme.diff_line(diff_line.line_type, false)
                )
            );
        }
    }
}
