use std::{fmt, str::FromStr};

use oxc_formatter_core::{IndentStyle, IndentWidth, LineEnding, LineWidth};

use crate::{
    formatter::{
        Buffer, Format, JsFormatContext, JsFormatter,
        prelude::{if_group_breaks, token},
        printer::PrinterOptions,
    },
    ir_transform::options::SortImportsOptions,
    write,
};

#[derive(Debug, Default, Clone)]
pub struct JsFormatOptions {
    /// The indent style.
    pub indent_style: IndentStyle,

    /// The indent width.
    pub indent_width: IndentWidth,

    /// The type of line ending.
    pub line_ending: LineEnding,

    /// What's the max width of a line. Defaults to 100.
    pub line_width: LineWidth,

    /// The style for quotes. Defaults to double.
    pub quote_style: QuoteStyle,

    /// The style for JSX quotes. Defaults to double.
    pub jsx_quote_style: QuoteStyle,

    /// When properties in objects are quoted. Defaults to as-needed.
    pub quote_properties: QuoteProperties,

    /// Print trailing commas wherever possible in multi-line comma-separated syntactic structures. Defaults to "all".
    pub trailing_commas: TrailingCommas,

    /// Whether the formatter prints semicolons for all statements, class members, and type members or only when necessary because of [ASI](https://tc39.es/ecma262/multipage/ecmascript-language-lexical-grammar.html#sec-automatic-semicolon-insertion).
    pub semicolons: Semicolons,

    /// Whether to add non-necessary parentheses to arrow functions. Defaults to "always".
    pub arrow_parentheses: ArrowParentheses,

    /// Whether to insert spaces around brackets in object literals. Defaults to true.
    pub bracket_spacing: BracketSpacing,

    /// Whether to hug the closing bracket of multiline HTML/JSX tags to the end of the last line, rather than being alone on the following line. Defaults to false.
    pub bracket_same_line: BracketSameLine,

    /// Attribute position style. By default auto.
    pub attribute_position: AttributePosition,

    /// Whether to expand object and array literals to multiple lines. Defaults to "auto".
    pub expand: Expand,

    /// Controls the position of operators in binary expressions. [**NOT SUPPORTED YET**]
    ///
    /// Accepted values are:
    /// - `"start"`: Places the operator at the beginning of the next line.
    /// - `"end"`: Places the operator at the end of the current line (default).
    pub experimental_operator_position: OperatorPosition,

    /// Try prettier's new ternary formatting before it becomes the default behavior. [**NOT SUPPORTED YET**]
    ///
    /// Valid options:
    /// - `true` - Use curious ternaries, with the question mark after the condition.
    /// - `false` - Retain the default behavior of ternaries; keep question marks on the same line as the consequent.
    pub experimental_ternaries: bool,

    /// Whether HTML whitespace sensitivity is set to "ignore".
    /// When true, HTML-in-JS templates always use hard line breaks for wrapping.
    pub html_whitespace_sensitivity_ignore: bool,

    /// Enable formatting for embedded languages (e.g., CSS, SQL, GraphQL) within template literals. Defaults to "auto".
    pub embedded_language_formatting: EmbeddedLanguageFormatting,

    /// Sort import statements. By default disabled.
    pub sort_imports: Option<SortImportsOptions>,

    /// Enable Tailwind CSS class sorting in JSX class/className attributes.
    /// When enabled, class strings will be collected and passed to a callback for sorting.
    /// Defaults to None (disabled).
    pub sort_tailwindcss: Option<SortTailwindcssOptions>,

    /// Enable JSDoc comment formatting.
    /// When enabled, JSDoc comments will be normalized and reformatted.
    /// Defaults to None (disabled).
    pub jsdoc: Option<JsdocOptions>,
}

