// src/textarea/widget.rs
#[cfg(feature = "gui")]
use ratatui::{
    buffer::Buffer,
    layout::{Alignment, Rect},
    style::Style,
    text::{Line, Span},
    widgets::{
        Block, BorderType, Borders, Paragraph, StatefulWidget, Widget,
    },
};

#[cfg(feature = "gui")]
use crate::data_provider::DataProvider;

#[cfg(feature = "gui")]
use crate::textarea::state::{
    compute_h_scroll_with_padding,
    count_wrapped_rows_indented,
    TextAreaState,
    TextOverflowMode,
};

#[cfg(feature = "gui")]
use unicode_width::UnicodeWidthChar;

#[cfg(feature = "gui")]
#[derive(Debug, Clone)]
pub struct TextArea<'a> {
    pub(crate) block: Option<Block<'a>>,
    pub(crate) style: Style,
    pub(crate) border_type: BorderType,
}

#[cfg(feature = "gui")]
impl<'a> Default for TextArea<'a> {
    fn default() -> Self {
        Self {
            block: Some(
                Block::default()
                    .borders(Borders::ALL)
                    .border_type(BorderType::Rounded),
            ),
            style: Style::default(),
            border_type: BorderType::Rounded,
        }
    }
}

#[cfg(feature = "gui")]
impl<'a> TextArea<'a> {
    pub fn block(mut self, block: Block<'a>) -> Self {
        self.block = Some(block);
        self
    }

    pub fn style(mut self, style: Style) -> Self {
        self.style = style;
        self
    }

    pub fn border_type(mut self, ty: BorderType) -> Self {
        self.border_type = ty;
        if let Some(b) = &mut self.block {
            *b = b.clone().border_type(ty);
        }
        self
    }
}

#[cfg(feature = "gui")]
fn display_width(s: &str) -> u16 {
    s.chars()
        .map(|c| UnicodeWidthChar::width(c).unwrap_or(0) as u16)
        .sum()
}

#[cfg(feature = "gui")]
fn display_cols_up_to(s: &str, char_count: usize) -> u16 {
    let mut cols: u16 = 0;
    for (i, ch) in s.chars().enumerate() {
        if i >= char_count {
            break;
        }
        cols = cols.saturating_add(UnicodeWidthChar::width(ch).unwrap_or(0) as u16);
    }
    cols
}

#[cfg(feature = "gui")]
fn slice_by_display_cols(s: &str, start_cols: u16, max_cols: u16) -> String {
    if max_cols == 0 {
        return String::new();
    }

    let mut current_cols: u16 = 0;
    let mut output = String::new();
    let mut taken: u16 = 0;
    let mut started = false;

    for ch in s.chars() {
        let w = UnicodeWidthChar::width(ch).unwrap_or(0) as u16;

        if !started {
            if current_cols.saturating_add(w) <= start_cols {
                current_cols = current_cols.saturating_add(w);
                continue;
            } else {
                started = true;
            }
        }

        if taken.saturating_add(w) > max_cols {
            break;
        }

        output.push(ch);
        taken = taken.saturating_add(w);
        current_cols = current_cols.saturating_add(w);
    }

    output
}

#[cfg(feature = "gui")]
fn clip_window_with_indicator_padded(
    text: &str,
    view_width: u16,
    indicator: char,
    start_cols: u16,
) -> Line<'static> {
    if view_width == 0 {
        return Line::from("");
    }

    let total = display_width(text);

    // Left indicator if we scrolled
    let show_left = start_cols > 0;
    let left_cols: u16 = if show_left { 1 } else { 0 };

    // Capacity for text if we also need a right indicator
    let cap_with_right = view_width.saturating_sub(left_cols + 1);

    // Do we still have content beyond this window?
    let remaining = total.saturating_sub(start_cols);
    let show_right = remaining > cap_with_right;

    // Final capacity for visible text
    let max_visible = if show_right {
        cap_with_right
    } else {
        view_width.saturating_sub(left_cols)
    };

    let visible = slice_by_display_cols(text, start_cols, max_visible);

    let mut spans: Vec<Span> = Vec::new();
    if show_left {
        spans.push(Span::raw(indicator.to_string()));
    }

    // Visible text
    spans.push(Span::raw(visible.clone()));

    // Place $ flush-right
    if show_right {
        let used_cols = left_cols + display_width(&visible);
        let right_pos = view_width.saturating_sub(1);
        let filler = right_pos.saturating_sub(used_cols);
        if filler > 0 {
            spans.push(Span::raw(" ".repeat(filler as usize)));
        }
        spans.push(Span::raw(indicator.to_string()));
    }

    Line::from(spans)
}

