// yt - A fully featured command line YouTube client
//
// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
// Copyright (C) 2025 uutils developers
// SPDX-License-Identifier: MIT
//
// This file is part of Yt.
//
// You should have received a copy of the License along with this program.
// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.

// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

use std::fmt::Write;

use linebreak::break_lines;
use parasplit::ParagraphStream;

mod linebreak;
mod parasplit;

#[derive(Debug)]
#[allow(clippy::struct_excessive_bools)]
pub struct FmtOptions {
    /// First and second line of paragraph
    /// may have different indentations, in which
    /// case the first line's indentation is preserved,
    /// and each subsequent line's indentation matches the second line.
    pub crown_margin: bool,

    /// Like the [`crown_margin`], except that the first and second line of a paragraph *must*
    /// have different indentation or they are treated as separate paragraphs.
    pub tagged_paragraph: bool,

    /// Attempt to detect and preserve mail headers in the input.
    /// Be careful when combining this with [`prefix`].
    pub mail: bool,

    /// Split lines only, do not reflow.
    pub split_only: bool,

    /// Insert exactly one space between words, and two between sentences.
    /// Sentence breaks in the input are detected as [?!.] followed by two spaces or a newline;
    /// other punctuation is not interpreted as a sentence break.
    pub uniform: bool,

    /// Reformat only lines beginning with PREFIX, reattaching PREFIX to reformatted lines.
    /// Unless [`exact_prefix`] is specified, leading whitespace will be ignored when matching PREFIX.
    pub prefix: Option<String>,

    /// Do not reformat lines beginning with ``ANTI_PREFIX``.
    /// Unless [`exact_anti_prefix`] is specified, leading whitespace will be ignored when matching ``ANTI_PREFIX``.
    pub anti_prefix: Option<String>,

    /// [`prefix`] must match at the beginning of the line with no preceding whitespace.
    pub exact_prefix: bool,

    /// [`anti_prefix`] must match at the beginning of the line with no preceding whitespace.
    pub exact_anti_prefix: bool,

    /// Fill output lines up to a maximum of WIDTH columns, default 75.
    pub width: usize,

    /// Goal width, default of 93% of WIDTH.
    /// Must be less than or equal to WIDTH.
    pub goal: usize,

    /// Break lines more quickly at the expense of a potentially more ragged appearance.
    pub quick: bool,

    /// Treat tabs as TABWIDTH spaces for determining line length, default 8.
    /// Note that this is used only for calculating line lengths; tabs are preserved in the output.
    pub tabwidth: usize,
}

impl FmtOptions {
    #[must_use]
    #[allow(clippy::cast_sign_loss)]
    #[allow(clippy::cast_possible_truncation)]
    #[allow(clippy::cast_precision_loss)]
    pub fn new(width: Option<usize>, goal: Option<usize>, tabwidth: Option<usize>) -> Self {
        // by default, goal is 93% of width
        const DEFAULT_GOAL_TO_WIDTH_RATIO: f64 = 0.93;
        const DEFAULT_WIDTH: usize = 75;

        FmtOptions {
            crown_margin: false,
            tagged_paragraph: false,
            mail: false,
            split_only: false,
            uniform: false,
            prefix: None,
            anti_prefix: None,
            exact_prefix: false,
            exact_anti_prefix: false,
            width: width.unwrap_or(DEFAULT_WIDTH),
            goal: goal.unwrap_or(
                ((width.unwrap_or(DEFAULT_WIDTH) as f64) * DEFAULT_GOAL_TO_WIDTH_RATIO).floor()
                    as usize,
            ),
            quick: false,
            tabwidth: tabwidth.unwrap_or(8),
        }
    }
}

/// Process text and format it according to the provided options.
///
/// # Arguments
///
/// * `text` - The text to process.
/// * `fmt_opts` - A reference to a [`FmtOptions`] structure containing the formatting options.
///
/// # Returns
///
/// The formatted [`String`].
#[must_use]
pub fn process_text(text: &str, fmt_opts: &FmtOptions) -> String {
    let mut output = String::new();

    let p_stream = ParagraphStream::new(fmt_opts, text);
    for para_result in p_stream {
        match para_result {
            Err(s) => {
                output.push_str(&s);
                output.push('\n');
            }
            Ok(para) => write!(output, "{}", break_lines(&para, fmt_opts))
                .expect("This is in-memory. It should not fail"),
        }
    }

    output
}