/// How to format JSDoc comment blocks: single-line, multi-line, or preserve original.
///
/// Maps to upstream's `jsdocCommentLineStrategy`.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum CommentLineStrategy {
    /// Convert to single-line `/** content */` when possible.
    #[default]
    SingleLine,
    /// Always use multi-line format.
    Multiline,
    /// Preserve original formatting (single-line stays single-line, multi-line stays multi-line).
    Keep,
}

/// Strategy for wrapping description lines at print width.
///
/// Maps to upstream's `jsdocLineWrappingStyle`.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum LineWrappingStyle {
    /// Always re-wrap text to fit within print width.
    #[default]
    Greedy,
    /// Preserve original line breaks if all lines fit within print width.
    Balance,
}

/// Options for JSDoc comment formatting.
#[derive(Debug, Clone)]
pub struct JsdocOptions {
    /// Capitalize the first letter of tag descriptions. Default: true.
    /// Maps to upstream's `jsdocCapitalizeDescription`.
    pub capitalize_descriptions: bool,
    /// How to format comment blocks. Default: SingleLine.
    /// Maps to upstream's `jsdocCommentLineStrategy`.
    pub comment_line_strategy: CommentLineStrategy,
    /// Add blank lines between different tag groups (e.g. between @param and @returns). Default: false.
    /// Maps to upstream's `jsdocSeparateTagGroups`.
    pub separate_tag_groups: bool,
    /// Add a blank line between the last @param and @returns. Default: false.
    /// Maps to upstream's `jsdocSeparateReturnsFromParam`.
    pub separate_returns_from_param: bool,
    /// Add spaces inside JSDoc type braces: `{string}` → `{ string }`. Default: false.
    /// Maps to upstream's `jsdocBracketSpacing`.
    pub bracket_spacing: bool,
    /// Add a trailing dot to the end of descriptions. Default: false.
    /// Maps to upstream's `jsdocDescriptionWithDot`.
    pub description_with_dot: bool,
    /// Append default values to @param descriptions (e.g. "Default is \`value\`"). Default: true.
    /// Maps to upstream's `jsdocAddDefaultToDescription`.
    pub add_default_to_description: bool,
    /// Use fenced code blocks (```) instead of 4-space indentation for code without a language tag. Default: false.
    /// Maps to upstream's `jsdocPreferCodeFences`.
    pub prefer_code_fences: bool,
    /// Strategy for wrapping description lines. Default: Greedy.
    /// Maps to upstream's `jsdocLineWrappingStyle`.
    pub line_wrapping_style: LineWrappingStyle,
    /// Emit `@description` tag instead of inline description. Default: false.
    /// Maps to upstream's `jsdocDescriptionTag`.
    pub description_tag: bool,
    /// Preserve indentation in unparsable @example code. Default: false.
    /// Maps to upstream's `jsdocKeepUnParseAbleExampleIndent`.
    pub keep_unparsable_example_indent: bool,
}

impl Default for JsdocOptions {
    fn default() -> Self {
        Self {
            capitalize_descriptions: true,
            comment_line_strategy: CommentLineStrategy::default(),
            separate_tag_groups: false,
            separate_returns_from_param: false,
            bracket_spacing: false,
            description_with_dot: false,
            add_default_to_description: true,
            prefer_code_fences: false,
            line_wrapping_style: LineWrappingStyle::default(),
            description_tag: false,
            keep_unparsable_example_indent: false,
        }
    }
}

/// Tailwind class sorting options that affect Rust-side collection.
///
/// The actual class ordering is performed by the host (Oxfmt)-supplied JS sorter
/// (`prettier-plugin-tailwindcss/sorter`), so the JS-only payload is NOT carried here.
///
/// This struct only carries fields the Rust formatter needs while
/// - detecting classes (`functions`, `attributes`)
/// - or preserving original whitespace (`preserve_whitespace`)
#[derive(Debug, Default, Clone)]
pub struct SortTailwindcssOptions {
    /// List of custom function names that contain Tailwind CSS classes.
    ///
    /// Example: `["clsx", "cn", "cva", "tw"]`
    /// Default: `[]`
    pub functions: Vec<String>,
    /// List of additional attributes to sort (beyond `class` and `className`).
    ///
    /// Example: `["myClassProp", ":class"]`
    /// Default: `[]`
    pub attributes: Vec<String>,
    /// Preserve whitespace around classes.
    ///
    /// Default: `false`
    pub preserve_whitespace: bool,
}