#[cfg(feature = "gui")]
fn wrap_segments_with_indent(
    s: &str,
    width: u16,
    indent: u16,
) -> Vec<String> {
    let mut segments: Vec<String> = Vec::new();
    if width == 0 {
        segments.push(String::new());
        return segments;
    }

    let indent = indent.min(width.saturating_sub(1));
    let cont_cap = width.saturating_sub(indent);
    let indent_str = " ".repeat(indent as usize);

    let mut buf = String::new();
    let mut used: u16 = 0;
    let mut first = true;

    for ch in s.chars() {
        let w = UnicodeWidthChar::width(ch).unwrap_or(0) as u16;
        let cap = if first { width } else { cont_cap };

        // Early-wrap: wrap before filling the last cell (and avoid empty segment)
        if used > 0 && used.saturating_add(w) >= cap {
            segments.push(buf);
            buf = String::new();
            used = 0;
            first = false;
            if indent > 0 {
                buf.push_str(&indent_str);
                used = indent;
            }
        }

        buf.push(ch);
        used = used.saturating_add(w);
    }

    segments.push(buf);
    segments
}

// Map visual row offset to (logical line, intra segment)
#[cfg(feature = "gui")]
fn resolve_start_line_and_intra_indented(
    state: &TextAreaState,
    inner: Rect,
) -> (usize, u16) {
    let provider = state.editor.data_provider();
    let total = provider.line_count();

    if total == 0 {
        return (0, 0);
    }

    let wrap = matches!(state.overflow_mode, TextOverflowMode::Wrap);
    let width = inner.width;
    let target_vis = state.scroll_y;

    if !wrap {
        let start = (target_vis as usize).min(total);
        return (start, 0);
    }

    let indent = state.wrap_indent_cols;

    let mut acc: u16 = 0;
    for i in 0..total {
        let s = provider.field_value(i);
        let rows = count_wrapped_rows_indented(s, width, indent);
        if acc.saturating_add(rows) > target_vis {
            let intra = target_vis.saturating_sub(acc);
            return (i, intra);
        }
        acc = acc.saturating_add(rows);
    }

    (total.saturating_sub(1), 0)
}

#[cfg(feature = "gui")]
impl<'a> StatefulWidget for TextArea<'a> {
    type State = TextAreaState;

    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
        state.ensure_visible(area, self.block.as_ref());

        let inner = if let Some(b) = &self.block {
            b.clone().render(area, buf);
            b.inner(area)
        } else {
            area
        };

        let edited_now = state.take_edited_flag();

        let wrap_mode = matches!(state.overflow_mode, TextOverflowMode::Wrap);
        let provider = state.editor.data_provider();
        let total = provider.line_count();

        let (start, intra) = resolve_start_line_and_intra_indented(state, inner);

        let mut display_lines: Vec<Line> = Vec::new();

        if total == 0 || start >= total {
            if let Some(ph) = &state.placeholder {
                display_lines.push(Line::from(Span::raw(ph.clone())));
            }
        } else if wrap_mode {
            // manual pre-wrap path (unchanged)
            let mut rows_left = inner.height;
            let indent = state.wrap_indent_cols;
            let mut i = start;
            while i < total && rows_left > 0 {
                let s = provider.field_value(i);
                let segments = wrap_segments_with_indent(s, inner.width, indent);
                let skip = if i == start { intra as usize } else { 0 };
                for seg in segments.into_iter().skip(skip) {
                    display_lines.push(Line::from(Span::raw(seg)));
                    rows_left = rows_left.saturating_sub(1);
                    if rows_left == 0 {
                        break;
                    }
                }
                i += 1;
            }
        } else {
            // Indicator mode: full inner width; RIGHT_PAD only affects cursor clamp and h-scroll
            let end = (start.saturating_add(inner.height as usize)).min(total);

            for i in start..end {
                let s = provider.field_value(i);
                match state.overflow_mode {
                    TextOverflowMode::Wrap => unreachable!(),
                    TextOverflowMode::Indicator { ch } => {
                        let fits = display_width(s) <= inner.width;

                        let start_cols = if i == state.current_field() {
                            let col_idx = state.display_cursor_position();
                            let cursor_cols = display_cols_up_to(s, col_idx);
                            let (target_h, _left_cols) =
                                compute_h_scroll_with_padding(cursor_cols, inner.width);

                            if fits {
                                if edited_now { target_h } else { 0 }
                            } else {
                                target_h.max(state.h_scroll)
                            }
                        } else {
                            0
                        };

                        display_lines.push(clip_window_with_indicator_padded(
                            s,
                            inner.width,
                            ch,
                            start_cols,
                        ));
                    }
                }
            }
        }

        let p = Paragraph::new(display_lines)
            .alignment(Alignment::Left)
            .style(self.style);

        // No Paragraph::wrap/scroll in wrap mode — we pre-wrap.
        p.render(inner, buf);
    }
}
