//! Utilities for rendering escape sequence errors as diagnostics.

use std::iter::once;
use std::ops::Range;

use rustc_errors::{Applicability, Handler};
use rustc_lexer::unescape::{EscapeError, Mode};
use rustc_span::{BytePos, Span};

pub(crate) fn emit_unescape_error(
    handler: &Handler,
    // interior part of the literal, without quotes
    lit: &str,
    // full span of the literal, including quotes
    span_with_quotes: Span,
    mode: Mode,
    // range of the error inside `lit`
    range: Range<usize>,
    error: EscapeError,
) {
    tracing::debug!(
        "emit_unescape_error: {:?}, {:?}, {:?}, {:?}, {:?}",
        lit,
        span_with_quotes,
        mode,
        range,
        error
    );
    let span = {
        let Range { start, end } = range;
        let (start, end) = (start as u32, end as u32);
        let lo = span_with_quotes.lo() + BytePos(start + 1);
        let hi = lo + BytePos(end - start);
        span_with_quotes.with_lo(lo).with_hi(hi)
    };
    let last_char = || {
        let c = lit[range.clone()].chars().rev().next().unwrap();
        let span = span.with_lo(span.hi() - BytePos(c.len_utf8() as u32));
        (c, span)
    };
    match error {
        EscapeError::LoneSurrogateUnicodeEscape => {
            handler
                .struct_span_err(span, "invalid unicode character escape")
                .help("unicode escape must not be a surrogate")
                .emit();
        }
        EscapeError::OutOfRangeUnicodeEscape => {
            handler
                .struct_span_err(span, "invalid unicode character escape")
                .help("unicode escape must be at most 10FFFF")
                .emit();
        }
        EscapeError::MoreThanOneChar => {
            let msg = if mode.is_bytes() {
                "if you meant to write a byte string literal, use double quotes"
            } else {
                "if you meant to write a `str` literal, use double quotes"
            };

            handler
                .struct_span_err(
                    span_with_quotes,
                    "character literal may only contain one codepoint",
                )
                .span_suggestion(
                    span_with_quotes,
                    msg,
                    format!("\"{}\"", lit),
                    Applicability::MachineApplicable,
                )
                .emit();
        }
        EscapeError::EscapeOnlyChar => {
            let (c, _span) = last_char();

            let mut msg = if mode.is_bytes() {
                "byte constant must be escaped: "
            } else {
                "character constant must be escaped: "
            }
            .to_string();
            push_escaped_char(&mut msg, c);

            handler.span_err(span, msg.as_str())
        }
        EscapeError::BareCarriageReturn => {
            let msg = if mode.in_double_quotes() {
                "bare CR not allowed in string, use \\r instead"
            } else {
                "character constant must be escaped: \\r"
            };
            handler.span_err(span, msg);
        }
        EscapeError::BareCarriageReturnInRawString => {
            assert!(mode.in_double_quotes());
            let msg = "bare CR not allowed in raw string";
            handler.span_err(span, msg);
        }
        EscapeError::InvalidEscape => {
            let (c, span) = last_char();

            let label =
                if mode.is_bytes() { "unknown byte escape" } else { "unknown character escape" };
            let mut msg = label.to_string();
            msg.push_str(": ");
            push_escaped_char(&mut msg, c);

            let mut diag = handler.struct_span_err(span, msg.as_str());
            diag.span_label(span, label);
            if c == '{' || c == '}' && !mode.is_bytes() {
                diag.help(
                    "if used in a formatting string, \
                           curly braces are escaped with `{{` and `}}`",
                );
            } else if c == '\r' {
                diag.help(
                    "this is an isolated carriage return; \
                           consider checking your editor and version control settings",
                );
            }
            diag.emit();
        }
        EscapeError::TooShortHexEscape => {
            handler.span_err(span, "numeric character escape is too short")
        }
        EscapeError::InvalidCharInHexEscape | EscapeError::InvalidCharInUnicodeEscape => {
            let (c, span) = last_char();

            let mut msg = if error == EscapeError::InvalidCharInHexEscape {
                "invalid character in numeric character escape: "
            } else {
                "invalid character in unicode escape: "
            }
            .to_string();
            push_escaped_char(&mut msg, c);

            handler.span_err(span, msg.as_str())
        }
        EscapeError::NonAsciiCharInByte => {
            assert!(mode.is_bytes());
            let (_c, span) = last_char();
            handler.span_err(
                span,
                "byte constant must be ASCII. \
                                    Use a \\xHH escape for a non-ASCII byte",
            )
        }
        EscapeError::NonAsciiCharInByteString => {
            assert!(mode.is_bytes());
            let (_c, span) = last_char();
            handler.span_err(span, "raw byte string must be ASCII")
        }
        EscapeError::OutOfRangeHexEscape => handler.span_err(
            span,
            "this form of character escape may only be used \
                                    with characters in the range [\\x00-\\x7f]",
        ),
        EscapeError::LeadingUnderscoreUnicodeEscape => {
            let (_c, span) = last_char();
            handler.span_err(span, "invalid start of unicode escape")
        }
        EscapeError::OverlongUnicodeEscape => {
            handler.span_err(span, "overlong unicode escape (must have at most 6 hex digits)")
        }
        EscapeError::UnclosedUnicodeEscape => {
            handler.span_err(span, "unterminated unicode escape (needed a `}`)")
        }
        EscapeError::NoBraceInUnicodeEscape => {
            let msg = "incorrect unicode escape sequence";
            let mut diag = handler.struct_span_err(span, msg);

            let mut suggestion = "\\u{".to_owned();
            let mut suggestion_len = 0;
            let (c, char_span) = last_char();
            let chars = once(c).chain(lit[range.end..].chars());
            for c in chars.take(6).take_while(|c| c.is_digit(16)) {
                suggestion.push(c);
                suggestion_len += c.len_utf8();
            }

            if suggestion_len > 0 {
                suggestion.push('}');
                let lo = char_span.lo();
                let hi = lo + BytePos(suggestion_len as u32);
                diag.span_suggestion(
                    span.with_lo(lo).with_hi(hi),
                    "format of unicode escape sequences uses braces",
                    suggestion,
                    Applicability::MaybeIncorrect,
                );
            } else {
                diag.span_label(span, msg);
                diag.help("format of unicode escape sequences is `\\u{...}`");
            }

            diag.emit();
        }
        EscapeError::UnicodeEscapeInByte => handler.span_err(
            span,
            "unicode escape sequences cannot be used \
                                    as a byte or in a byte string",
        ),
        EscapeError::EmptyUnicodeEscape => {
            handler.span_err(span, "empty unicode escape (must have at least 1 hex digit)")
        }
        EscapeError::ZeroChars => handler.span_err(span, "empty character literal"),
        EscapeError::LoneSlash => handler.span_err(span, "invalid trailing slash in literal"),
    }
}

/// Pushes a character to a message string for error reporting
pub(crate) fn push_escaped_char(msg: &mut String, c: char) {
    match c {
        '\u{20}'..='\u{7e}' => {
            // Don't escape \, ' or " for user-facing messages
            msg.push(c);
        }
        _ => {
            msg.extend(c.escape_default());
        }
    }
}