impl JsFormatOptions {
    pub fn new() -> Self {
        Self {
            indent_style: IndentStyle::default(),
            indent_width: IndentWidth::default(),
            line_ending: LineEnding::default(),
            line_width: LineWidth::default(),
            quote_style: QuoteStyle::default(),
            jsx_quote_style: QuoteStyle::default(),
            quote_properties: QuoteProperties::default(),
            trailing_commas: TrailingCommas::default(),
            semicolons: Semicolons::default(),
            arrow_parentheses: ArrowParentheses::default(),
            bracket_spacing: BracketSpacing::default(),
            bracket_same_line: BracketSameLine::default(),
            attribute_position: AttributePosition::default(),
            expand: Expand::default(),
            experimental_operator_position: OperatorPosition::default(),
            experimental_ternaries: false,
            html_whitespace_sensitivity_ignore: false,
            embedded_language_formatting: EmbeddedLanguageFormatting::default(),
            sort_imports: None,
            sort_tailwindcss: None,
            jsdoc: None,
        }
    }
}

impl oxc_formatter_core::FormatOptions for JsFormatOptions {
    fn indent_style(&self) -> IndentStyle {
        self.indent_style
    }

    fn indent_width(&self) -> IndentWidth {
        self.indent_width
    }

    fn line_width(&self) -> LineWidth {
        self.line_width
    }

    fn line_ending(&self) -> LineEnding {
        self.line_ending
    }

    fn as_print_options(&self) -> PrinterOptions {
        PrinterOptions::default()
            .with_indent_style(self.indent_style)
            .with_indent_width(self.indent_width)
            .with_print_width(self.line_width.into())
            .with_line_ending(self.line_ending)
    }
}

impl fmt::Display for JsFormatOptions {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        writeln!(f, "Indent style: {}", self.indent_style)?;
        writeln!(f, "Indent width: {}", self.indent_width.value())?;
        writeln!(f, "Line ending: {}", self.line_ending)?;
        writeln!(f, "Line width: {}", self.line_width.value())?;
        writeln!(f, "Quote style: {} Quotes", self.quote_style)?;
        writeln!(f, "JSX quote style: {} Quotes", self.jsx_quote_style)?;
        writeln!(f, "Quote properties: {}", self.quote_properties)?;
        writeln!(f, "Trailing commas: {}", self.trailing_commas)?;
        writeln!(f, "Semicolons: {}", self.semicolons)?;
        writeln!(f, "Arrow parentheses: {}", self.arrow_parentheses)?;
        writeln!(f, "Bracket spacing: {}", self.bracket_spacing.value())?;
        writeln!(f, "Bracket same line: {}", self.bracket_same_line.value())?;
        writeln!(f, "Attribute Position: {}", self.attribute_position)?;
        writeln!(f, "Expand lists: {}", self.expand)?;
        writeln!(f, "Experimental operator position: {}", self.experimental_operator_position)?;
        writeln!(f, "Embedded language formatting: {}", self.embedded_language_formatting)?;
        writeln!(f, "Sort imports: {:?}", self.sort_imports)?;
        writeln!(f, "Sort tailwindcss: {:?}", self.sort_tailwindcss)?;
        writeln!(f, "JSDoc: {:?}", self.jsdoc)
    }
}

#[derive(Eq, PartialEq, Debug, Copy, Clone, Hash)]
pub struct TabWidth(u8);

impl TabWidth {
    /// Returns the numeric value for this [TabWidth]
    pub fn value(self) -> u8 {
        self.0
    }
}

impl From<u8> for TabWidth {
    fn from(value: u8) -> Self {
        TabWidth(value)
    }
}

