//! Display line calculations for wrapped text.
//!
//! This module provides utilities for calculating display line positions
//! when text wraps across multiple terminal lines. Used by `gj`/`gk` commands
//! for display-line-aware cursor movement.
//!
//! # Design Philosophy
//!
//! Display line calculations are a **policy** decision - the kernel provides
//! buffer primitives, but how to interpret "display lines" based on terminal
//! width is up to the editor module.
//!
//! # Tab Handling
//!
//! Tabs are contextual - their display width depends on the current column:
//! - Tab at column 0 with tabstop=4 takes 4 columns (advances to column 4)
//! - Tab at column 2 with tabstop=4 takes 2 columns (advances to column 4)
//! - Formula: `tab_width = tabstop - (column % tabstop)`
//!
//! # CJK / Unicode Width
//!
//! East Asian characters (CJK) typically render as 2 columns ("fullwidth").
//! This module uses the `unicode-width` crate to determine character widths:
//! - ASCII and most Latin characters: 1 column
//! - CJK ideographs, fullwidth forms: 2 columns
//! - Combining marks, zero-width chars: 0 columns
//!
//! # Limitations
//!
//! Current implementation:
//! - Simple division-based wrapping (no word wrap)
//! - Terminal must support Unicode for CJK to display correctly

use unicode_width::UnicodeWidthChar;

/// Calculate number of display lines for a buffer line.
///
/// A display line is a single row on the terminal. Long lines wrap to
/// multiple display lines based on terminal width.
///
/// # Arguments
///
/// * `line` - The buffer line content
/// * `terminal_width` - Terminal width in columns
///
/// # Returns
///
/// Number of display lines (minimum 1, even for empty lines)
///
/// # Example
///
/// ```ignore
/// // "hello world" on 80-column terminal = 1 display line
/// assert_eq!(display_line_count("hello world", 80), 1);
///
/// // 100 chars on 80-column terminal = 2 display lines
/// assert_eq!(display_line_count(&"x".repeat(100), 80), 2);
/// ```
#[must_use]
pub fn display_line_count(line: &str, terminal_width: usize) -> usize {
    if terminal_width == 0 {
        return 1;
    }

    let display_width: usize = line.chars().map(char_display_width).sum();
    if display_width == 0 {
        return 1;
    }

    display_width.div_ceil(terminal_width)
}

/// Get display line position within a wrapped line.
///
/// Given a column position in a buffer line and terminal width, returns
/// which display line the column falls on and the column within that
/// display line.
///
/// # Arguments
///
/// * `column` - Column position in the buffer line (0-indexed)
/// * `terminal_width` - Terminal width in columns
///
/// # Returns
///
/// Tuple of (`display_line_index`, `column_within_display_line`)
///
/// # Example
///
/// ```ignore
/// // Column 5 on 80-column terminal = display line 0, column 5
/// assert_eq!(display_position(5, 80), (0, 5));
///
/// // Column 85 on 80-column terminal = display line 1, column 5
/// assert_eq!(display_position(85, 80), (1, 5));
/// ```
#[must_use]
pub const fn display_position(column: usize, terminal_width: usize) -> (usize, usize) {
    if terminal_width == 0 {
        return (0, column);
    }

    let display_line = column / terminal_width;
    let display_column = column % terminal_width;
    (display_line, display_column)
}

/// Convert display position back to buffer column.
///
/// Given a display line index and column within that display line,
/// calculates the corresponding buffer column.
///
/// # Arguments
///
/// * `display_line` - Display line index (0-indexed)
/// * `display_column` - Column within the display line (0-indexed)
/// * `terminal_width` - Terminal width in columns
///
/// # Returns
///
/// Buffer column position
///
/// # Example
///
/// ```ignore
/// // Display line 1, column 5 on 80-column terminal = buffer column 85
/// assert_eq!(buffer_column(1, 5, 80), 85);
/// ```
#[must_use]
pub const fn buffer_column(
    display_line: usize,
    display_column: usize,
    terminal_width: usize,
) -> usize {
    display_line * terminal_width + display_column
}

// ============================================================================
// Tab-aware display calculations
// ============================================================================

