//! Quelltextpflege mit dem regulären Lexer und Parser; keine zweite BASIC-Grammatik.
use crate::{
    ast::{ProcKind, ProcSig, Stmt, TypeName},
    lexer::{self, TokenKind},
    parser,
};

pub fn normalize_line(line: &str) -> String {
    let mut result = line.to_owned();
    for (range, text) in normalization_edits(line).into_iter().rev() {
        result.replace_range(range, &text);
    }
    result
}

pub fn normalization_edits(line: &str) -> Vec<(std::ops::Range<usize>, String)> {
    let lexed = lexer::lex(line);
    if !lexed.diagnostics.is_empty() {
        return Vec::new();
    }
    let offsets: Vec<_> = line
        .char_indices()
        .map(|(i, _)| i)
        .chain([line.len()])
        .collect();
    let mut edits = Vec::new();
    for token in &lexed.tokens {
        if let TokenKind::Kw(kw) = token.kind {
            let start = offsets[(token.pos.column as usize - 1).min(offsets.len() - 1)];
            let word = format!("{kw:?}").to_uppercase();
            let end = start + word.len();
            if line
                .get(start..end)
                .is_some_and(|s| s.eq_ignore_ascii_case(&word))
            {
                edits.push((start..end, word));
                let gap = line[end..]
                    .chars()
                    .take_while(|c| matches!(c, ' ' | '\t'))
                    .count();
                // DATA-Nutztext beginnt direkt hinter dem Keyword und bleibt bytegenau.
                if gap > 1 && kw != lexer::Kw::Data {
                    edits.push((end..end + gap, " ".into()));
                }
            }
        }
    }
    let mut result = line.to_owned();
    edits.retain(|(r, t)| &line[r.clone()] != t);
    for (range, text) in edits.iter().rev() {
        result.replace_range(range.clone(), text);
    }
    let kinds = |s: &str| {
        lexer::lex(s)
            .tokens
            .into_iter()
            .map(|t| t.kind)
            .collect::<Vec<_>>()
    };
    if kinds(line) == kinds(&result) {
        edits
    } else {
        Vec::new()
    }
}

pub fn type_text(ty: &TypeName) -> String {
    match ty {
        TypeName::Str => "STRING".into(),
        TypeName::FixedStr(n) => format!("STRING * {n}"),
        TypeName::Udt(name) => name.clone(),
        other => format!("{other:?}").to_uppercase(),
    }
}
pub fn signature(sig: &ProcSig) -> String {
    let suffix = |s: Option<lexer::Suffix>| s.map(|s| s.as_char().to_string()).unwrap_or_default();
    format!(
        "{} {}{} ({})",
        if sig.kind == ProcKind::Sub {
            "SUB"
        } else {
            "FUNCTION"
        },
        sig.name,
        suffix(sig.suffix),
        sig.params
            .iter()
            .map(|p| {
                format!(
                    "{}{}{}{}",
                    p.name,
                    suffix(p.suffix),
                    if p.array { "()" } else { "" },
                    p.as_type
                        .as_ref()
                        .map(|t| format!(" AS {}", type_text(t)))
                        .unwrap_or_default()
                )
            })
            .collect::<Vec<_>>()
            .join(", ")
    )
}

pub const DECLARE_BEGIN: &str = "' $IDE DECLARE BEGIN";
pub const DECLARE_END: &str = "' $IDE DECLARE END";

/// Bei unsicherem Entwurf bleibt der Text vollständig unverändert; Meldung für die IDE.
pub fn maintain_declarations(source: &str) -> (String, Option<String>) {
    maintain_declarations_with(
        source,
        |clean| {
            let lexed = lexer::lex(clean);
            let parsed = parser::parse("EDITOR", &lexed.tokens);
            if let Some(d) = lexed.diagnostics.first().or(parsed.diagnostics.first()) {
                return Err(d.to_string());
            }
            Ok(parsed.module)
        },
        |candidate| {
            let analysis = crate::analyze_source("EDITOR", candidate);
            analysis
                .diagnostics
                .first()
                .map_or(Ok(()), |d| Err(d.to_string()))
        },
    )
}

