// Copyright (c) 2026 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.

#[path = "history_summary.rs"]
mod summary;

#[cfg(test)]
use self::summary::calc_char_diff;
use tui::{
    layout::Constraint,
    style::{Color, Modifier, Style},
    symbols,
    text::{Line, Span, Text},
    widgets::{Block, Borders, Cell, Row, Table, TableState},
    Frame,
};

#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct History {
    /// timestamp
    pub timestamp: String,

    /// result status
    pub status: bool,

    /// history number.
    /// This value will be the same as the index number of App.result in `app.rs``.
    pub num: u16,

    /// summary
    pub summary: HistorySummary,
}

#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct HistorySummary {
    pub line_add: u64,
    pub line_rem: u64,
    pub char_add: u64,
    pub char_rem: u64,
}

impl HistorySummary {
    pub fn init() -> Self {
        Self {
            line_add: 0,
            line_rem: 0,
            char_add: 0,
            char_rem: 0,
        }
    }
}

pub struct HistoryArea {
    ///
    pub area: tui::layout::Rect,

    ///
    pub active: bool,

    /// View data

    /// History data.
    ///
    data: Vec<Vec<History>>,

    /// State information including the selected position
    state: TableState,

    /// Set summary display mode.
    summary: bool,

    /// is enable border
    border: bool,

    /// is hide header
    hide_header: bool,

    /// is enable scroll bar
    scroll_bar: bool,

    /// enable character diff
    enable_char_diff: bool,
}

/// History Area Object Trait
impl HistoryArea {
    ///
    pub fn new() -> Self {
        //! new Self
        Self {
            area: tui::layout::Rect::new(0, 0, 0, 0),
            active: false,
            data: vec![vec![History {
                timestamp: "latest                 ".to_string(),
                status: true,
                num: 0,
                summary: HistorySummary::init(),
            }]],
            state: TableState::default(),
            summary: false,
            border: false,
            hide_header: false,
            scroll_bar: false,
            enable_char_diff: false,
        }
    }

    ///
    pub fn set_enable_char_diff(&mut self, enable_char_diff: bool) {
        self.enable_char_diff = enable_char_diff;
    }

    ///
    pub fn set_area(&mut self, area: tui::layout::Rect) {
        self.area = area;
    }

    ///
    pub fn set_active(&mut self, active: bool) {
        self.active = active;
    }

    ///
    pub fn set_latest_status(&mut self, latest_status: bool) {
        self.data[0][0].status = latest_status;
    }

    ///
    pub fn set_border(&mut self, border: bool) {
        self.border = border;
    }

    ///
    pub fn set_scroll_bar(&mut self, scroll_bar: bool) {
        self.scroll_bar = scroll_bar;
    }

    ///
    pub fn set_summary(&mut self, summary: bool) {
        self.summary = summary;
    }

    ///
    pub fn set_hide_header(&mut self, hide_header: bool) {
        self.hide_header = hide_header;
    }

    ///
    pub fn update(
        &mut self,
        timestamp: String,
        status: bool,
        num: u16,
        history_summary: HistorySummary,
    ) {
        // set result statu to latest
        self.set_latest_status(status);

        // insert latest timestamp
        self.data.insert(
            1,
            vec![History {
                timestamp,
                status,
                num,
                summary: history_summary,
            }],
        );
    }

    ///
    pub fn delete(&mut self, index: usize) {
        // find index
        let mut i = 0;
        for d in self.data.iter() {
            if d[0].num == index as u16 {
                break;
            }
            i += 1;
        }

        if i < self.data.len() {
            self.data.remove(i);
        }

        // set select num
        self.state.select(Some(0));
    }

    ///
    pub fn reset_history_data(&mut self, data: Vec<Vec<History>>) {
        // @TODO: output mode切り替えでも使えるようにするため、indexを受け取るようにする
        // update data
        self.data = data;

        // set select num
        self.state.select(Some(0));
    }

