use clap::Args;
use mk_codec::string_layer::bch::{ALPHABET, BchCode, DecodedString};
use serde::Serialize;
use crate::cmd::read_mk1_strings;
use crate::error::{CliError, Result};
#[derive(Args, Debug)]
pub struct RepairArgs {
pub mk1_strings: Vec<String>,
#[arg(long)]
pub json: bool,
}
#[derive(Debug, Clone)]
struct RepairDetail {
chunk_index: usize,
original_chunk: String,
corrected_chunk: String,
corrected_positions: Vec<(usize, char, char)>,
}
pub fn run(args: RepairArgs) -> Result<u8> {
let strings = read_mk1_strings(&args.mk1_strings)?;
let mut reports: Vec<RepairDetail> = Vec::with_capacity(strings.len());
let mut corrected_chunks: Vec<String> = Vec::with_capacity(strings.len());
for (idx, original) in strings.iter().enumerate() {
let decoded = mk_codec::string_layer::decode_string(original)?;
let (corrected_chunk, corrected_positions) =
reconstruct_corrected(original, &decoded);
reports.push(RepairDetail {
chunk_index: idx,
original_chunk: original.clone(),
corrected_chunk: corrected_chunk.clone(),
corrected_positions,
});
corrected_chunks.push(corrected_chunk);
}
let any_correction = reports.iter().any(|r| !r.corrected_positions.is_empty());
if args.json {
emit_json(&corrected_chunks, &reports)?;
} else {
emit_text(&corrected_chunks, &reports)?;
}
Ok(if any_correction { 5 } else { 0 })
}
fn reconstruct_corrected(
original: &str,
decoded: &DecodedString,
) -> (String, Vec<(usize, char, char)>) {
let sep_pos = original
.rfind('1')
.expect("mk1 input passed BCH decode; must contain bech32 separator '1'");
let (prefix, rest) = original.split_at(sep_pos);
let data_part_raw: Vec<char> = rest[1..].chars().collect();
let mut corrected_positions: Vec<(usize, char, char)> =
Vec::with_capacity(decoded.corrected_positions.len());
let mut data_chars = data_part_raw.clone();
for &pos in &decoded.corrected_positions {
let was = data_chars
.get(pos)
.copied()
.unwrap_or('?');
let now = decoded.corrected_char_at(pos);
if pos < data_chars.len() {
data_chars[pos] = now;
}
corrected_positions.push((pos, was, now));
}
let mut out = String::with_capacity(prefix.len() + 1 + decoded.data_with_checksum.len());
out.push_str(prefix);
out.push('1');
for &v in &decoded.data_with_checksum {
out.push(ALPHABET[v as usize] as char);
}
let _ = match decoded.code {
BchCode::Regular => "regular",
BchCode::Long => "long",
};
(out, corrected_positions)
}
fn emit_text(corrected_chunks: &[String], reports: &[RepairDetail]) -> Result<()> {
let any_correction = reports.iter().any(|r| !r.corrected_positions.is_empty());
if any_correction {
println!("# Repair report");
for r in reports {
if r.corrected_positions.is_empty() {
continue;
}
let n = r.corrected_positions.len();
let plural = if n == 1 { "correction" } else { "corrections" };
let mut line = format!("# mk1 chunk {}: {} {} at ", r.chunk_index, n, plural);
for (i, (pos, was, now)) in r.corrected_positions.iter().enumerate() {
if i > 0 {
line.push_str(", ");
}
line.push_str(&format!("position {pos}: '{was}' -> '{now}'"));
}
println!("{line}");
}
}
for chunk in corrected_chunks {
println!("{chunk}");
}
Ok(())
}
#[derive(Serialize)]
struct RepairJson<'a> {
schema_version: &'static str,
kind: &'static str,
corrected_chunks: &'a [String],
repairs: Vec<RepairJsonDetail<'a>>,
}
#[derive(Serialize)]
struct RepairJsonDetail<'a> {
chunk_index: usize,
original_chunk: &'a str,
corrected_chunk: &'a str,
corrected_positions: Vec<RepairJsonPosition>,
}
#[derive(Serialize)]
struct RepairJsonPosition {
position: usize,
was: String,
now: String,
}
fn emit_json(corrected_chunks: &[String], reports: &[RepairDetail]) -> Result<()> {
let envelope = RepairJson {
schema_version: "1",
kind: "mk1",
corrected_chunks,
repairs: reports
.iter()
.filter(|r| !r.corrected_positions.is_empty())
.map(|r| RepairJsonDetail {
chunk_index: r.chunk_index,
original_chunk: &r.original_chunk,
corrected_chunk: &r.corrected_chunk,
corrected_positions: r
.corrected_positions
.iter()
.map(|(p, w, n)| RepairJsonPosition {
position: *p,
was: w.to_string(),
now: n.to_string(),
})
.collect(),
})
.collect(),
};
let body = serde_json::to_string(&envelope)
.map_err(|e| CliError::UsageError(format!("repair JSON serialize: {e}")))?;
println!("{body}");
Ok(())
}