/// Calculate display width of a line accounting for tabs.
///
/// Each tab character expands to fill until the next tab stop. The width
/// depends on the current display column position.
///
/// # Arguments
///
/// * `line` - The buffer line content
/// * `tabstop` - Tab stop width (typically 4 or 8)
///
/// # Returns
///
/// Total display width in columns
///
/// # Example
///
/// ```ignore
/// // "a\tb" with tabstop=4: 'a' (1) + tab (3 to reach col 4) + 'b' (1) = 5
/// assert_eq!(display_width_with_tabs("a\tb", 4), 5);
/// ```
#[must_use]
pub fn display_width_with_tabs(line: &str, tabstop: usize) -> usize {
    let tabstop = tabstop.max(1); // Prevent division by zero
    let mut display_col = 0;

    for c in line.chars() {
        if c == '\t' {
            // Advance to next tab stop
            let tab_width = tabstop - (display_col % tabstop);
            display_col += tab_width;
        } else {
            display_col += 1;
        }
    }

    display_col
}

/// Calculate number of display lines for a buffer line, accounting for tabs.
///
/// # Arguments
///
/// * `line` - The buffer line content
/// * `terminal_width` - Terminal width in columns
/// * `tabstop` - Tab stop width
///
/// # Returns
///
/// Number of display lines (minimum 1)
#[must_use]
pub fn display_line_count_with_tabs(line: &str, terminal_width: usize, tabstop: usize) -> usize {
    if terminal_width == 0 {
        return 1;
    }

    let width = display_width_with_tabs(line, tabstop);
    if width == 0 {
        return 1;
    }

    width.div_ceil(terminal_width)
}

/// Get display position for a buffer column, accounting for tabs.
///
/// Calculates which display line and column a buffer column falls on,
/// properly handling tab expansion.
///
/// # Arguments
///
/// * `line` - The buffer line content
/// * `buffer_col` - Column position in the buffer (character index, 0-indexed)
/// * `terminal_width` - Terminal width in columns
/// * `tabstop` - Tab stop width
///
/// # Returns
///
/// Tuple of (`display_line_index`, `column_within_display_line`)
#[must_use]
pub fn display_position_with_tabs(
    line: &str,
    buffer_col: usize,
    terminal_width: usize,
    tabstop: usize,
) -> (usize, usize) {
    let tabstop = tabstop.max(1);
    if terminal_width == 0 {
        // Fallback: calculate display column without wrapping
        let display_col = display_col_from_buffer_col(line, buffer_col, tabstop);
        return (0, display_col);
    }

    let display_col = display_col_from_buffer_col(line, buffer_col, tabstop);
    let display_line = display_col / terminal_width;
    let col_in_line = display_col % terminal_width;

    (display_line, col_in_line)
}

/// Convert buffer column to display column, accounting for tabs.
///
/// # Arguments
///
/// * `line` - The buffer line content
/// * `buffer_col` - Column position in buffer (character index)
/// * `tabstop` - Tab stop width
///
/// # Returns
///
/// Display column position
#[must_use]
pub fn display_col_from_buffer_col(line: &str, buffer_col: usize, tabstop: usize) -> usize {
    let tabstop = tabstop.max(1);
    let mut display_col = 0;

    for (i, c) in line.chars().enumerate() {
        if i >= buffer_col {
            break;
        }
        if c == '\t' {
            let tab_width = tabstop - (display_col % tabstop);
            display_col += tab_width;
        } else {
            display_col += 1;
        }
    }

    display_col
}

/// Convert display column to buffer column, accounting for tabs.
///
/// Given a target display column, finds the buffer column (character index)
/// that corresponds to that display position.
///
/// # Arguments
///
/// * `line` - The buffer line content
/// * `target_display_col` - Target display column
/// * `tabstop` - Tab stop width
///
/// # Returns
///
/// Buffer column (character index)
#[must_use]
pub fn buffer_col_from_display_col(line: &str, target_display_col: usize, tabstop: usize) -> usize {
    let tabstop = tabstop.max(1);
    let mut display_col = 0;
    let mut buffer_col = 0;

    for c in line.chars() {
        if display_col >= target_display_col {
            break;
        }

        let char_width = if c == '\t' {
            tabstop - (display_col % tabstop)
        } else {
            1
        };

        // Check if we'd overshoot
        if display_col + char_width > target_display_col {
            // We're inside a tab or at the target
            break;
        }

        display_col += char_width;
        buffer_col += 1;
    }

    buffer_col
}

// ============================================================================
// Unicode-aware display calculations (tabs + CJK)
// ============================================================================

/// Get the display width of a single character.
///
/// Returns the number of terminal columns the character occupies:
/// - Tab: contextual (use `char_display_width_at` instead)
/// - Most characters: 1 column
/// - CJK / fullwidth: 2 columns
/// - Combining marks, zero-width: 0 columns
#[must_use]
pub fn char_display_width(c: char) -> usize {
    if c == '\t' {
        // Tab width is contextual - caller should use char_display_width_at
        1
    } else {
        c.width().unwrap_or(0)
    }
}