/// Der Speicherhook kann die reguläre Projektauflösung samt Includes und Forms verwenden.
pub fn maintain_declarations_with(
    source: &str,
    resolve: impl Fn(&str) -> Result<crate::ast::Module, String>,
    validate: impl Fn(&str) -> Result<(), String>,
) -> (String, Option<String>) {
    let skip = |why: String| {
        (
            source.to_owned(),
            Some(format!("DECLARE-Pflege übersprungen: {why}")),
        )
    };
    let lines: Vec<_> = source.split_inclusive('\n').collect();
    let begins: Vec<_> = lines
        .iter()
        .enumerate()
        .filter(|(_, l)| l.trim_end() == DECLARE_BEGIN)
        .map(|(i, _)| i)
        .collect();
    let ends: Vec<_> = lines
        .iter()
        .enumerate()
        .filter(|(_, l)| l.trim_end() == DECLARE_END)
        .map(|(i, _)| i)
        .collect();
    let old = match (begins.as_slice(), ends.as_slice()) {
        ([], []) => None,
        ([a], [b]) if a < b => Some(*a..b + 1),
        _ => return skip("uneindeutiger IDE-Bereich".into()),
    };
    let clean: String = lines
        .iter()
        .enumerate()
        .filter(|(i, _)| !old.as_ref().is_some_and(|r| r.contains(i)))
        .map(|(_, l)| *l)
        .collect();
    let module = match resolve(&clean) {
        Ok(module) => module,
        Err(e) => return skip(e),
    };
    let exports = crate::sema::export_declarations(&module, &[]);
    let mut generated = Vec::new();
    for stmt in exports {
        let Stmt::Declare { sig, .. } = stmt else {
            continue;
        };
        let hands: Vec<_> = module
            .body
            .iter()
            .filter_map(|s| match s {
                Stmt::Declare { sig: hand, pos } if hand.name == sig.name => Some((hand, pos)),
                _ => None,
            })
            .collect();
        if hands.is_empty() {
            generated.push(format!("DECLARE {}", signature(&sig)));
        }
    }
    let mut result = clean.clone();
    if !generated.is_empty() {
        let nl = if source.contains("\r\n") {
            "\r\n"
        } else {
            "\n"
        };
        let local = parser::parse("EDITOR", &lexer::lex(&clean).tokens);
        let first_line = local.module.procs.first().map_or(1, |p| p.pos.line);
        let at = if first_line <= 1 {
            0
        } else {
            clean
                .match_indices('\n')
                .nth(first_line as usize - 2)
                .map_or(clean.len(), |(i, _)| i + 1)
        };
        result.insert_str(
            at,
            &format!(
                "{DECLARE_BEGIN}{nl}{}{nl}{DECLARE_END}{nl}",
                generated.join(nl)
            ),
        );
    }
    if let Err(e) = validate(&result) {
        return skip(e);
    }
    (result, None)
}