impl From<TabWidth> for u8 {
    fn from(width: TabWidth) -> Self {
        width.0
    }
}

/// Which ASCII quote character delimits a string literal — JS/TS's public quote option.
///
/// Owned by this crate: each language formatter owns its own quote-style vocabulary.
/// `oxc_formatter_core` deals only in raw quote bytes (see `spec::string::normalize_string`),
/// so this is intentionally *not* lifted into core.
#[derive(Debug, Default, Clone, Copy, Eq, Hash, PartialEq)]
pub enum QuoteStyle {
    #[default]
    Double,
    Single,
}

impl QuoteStyle {
    pub fn from_byte(byte: u8) -> Option<Self> {
        match byte {
            b'"' => Some(Self::Double),
            b'\'' => Some(Self::Single),
            _ => None,
        }
    }

    pub const fn as_char(self) -> char {
        match self {
            Self::Double => '"',
            Self::Single => '\'',
        }
    }

    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Double => "\"",
            Self::Single => "'",
        }
    }

    pub const fn as_byte(self) -> u8 {
        self.as_char() as u8
    }

    /// Returns the opposite quote.
    #[must_use]
    pub const fn other(self) -> Self {
        match self {
            Self::Double => Self::Single,
            Self::Single => Self::Double,
        }
    }

    pub const fn is_double(self) -> bool {
        matches!(self, Self::Double)
    }
}

impl fmt::Display for QuoteStyle {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(match self {
            Self::Double => "Double",
            Self::Single => "Single",
        })
    }
}

#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub enum QuoteProperties {
    /// Only add quotes around object properties where required.
    #[default]
    AsNeeded,
    /// Respect the input use of quotes in object properties.
    Preserve,
    /// If at least one property in an object requires quotes, quote all properties.
    Consistent,
}

impl QuoteProperties {
    pub const fn is_consistent(self) -> bool {
        matches!(self, Self::Consistent)
    }
}

impl FromStr for QuoteProperties {
    type Err = &'static str;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "as-needed" => Ok(Self::AsNeeded),
            "preserve" => Ok(Self::Preserve),
            "consistent" => Ok(Self::Consistent),
            _ => Err("Value not supported for QuoteProperties"),
        }
    }
}

impl fmt::Display for QuoteProperties {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = match self {
            QuoteProperties::AsNeeded => "As needed",
            QuoteProperties::Preserve => "Preserve",
            QuoteProperties::Consistent => "Consistent",
        };
        f.write_str(s)
    }
}

/// Print trailing commas wherever possible in multi-line comma-separated syntactic structures.
/// Mirrors Prettier's `trailingComma` option.
///
/// The `All` vs `Es5` distinction only matters for languages with constructs beyond ES5 (e.g. function, type parameters).
#[derive(Clone, Copy, Default, Debug, Eq, Hash, PartialEq)]
pub enum TrailingCommas {
    /// Trailing commas wherever possible (including function parameters and calls).
    #[default]
    All,
    /// Trailing commas where valid in ES5 (objects, arrays, etc.). No trailing commas in type parameters in TypeScript.
    Es5,
    /// No trailing commas.
    None,
}

impl TrailingCommas {
    pub const fn is_es5(self) -> bool {
        matches!(self, TrailingCommas::Es5)
    }

    pub const fn is_all(self) -> bool {
        matches!(self, TrailingCommas::All)
    }

    pub const fn is_none(self) -> bool {
        matches!(self, TrailingCommas::None)
    }
}

impl FromStr for TrailingCommas {
    type Err = &'static str;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "es5" => Ok(Self::Es5),
            "all" => Ok(Self::All),
            "none" => Ok(Self::None),
            // TODO: replace this error with a diagnostic
            _ => Err("Value not supported for TrailingCommas"),
        }
    }
}

impl fmt::Display for TrailingCommas {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let s = match self {
            TrailingCommas::Es5 => "ES5",
            TrailingCommas::All => "All",
            TrailingCommas::None => "None",
        };
        f.write_str(s)
    }
}