    ///
    pub fn draw(&mut self, frame: &mut Frame) {
        // insert latest timestamp
        const LATEST_COLOR: Color = Color::Blue;
        let draw_data = &self.data;

        let rows = draw_data.iter().enumerate().map(|(ix, item)| {
            // set table height
            let height = match ix {
                0 => 1,
                _ => {
                    if self.summary {
                        if self.enable_char_diff {
                            3
                        } else {
                            2
                        }
                    } else {
                        1
                    }
                }
            };

            // set cell data
            let cells = item.iter().map(|c| {
                // cell style
                let cell_style = Style::default().fg(match ix {
                    0 => LATEST_COLOR,
                    _ => match c.status {
                        true => Color::Green,
                        false => Color::Red,
                    },
                });

                // line1: timestamp
                let line1 = Line::from(vec![Span::styled(c.timestamp.as_str(), cell_style)]);

                // line2: line summary
                let line2 = Line::from(vec![
                    Span::styled("Line: ", Color::Reset),
                    Span::styled(
                        format!("+{:>7}", c.summary.line_add.to_string()),
                        Color::Green,
                    ),
                    Span::styled(" ", Color::Reset),
                    Span::styled(
                        format!("-{:>7}", c.summary.line_rem.to_string()),
                        Color::Red,
                    ),
                ]);

                // line3: char summary
                let line3 = Line::from(vec![
                    Span::styled("Char: ", Color::Reset),
                    Span::styled(
                        format!("+{:>7}", c.summary.char_add.to_string()),
                        Color::Green,
                    ),
                    Span::styled(" ", Color::Reset),
                    Span::styled(
                        format!("-{:>7}", c.summary.char_rem.to_string()),
                        Color::Red,
                    ),
                ]);

                // set text
                let text = match self.summary {
                    true => {
                        if self.enable_char_diff {
                            Text::from(vec![line1, line2, line3])
                        } else {
                            Text::from(vec![line1, line2])
                        }
                    }
                    false => Text::from(vec![line1]),
                };

                // cell object
                Cell::from(text)
            });

            Row::new(cells).height(height as u16)
        });

        let base_selected_style = Style::default()
            .bg(Color::DarkGray)
            .add_modifier(Modifier::BOLD);
        let selected_style = match self.active {
            true => match self.get_state_select() == 0 {
                true => base_selected_style.fg(Color::Gray).bg(LATEST_COLOR), // Necessary to make >> blue
                false => base_selected_style,
            },
            false => base_selected_style.fg(Color::Gray),
        };

        let pane_block: Block<'_>;
        let history_width: u16;
        if self.border {
            history_width = crate::HISTORY_WIDTH + 1;
            if self.hide_header {
                pane_block = Block::default();
            } else {
                pane_block = Block::default()
                    .borders(Borders::TOP)
                    .border_style(Style::default().fg(Color::DarkGray))
                    .border_set(symbols::border::Set {
                        top_left: symbols::line::NORMAL.horizontal_down,
                        ..symbols::border::PLAIN
                    });
            }
        } else {
            history_width = crate::HISTORY_WIDTH;
            pane_block = Block::default()
        }

        let table = Table::new(rows, [Constraint::Length(history_width)])
            .block(pane_block)
            .row_highlight_style(selected_style)
            .highlight_symbol(">>")
            .widths([Constraint::Percentage(100)]);

        // render table
        frame.render_stateful_widget(table, self.area, &mut self.state);
    }

    ///
    pub fn get_history_size(&self) -> usize {
        self.data.len()
    }

    /// Get latest result index.
    /// (This is the index of the latest result excluding "latest" row.)
    #[allow(dead_code)]
    pub fn get_results_latest_index(&self) -> usize {
        if self.data.len() > 1 {
            self.data[1][0].num as usize
        } else {
            0
        }
    }

    ///
    pub fn get_state_select(&self) -> usize {
        let i = match self.state.selected() {
            Some(i) => i,
            None => self.data.len() - 1,
        };

        if self.data.len() > i {
            self.data[i][0].num as usize
        } else {
            0
        }
    }

    ///
    pub fn set_state_select(&mut self, index: usize) {
        // find index
        let mut i = 0;
        for d in self.data.iter() {
            if d[0].num == index as u16 {
                break;
            }
            i += 1;
        }

        self.state.select(Some(i));
    }

    ///
    pub fn next(&mut self, num: usize) {
        let i = match self.state.selected() {
            Some(i) => i.saturating_sub(num),
            None => 0,
        };
        self.state.select(Some(i));
    }

    ///
    pub fn previous(&mut self, num: usize) {
        let i = match self.state.selected() {
            Some(i) => {
                if i + num < self.data.len() - 1 {
                    i + num
                } else {
                    self.data.len() - 1
                }
            }
            None => 0,
        };
        self.state.select(Some(i));
    }

