use rustpython_parser::ast::Location;

use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::source_code::Locator;
use ruff_python_ast::types::Range;

/// Return `true` if the given string is a radix literal (e.g., `0b101`).
pub fn is_radix_literal(content: &str) -> bool {
    content.starts_with("0b")
        || content.starts_with("0o")
        || content.starts_with("0x")
        || content.starts_with("0B")
        || content.starts_with("0O")
        || content.starts_with("0X")
}

/// Find the first token in the given range that satisfies the given predicate.
pub fn find_tok(
    location: Location,
    end_location: Location,
    locator: &Locator,
    f: impl Fn(rustpython_parser::Tok) -> bool,
) -> (Location, Location) {
    for (start, tok, end) in rustpython_parser::lexer::lex_located(
        locator.slice(Range::new(location, end_location)),
        rustpython_parser::Mode::Module,
        location,
    )
    .flatten()
    {
        if f(tok) {
            return (start, end);
        }
    }
    unreachable!(
        "Failed to find token in range {:?}..{:?}",
        location, end_location
    )
}

/// Expand the range of a compound statement.
///
/// `location` is the start of the compound statement (e.g., the `if` in `if x:`).
/// `end_location` is the end of the last statement in the body.
pub fn expand_indented_block(
    location: Location,
    end_location: Location,
    locator: &Locator,
) -> (Location, Location) {
    let contents = locator.contents();
    let start_index = locator.offset(location);
    let end_index = locator.offset(end_location);

    // Find the colon, which indicates the end of the header.
    let mut nesting = 0;
    let mut colon = None;
    for (start, tok, _end) in rustpython_parser::lexer::lex_located(
        &contents[start_index..end_index],
        rustpython_parser::Mode::Module,
        location,
    )
    .flatten()
    {
        match tok {
            rustpython_parser::Tok::Colon if nesting == 0 => {
                colon = Some(start);
                break;
            }
            rustpython_parser::Tok::Lpar
            | rustpython_parser::Tok::Lsqb
            | rustpython_parser::Tok::Lbrace => nesting += 1,
            rustpython_parser::Tok::Rpar
            | rustpython_parser::Tok::Rsqb
            | rustpython_parser::Tok::Rbrace => nesting -= 1,
            _ => {}
        }
    }
    let colon_location = colon.unwrap();
    let colon_index = locator.offset(colon_location);

    // From here, we have two options: simple statement or compound statement.
    let indent = rustpython_parser::lexer::lex_located(
        &contents[colon_index..end_index],
        rustpython_parser::Mode::Module,
        colon_location,
    )
    .flatten()
    .find_map(|(start, tok, _end)| match tok {
        rustpython_parser::Tok::Indent => Some(start),
        _ => None,
    });

    let Some(indent_location) = indent else {
        // Simple statement: from the colon to the end of the line.
        return (colon_location, Location::new(end_location.row() + 1, 0));
    };

    // Compound statement: from the colon to the end of the block.
    let mut offset = 0;
    for (index, line) in contents[end_index..]
        .universal_newlines()
        .skip(1)
        .enumerate()
    {
        if line.is_empty() {
            continue;
        }

        if line
            .chars()
            .take(indent_location.column())
            .all(char::is_whitespace)
        {
            offset = index + 1;
        } else {
            break;
        }
    }

    let end_location = Location::new(end_location.row() + 1 + offset, 0);
    (colon_location, end_location)
}

/// Return true if the `orelse` block of an `if` statement is an `elif` statement.
pub fn is_elif(orelse: &[rustpython_parser::ast::Stmt], locator: &Locator) -> bool {
    if orelse.len() == 1 && matches!(orelse[0].node, rustpython_parser::ast::StmtKind::If { .. }) {
        let contents = locator.skip(orelse[0].location);
        if contents.starts_with("elif") {
            return true;
        }
    }
    false
}