#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub enum Semicolons {
    #[default]
    Always,
    AsNeeded,
}

impl Semicolons {
    pub const fn is_as_needed(self) -> bool {
        matches!(self, Self::AsNeeded)
    }

    pub const fn is_always(self) -> bool {
        matches!(self, Self::Always)
    }
}

impl FromStr for Semicolons {
    type Err = &'static str;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "as-needed" => Ok(Self::AsNeeded),
            "always" => Ok(Self::Always),
            _ => Err(
                "Value not supported for Semicolons. Supported values are 'as-needed' and 'always'.",
            ),
        }
    }
}

impl fmt::Display for Semicolons {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = match self {
            Semicolons::AsNeeded => "As needed",
            Semicolons::Always => "Always",
        };
        f.write_str(s)
    }
}

#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub enum ArrowParentheses {
    #[default]
    Always,
    AsNeeded,
}

impl ArrowParentheses {
    pub const fn is_as_needed(self) -> bool {
        matches!(self, Self::AsNeeded)
    }

    pub const fn is_always(self) -> bool {
        matches!(self, Self::Always)
    }
}

impl FromStr for ArrowParentheses {
    type Err = &'static str;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "as-needed" => Ok(Self::AsNeeded),
            "always" => Ok(Self::Always),
            _ => Err(
                "Value not supported for Arrow parentheses. Supported values are 'as-needed' and 'always'.",
            ),
        }
    }
}

impl fmt::Display for ArrowParentheses {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = match self {
            ArrowParentheses::AsNeeded => "As needed",
            ArrowParentheses::Always => "Always",
        };
        f.write_str(s)
    }
}

/// This enum is used within formatting functions to print or omit trailing commas.
#[derive(Debug, Copy, Clone)]
pub enum FormatTrailingCommas {
    /// Print trailing commas if the option is [TrailingCommas::All].
    All,
    /// Print trailing commas if the option is [TrailingCommas::All] or [TrailingCommas::Es5].
    ES5,
}

#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub enum TrailingSeparator {
    /// A trailing separator is allowed and preferred
    #[default]
    Allowed,
    /// A trailing separator is not allowed
    Disallowed,
    /// A trailing separator is mandatory for the syntax to be correct
    Mandatory,
    /// A trailing separator might be present, but the consumer
    /// decides to remove it
    Omit,
}

impl FormatTrailingCommas {
    /// This function returns corresponding [TrailingSeparator] for `format_separated` function.
    pub fn trailing_separator(self, options: &JsFormatOptions) -> TrailingSeparator {
        if options.trailing_commas.is_none() {
            return TrailingSeparator::Omit;
        }

        match self {
            FormatTrailingCommas::All => {
                if options.trailing_commas.is_all() {
                    TrailingSeparator::Allowed
                } else {
                    TrailingSeparator::Omit
                }
            }
            FormatTrailingCommas::ES5 => TrailingSeparator::Allowed,
        }
    }
}

impl<'a> Format<'a, JsFormatContext<'a>> for FormatTrailingCommas {
    fn fmt(&self, f: &mut JsFormatter<'_, 'a>) {
        if f.options().trailing_commas.is_none() {
            return;
        }

        if matches!(self, FormatTrailingCommas::ES5) || f.options().trailing_commas.is_all() {
            write!(f, [if_group_breaks(&token(","))]);
        }
    }
}

#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub enum AttributePosition {
    #[default]
    Auto,
    Multiline,
}

impl fmt::Display for AttributePosition {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let s = match self {
            AttributePosition::Auto => "Auto",
            AttributePosition::Multiline => "Multiline",
        };
        f.write_str(s)
    }
}

impl FromStr for AttributePosition {
    type Err = &'static str;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "multiline" => Ok(Self::Multiline),
            "auto" => Ok(Self::Auto),
            _ => Err(
                "Value not supported for attribute_position. Supported values are 'auto' and 'multiline'.",
            ),
        }
    }
}