    ///
    pub fn click_row(&mut self, row: u16) {
        let first_row = self.state.offset();

        let mut select_num: usize;

        let border_row_num: usize = if self.border { 1 } else { 0 };

        if self.summary {
            if row == (border_row_num as u16) {
                select_num = row as usize - border_row_num;
            } else if row < border_row_num as u16 {
                select_num = 0;
            } else {
                let row_count = if self.enable_char_diff { 3 } else { 2 };

                if first_row == 0 {
                    select_num = ((row - 1 - border_row_num as u16) / row_count + 1) as usize;
                } else {
                    select_num = ((row - border_row_num as u16) / row_count) as usize;
                }
            }
        } else {
            select_num = row as usize;
            if row > 0 {
                select_num -= border_row_num;
            }
        }

        if select_num < self.data.len() {
            self.state.select(Some(select_num + first_row));
        }
    }
}

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

    #[test]
    fn history_summary_calc_counts_line_changes_without_char_diff() {
        let mut summary = HistorySummary::init();
        summary.calc("alpha\nbeta\n", "alpha\ngamma\n", false, false);

        assert_eq!(summary.line_add, 1);
        assert_eq!(summary.line_rem, 1);
        assert_eq!(summary.char_add, 0);
        assert_eq!(summary.char_rem, 0);
    }

    #[test]
    fn history_summary_calc_counts_character_changes_when_enabled() {
        let mut summary = HistorySummary::init();
        summary.calc("abc\n", "adc\n", true, false);

        assert_eq!(summary.line_add, 1);
        assert_eq!(summary.line_rem, 1);
        assert_eq!(summary.char_add, 1);
        assert_eq!(summary.char_rem, 1);
    }

    #[test]
    fn calc_char_diff_handles_replace_blocks() {
        let diff = TextDiff::from_lines("kitten\n", "sitten\n");
        let (char_add, char_rem) = calc_char_diff(diff.iter_all_changes().collect());

        assert_eq!((char_add, char_rem), (1, 1));
    }

    #[test]
    fn history_summary_ignores_whitespace_amount_when_enabled() {
        let mut summary = HistorySummary::init();
        summary.calc("alpha  beta\n", "alpha   beta\n", true, true);

        assert_eq!(summary.line_add, 0);
        assert_eq!(summary.line_rem, 0);
        assert_eq!(summary.char_add, 0);
        assert_eq!(summary.char_rem, 0);
    }

    #[test]
    fn history_area_update_delete_and_selection_follow_history_numbers() {
        let mut area = HistoryArea::new();
        let summary = HistorySummary {
            line_add: 1,
            line_rem: 2,
            char_add: 3,
            char_rem: 4,
        };

        area.update(
            "2026-04-08 12:00:00.000".to_string(),
            false,
            5,
            summary.clone(),
        );
        area.update("2026-04-08 12:00:01.000".to_string(), true, 6, summary);
        area.set_state_select(5);

        assert_eq!(area.get_history_size(), 3);
        assert_eq!(area.get_results_latest_index(), 6);
        assert_eq!(area.get_state_select(), 5);

        area.delete(5);

        assert_eq!(area.get_history_size(), 2);
        assert_eq!(area.get_state_select(), 0);
    }

    #[test]
    fn history_area_next_and_previous_stay_within_bounds() {
        let mut area = HistoryArea::new();
        let summary = HistorySummary::init();
        area.update(
            "2026-04-08 12:00:00.000".to_string(),
            true,
            1,
            summary.clone(),
        );
        area.update("2026-04-08 12:00:01.000".to_string(), true, 2, summary);

        area.set_state_select(2);
        area.next(1);
        assert_eq!(area.get_state_select(), 0);

        area.previous(10);
        assert_eq!(area.get_state_select(), 1);
    }

    #[test]
    fn history_area_click_row_accounts_for_summary_rows() {
        let mut area = HistoryArea::new();
        let summary = HistorySummary::init();
        area.update(
            "2026-04-08 12:00:00.000".to_string(),
            true,
            1,
            summary.clone(),
        );
        area.update("2026-04-08 12:00:01.000".to_string(), true, 2, summary);
        area.set_summary(true);
        area.set_enable_char_diff(true);

        area.click_row(4);

        assert_eq!(area.get_state_select(), 1);
    }
}
