use crate::types::TableCell;
use gpui::{AnyElement, Entity};
use std::ops::Range;
use ui::Table;
use ui::TableColumnWidths;
use ui::TableResizeBehavior;
use ui::UncheckedTableRow;
use ui::{DefiniteLength, div, prelude::*};

use crate::{
    CsvPreviewView,
    settings::RowRenderMechanism,
    types::{AnyColumn, DisplayCellId, DisplayRow},
};

impl CsvPreviewView {
    /// Creates a new table.
    /// Column number is derived from the `TableColumnWidths` entity.
    pub(crate) fn create_table(
        &self,
        current_widths: &Entity<TableColumnWidths>,
        cx: &mut Context<Self>,
    ) -> AnyElement {
        let cols = current_widths.read(cx).cols();
        let remaining_col_number = cols - 1;
        let fraction = if remaining_col_number > 0 {
            1. / remaining_col_number as f32
        } else {
            1. // only column with line numbers is present. Put 100%, but it will be overwritten anyways :D
        };
        let mut widths = vec![DefiniteLength::Fraction(fraction); cols];
        let line_number_width = self.calculate_row_identifier_column_width();
        widths[0] = DefiniteLength::Absolute(AbsoluteLength::Pixels(line_number_width.into()));

        let mut resize_behaviors = vec![TableResizeBehavior::Resizable; cols];
        resize_behaviors[0] = TableResizeBehavior::None;

        self.create_table_inner(
            self.engine.contents.rows.len(),
            widths,
            resize_behaviors,
            current_widths,
            cx,
        )
    }

    fn create_table_inner(
        &self,
        row_count: usize,
        widths: UncheckedTableRow<DefiniteLength>,
        resize_behaviors: UncheckedTableRow<TableResizeBehavior>,
        current_widths: &Entity<TableColumnWidths>,
        cx: &mut Context<Self>,
    ) -> AnyElement {
        let cols = widths.len();
        // Create headers array with interactive elements
        let mut headers = Vec::with_capacity(cols);

        headers.push(self.create_row_identifier_header(cx));

        // Add the actual CSV headers with sort buttons
        for i in 0..(cols - 1) {
            let header_text = self
                .engine
                .contents
                .headers
                .get(AnyColumn(i))
                .and_then(|h| h.display_value().cloned())
                .unwrap_or_else(|| format!("Col {}", i + 1).into());

            headers.push(self.create_header_element_with_sort_button(
                header_text,
                cx,
                AnyColumn::from(i),
            ));
        }

        Table::new(cols)
            .interactable(&self.table_interaction_state)
            .striped()
            .column_widths(widths)
            .resizable_columns(resize_behaviors, current_widths, cx)
            .header(headers)
            .disable_base_style()
            .map(|table| {
                let row_identifier_text_color = cx.theme().colors().editor_line_number;
                match self.settings.rendering_with {
                    RowRenderMechanism::VariableList => {
                        table.variable_row_height_list(row_count, self.list_state.clone(), {
                            cx.processor(move |this, display_row: usize, _window, cx| {
                                this.performance_metrics.rendered_indices.push(display_row);

                                let display_row = DisplayRow(display_row);
                                Self::render_single_table_row(
                                    this,
                                    cols,
                                    display_row,
                                    row_identifier_text_color,
                                    cx,
                                )
                                .unwrap_or_else(|| panic!("Expected to render a table row"))
                            })
                        })
                    }
                    RowRenderMechanism::UniformList => {
                        table.uniform_list("csv-table", row_count, {
                            cx.processor(move |this, range: Range<usize>, _window, cx| {
                                // Record all display indices in the range for performance metrics
                                this.performance_metrics
                                    .rendered_indices
                                    .extend(range.clone());

                                range
                                    .filter_map(|display_index| {
                                        Self::render_single_table_row(
                                            this,
                                            cols,
                                            DisplayRow(display_index),
                                            row_identifier_text_color,
                                            cx,
                                        )
                                    })
                                    .collect()
                            })
                        })
                    }
                }
            })
            .into_any_element()
    }

    /// Render a single table row
    ///
    /// Used both by UniformList and VariableRowHeightList
    fn render_single_table_row(
        this: &CsvPreviewView,
        cols: usize,
        display_row: DisplayRow,
        row_identifier_text_color: gpui::Hsla,
        cx: &Context<CsvPreviewView>,
    ) -> Option<UncheckedTableRow<AnyElement>> {
        // Get the actual row index from our sorted indices
        let data_row = this.engine.d2d_mapping().get_data_row(display_row)?;
        let row = this.engine.contents.get_row(data_row)?;

        let mut elements = Vec::with_capacity(cols);
        elements.push(this.create_row_identifier_cell(display_row, data_row, cx)?);

        // Remaining columns: actual CSV data
        for col in (0..this.engine.contents.number_of_cols).map(AnyColumn) {
            let table_cell = row.expect_get(col);

            // TODO: Introduce `<null>` cell type
            let cell_content = table_cell.display_value().cloned().unwrap_or_default();

            let display_cell_id = DisplayCellId::new(display_row, col);

            let cell = div().size_full().whitespace_nowrap().text_ellipsis().child(
                CsvPreviewView::create_selectable_cell(
                    display_cell_id,
                    cell_content,
                    this.settings.vertical_alignment,
                    this.settings.font_type,
                    cx,
                ),
            );

            elements.push(
                div()
                    .size_full()
                    .when(this.settings.show_debug_info, |parent| {
                        parent.child(div().text_color(row_identifier_text_color).child(
                            match table_cell {
                                TableCell::Real { position: pos, .. } => {
                                    let slv = pos.start.timestamp().value;
                                    let so = pos.start.offset;
                                    let elv = pos.end.timestamp().value;
                                    let eo = pos.end.offset;
                                    format!("Pos {so}(L{slv})-{eo}(L{elv})")
                                }
                                TableCell::Virtual => "Virtual cell".into(),
                            },
                        ))
                    })
                    .text_ui(cx)
                    .child(cell)
                    .into_any_element(),
            );
        }

        Some(elements)
    }
}