/// Whether objects keep their authored multi-line shape or collapse to one line when they fit.
/// Mirrors Prettier's `objectWrap` option.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub enum Expand {
    /// `objectWrap: "preserve"`.
    /// An object stays multi-line when there is a newline right after `{` in the source;
    /// otherwise it collapses when it fits.
    #[default]
    Auto,
    /// `objectWrap: "collapse"`.
    /// Objects collapse when they fit regardless of the authored shape.
    Never,
}

impl FromStr for Expand {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "auto" => Ok(Self::Auto),
            "never" => Ok(Self::Never),
            _ => Err(std::format!("unknown expand literal: {s}")),
        }
    }
}

impl fmt::Display for Expand {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let s = match self {
            Expand::Auto => "Auto",
            Expand::Never => "Never",
        };
        f.write_str(s)
    }
}

/// Whether to insert spaces around brackets in object.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct BracketSpacing(bool);

impl BracketSpacing {
    pub fn value(self) -> bool {
        self.0
    }
}

impl Default for BracketSpacing {
    fn default() -> Self {
        Self(true)
    }
}

impl From<bool> for BracketSpacing {
    fn from(value: bool) -> Self {
        Self(value)
    }
}

/// Put the `>` of a multi-line HTML or JSX element at the end of the last line instead of being alone on the next line (does not apply to self closing elements).
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct BracketSameLine(bool);

impl BracketSameLine {
    /// Return the boolean value for this [BracketSameLine]
    pub fn value(self) -> bool {
        self.0
    }
}

impl From<bool> for BracketSameLine {
    fn from(value: bool) -> Self {
        Self(value)
    }
}

impl fmt::Display for BracketSameLine {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        fmt::Display::fmt(&self.value(), f)
    }
}

impl FromStr for BracketSameLine {
    type Err = &'static str;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match bool::from_str(s) {
            Ok(value) => Ok(Self(value)),
            Err(_) => Err(
                "Value not supported for BracketSameLine. Supported values are 'true' and 'false'.",
            ),
        }
    }
}

#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub enum OperatorPosition {
    /// When binary expressions wrap lines, print operators at the start of new lines.
    Start,
    // Default behavior; when binary expressions wrap lines, print operators at the end of previous lines.
    #[default]
    End,
}

impl OperatorPosition {
    pub const fn is_start(self) -> bool {
        matches!(self, Self::Start)
    }

    pub const fn is_end(self) -> bool {
        matches!(self, Self::End)
    }
}

impl FromStr for OperatorPosition {
    type Err = &'static str;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "start" => Ok(Self::Start),
            "end" => Ok(Self::End),
            _ => Err("Value not supported for OperatorPosition"),
        }
    }
}

impl fmt::Display for OperatorPosition {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let s = match self {
            OperatorPosition::Start => "Start",
            OperatorPosition::End => "End",
        };
        f.write_str(s)
    }
}

#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub enum EmbeddedLanguageFormatting {
    /// Enable formatting for embedded languages.
    #[default]
    Auto,
    /// Disable formatting for embedded languages.
    Off,
}

impl EmbeddedLanguageFormatting {
    pub const fn is_auto(self) -> bool {
        matches!(self, Self::Auto)
    }

    pub const fn is_off(self) -> bool {
        matches!(self, Self::Off)
    }
}

impl FromStr for EmbeddedLanguageFormatting {
    type Err = &'static str;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "auto" => Ok(Self::Auto),
            "off" => Ok(Self::Off),
            _ => Err("Value not supported for EmbeddedLanguageFormatting"),
        }
    }
}

impl fmt::Display for EmbeddedLanguageFormatting {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let s = match self {
            EmbeddedLanguageFormatting::Auto => "Auto",
            EmbeddedLanguageFormatting::Off => "Off",
        };
        f.write_str(s)
    }
}