pub fn new_procedure(
    source: &str,
    at: usize,
    name: &str,
    function: bool,
) -> Result<String, String> {
    let lexed = lexer::lex(name);
    if !matches!(
        lexed.tokens.as_slice(),
        [
            lexer::Token {
                kind: TokenKind::Ident { suffix: None, .. },
                ..
            },
            lexer::Token {
                kind: TokenKind::Eol,
                ..
            },
            lexer::Token {
                kind: TokenKind::Eof,
                ..
            }
        ]
    ) {
        return Err("Ungültiger Prozedurname".into());
    }
    if !matches!(&lexed.tokens[0].kind, TokenKind::Ident { name: token_name, .. } if *token_name == name.to_uppercase())
        || !source.is_char_boundary(at)
        || at > source.len()
    {
        return Err("Ungültiger Prozedurname oder Cursor".into());
    }
    let source_tokens = lexer::lex(source);
    let parsed = parser::parse("EDITOR", &source_tokens.tokens);
    if !source_tokens.diagnostics.is_empty() || !parsed.diagnostics.is_empty() {
        return Err("Vor Prozedurerzeugung den unvollständigen Quelltext abschließen".into());
    }
    if !name.as_bytes().first().is_some_and(u8::is_ascii_alphabetic) {
        return Err("Prozedurname muss mit ASCII-Buchstaben beginnen".into());
    }
    let upper = name.to_uppercase();
    if parsed.module.procs.iter().any(|p| p.sig.name == upper)
        || parsed
            .module
            .body
            .iter()
            .any(|s| matches!(s, Stmt::Declare {sig, ..} if sig.name == upper))
    {
        return Err("Prozedurname bereits vorhanden".into());
    }
    let line = source[..at].bytes().filter(|b| *b == b'\n').count() as u32 + 1;
    let mut defaults = std::array::from_fn::<_, 26, _>(|_| Some(TypeName::Single));
    let current = parsed
        .module
        .procs
        .iter()
        .rfind(|p| p.pos.line <= line && line <= p.end_pos.line);
    let module_default = |stmt: &Stmt| matches!(stmt, Stmt::DefType { .. });
    if current.is_some() {
        for stmt in parsed.module.body.iter().filter(|s| module_default(s)) {
            if let Stmt::DefType { ty, ranges, .. } = stmt {
                for (a, b) in ranges {
                    for c in *a..=*b {
                        if c.is_ascii_alphabetic() {
                            defaults[c.to_ascii_uppercase() as usize - 'A' as usize] =
                                Some(ty.clone());
                        }
                    }
                }
            }
        }
    }
    let mut statements: Vec<_> = parsed
        .module
        .body
        .iter()
        .filter(|_| current.is_none())
        .chain(current.into_iter().flat_map(|p| p.body.iter()))
        .collect();
    statements.sort_by_key(|s| crate::sema::stmt_pos(s).line);
    for stmt in statements {
        if let Stmt::DefType { ty, ranges, pos } = stmt {
            if pos.line <= line {
                for (a, b) in ranges {
                    for c in *a..=*b {
                        if c.is_ascii_alphabetic() {
                            defaults[c.to_ascii_uppercase() as usize - 'A' as usize] =
                                Some(ty.clone());
                        }
                    }
                }
            }
        }
    }
    let kind = if function { "FUNCTION" } else { "SUB" };
    let mut result = format!("\n{kind} {name}");
    if function {
        // Rückgabetyp wird am Header festgehalten.
        let ty = defaults[upper.as_bytes()[0] as usize - b'A' as usize]
            .as_ref()
            .unwrap_or(&TypeName::Single);
        result.push(match ty {
            TypeName::Integer => '%',
            TypeName::Long => '&',
            TypeName::Double => '#',
            TypeName::Currency => '@',
            TypeName::Str => '$',
            _ => '!',
        });
    }
    result.push_str(" ()\n");
    let mut i = 0;
    while i < defaults.len() {
        let Some(ty) = &defaults[i] else {
            i += 1;
            continue;
        };
        let start = i;
        while i + 1 < defaults.len() && defaults[i + 1].as_ref() == Some(ty) {
            i += 1;
        }
        let kw = match ty {
            TypeName::Integer => "DEFINT",
            TypeName::Long => "DEFLNG",
            TypeName::Double => "DEFDBL",
            TypeName::Currency => "DEFCUR",
            TypeName::Str => "DEFSTR",
            _ => "DEFSNG",
        };
        result.push_str(&format!(
            "{kw} {}{}\n",
            (b'A' + start as u8) as char,
            if start == i {
                String::new()
            } else {
                format!("-{}", (b'A' + i as u8) as char)
            }
        ));
        i += 1;
    }
    result.push_str(&format!("\nEND {kind}\n"));
    Ok(result)
}