/// Get the display width of a character at a specific column.
///
/// Handles both tabs (contextual width) and Unicode characters (intrinsic width).
#[must_use]
pub fn char_display_width_at(c: char, display_col: usize, tabstop: usize) -> usize {
    let tabstop = tabstop.max(1);
    if c == '\t' {
        tabstop - (display_col % tabstop)
    } else {
        c.width().unwrap_or(0)
    }
}

/// Calculate display width of a line accounting for tabs and Unicode width.
///
/// This is the most comprehensive width function - it handles:
/// - Tab expansion based on column position
/// - CJK double-width characters
/// - Combining marks (width 0)
///
/// # Arguments
///
/// * `line` - The buffer line content
/// * `tabstop` - Tab stop width (typically 4 or 8)
///
/// # Returns
///
/// Total display width in columns
#[must_use]
pub fn display_width_unicode(line: &str, tabstop: usize) -> usize {
    let tabstop = tabstop.max(1);
    let mut display_col = 0;

    for c in line.chars() {
        display_col += char_display_width_at(c, display_col, tabstop);
    }

    display_col
}

/// Calculate number of display lines for a buffer line, with full Unicode support.
///
/// # Arguments
///
/// * `line` - The buffer line content
/// * `terminal_width` - Terminal width in columns
/// * `tabstop` - Tab stop width
///
/// # Returns
///
/// Number of display lines (minimum 1)
#[must_use]
pub fn display_line_count_unicode(line: &str, terminal_width: usize, tabstop: usize) -> usize {
    if terminal_width == 0 {
        return 1;
    }

    let width = display_width_unicode(line, tabstop);
    if width == 0 {
        return 1;
    }

    width.div_ceil(terminal_width)
}

/// Convert buffer column to display column, with full Unicode support.
///
/// # Arguments
///
/// * `line` - The buffer line content
/// * `buffer_col` - Column position in buffer (character index)
/// * `tabstop` - Tab stop width
///
/// # Returns
///
/// Display column position
#[must_use]
pub fn display_col_from_buffer_col_unicode(line: &str, buffer_col: usize, tabstop: usize) -> usize {
    let tabstop = tabstop.max(1);
    let mut display_col = 0;

    for (i, c) in line.chars().enumerate() {
        if i >= buffer_col {
            break;
        }
        display_col += char_display_width_at(c, display_col, tabstop);
    }

    display_col
}

/// Convert display column to buffer column, with full Unicode support.
///
/// Given a target display column, finds the buffer column (character index)
/// that corresponds to that display position. If the target falls inside
/// a wide character (like CJK), returns the buffer column of that character.
///
/// # Arguments
///
/// * `line` - The buffer line content
/// * `target_display_col` - Target display column
/// * `tabstop` - Tab stop width
///
/// # Returns
///
/// Buffer column (character index)
#[must_use]
pub fn buffer_col_from_display_col_unicode(
    line: &str,
    target_display_col: usize,
    tabstop: usize,
) -> usize {
    let tabstop = tabstop.max(1);
    let mut display_col = 0;
    let mut buffer_col = 0;

    for c in line.chars() {
        if display_col >= target_display_col {
            break;
        }

        let char_width = char_display_width_at(c, display_col, tabstop);

        // Check if we'd overshoot (landing inside a wide char)
        if display_col + char_width > target_display_col {
            // We're inside a wide character - stay at this buffer column
            break;
        }

        display_col += char_width;
        buffer_col += 1;
    }

    buffer_col
}

/// Get display position for a buffer column, with full Unicode support.
///
/// Calculates which display line and column a buffer column falls on,
/// properly handling tab expansion and CJK characters.
///
/// # Arguments
///
/// * `line` - The buffer line content
/// * `buffer_col` - Column position in the buffer (character index, 0-indexed)
/// * `terminal_width` - Terminal width in columns
/// * `tabstop` - Tab stop width
///
/// # Returns
///
/// Tuple of (`display_line_index`, `column_within_display_line`)
#[must_use]
pub fn display_position_unicode(
    line: &str,
    buffer_col: usize,
    terminal_width: usize,
    tabstop: usize,
) -> (usize, usize) {
    let tabstop = tabstop.max(1);
    if terminal_width == 0 {
        let display_col = display_col_from_buffer_col_unicode(line, buffer_col, tabstop);
        return (0, display_col);
    }

    let display_col = display_col_from_buffer_col_unicode(line, buffer_col, tabstop);
    let display_line = display_col / terminal_width;
    let col_in_line = display_col % terminal_width;

    (display_line, col_in_line)
}
