#![cfg_attr(any(docsrs, feature = "unstable"), feature(doc_cfg))]
pub mod args;
pub mod ext;
pub mod format;
pub mod output_scripts;
pub mod scripts;
pub mod types;
pub mod utils;

use ext::path::PathBufExt;
use scripts::base::ArchiveContent;

fn escape_dep_string(s: &str) -> String {
    s.replace("\\", "\\\\").replace(" ", "\\ ")
}

fn get_encoding(
    arg: &args::Arg,
    builder: &Box<dyn scripts::ScriptBuilder + Send + Sync>,
) -> types::Encoding {
    match &arg.encoding {
        Some(enc) => {
            return match enc {
                &types::TextEncoding::Default => builder.default_encoding(),
                &types::TextEncoding::Auto => types::Encoding::Auto,
                &types::TextEncoding::Cp932 => types::Encoding::Cp932,
                &types::TextEncoding::Utf8 => types::Encoding::Utf8,
                &types::TextEncoding::Gb2312 => types::Encoding::Gb2312,
            };
        }
        None => {}
    }
    #[cfg(windows)]
    match &arg.code_page {
        Some(code_page) => {
            return types::Encoding::CodePage(*code_page);
        }
        None => {}
    }
    builder.default_encoding()
}

fn get_archived_encoding(
    arg: &args::Arg,
    builder: &Box<dyn scripts::ScriptBuilder + Send + Sync>,
    encoding: types::Encoding,
) -> types::Encoding {
    match &arg.archive_encoding {
        Some(enc) => {
            return match enc {
                &types::TextEncoding::Default => builder
                    .default_archive_encoding()
                    .unwrap_or_else(|| builder.default_encoding()),
                &types::TextEncoding::Auto => types::Encoding::Auto,
                &types::TextEncoding::Cp932 => types::Encoding::Cp932,
                &types::TextEncoding::Utf8 => types::Encoding::Utf8,
                &types::TextEncoding::Gb2312 => types::Encoding::Gb2312,
            };
        }
        None => {}
    }
    #[cfg(windows)]
    match &arg.archive_code_page {
        Some(code_page) => {
            return types::Encoding::CodePage(*code_page);
        }
        None => {}
    }
    builder.default_archive_encoding().unwrap_or(encoding)
}

fn get_input_output_script_encoding(arg: &args::Arg) -> types::Encoding {
    match &arg.encoding {
        Some(enc) => {
            return match enc {
                &types::TextEncoding::Default => types::Encoding::Utf8,
                &types::TextEncoding::Auto => types::Encoding::Utf8,
                &types::TextEncoding::Cp932 => types::Encoding::Cp932,
                &types::TextEncoding::Utf8 => types::Encoding::Utf8,
                &types::TextEncoding::Gb2312 => types::Encoding::Gb2312,
            };
        }
        None => {}
    }
    #[cfg(windows)]
    match &arg.code_page {
        Some(code_page) => {
            return types::Encoding::CodePage(*code_page);
        }
        None => {}
    }
    types::Encoding::Utf8
}

fn get_output_encoding(arg: &args::Arg) -> types::Encoding {
    match &arg.output_encoding {
        Some(enc) => {
            return match enc {
                &types::TextEncoding::Default => types::Encoding::Utf8,
                &types::TextEncoding::Auto => types::Encoding::Utf8,
                &types::TextEncoding::Cp932 => types::Encoding::Cp932,
                &types::TextEncoding::Utf8 => types::Encoding::Utf8,
                &types::TextEncoding::Gb2312 => types::Encoding::Gb2312,
            };
        }
        None => {}
    }
    #[cfg(windows)]
    match &arg.output_code_page {
        Some(code_page) => {
            return types::Encoding::CodePage(*code_page);
        }
        None => {}
    }
    types::Encoding::Utf8
}

fn get_patched_encoding(
    arg: &args::ImportArgs,
    builder: &Box<dyn scripts::ScriptBuilder + Send + Sync>,
) -> types::Encoding {
    match &arg.patched_encoding {
        Some(enc) => {
            return match enc {
                &types::TextEncoding::Default => builder.default_patched_encoding(),
                &types::TextEncoding::Auto => types::Encoding::Utf8,
                &types::TextEncoding::Cp932 => types::Encoding::Cp932,
                &types::TextEncoding::Utf8 => types::Encoding::Utf8,
                &types::TextEncoding::Gb2312 => types::Encoding::Gb2312,
            };
        }
        None => {}
    }
    #[cfg(windows)]
    match &arg.patched_code_page {
        Some(code_page) => {
            return types::Encoding::CodePage(*code_page);
        }
        None => {}
    }
    builder.default_patched_encoding()
}

fn get_patched_archive_encoding(
    arg: &args::ImportArgs,
    builder: &Box<dyn scripts::ScriptBuilder + Send + Sync>,
    encoding: types::Encoding,
) -> types::Encoding {
    match &arg.patched_archive_encoding {
        Some(enc) => {
            return match enc {
                &types::TextEncoding::Default => {
                    builder.default_archive_encoding().unwrap_or(encoding)
                }
                &types::TextEncoding::Auto => types::Encoding::Utf8,
                &types::TextEncoding::Cp932 => types::Encoding::Cp932,
                &types::TextEncoding::Utf8 => types::Encoding::Utf8,
                &types::TextEncoding::Gb2312 => types::Encoding::Gb2312,
            };
        }
        None => {}
    }
    #[cfg(windows)]
    match &arg.patched_archive_code_page {
        Some(code_page) => {
            return types::Encoding::CodePage(*code_page);
        }
        None => {}
    }
    builder.default_archive_encoding().unwrap_or(encoding)
}

pub fn parse_script(
    filename: &str,
    arg: &args::Arg,
    config: std::sync::Arc<types::ExtraConfig>,
) -> anyhow::Result<(
    Box<dyn scripts::Script>,
    &'static Box<dyn scripts::ScriptBuilder + Send + Sync>,
)> {
    match &arg.script_type {
        Some(typ) => {
            for builder in scripts::BUILDER.iter() {
                if typ == builder.script_type() {
                    let encoding = get_encoding(arg, builder);
                    let archive_encoding = get_archived_encoding(arg, builder, encoding);
                    return Ok((
                        builder.build_script_from_file(
                            filename,
                            encoding,
                            archive_encoding,
                            &config,
                            None,
                        )?,
                        builder,
                    ));
                }
            }
        }
        _ => {}
    }
    let mut exts_builder = Vec::new();
    for builder in scripts::BUILDER.iter() {
        let exts = builder.extensions();
        for ext in exts {
            if filename.to_lowercase().ends_with(ext) {
                exts_builder.push(builder);
                break;
            }
        }
    }
    let exts_builder = if exts_builder.is_empty() {
        scripts::BUILDER.iter().collect::<Vec<_>>()
    } else {
        exts_builder
    };
    if exts_builder.len() == 1 {
        let builder = exts_builder.first().unwrap();
        let encoding = get_encoding(arg, builder);
        let archive_encoding = get_archived_encoding(arg, builder, encoding);
        return Ok((
            builder.build_script_from_file(filename, encoding, archive_encoding, &config, None)?,
            builder,
        ));
    }
    let mut buf = [0u8; 1024];
    let mut size = 0;
    if filename != "-" {
        let mut f = std::fs::File::open(filename)?;
        size = std::io::Read::read(&mut f, &mut buf)?;
    }
    let mut scores = Vec::new();
    for builder in exts_builder.iter() {
        if let Some(score) = builder.is_this_format(filename, &buf, size) {
            scores.push((score, builder));
        }
    }
    if scores.is_empty() {
        return Err(anyhow::anyhow!("Unsupported script type"));
    }
    let max_score = scores.iter().map(|s| s.0).max().unwrap();
    let mut best_builders = Vec::new();
    for (score, builder) in scores.iter() {
        if *score == max_score {
            best_builders.push(builder);
        }
    }
    if best_builders.len() == 1 {
        let builder = best_builders.first().unwrap();
        let encoding = get_encoding(arg, builder);
        let archive_encoding = get_archived_encoding(arg, builder, encoding);
        return Ok((
            builder.build_script_from_file(filename, encoding, archive_encoding, &config, None)?,
            builder,
        ));
    }
    if best_builders.len() > 1 {
        eprintln!(
            "Multiple script types found for {}: {:?}",
            filename, best_builders
        );
        return Err(anyhow::anyhow!("Multiple script types found"));
    }
    Err(anyhow::anyhow!("Unsupported script type"))
}

pub fn parse_script_from_archive<'a>(
    file: &mut Box<dyn ArchiveContent + Send + Sync + 'a>,
    arg: &args::Arg,
    config: std::sync::Arc<types::ExtraConfig>,
    archive: &Box<dyn scripts::Script>,
) -> anyhow::Result<(
    Box<dyn scripts::Script>,
    &'static Box<dyn scripts::ScriptBuilder + Send + Sync>,
)> {
    match file.script_type() {
        Some(typ) => {
            for builder in scripts::BUILDER.iter() {
                if typ == builder.script_type() {
                    let encoding = get_encoding(arg, builder);
                    let archive_encoding = get_archived_encoding(arg, builder, encoding);
                    return Ok((
                        builder.build_script(
                            file.data()?,
                            file.name(),
                            encoding,
                            archive_encoding,
                            &config,
                            Some(archive),
                        )?,
                        builder,
                    ));
                }
            }
        }
        _ => {}
    }
    let mut exts_builder = Vec::new();
    for builder in scripts::BUILDER.iter() {
        let exts = builder.extensions();
        for ext in exts {
            if file.name().to_lowercase().ends_with(ext) {
                exts_builder.push(builder);
                break;
            }
        }
    }
    let exts_builder = if exts_builder.is_empty() {
        scripts::BUILDER.iter().collect::<Vec<_>>()
    } else {
        exts_builder
    };
    if exts_builder.len() == 1 {
        let builder = exts_builder.first().unwrap();
        let encoding = get_encoding(arg, builder);
        let archive_encoding = get_archived_encoding(arg, builder, encoding);
        return Ok((
            builder.build_script(
                file.data()?,
                file.name(),
                encoding,
                archive_encoding,
                &config,
                Some(archive),
            )?,
            builder,
        ));
    }
    let buf = file.data()?;
    let mut scores = Vec::new();
    for builder in exts_builder.iter() {
        if let Some(score) = builder.is_this_format(file.name(), buf.as_slice(), buf.len()) {
            scores.push((score, builder));
        }
    }
    if scores.is_empty() {
        return Err(anyhow::anyhow!("Unsupported script type"));
    }
    let max_score = scores.iter().map(|s| s.0).max().unwrap();
    let mut best_builders = Vec::new();
    for (score, builder) in scores.iter() {
        if *score == max_score {
            best_builders.push(builder);
        }
    }
    if best_builders.len() == 1 {
        let builder = best_builders.first().unwrap();
        let encoding = get_encoding(arg, builder);
        let archive_encoding = get_archived_encoding(arg, builder, encoding);
        return Ok((
            builder.build_script(
                buf,
                file.name(),
                encoding,
                archive_encoding,
                &config,
                Some(archive),
            )?,
            builder,
        ));
    }
    if best_builders.len() > 1 {
        eprintln!(
            "Multiple script types found for {}: {:?}",
            file.name(),
            best_builders
        );
        return Err(anyhow::anyhow!("Multiple script types found"));
    }
    Err(anyhow::anyhow!("Unsupported script type"))
}

pub fn export_script(
    filename: &str,
    arg: &args::Arg,
    config: std::sync::Arc<types::ExtraConfig>,
    output: &Option<String>,
    root_dir: Option<&std::path::Path>,
    #[cfg(feature = "image")] img_threadpool: Option<
        &utils::threadpool::ThreadPool<Result<(), anyhow::Error>>,
    >,
) -> anyhow::Result<types::ScriptResult> {
    eprintln!("Exporting {}", filename);
    let script = parse_script(filename, arg, config.clone())?.0;
    if script.is_archive() {
        let odir = match output.as_ref() {
            Some(output) => {
                let mut pb = std::path::PathBuf::from(output);
                let filename = std::path::PathBuf::from(filename);
                if let Some(root_dir) = root_dir {
                    let rpath = utils::files::relative_path(root_dir, &filename);
                    if let Some(parent) = rpath.parent() {
                        pb.push(parent);
                    }
                    if let Some(fname) = filename.file_name() {
                        pb.push(fname);
                    }
                }
                pb.set_extension("");
                if let Some(ext) = script.archive_output_ext() {
                    pb.set_extension(ext);
                }
                pb.to_string_lossy().into_owned()
            }
            None => {
                let mut pb = std::path::PathBuf::from(filename);
                pb.set_extension("");
                if let Some(ext) = script.archive_output_ext() {
                    pb.set_extension(ext);
                }
                pb.to_string_lossy().into_owned()
            }
        };
        if !std::fs::exists(&odir)? {
            std::fs::create_dir_all(&odir)?;
        }
        for (i, filename) in script.iter_archive_filename()?.enumerate() {
            let filename = match filename {
                Ok(f) => f,
                Err(e) => {
                    eprintln!("Error reading archive filename: {}", e);
                    COUNTER.inc_error();
                    if arg.backtrace {
                        eprintln!("Backtrace: {}", e.backtrace());
                    }
                    continue;
                }
            };
            let mut f = match script.open_file(i) {
                Ok(f) => f,
                Err(e) => {
                    eprintln!("Error opening file {}: {}", filename, e);
                    COUNTER.inc_error();
                    if arg.backtrace {
                        eprintln!("Backtrace: {}", e.backtrace());
                    }
                    continue;
                }
            };
            if arg.force_script || f.is_script() {
                let (script_file, _) =
                    match parse_script_from_archive(&mut f, arg, config.clone(), &script) {
                        Ok(s) => s,
                        Err(e) => {
                            eprintln!("Error parsing script '{}' from archive: {}", filename, e);
                            COUNTER.inc_error();
                            if arg.backtrace {
                                eprintln!("Backtrace: {}", e.backtrace());
                            }
                            continue;
                        }
                    };
                #[cfg(feature = "image")]
                if script_file.is_image() {
                    if script_file.is_multi_image() {
                        for i in script_file.export_multi_image()? {
                            let img_data = match i {
                                Ok(data) => data,
                                Err(e) => {
                                    eprintln!("Error exporting image: {}", e);
                                    COUNTER.inc_error();
                                    if arg.backtrace {
                                        eprintln!("Backtrace: {}", e.backtrace());
                                    }
                                    continue;
                                }
                            };
                            let out_type = arg.image_type.unwrap_or(types::ImageOutputType::Png);
                            let mut out_path = std::path::PathBuf::from(&odir);
                            if !arg.image_output_flat {
                                out_path.push(f.name());
                                out_path.set_extension("");
                                out_path.push(img_data.name);
                            } else {
                                let name = std::path::Path::new(f.name());
                                out_path.push(format!(
                                    "{}_{}",
                                    name.file_stem().unwrap_or_default().to_string_lossy(),
                                    img_data.name
                                ));
                            }
                            out_path.set_extension(out_type.as_ref());
                            match utils::files::make_sure_dir_exists(&out_path) {
                                Ok(_) => {}
                                Err(e) => {
                                    eprintln!(
                                        "Error creating parent directory for {}: {}",
                                        out_path.display(),
                                        e
                                    );
                                    COUNTER.inc_error();
                                    continue;
                                }
                            }
                            if let Some(threadpool) = img_threadpool {
                                let outpath = out_path.to_string_lossy().into_owned();
                                let config = config.clone();
                                threadpool.execute(
                                    move |_| {
                                        utils::img::encode_img(
                                            img_data.data,
                                            out_type,
                                            &outpath,
                                            &config,
                                        )
                                        .map_err(|e| {
                                            anyhow::anyhow!(
                                                "Failed to encode image {}: {}",
                                                outpath,
                                                e
                                            )
                                        })
                                    },
                                    true,
                                )?;
                                continue;
                            } else {
                                match utils::img::encode_img(
                                    img_data.data,
                                    out_type,
                                    &out_path.to_string_lossy(),
                                    &config,
                                ) {
                                    Ok(_) => {}
                                    Err(e) => {
                                        eprintln!("Error encoding image: {}", e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                }
                                COUNTER.inc(types::ScriptResult::Ok);
                            }
                        }
                        COUNTER.inc(types::ScriptResult::Ok);
                        continue;
                    }
                    let img_data = match script_file.export_image() {
                        Ok(data) => data,
                        Err(e) => {
                            eprintln!("Error exporting image: {}", e);
                            COUNTER.inc_error();
                            if arg.backtrace {
                                eprintln!("Backtrace: {}", e.backtrace());
                            }
                            continue;
                        }
                    };
                    let out_type = arg.image_type.unwrap_or(types::ImageOutputType::Png);
                    let mut out_path = std::path::PathBuf::from(&odir).join(f.name());
                    out_path.set_extension(out_type.as_ref());
                    match utils::files::make_sure_dir_exists(&out_path) {
                        Ok(_) => {}
                        Err(e) => {
                            eprintln!(
                                "Error creating parent directory for {}: {}",
                                out_path.display(),
                                e
                            );
                            COUNTER.inc_error();
                            continue;
                        }
                    }
                    if let Some(threadpool) = img_threadpool {
                        let outpath = out_path.to_string_lossy().into_owned();
                        let config = config.clone();
                        threadpool.execute(
                            move |_| {
                                utils::img::encode_img(img_data, out_type, &outpath, &config)
                                    .map_err(|e| {
                                        anyhow::anyhow!("Failed to encode image {}: {}", outpath, e)
                                    })
                            },
                            true,
                        )?;
                        continue;
                    } else {
                        match utils::img::encode_img(
                            img_data,
                            out_type,
                            &out_path.to_string_lossy(),
                            &config,
                        ) {
                            Ok(_) => {}
                            Err(e) => {
                                eprintln!("Error encoding image: {}", e);
                                COUNTER.inc_error();
                                continue;
                            }
                        }
                        COUNTER.inc(types::ScriptResult::Ok);
                    }
                    continue;
                }
                let mut of = match &arg.output_type {
                    Some(t) => t.clone(),
                    None => script_file.default_output_script_type(),
                };
                if !script_file.is_output_supported(of) {
                    of = script_file.default_output_script_type();
                }
                if !arg.no_multi_message && !of.is_custom() && script_file.multiple_message_files()
                {
                    let mmes = script_file.extract_multiple_messages()?;
                    if mmes.is_empty() {
                        eprintln!("No messages found in {}", f.name());
                        COUNTER.inc(types::ScriptResult::Ignored);
                        continue;
                    }
                    let ext = of.as_ref();
                    let mut out_dir = std::path::PathBuf::from(&odir).join(f.name());
                    if arg.output_no_extra_ext {
                        out_dir.remove_all_extensions();
                    } else {
                        out_dir.set_extension("");
                    }
                    std::fs::create_dir_all(&out_dir)?;
                    for (name, data) in mmes {
                        let ofp = out_dir.join(name).with_extension(ext);
                        match of {
                            types::OutputScriptType::Json => {
                                let enc = get_output_encoding(arg);
                                let s = match serde_json::to_string_pretty(&data) {
                                    Ok(s) => s,
                                    Err(e) => {
                                        eprintln!("Error serializing messages to JSON: {}", e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                };
                                let b = match utils::encoding::encode_string(enc, &s, false) {
                                    Ok(b) => b,
                                    Err(e) => {
                                        eprintln!("Error encoding string: {}", e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                };
                                let mut f = match utils::files::write_file(&ofp) {
                                    Ok(f) => f,
                                    Err(e) => {
                                        eprintln!("Error writing file {}: {}", ofp.display(), e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                };
                                match f.write_all(&b) {
                                    Ok(_) => {}
                                    Err(e) => {
                                        eprintln!("Error writing to file {}: {}", ofp.display(), e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                }
                            }
                            types::OutputScriptType::M3t
                            | types::OutputScriptType::M3ta
                            | types::OutputScriptType::M3tTxt => {
                                let enc = get_output_encoding(arg);
                                let s =
                                    output_scripts::m3t::M3tDumper::dump(&data, arg.m3t_no_quote);
                                let b = match utils::encoding::encode_string(enc, &s, false) {
                                    Ok(b) => b,
                                    Err(e) => {
                                        eprintln!("Error encoding string: {}", e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                };
                                let mut f = match utils::files::write_file(&ofp) {
                                    Ok(f) => f,
                                    Err(e) => {
                                        eprintln!("Error writing file {}: {}", ofp.display(), e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                };
                                match f.write_all(&b) {
                                    Ok(_) => {}
                                    Err(e) => {
                                        eprintln!("Error writing to file {}: {}", ofp.display(), e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                }
                            }
                            types::OutputScriptType::Yaml => {
                                let enc = get_output_encoding(arg);
                                let s = match serde_yaml_ng::to_string(&data) {
                                    Ok(s) => s,
                                    Err(e) => {
                                        eprintln!("Error serializing messages to YAML: {}", e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                };
                                let b = match utils::encoding::encode_string(enc, &s, false) {
                                    Ok(b) => b,
                                    Err(e) => {
                                        eprintln!("Error encoding string: {}", e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                };
                                let mut f = match utils::files::write_file(&ofp) {
                                    Ok(f) => f,
                                    Err(e) => {
                                        eprintln!("Error writing file {}: {}", ofp.display(), e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                };
                                match f.write_all(&b) {
                                    Ok(_) => {}
                                    Err(e) => {
                                        eprintln!("Error writing to file {}: {}", ofp.display(), e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                }
                            }
                            types::OutputScriptType::Pot | types::OutputScriptType::Po => {
                                let enc = get_output_encoding(arg);
                                let s = match output_scripts::po::PoDumper::new().dump(&data, enc) {
                                    Ok(s) => s,
                                    Err(e) => {
                                        eprintln!("Error dumping messages to PO format: {}", e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                };
                                let b = match utils::encoding::encode_string(enc, &s, false) {
                                    Ok(b) => b,
                                    Err(e) => {
                                        eprintln!("Error encoding string: {}", e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                };
                                let mut f = match utils::files::write_file(&ofp) {
                                    Ok(f) => f,
                                    Err(e) => {
                                        eprintln!("Error writing file {}: {}", ofp.display(), e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                };
                                match f.write_all(&b) {
                                    Ok(_) => {}
                                    Err(e) => {
                                        eprintln!("Error writing to file {}: {}", ofp.display(), e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                }
                            }
                            types::OutputScriptType::Custom => {}
                        }
                    }
                    COUNTER.inc(types::ScriptResult::Ok);
                    continue;
                }
                let mes = if of.is_custom() {
                    Vec::new()
                } else {
                    match script_file.extract_messages() {
                        Ok(mes) => mes,
                        Err(e) => {
                            eprintln!("Error extracting messages from {}: {}", f.name(), e);
                            COUNTER.inc_error();
                            if arg.backtrace {
                                eprintln!("Backtrace: {}", e.backtrace());
                            }
                            continue;
                        }
                    }
                };
                if !of.is_custom() && mes.is_empty() {
                    eprintln!("No messages found in {}", f.name());
                    COUNTER.inc(types::ScriptResult::Ignored);
                    continue;
                }
                let mut out_path = std::path::PathBuf::from(&odir).join(f.name());
                if arg.output_no_extra_ext {
                    out_path.remove_all_extensions();
                }
                out_path.set_extension(if of.is_custom() {
                    script_file.custom_output_extension()
                } else {
                    of.as_ref()
                });
                match utils::files::make_sure_dir_exists(&out_path) {
                    Ok(_) => {}
                    Err(e) => {
                        eprintln!(
                            "Error creating parent directory for {}: {}",
                            out_path.display(),
                            e
                        );
                        COUNTER.inc_error();
                        continue;
                    }
                }
                match of {
                    types::OutputScriptType::Json => {
                        let enc = get_output_encoding(arg);
                        let s = match serde_json::to_string_pretty(&mes) {
                            Ok(s) => s,
                            Err(e) => {
                                eprintln!("Error serializing messages to JSON: {}", e);
                                COUNTER.inc_error();
                                continue;
                            }
                        };
                        let b = match utils::encoding::encode_string(enc, &s, false) {
                            Ok(b) => b,
                            Err(e) => {
                                eprintln!("Error encoding string: {}", e);
                                COUNTER.inc_error();
                                continue;
                            }
                        };
                        let mut f = match utils::files::write_file(&out_path) {
                            Ok(f) => f,
                            Err(e) => {
                                eprintln!("Error writing file {}: {}", out_path.display(), e);
                                COUNTER.inc_error();
                                continue;
                            }
                        };
                        match f.write_all(&b) {
                            Ok(_) => {}
                            Err(e) => {
                                eprintln!("Error writing to file {}: {}", out_path.display(), e);
                                COUNTER.inc_error();
                                continue;
                            }
                        }
                    }
                    types::OutputScriptType::M3t
                    | types::OutputScriptType::M3ta
                    | types::OutputScriptType::M3tTxt => {
                        let enc = get_output_encoding(arg);
                        let s = output_scripts::m3t::M3tDumper::dump(&mes, arg.m3t_no_quote);
                        let b = match utils::encoding::encode_string(enc, &s, false) {
                            Ok(b) => b,
                            Err(e) => {
                                eprintln!("Error encoding string: {}", e);
                                COUNTER.inc_error();
                                continue;
                            }
                        };
                        let mut f = match utils::files::write_file(&out_path) {
                            Ok(f) => f,
                            Err(e) => {
                                eprintln!("Error writing file {}: {}", out_path.display(), e);
                                COUNTER.inc_error();
                                continue;
                            }
                        };
                        match f.write_all(&b) {
                            Ok(_) => {}
                            Err(e) => {
                                eprintln!("Error writing to file {}: {}", out_path.display(), e);
                                COUNTER.inc_error();
                                continue;
                            }
                        }
                    }
                    types::OutputScriptType::Yaml => {
                        let enc = get_output_encoding(arg);
                        let s = match serde_yaml_ng::to_string(&mes) {
                            Ok(s) => s,
                            Err(e) => {
                                eprintln!("Error serializing messages to YAML: {}", e);
                                COUNTER.inc_error();
                                continue;
                            }
                        };
                        let b = match utils::encoding::encode_string(enc, &s, false) {
                            Ok(b) => b,
                            Err(e) => {
                                eprintln!("Error encoding string: {}", e);
                                COUNTER.inc_error();
                                continue;
                            }
                        };
                        let mut f = match utils::files::write_file(&out_path) {
                            Ok(f) => f,
                            Err(e) => {
                                eprintln!("Error writing file {}: {}", out_path.display(), e);
                                COUNTER.inc_error();
                                continue;
                            }
                        };
                        match f.write_all(&b) {
                            Ok(_) => {}
                            Err(e) => {
                                eprintln!("Error writing to file {}: {}", out_path.display(), e);
                                COUNTER.inc_error();
                                continue;
                            }
                        }
                    }
                    types::OutputScriptType::Pot | types::OutputScriptType::Po => {
                        let enc = get_output_encoding(arg);
                        let s = match output_scripts::po::PoDumper::new().dump(&mes, enc) {
                            Ok(s) => s,
                            Err(e) => {
                                eprintln!("Error dumping messages to PO format: {}", e);
                                COUNTER.inc_error();
                                continue;
                            }
                        };
                        let b = match utils::encoding::encode_string(enc, &s, false) {
                            Ok(b) => b,
                            Err(e) => {
                                eprintln!("Error encoding string: {}", e);
                                COUNTER.inc_error();
                                continue;
                            }
                        };
                        let mut f = match utils::files::write_file(&out_path) {
                            Ok(f) => f,
                            Err(e) => {
                                eprintln!("Error writing file {}: {}", out_path.display(), e);
                                COUNTER.inc_error();
                                continue;
                            }
                        };
                        match f.write_all(&b) {
                            Ok(_) => {}
                            Err(e) => {
                                eprintln!("Error writing to file {}: {}", out_path.display(), e);
                                COUNTER.inc_error();
                                continue;
                            }
                        }
                    }
                    types::OutputScriptType::Custom => {
                        let enc = get_output_encoding(arg);
                        if let Err(e) = script_file.custom_export(&out_path, enc) {
                            eprintln!("Error exporting custom script: {}", e);
                            COUNTER.inc_error();
                            continue;
                        }
                    }
                }
            } else {
                let out_path = std::path::PathBuf::from(&odir).join(f.name());
                match utils::files::make_sure_dir_exists(&out_path) {
                    Ok(_) => {}
                    Err(e) => {
                        eprintln!(
                            "Error creating parent directory for {}: {}",
                            out_path.display(),
                            e
                        );
                        COUNTER.inc_error();
                        continue;
                    }
                }
                match utils::files::write_file(&out_path) {
                    Ok(mut fi) => match std::io::copy(&mut f, &mut fi) {
                        Ok(_) => {}
                        Err(e) => {
                            eprintln!("Error writing to file {}: {}", out_path.display(), e);
                            COUNTER.inc_error();
                            continue;
                        }
                    },
                    Err(e) => {
                        eprintln!("Error writing file {}: {}", out_path.display(), e);
                        COUNTER.inc_error();
                        continue;
                    }
                }
            }
            COUNTER.inc(types::ScriptResult::Ok);
        }
        return Ok(types::ScriptResult::Ok);
    }
    #[cfg(feature = "image")]
    if script.is_image() {
        if script.is_multi_image() {
            for i in script.export_multi_image()? {
                let img_data = match i {
                    Ok(data) => data,
                    Err(e) => {
                        eprintln!("Error exporting image: {}", e);
                        COUNTER.inc_error();
                        if arg.backtrace {
                            eprintln!("Backtrace: {}", e.backtrace());
                        }
                        continue;
                    }
                };
                let out_type = arg.image_type.unwrap_or(types::ImageOutputType::Png);
                let f = match output.as_ref() {
                    Some(output) => {
                        if let Some(root_dir) = root_dir {
                            let f = std::path::PathBuf::from(filename);
                            let mut pb = std::path::PathBuf::from(output);
                            let rpath = utils::files::relative_path(root_dir, &f);
                            if let Some(parent) = rpath.parent() {
                                pb.push(parent);
                            }
                            if !arg.image_output_flat {
                                if let Some(fname) = f.file_name() {
                                    pb.push(fname);
                                    if arg.output_no_extra_ext {
                                        pb.remove_all_extensions();
                                    } else {
                                        pb.set_extension("");
                                    }
                                }
                                pb.push(img_data.name);
                            } else {
                                pb.push(format!(
                                    "{}_{}",
                                    f.file_stem().unwrap_or_default().to_string_lossy(),
                                    img_data.name
                                ));
                            }
                            pb.set_extension(out_type.as_ref());
                            pb.to_string_lossy().into_owned()
                        } else {
                            let mut pb = std::path::PathBuf::from(output);
                            if arg.image_output_flat {
                                let f = std::path::PathBuf::from(filename);
                                pb.push(format!(
                                    "{}_{}",
                                    f.file_stem().unwrap_or_default().to_string_lossy(),
                                    img_data.name
                                ));
                            } else {
                                pb.push(img_data.name);
                                if arg.output_no_extra_ext {
                                    pb.remove_all_extensions();
                                } else {
                                    pb.set_extension("");
                                }
                            }
                            pb.set_extension(out_type.as_ref());
                            pb.to_string_lossy().into_owned()
                        }
                    }
                    None => {
                        let mut pb = std::path::PathBuf::from(filename);
                        if arg.image_output_flat {
                            let f = std::path::PathBuf::from(filename);
                            pb.set_file_name(format!(
                                "{}_{}",
                                f.file_stem().unwrap_or_default().to_string_lossy(),
                                img_data.name
                            ));
                        } else {
                            if arg.output_no_extra_ext {
                                pb.remove_all_extensions();
                            } else {
                                pb.set_extension("");
                            }
                            pb.push(img_data.name);
                        }
                        pb.set_extension(out_type.as_ref());
                        pb.to_string_lossy().into_owned()
                    }
                };
                match utils::files::make_sure_dir_exists(&f) {
                    Ok(_) => {}
                    Err(e) => {
                        eprintln!("Error creating parent directory for {}: {}", f, e);
                        COUNTER.inc_error();
                        continue;
                    }
                }
                if let Some(threadpool) = img_threadpool {
                    let outpath = f.clone();
                    let config = config.clone();
                    threadpool.execute(
                        move |_| {
                            utils::img::encode_img(img_data.data, out_type, &outpath, &config)
                                .map_err(|e| {
                                    anyhow::anyhow!("Failed to encode image {}: {}", outpath, e)
                                })
                        },
                        true,
                    )?;
                    continue;
                } else {
                    match utils::img::encode_img(img_data.data, out_type, &f, &config) {
                        Ok(_) => {}
                        Err(e) => {
                            eprintln!("Error encoding image: {}", e);
                            COUNTER.inc_error();
                            continue;
                        }
                    }
                    COUNTER.inc(types::ScriptResult::Ok);
                }
            }
            return Ok(types::ScriptResult::Ok);
        }
        let img_data = script.export_image()?;
        let out_type = arg.image_type.unwrap_or_else(|| {
            if root_dir.is_some() {
                types::ImageOutputType::Png
            } else {
                output
                    .as_ref()
                    .and_then(|s| types::ImageOutputType::try_from(std::path::Path::new(s)).ok())
                    .unwrap_or(types::ImageOutputType::Png)
            }
        });
        let f = if filename == "-" {
            String::from("-")
        } else {
            match output.as_ref() {
                Some(output) => {
                    if let Some(root_dir) = root_dir {
                        let f = std::path::PathBuf::from(filename);
                        let mut pb = std::path::PathBuf::from(output);
                        let rpath = utils::files::relative_path(root_dir, &f);
                        if let Some(parent) = rpath.parent() {
                            pb.push(parent);
                        }
                        if let Some(fname) = f.file_name() {
                            pb.push(fname);
                        }
                        if arg.output_no_extra_ext {
                            pb.remove_all_extensions();
                        }
                        pb.set_extension(out_type.as_ref());
                        pb.to_string_lossy().into_owned()
                    } else {
                        output.clone()
                    }
                }
                None => {
                    let mut pb = std::path::PathBuf::from(filename);
                    if arg.output_no_extra_ext {
                        pb.remove_all_extensions();
                    }
                    pb.set_extension(out_type.as_ref());
                    pb.to_string_lossy().into_owned()
                }
            }
        };
        utils::files::make_sure_dir_exists(&f)?;
        if let Some(threadpool) = img_threadpool {
            let outpath = f.clone();
            let config = config.clone();
            threadpool.execute(
                move |_| {
                    utils::img::encode_img(img_data, out_type, &outpath, &config)
                        .map_err(|e| anyhow::anyhow!("Failed to encode image {}: {}", outpath, e))
                },
                true,
            )?;
            return Ok(types::ScriptResult::Uncount);
        } else {
            utils::img::encode_img(img_data, out_type, &f, &config)?;
        }
        return Ok(types::ScriptResult::Ok);
    }
    let mut of = match &arg.output_type {
        Some(t) => t.clone(),
        None => script.default_output_script_type(),
    };
    if !script.is_output_supported(of) {
        of = script.default_output_script_type();
    }
    if !arg.no_multi_message && !of.is_custom() && script.multiple_message_files() {
        let mmes = script.extract_multiple_messages()?;
        if mmes.is_empty() {
            eprintln!("No messages found");
            return Ok(types::ScriptResult::Ignored);
        }
        let ext = of.as_ref();
        let out_dir = if let Some(output) = output.as_ref() {
            if let Some(root_dir) = root_dir {
                let f = std::path::PathBuf::from(filename);
                let mut pb = std::path::PathBuf::from(output);
                let rpath = utils::files::relative_path(root_dir, &f);
                if let Some(parent) = rpath.parent() {
                    pb.push(parent);
                }
                if let Some(fname) = f.file_name() {
                    pb.push(fname);
                }
                if arg.output_no_extra_ext {
                    pb.remove_all_extensions();
                } else {
                    pb.set_extension("");
                }
                pb.to_string_lossy().into_owned()
            } else {
                output.clone()
            }
        } else {
            let mut pb = std::path::PathBuf::from(filename);
            if arg.output_no_extra_ext {
                pb.remove_all_extensions();
            } else {
                pb.set_extension("");
            }
            pb.to_string_lossy().into_owned()
        };
        std::fs::create_dir_all(&out_dir)?;
        let outdir = std::path::PathBuf::from(&out_dir);
        for (name, data) in mmes {
            let ofp = outdir.join(name).with_extension(ext);
            match of {
                types::OutputScriptType::Json => {
                    let enc = get_output_encoding(arg);
                    let s = match serde_json::to_string_pretty(&data) {
                        Ok(s) => s,
                        Err(e) => {
                            eprintln!("Error serializing messages to JSON: {}", e);
                            COUNTER.inc_error();
                            continue;
                        }
                    };
                    let b = match utils::encoding::encode_string(enc, &s, false) {
                        Ok(b) => b,
                        Err(e) => {
                            eprintln!("Error encoding string: {}", e);
                            COUNTER.inc_error();
                            continue;
                        }
                    };
                    let mut f = match utils::files::write_file(&ofp) {
                        Ok(f) => f,
                        Err(e) => {
                            eprintln!("Error writing file {}: {}", ofp.display(), e);
                            COUNTER.inc_error();
                            continue;
                        }
                    };
                    match f.write_all(&b) {
                        Ok(_) => {}
                        Err(e) => {
                            eprintln!("Error writing to file {}: {}", ofp.display(), e);
                            COUNTER.inc_error();
                            continue;
                        }
                    }
                }
                types::OutputScriptType::M3t
                | types::OutputScriptType::M3ta
                | types::OutputScriptType::M3tTxt => {
                    let enc = get_output_encoding(arg);
                    let s = output_scripts::m3t::M3tDumper::dump(&data, arg.m3t_no_quote);
                    let b = match utils::encoding::encode_string(enc, &s, false) {
                        Ok(b) => b,
                        Err(e) => {
                            eprintln!("Error encoding string: {}", e);
                            COUNTER.inc_error();
                            continue;
                        }
                    };
                    let mut f = match utils::files::write_file(&ofp) {
                        Ok(f) => f,
                        Err(e) => {
                            eprintln!("Error writing file {}: {}", ofp.display(), e);
                            COUNTER.inc_error();
                            continue;
                        }
                    };
                    match f.write_all(&b) {
                        Ok(_) => {}
                        Err(e) => {
                            eprintln!("Error writing to file {}: {}", ofp.display(), e);
                            COUNTER.inc_error();
                            continue;
                        }
                    }
                }
                types::OutputScriptType::Yaml => {
                    let enc = get_output_encoding(arg);
                    let s = match serde_yaml_ng::to_string(&data) {
                        Ok(s) => s,
                        Err(e) => {
                            eprintln!("Error serializing messages to YAML: {}", e);
                            COUNTER.inc_error();
                            continue;
                        }
                    };
                    let b = match utils::encoding::encode_string(enc, &s, false) {
                        Ok(b) => b,
                        Err(e) => {
                            eprintln!("Error encoding string: {}", e);
                            COUNTER.inc_error();
                            continue;
                        }
                    };
                    let mut f = match utils::files::write_file(&ofp) {
                        Ok(f) => f,
                        Err(e) => {
                            eprintln!("Error writing file {}: {}", ofp.display(), e);
                            COUNTER.inc_error();
                            continue;
                        }
                    };
                    match f.write_all(&b) {
                        Ok(_) => {}
                        Err(e) => {
                            eprintln!("Error writing to file {}: {}", ofp.display(), e);
                            COUNTER.inc_error();
                            continue;
                        }
                    }
                }
                types::OutputScriptType::Pot | types::OutputScriptType::Po => {
                    let enc = get_output_encoding(arg);
                    let s = match output_scripts::po::PoDumper::new().dump(&data, enc) {
                        Ok(s) => s,
                        Err(e) => {
                            eprintln!("Error dumping messages to PO format: {}", e);
                            COUNTER.inc_error();
                            continue;
                        }
                    };
                    let b = match utils::encoding::encode_string(enc, &s, false) {
                        Ok(b) => b,
                        Err(e) => {
                            eprintln!("Error encoding string: {}", e);
                            COUNTER.inc_error();
                            continue;
                        }
                    };
                    let mut f = match utils::files::write_file(&ofp) {
                        Ok(f) => f,
                        Err(e) => {
                            eprintln!("Error writing file {}: {}", ofp.display(), e);
                            COUNTER.inc_error();
                            continue;
                        }
                    };
                    match f.write_all(&b) {
                        Ok(_) => {}
                        Err(e) => {
                            eprintln!("Error writing to file {}: {}", ofp.display(), e);
                            COUNTER.inc_error();
                            continue;
                        }
                    }
                }
                types::OutputScriptType::Custom => {}
            }
            COUNTER.inc(types::ScriptResult::Ok);
        }
        return Ok(types::ScriptResult::Ok);
    }
    let mes = if of.is_custom() {
        Vec::new()
    } else {
        script.extract_messages()?
    };
    if !of.is_custom() && mes.is_empty() {
        eprintln!("No messages found");
        return Ok(types::ScriptResult::Ignored);
    }
    let ext = if of.is_custom() {
        script.custom_output_extension()
    } else {
        of.as_ref()
    };
    let f = if filename == "-" {
        String::from("-")
    } else {
        match output.as_ref() {
            Some(output) => {
                if let Some(root_dir) = root_dir {
                    let f = std::path::PathBuf::from(filename);
                    let mut pb = std::path::PathBuf::from(output);
                    let rpath = utils::files::relative_path(root_dir, &f);
                    if let Some(parent) = rpath.parent() {
                        pb.push(parent);
                    }
                    if let Some(fname) = f.file_name() {
                        pb.push(fname);
                    }
                    if arg.output_no_extra_ext {
                        pb.remove_all_extensions();
                    }
                    pb.set_extension(ext);
                    pb.to_string_lossy().into_owned()
                } else {
                    output.clone()
                }
            }
            None => {
                let mut pb = std::path::PathBuf::from(filename);
                if arg.output_no_extra_ext {
                    pb.remove_all_extensions();
                }
                pb.set_extension(ext);
                pb.to_string_lossy().into_owned()
            }
        }
    };
    utils::files::make_sure_dir_exists(&f)?;
    match of {
        types::OutputScriptType::Json => {
            let enc = get_output_encoding(arg);
            let s = serde_json::to_string_pretty(&mes)?;
            let b = utils::encoding::encode_string(enc, &s, false)?;
            let mut f = utils::files::write_file(&f)?;
            f.write_all(&b)?;
        }
        types::OutputScriptType::M3t
        | types::OutputScriptType::M3ta
        | types::OutputScriptType::M3tTxt => {
            let enc = get_output_encoding(arg);
            let s = output_scripts::m3t::M3tDumper::dump(&mes, arg.m3t_no_quote);
            let b = utils::encoding::encode_string(enc, &s, false)?;
            let mut f = utils::files::write_file(&f)?;
            f.write_all(&b)?;
        }
        types::OutputScriptType::Yaml => {
            let enc = get_output_encoding(arg);
            let s = serde_yaml_ng::to_string(&mes)?;
            let b = utils::encoding::encode_string(enc, &s, false)?;
            let mut f = utils::files::write_file(&f)?;
            f.write_all(&b)?;
        }
        types::OutputScriptType::Pot | types::OutputScriptType::Po => {
            let enc = get_output_encoding(arg);
            let s = output_scripts::po::PoDumper::new().dump(&mes, enc)?;
            let b = utils::encoding::encode_string(enc, &s, false)?;
            let mut f = utils::files::write_file(&f)?;
            f.write_all(&b)?;
        }
        types::OutputScriptType::Custom => {
            let enc = get_output_encoding(arg);
            script.custom_export(f.as_ref(), enc)?;
        }
    }
    Ok(types::ScriptResult::Ok)
}

pub fn import_script(
    filename: &str,
    arg: &args::Arg,
    config: std::sync::Arc<types::ExtraConfig>,
    imp_cfg: &args::ImportArgs,
    root_dir: Option<&std::path::Path>,
    name_csv: Option<&std::collections::HashMap<String, String>>,
    repl: Option<&types::ReplacementTable>,
    mut dep_graph: Option<&mut (String, Vec<String>)>,
) -> anyhow::Result<types::ScriptResult> {
    eprintln!("Importing {}", filename);
    if let Some(dep_graph) = dep_graph.as_mut() {
        dep_graph.1.push(filename.to_string());
    }
    let (script, builder) = parse_script(filename, arg, config.clone())?;
    if script.is_archive() {
        let odir = {
            let mut pb = std::path::PathBuf::from(&imp_cfg.output);
            let filename = std::path::PathBuf::from(filename);
            if let Some(root_dir) = root_dir {
                let rpath = utils::files::relative_path(root_dir, &filename);
                if let Some(parent) = rpath.parent() {
                    pb.push(parent);
                }
                if let Some(fname) = filename.file_name() {
                    pb.push(fname);
                }
            }
            pb.set_extension("");
            if let Some(ext) = script.archive_output_ext() {
                pb.set_extension(ext);
            }
            pb.to_string_lossy().into_owned()
        };
        let files: Vec<_> = script.iter_archive_filename()?.collect();
        let files = files.into_iter().filter_map(|f| f.ok()).collect::<Vec<_>>();
        let patched_f = if let Some(root_dir) = root_dir {
            let f = std::path::PathBuf::from(filename);
            let mut pb = std::path::PathBuf::from(&imp_cfg.patched);
            let rpath = utils::files::relative_path(root_dir, &f);
            if let Some(parent) = rpath.parent() {
                pb.push(parent);
            }
            if let Some(fname) = f.file_name() {
                pb.push(fname);
            }
            pb.set_extension(builder.extensions().first().unwrap_or(&""));
            pb.to_string_lossy().into_owned()
        } else {
            imp_cfg.patched.clone()
        };
        if let Some(dep_graph) = dep_graph.as_mut() {
            dep_graph.0 = patched_f.clone();
        }
        let files: Vec<_> = files.iter().map(|s| s.as_str()).collect();
        let pencoding = get_patched_encoding(imp_cfg, builder);
        let enc = get_patched_archive_encoding(imp_cfg, builder, pencoding);
        utils::files::make_sure_dir_exists(&patched_f)?;
        let mut arch = builder.create_archive(&patched_f, &files, enc, &config)?;
        for (index, filename) in script.iter_archive_filename()?.enumerate() {
            let filename = match filename {
                Ok(f) => f,
                Err(e) => {
                    eprintln!("Error reading archive filename: {}", e);
                    COUNTER.inc_error();
                    if arg.backtrace {
                        eprintln!("Backtrace: {}", e.backtrace());
                    }
                    continue;
                }
            };
            let mut f = match script.open_file(index) {
                Ok(f) => f,
                Err(e) => {
                    eprintln!("Error opening file {}: {}", filename, e);
                    COUNTER.inc_error();
                    if arg.backtrace {
                        eprintln!("Backtrace: {}", e.backtrace());
                    }
                    continue;
                }
            };
            if arg.force_script || f.is_script() {
                let mut writer = arch.new_file(f.name(), None)?;
                let (script_file, _) =
                    match parse_script_from_archive(&mut f, arg, config.clone(), &script) {
                        Ok(s) => s,
                        Err(e) => {
                            eprintln!("Error parsing script '{}' from archive: {}", filename, e);
                            COUNTER.inc_error();
                            if arg.backtrace {
                                eprintln!("Backtrace: {}", e.backtrace());
                            }
                            continue;
                        }
                    };
                let mut of = match &arg.output_type {
                    Some(t) => t.clone(),
                    None => script_file.default_output_script_type(),
                };
                if !script_file.is_output_supported(of) {
                    of = script_file.default_output_script_type();
                }
                if !arg.no_multi_message && !of.is_custom() && script_file.multiple_message_files()
                {
                    let out_dir = std::path::PathBuf::from(&odir)
                        .join(f.name())
                        .with_extension("");
                    let outfiles = utils::files::find_ext_files(
                        &out_dir.to_string_lossy(),
                        false,
                        &[of.as_ref()],
                    )?;
                    if outfiles.is_empty() {
                        if imp_cfg.warn_when_output_file_not_found {
                            eprintln!(
                                "Warning: No output files found in {}, using file from original archive.",
                                out_dir.display()
                            );
                            COUNTER.inc_warning();
                        } else {
                            COUNTER.inc(types::ScriptResult::Ignored);
                        }
                        continue;
                    }
                    if let Some(dep_graph) = dep_graph.as_mut() {
                        dep_graph.1.extend_from_slice(&outfiles);
                    }
                    let fmt = match imp_cfg.patched_format {
                        Some(fmt) => match fmt {
                            types::FormatType::Fixed => types::FormatOptions::Fixed {
                                length: imp_cfg.patched_fixed_length.unwrap_or(32),
                                keep_original: imp_cfg.patched_keep_original,
                                break_words: imp_cfg.patched_break_words,
                                insert_fullwidth_space_at_line_start: imp_cfg
                                    .patched_insert_fullwidth_space_at_line_start,
                                break_with_sentence: imp_cfg.patched_break_with_sentence,
                                #[cfg(feature = "jieba")]
                                break_chinese_words: !imp_cfg.patched_no_break_chinese_words,
                                #[cfg(feature = "jieba")]
                                jieba_dict: arg.jieba_dict.clone(),
                                no_remove_space_at_line_start: imp_cfg
                                    .patched_no_remove_space_at_line_start,
                            },
                            types::FormatType::None => types::FormatOptions::None,
                        },
                        None => script.default_format_type(),
                    };
                    let mut mmes = std::collections::HashMap::new();
                    for out_f in outfiles {
                        let name = utils::files::relative_path(&out_dir, &out_f)
                            .with_extension("")
                            .to_string_lossy()
                            .into_owned();
                        let mut mes = match of {
                            types::OutputScriptType::Json => {
                                let enc = get_output_encoding(arg);
                                let b = match utils::files::read_file(&out_f) {
                                    Ok(b) => b,
                                    Err(e) => {
                                        eprintln!("Error reading file {}: {}", out_f, e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                };
                                let s = match utils::encoding::decode_to_string(enc, &b, true) {
                                    Ok(s) => s,
                                    Err(e) => {
                                        eprintln!("Error decoding string: {}", e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                };
                                match serde_json::from_str::<Vec<types::Message>>(&s) {
                                    Ok(mes) => mes,
                                    Err(e) => {
                                        eprintln!("Error parsing JSON: {}", e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                }
                            }
                            types::OutputScriptType::M3t
                            | types::OutputScriptType::M3ta
                            | types::OutputScriptType::M3tTxt => {
                                let enc = get_output_encoding(arg);
                                let b = match utils::files::read_file(&out_f) {
                                    Ok(b) => b,
                                    Err(e) => {
                                        eprintln!("Error reading file {}: {}", out_f, e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                };
                                let s = match utils::encoding::decode_to_string(enc, &b, true) {
                                    Ok(s) => s,
                                    Err(e) => {
                                        eprintln!("Error decoding string: {}", e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                };
                                let mut parser = output_scripts::m3t::M3tParser::new(
                                    &s,
                                    arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
                                    arg.m3t_use_original_text,
                                );
                                match parser.parse() {
                                    Ok(mes) => mes,
                                    Err(e) => {
                                        eprintln!("Error parsing M3T: {}", e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                }
                            }
                            types::OutputScriptType::Yaml => {
                                let enc = get_output_encoding(arg);
                                let b = match utils::files::read_file(&out_f) {
                                    Ok(b) => b,
                                    Err(e) => {
                                        eprintln!("Error reading file {}: {}", out_f, e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                };
                                let s = match utils::encoding::decode_to_string(enc, &b, true) {
                                    Ok(s) => s,
                                    Err(e) => {
                                        eprintln!("Error decoding string: {}", e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                };
                                match serde_yaml_ng::from_str::<Vec<types::Message>>(&s) {
                                    Ok(mes) => mes,
                                    Err(e) => {
                                        eprintln!("Error parsing YAML: {}", e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                }
                            }
                            types::OutputScriptType::Pot | types::OutputScriptType::Po => {
                                let enc = get_output_encoding(arg);
                                let b = match utils::files::read_file(&out_f) {
                                    Ok(b) => b,
                                    Err(e) => {
                                        eprintln!("Error reading file {}: {}", out_f, e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                };
                                let s = match utils::encoding::decode_to_string(enc, &b, true) {
                                    Ok(s) => s,
                                    Err(e) => {
                                        eprintln!("Error decoding string: {}", e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                };
                                match output_scripts::po::PoParser::new(
                                    &s,
                                    arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
                                )
                                .parse()
                                {
                                    Ok(mes) => mes,
                                    Err(e) => {
                                        eprintln!("Error parsing PO: {}", e);
                                        COUNTER.inc_error();
                                        continue;
                                    }
                                }
                            }
                            types::OutputScriptType::Custom => Vec::new(),
                        };
                        if mes.is_empty() {
                            eprintln!(
                                "No messages found in {}, using file from original archive.",
                                out_f
                            );
                            continue;
                        }
                        match name_csv {
                            Some(name_table) => {
                                utils::name_replacement::replace_message(&mut mes, name_table);
                            }
                            None => {}
                        }
                        format::fmt_message(&mut mes, fmt.clone(), *builder.script_type())?;
                        mmes.insert(name, mes);
                    }
                    if mmes.is_empty() {
                        COUNTER.inc(types::ScriptResult::Ignored);
                        continue;
                    }
                    let encoding = get_patched_encoding(imp_cfg, builder);
                    match script_file.import_multiple_messages(
                        mmes,
                        writer,
                        f.name(),
                        encoding,
                        repl,
                    ) {
                        Ok(_) => {}
                        Err(e) => {
                            eprintln!("Error importing messages to script '{}': {}", filename, e);
                            COUNTER.inc_error();
                            if arg.backtrace {
                                eprintln!("Backtrace: {}", e.backtrace());
                            }
                            continue;
                        }
                    }
                    COUNTER.inc(types::ScriptResult::Ok);
                    continue;
                }
                #[cfg(feature = "image")]
                if script_file.is_image() {
                    let out_type = arg.image_type.unwrap_or(types::ImageOutputType::Png);
                    let mut out_path = std::path::PathBuf::from(&odir).join(f.name());
                    if arg.output_no_extra_ext {
                        out_path.remove_all_extensions();
                    }
                    out_path.set_extension(out_type.as_ref());
                    if !out_path.exists() {
                        out_path = std::path::PathBuf::from(&odir).join(f.name());
                        if !out_path.exists() {
                            if imp_cfg.warn_when_output_file_not_found {
                                eprintln!(
                                    "Warning: File {} does not exist, using file from original archive.",
                                    out_path.display()
                                );
                                COUNTER.inc_warning();
                            }
                            match std::io::copy(&mut f, &mut writer) {
                                Ok(_) => {}
                                Err(e) => {
                                    eprintln!(
                                        "Error writing to file {}: {}",
                                        out_path.display(),
                                        e
                                    );
                                    COUNTER.inc_error();
                                    continue;
                                }
                            }
                        } else {
                            if let Some(dep_graph) = dep_graph.as_mut() {
                                dep_graph.1.push(out_path.to_string_lossy().into_owned());
                            }
                            let file = match std::fs::File::open(&out_path) {
                                Ok(f) => f,
                                Err(e) => {
                                    eprintln!("Error opening file {}: {}", out_path.display(), e);
                                    COUNTER.inc_error();
                                    continue;
                                }
                            };
                            let mut f = std::io::BufReader::new(file);
                            match std::io::copy(&mut f, &mut writer) {
                                Ok(_) => {}
                                Err(e) => {
                                    eprintln!(
                                        "Error writing to file {}: {}",
                                        out_path.display(),
                                        e
                                    );
                                    COUNTER.inc_error();
                                    continue;
                                }
                            }
                        }
                    }
                    if let Some(dep_graph) = dep_graph.as_mut() {
                        dep_graph.1.push(out_path.to_string_lossy().into_owned());
                    }
                    let img_data =
                        match utils::img::decode_img(out_type, &out_path.to_string_lossy()) {
                            Ok(data) => data,
                            Err(e) => {
                                eprintln!("Error decoding image {}: {}", out_path.display(), e);
                                COUNTER.inc_error();
                                continue;
                            }
                        };
                    if let Err(err) =
                        script_file.import_image(img_data, &out_path.to_string_lossy(), writer)
                    {
                        eprintln!("Error importing image to script '{}': {}", filename, err);
                        COUNTER.inc_error();
                        if arg.backtrace {
                            eprintln!("Backtrace: {}", err.backtrace());
                        }
                        continue;
                    }
                    continue;
                }
                let mut out_path = std::path::PathBuf::from(&odir).join(f.name());
                if arg.output_no_extra_ext {
                    out_path.remove_all_extensions();
                }
                let ext = if of.is_custom() {
                    script_file.custom_output_extension()
                } else {
                    of.as_ref()
                };
                out_path.set_extension(ext);
                if !out_path.exists() {
                    out_path = std::path::PathBuf::from(&odir).join(f.name());
                    if !out_path.exists() {
                        if imp_cfg.warn_when_output_file_not_found {
                            eprintln!(
                                "Warning: File {} does not exist, using file from original archive.",
                                out_path.display()
                            );
                            COUNTER.inc_warning();
                        }
                        match std::io::copy(&mut f, &mut writer) {
                            Ok(_) => {}
                            Err(e) => {
                                eprintln!("Error writing to file {}: {}", out_path.display(), e);
                                COUNTER.inc_error();
                                continue;
                            }
                        }
                        COUNTER.inc(types::ScriptResult::Ok);
                        continue;
                    } else {
                        if let Some(dep_graph) = dep_graph.as_mut() {
                            dep_graph.1.push(out_path.to_string_lossy().into_owned());
                        }
                        let file = match std::fs::File::open(&out_path) {
                            Ok(f) => f,
                            Err(e) => {
                                eprintln!("Error opening file {}: {}", out_path.display(), e);
                                COUNTER.inc_error();
                                continue;
                            }
                        };
                        let mut f = std::io::BufReader::new(file);
                        match std::io::copy(&mut f, &mut writer) {
                            Ok(_) => {}
                            Err(e) => {
                                eprintln!("Error writing to file {}: {}", out_path.display(), e);
                                COUNTER.inc_error();
                                continue;
                            }
                        }
                        COUNTER.inc(types::ScriptResult::Ok);
                        continue;
                    }
                }
                if let Some(dep_graph) = dep_graph.as_mut() {
                    dep_graph.1.push(out_path.to_string_lossy().into_owned());
                }
                let mut mes = match of {
                    types::OutputScriptType::Json => {
                        let enc = get_output_encoding(arg);
                        let b = match utils::files::read_file(&out_path) {
                            Ok(b) => b,
                            Err(e) => {
                                eprintln!("Error reading file {}: {}", out_path.display(), e);
                                COUNTER.inc_error();
                                continue;
                            }
                        };
                        let s = match utils::encoding::decode_to_string(enc, &b, true) {
                            Ok(s) => s,
                            Err(e) => {
                                eprintln!("Error decoding string: {}", e);
                                COUNTER.inc_error();
                                continue;
                            }
                        };
                        match serde_json::from_str::<Vec<types::Message>>(&s) {
                            Ok(mes) => mes,
                            Err(e) => {
                                eprintln!("Error parsing JSON: {}", e);
                                COUNTER.inc_error();
                                continue;
                            }
                        }
                    }
                    types::OutputScriptType::M3t
                    | types::OutputScriptType::M3ta
                    | types::OutputScriptType::M3tTxt => {
                        let enc = get_output_encoding(arg);
                        let b = match utils::files::read_file(&out_path) {
                            Ok(b) => b,
                            Err(e) => {
                                eprintln!("Error reading file {}: {}", out_path.display(), e);
                                COUNTER.inc_error();
                                continue;
                            }
                        };
                        let s = match utils::encoding::decode_to_string(enc, &b, true) {
                            Ok(s) => s,
                            Err(e) => {
                                eprintln!("Error decoding string: {}", e);
                                COUNTER.inc_error();
                                continue;
                            }
                        };
                        let mut parser = output_scripts::m3t::M3tParser::new(
                            &s,
                            arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
                            arg.m3t_use_original_text,
                        );
                        match parser.parse() {
                            Ok(mes) => mes,
                            Err(e) => {
                                eprintln!("Error parsing M3T: {}", e);
                                COUNTER.inc_error();
                                continue;
                            }
                        }
                    }
                    types::OutputScriptType::Yaml => {
                        let enc = get_output_encoding(arg);
                        let b = match utils::files::read_file(&out_path) {
                            Ok(b) => b,
                            Err(e) => {
                                eprintln!("Error reading file {}: {}", out_path.display(), e);
                                COUNTER.inc_error();
                                continue;
                            }
                        };
                        let s = match utils::encoding::decode_to_string(enc, &b, true) {
                            Ok(s) => s,
                            Err(e) => {
                                eprintln!("Error decoding string: {}", e);
                                COUNTER.inc_error();
                                continue;
                            }
                        };
                        match serde_yaml_ng::from_str::<Vec<types::Message>>(&s) {
                            Ok(mes) => mes,
                            Err(e) => {
                                eprintln!("Error parsing YAML: {}", e);
                                COUNTER.inc_error();
                                continue;
                            }
                        }
                    }
                    types::OutputScriptType::Pot | types::OutputScriptType::Po => {
                        let enc = get_output_encoding(arg);
                        let b = match utils::files::read_file(&out_path) {
                            Ok(b) => b,
                            Err(e) => {
                                eprintln!("Error reading file {}: {}", out_path.display(), e);
                                COUNTER.inc_error();
                                continue;
                            }
                        };
                        let s = match utils::encoding::decode_to_string(enc, &b, true) {
                            Ok(s) => s,
                            Err(e) => {
                                eprintln!("Error decoding string: {}", e);
                                COUNTER.inc_error();
                                continue;
                            }
                        };
                        let mut parser = output_scripts::po::PoParser::new(
                            &s,
                            arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
                        );
                        match parser.parse() {
                            Ok(mes) => mes,
                            Err(e) => {
                                eprintln!("Error parsing PO: {}", e);
                                COUNTER.inc_error();
                                continue;
                            }
                        }
                    }
                    types::OutputScriptType::Custom => {
                        Vec::new() // Custom scripts handle their own messages
                    }
                };
                if !of.is_custom() && mes.is_empty() {
                    eprintln!("No messages found in {}", f.name());
                    COUNTER.inc(types::ScriptResult::Ignored);
                    continue;
                }
                let encoding = get_patched_encoding(imp_cfg, builder);
                if of.is_custom() {
                    let enc = get_output_encoding(arg);
                    match script_file.custom_import(
                        &out_path.to_string_lossy(),
                        writer,
                        encoding,
                        enc,
                    ) {
                        Ok(_) => {}
                        Err(e) => {
                            eprintln!("Error importing custom script: {}", e);
                            COUNTER.inc_error();
                            continue;
                        }
                    }
                    COUNTER.inc(types::ScriptResult::Ok);
                    continue;
                }
                let fmt = match imp_cfg.patched_format {
                    Some(fmt) => match fmt {
                        types::FormatType::Fixed => types::FormatOptions::Fixed {
                            length: imp_cfg.patched_fixed_length.unwrap_or(32),
                            keep_original: imp_cfg.patched_keep_original,
                            break_words: imp_cfg.patched_break_words,
                            insert_fullwidth_space_at_line_start: imp_cfg
                                .patched_insert_fullwidth_space_at_line_start,
                            break_with_sentence: imp_cfg.patched_break_with_sentence,
                            #[cfg(feature = "jieba")]
                            break_chinese_words: !imp_cfg.patched_no_break_chinese_words,
                            #[cfg(feature = "jieba")]
                            jieba_dict: arg.jieba_dict.clone(),
                            no_remove_space_at_line_start: imp_cfg
                                .patched_no_remove_space_at_line_start,
                        },
                        types::FormatType::None => types::FormatOptions::None,
                    },
                    None => script_file.default_format_type(),
                };
                match name_csv {
                    Some(name_table) => {
                        utils::name_replacement::replace_message(&mut mes, name_table);
                    }
                    None => {}
                }
                format::fmt_message(&mut mes, fmt, *builder.script_type())?;
                if let Err(e) = script_file.import_messages(
                    mes,
                    writer,
                    &out_path.to_string_lossy(),
                    encoding,
                    repl,
                ) {
                    eprintln!("Error importing messages: {}", e);
                    COUNTER.inc_error();
                    continue;
                }
            } else {
                let out_path = std::path::PathBuf::from(&odir).join(f.name());
                let size = if out_path.is_file() {
                    match std::fs::metadata(&out_path) {
                        Ok(meta) => Some(meta.len()),
                        Err(e) => {
                            eprintln!(
                                "Error getting metadata for file {}: {}",
                                out_path.display(),
                                e
                            );
                            COUNTER.inc_error();
                            continue;
                        }
                    }
                } else {
                    None
                };
                let mut writer = arch.new_file_non_seek(f.name(), size)?;
                if out_path.is_file() {
                    if let Some(dep_graph) = dep_graph.as_mut() {
                        dep_graph.1.push(out_path.to_string_lossy().into_owned());
                    }
                    let f = match std::fs::File::open(&out_path) {
                        Ok(f) => f,
                        Err(e) => {
                            eprintln!("Error opening file {}: {}", out_path.display(), e);
                            COUNTER.inc_error();
                            continue;
                        }
                    };
                    let mut f = std::io::BufReader::new(f);
                    match std::io::copy(&mut f, &mut writer) {
                        Ok(_) => {}
                        Err(e) => {
                            eprintln!("Error writing to file {}: {}", out_path.display(), e);
                            COUNTER.inc_error();
                            continue;
                        }
                    }
                } else {
                    eprintln!(
                        "Warning: File {} does not exist, use file from original archive.",
                        out_path.display()
                    );
                    COUNTER.inc_warning();
                    match std::io::copy(&mut f, &mut writer) {
                        Ok(_) => {}
                        Err(e) => {
                            eprintln!("Error writing to file {}: {}", out_path.display(), e);
                            COUNTER.inc_error();
                            continue;
                        }
                    }
                }
            }
            COUNTER.inc(types::ScriptResult::Ok);
        }
        arch.write_header()?;
        return Ok(types::ScriptResult::Ok);
    }
    #[cfg(feature = "image")]
    if script.is_image() {
        let out_type = arg.image_type.unwrap_or_else(|| {
            if root_dir.is_some() {
                types::ImageOutputType::Png
            } else {
                types::ImageOutputType::try_from(std::path::Path::new(&imp_cfg.output))
                    .unwrap_or(types::ImageOutputType::Png)
            }
        });
        let out_f = if let Some(root_dir) = root_dir {
            let f = std::path::PathBuf::from(filename);
            let mut pb = std::path::PathBuf::from(&imp_cfg.output);
            let rpath = utils::files::relative_path(root_dir, &f);
            if let Some(parent) = rpath.parent() {
                pb.push(parent);
            }
            if let Some(fname) = f.file_name() {
                pb.push(fname);
            }
            if arg.output_no_extra_ext {
                pb.remove_all_extensions();
            }
            pb.set_extension(out_type.as_ref());
            pb.to_string_lossy().into_owned()
        } else {
            imp_cfg.output.clone()
        };
        if let Some(dep_graph) = dep_graph.as_mut() {
            dep_graph.1.push(out_f.clone());
        }
        let data = utils::img::decode_img(out_type, &out_f)?;
        let patched_f = if let Some(root_dir) = root_dir {
            let f = std::path::PathBuf::from(filename);
            let mut pb = std::path::PathBuf::from(&imp_cfg.patched);
            let rpath = utils::files::relative_path(root_dir, &f);
            if let Some(parent) = rpath.parent() {
                pb.push(parent);
            }
            if let Some(fname) = f.file_name() {
                pb.push(fname);
            }
            pb.set_extension(builder.extensions().first().unwrap_or(&""));
            pb.to_string_lossy().into_owned()
        } else {
            imp_cfg.patched.clone()
        };
        if let Some(dep_graph) = dep_graph.as_mut() {
            dep_graph.0 = patched_f.clone();
        }
        utils::files::make_sure_dir_exists(&patched_f)?;
        script.import_image_filename(data, &out_f, &patched_f)?;
        return Ok(types::ScriptResult::Ok);
    }
    let mut of = match &arg.output_type {
        Some(t) => t.clone(),
        None => script.default_output_script_type(),
    };
    if !script.is_output_supported(of) {
        of = script.default_output_script_type();
    }
    if !arg.no_multi_message && !of.is_custom() && script.multiple_message_files() {
        let out_dir = if let Some(root_dir) = root_dir {
            let f = std::path::PathBuf::from(filename);
            let mut pb = std::path::PathBuf::from(&imp_cfg.output);
            let rpath = utils::files::relative_path(root_dir, &f);
            if let Some(parent) = rpath.parent() {
                pb.push(parent);
            }
            if let Some(fname) = f.file_name() {
                pb.push(fname);
            }
            if arg.output_no_extra_ext {
                pb.remove_all_extensions();
            } else {
                pb.set_extension("");
            }
            pb.to_string_lossy().into_owned()
        } else {
            imp_cfg.output.clone()
        };
        let outfiles = utils::files::find_ext_files(&out_dir, false, &[of.as_ref()])?;
        if outfiles.is_empty() {
            eprintln!("No output files found");
            return Ok(types::ScriptResult::Ignored);
        }
        if let Some(dep_graph) = dep_graph.as_mut() {
            dep_graph.1.extend_from_slice(&outfiles);
        }
        let fmt = match imp_cfg.patched_format {
            Some(fmt) => match fmt {
                types::FormatType::Fixed => types::FormatOptions::Fixed {
                    length: imp_cfg.patched_fixed_length.unwrap_or(32),
                    keep_original: imp_cfg.patched_keep_original,
                    break_words: imp_cfg.patched_break_words,
                    insert_fullwidth_space_at_line_start: imp_cfg
                        .patched_insert_fullwidth_space_at_line_start,
                    break_with_sentence: imp_cfg.patched_break_with_sentence,
                    #[cfg(feature = "jieba")]
                    break_chinese_words: !imp_cfg.patched_no_break_chinese_words,
                    #[cfg(feature = "jieba")]
                    jieba_dict: arg.jieba_dict.clone(),
                    no_remove_space_at_line_start: imp_cfg.patched_no_remove_space_at_line_start,
                },
                types::FormatType::None => types::FormatOptions::None,
            },
            None => script.default_format_type(),
        };
        let mut mmes = std::collections::HashMap::new();
        for out_f in outfiles {
            let name = utils::files::relative_path(&out_dir, &out_f)
                .with_extension("")
                .to_string_lossy()
                .into_owned();
            let mut mes = match of {
                types::OutputScriptType::Json => {
                    let enc = get_output_encoding(arg);
                    let b = utils::files::read_file(&out_f)?;
                    let s = utils::encoding::decode_to_string(enc, &b, true)?;
                    serde_json::from_str::<Vec<types::Message>>(&s)?
                }
                types::OutputScriptType::M3t
                | types::OutputScriptType::M3ta
                | types::OutputScriptType::M3tTxt => {
                    let enc = get_output_encoding(arg);
                    let b = utils::files::read_file(&out_f)?;
                    let s = utils::encoding::decode_to_string(enc, &b, true)?;
                    let mut parser = output_scripts::m3t::M3tParser::new(
                        &s,
                        arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
                        arg.m3t_use_original_text,
                    );
                    parser.parse()?
                }
                types::OutputScriptType::Yaml => {
                    let enc = get_output_encoding(arg);
                    let b = utils::files::read_file(&out_f)?;
                    let s = utils::encoding::decode_to_string(enc, &b, true)?;
                    serde_yaml_ng::from_str::<Vec<types::Message>>(&s)?
                }
                types::OutputScriptType::Pot | types::OutputScriptType::Po => {
                    let enc = get_output_encoding(arg);
                    let b = utils::files::read_file(&out_f)?;
                    let s = utils::encoding::decode_to_string(enc, &b, true)?;
                    let mut parser = output_scripts::po::PoParser::new(
                        &s,
                        arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
                    );
                    parser.parse()?
                }
                types::OutputScriptType::Custom => {
                    Vec::new() // Custom scripts handle their own messages
                }
            };
            if mes.is_empty() {
                eprintln!("No messages found in {}", out_f);
                continue;
            }
            match name_csv {
                Some(name_table) => {
                    utils::name_replacement::replace_message(&mut mes, name_table);
                }
                None => {}
            }
            format::fmt_message(&mut mes, fmt.clone(), *builder.script_type())?;
            mmes.insert(name, mes);
        }
        let patched_f = if let Some(root_dir) = root_dir {
            let f = std::path::PathBuf::from(filename);
            let mut pb = std::path::PathBuf::from(&imp_cfg.patched);
            let rpath = utils::files::relative_path(root_dir, &f);
            if let Some(parent) = rpath.parent() {
                pb.push(parent);
            }
            if let Some(fname) = f.file_name() {
                pb.push(fname);
            }
            pb.set_extension(builder.extensions().first().unwrap_or(&""));
            pb.to_string_lossy().into_owned()
        } else {
            imp_cfg.patched.clone()
        };
        if let Some(dep_graph) = dep_graph.as_mut() {
            dep_graph.0 = patched_f.clone();
        }
        utils::files::make_sure_dir_exists(&patched_f)?;
        let encoding = get_patched_encoding(imp_cfg, builder);
        script.import_multiple_messages_filename(mmes, &patched_f, encoding, repl)?;
        return Ok(types::ScriptResult::Ok);
    }
    let out_f = if let Some(root_dir) = root_dir {
        let f = std::path::PathBuf::from(filename);
        let mut pb = std::path::PathBuf::from(&imp_cfg.output);
        let rpath = utils::files::relative_path(root_dir, &f);
        if let Some(parent) = rpath.parent() {
            pb.push(parent);
        }
        if let Some(fname) = f.file_name() {
            pb.push(fname);
        }
        if arg.output_no_extra_ext {
            pb.remove_all_extensions();
        }
        pb.set_extension(if of.is_custom() {
            script.custom_output_extension()
        } else {
            of.as_ref()
        });
        pb.to_string_lossy().into_owned()
    } else {
        imp_cfg.output.clone()
    };
    if !std::fs::exists(&out_f).unwrap_or(false) {
        eprintln!("Output file does not exist");
        return Ok(types::ScriptResult::Ignored);
    }
    if let Some(dep_graph) = dep_graph.as_mut() {
        dep_graph.1.push(out_f.clone());
    }
    let mut mes = match of {
        types::OutputScriptType::Json => {
            let enc = get_output_encoding(arg);
            let b = utils::files::read_file(&out_f)?;
            let s = utils::encoding::decode_to_string(enc, &b, true)?;
            serde_json::from_str::<Vec<types::Message>>(&s)?
        }
        types::OutputScriptType::M3t
        | types::OutputScriptType::M3ta
        | types::OutputScriptType::M3tTxt => {
            let enc = get_output_encoding(arg);
            let b = utils::files::read_file(&out_f)?;
            let s = utils::encoding::decode_to_string(enc, &b, true)?;
            let mut parser = output_scripts::m3t::M3tParser::new(
                &s,
                arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
                arg.m3t_use_original_text,
            );
            parser.parse()?
        }
        types::OutputScriptType::Yaml => {
            let enc = get_output_encoding(arg);
            let b = utils::files::read_file(&out_f)?;
            let s = utils::encoding::decode_to_string(enc, &b, true)?;
            serde_yaml_ng::from_str::<Vec<types::Message>>(&s)?
        }
        types::OutputScriptType::Pot | types::OutputScriptType::Po => {
            let enc = get_output_encoding(arg);
            let b = utils::files::read_file(&out_f)?;
            let s = utils::encoding::decode_to_string(enc, &b, true)?;
            let mut parser = output_scripts::po::PoParser::new(
                &s,
                arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
            );
            parser.parse()?
        }
        types::OutputScriptType::Custom => {
            Vec::new() // Custom scripts handle their own messages
        }
    };
    if !of.is_custom() && mes.is_empty() {
        eprintln!("No messages found");
        return Ok(types::ScriptResult::Ignored);
    }
    let encoding = get_patched_encoding(imp_cfg, builder);
    let patched_f = if let Some(root_dir) = root_dir {
        let f = std::path::PathBuf::from(filename);
        let mut pb = std::path::PathBuf::from(&imp_cfg.patched);
        let rpath = utils::files::relative_path(root_dir, &f);
        if let Some(parent) = rpath.parent() {
            pb.push(parent);
        }
        if let Some(fname) = f.file_name() {
            pb.push(fname);
        }
        pb.set_extension(builder.extensions().first().unwrap_or(&""));
        pb.to_string_lossy().into_owned()
    } else {
        imp_cfg.patched.clone()
    };
    if let Some(dep_graph) = dep_graph.as_mut() {
        dep_graph.0 = patched_f.clone();
    }
    utils::files::make_sure_dir_exists(&patched_f)?;
    if of.is_custom() {
        let enc = get_output_encoding(arg);
        script.custom_import_filename(&out_f, &patched_f, encoding, enc)?;
        return Ok(types::ScriptResult::Ok);
    }
    let fmt = match imp_cfg.patched_format {
        Some(fmt) => match fmt {
            types::FormatType::Fixed => types::FormatOptions::Fixed {
                length: imp_cfg.patched_fixed_length.unwrap_or(32),
                keep_original: imp_cfg.patched_keep_original,
                break_words: imp_cfg.patched_break_words,
                insert_fullwidth_space_at_line_start: imp_cfg
                    .patched_insert_fullwidth_space_at_line_start,
                break_with_sentence: imp_cfg.patched_break_with_sentence,
                #[cfg(feature = "jieba")]
                break_chinese_words: !imp_cfg.patched_no_break_chinese_words,
                #[cfg(feature = "jieba")]
                jieba_dict: arg.jieba_dict.clone(),
                no_remove_space_at_line_start: imp_cfg.patched_no_remove_space_at_line_start,
            },
            types::FormatType::None => types::FormatOptions::None,
        },
        None => script.default_format_type(),
    };
    match name_csv {
        Some(name_table) => {
            utils::name_replacement::replace_message(&mut mes, name_table);
        }
        None => {}
    }
    format::fmt_message(&mut mes, fmt, *builder.script_type())?;

    script.import_messages_filename(mes, &patched_f, encoding, repl)?;
    Ok(types::ScriptResult::Ok)
}

pub fn pack_archive(
    input: &str,
    output: Option<&str>,
    arg: &args::Arg,
    config: std::sync::Arc<types::ExtraConfig>,
    backslash: bool,
) -> anyhow::Result<()> {
    let typ = match &arg.script_type {
        Some(t) => t,
        None => {
            return Err(anyhow::anyhow!("No script type specified"));
        }
    };
    let (mut files, isdir) = utils::files::collect_files(input, arg.recursive, true)
        .map_err(|e| anyhow::anyhow!("Error collecting files: {}", e))?;
    if !isdir {
        return Err(anyhow::anyhow!("Input must be a directory for packing"));
    }
    let re_files: Vec<String> = files
        .iter()
        .filter_map(|f| {
            std::path::PathBuf::from(f)
                .strip_prefix(input)
                .ok()
                .and_then(|p| {
                    p.to_str().map(|s| {
                        if backslash {
                            s.replace("/", "\\").trim_start_matches("\\").to_owned()
                        } else {
                            s.replace("\\", "/").trim_start_matches("/").to_owned()
                        }
                    })
                })
        })
        .collect();
    let mut reff = re_files.iter().map(|s| s.as_str()).collect::<Vec<_>>();
    let builder = scripts::BUILDER
        .iter()
        .find(|b| b.script_type() == typ)
        .ok_or_else(|| anyhow::anyhow!("Unsupported script type"))?;
    let output = match output {
        Some(output) => output.to_string(),
        None => {
            let mut pb = std::path::PathBuf::from(input);
            let ext = builder.extensions().first().unwrap_or(&"unk");
            pb.set_extension(ext);
            if pb.to_string_lossy() == input {
                pb.set_extension(format!("{}.{}", ext, ext));
            }
            pb.to_string_lossy().into_owned()
        }
    };
    let mut archive = builder.create_archive(
        &output,
        &reff,
        get_archived_encoding(arg, builder, get_encoding(arg, builder)),
        &config,
    )?;
    if let Some(pre) = archive.prelist()? {
        let mut index = 0;
        for name in pre {
            let name = name?;
            if let Some(pos) = reff.iter().position(|&n| n == name) {
                let re = reff.remove(pos);
                let ff = files.remove(pos);
                reff.insert(index, re);
                files.insert(index, ff);
                index += 1;
            } else {
                eprintln!("Warning: Prelist file {} not found in input files", name);
                COUNTER.inc_warning();
            }
        }
    }
    for (file, name) in files.iter().zip(reff) {
        let mut f = match std::fs::File::open(file) {
            Ok(f) => f,
            Err(e) => {
                eprintln!("Error opening file {}: {}", file, e);
                COUNTER.inc_error();
                continue;
            }
        };
        let size = match std::fs::metadata(file) {
            Ok(meta) => meta.len(),
            Err(e) => {
                eprintln!("Error getting metadata for file {}: {}", file, e);
                COUNTER.inc_error();
                continue;
            }
        };
        let mut wf = match archive.new_file_non_seek(name, Some(size)) {
            Ok(f) => f,
            Err(e) => {
                eprintln!("Error creating file {} in archive: {}", name, e);
                COUNTER.inc_error();
                continue;
            }
        };
        match std::io::copy(&mut f, &mut wf) {
            Ok(_) => {
                COUNTER.inc(types::ScriptResult::Ok);
            }
            Err(e) => {
                eprintln!("Error writing to file {} in archive: {}", name, e);
                COUNTER.inc_error();
                continue;
            }
        }
    }
    archive.write_header()?;
    Ok(())
}

pub fn pack_archive_v2(
    input: &[&str],
    output: Option<&str>,
    arg: &args::Arg,
    config: std::sync::Arc<types::ExtraConfig>,
    backslash: bool,
    no_dir: bool,
    dep_file: Option<&str>,
) -> anyhow::Result<()> {
    let typ = match &arg.script_type {
        Some(t) => t,
        None => {
            return Err(anyhow::anyhow!("No script type specified"));
        }
    };
    // File List in real path
    let mut files = Vec::new();
    // File list in archive path
    let mut re_files = Vec::new();
    for i in input {
        let (fs, is_dir) = utils::files::collect_files(i, arg.recursive, true)?;
        if is_dir {
            files.extend_from_slice(&fs);
            for n in fs.iter() {
                if no_dir {
                    if let Some(p) = std::path::PathBuf::from(n).file_name() {
                        re_files.push(p.to_string_lossy().into_owned());
                    } else {
                        return Err(anyhow::anyhow!("Failed to get filename from {}", n));
                    }
                } else {
                    if let Some(p) = {
                        std::path::PathBuf::from(n)
                            .strip_prefix(i)
                            .ok()
                            .and_then(|p| {
                                p.to_str().map(|s| {
                                    if backslash {
                                        s.replace("/", "\\").trim_start_matches("\\").to_owned()
                                    } else {
                                        s.replace("\\", "/").trim_start_matches("/").to_owned()
                                    }
                                })
                            })
                    } {
                        re_files.push(p);
                    } else {
                        return Err(anyhow::anyhow!("Failed to get relative path from {}", n));
                    }
                }
            }
        } else {
            files.push(i.to_string());
            let p = std::path::PathBuf::from(i);
            if let Some(fname) = p.file_name() {
                re_files.push(fname.to_string_lossy().into_owned());
            } else {
                return Err(anyhow::anyhow!("Failed to get filename from {}", i));
            }
        }
    }
    let mut reff = re_files.iter().map(|s| s.as_str()).collect::<Vec<_>>();
    let builder = scripts::BUILDER
        .iter()
        .find(|b| b.script_type() == typ)
        .ok_or_else(|| anyhow::anyhow!("Unsupported script type"))?;
    let output = match output {
        Some(output) => output.to_string(),
        None => {
            let mut pb = std::path::PathBuf::from(input[0]);
            let ext = builder.extensions().first().unwrap_or(&"unk");
            pb.set_extension(ext);
            if pb.to_string_lossy() == input[0] {
                pb.set_extension(format!("{}.{}", ext, ext));
            }
            pb.to_string_lossy().into_owned()
        }
    };
    let mut archive = builder.create_archive(
        &output,
        &reff,
        get_archived_encoding(arg, builder, get_encoding(arg, builder)),
        &config,
    )?;
    if let Some(pre) = archive.prelist()? {
        let mut index = 0;
        for name in pre {
            let name = name?;
            if let Some(pos) = reff.iter().position(|&n| n == name) {
                let re = reff.remove(pos);
                let ff = files.remove(pos);
                reff.insert(index, re);
                files.insert(index, ff);
                index += 1;
            } else {
                eprintln!("Warning: Prelist file {} not found in input files", name);
                COUNTER.inc_warning();
            }
        }
    }
    if let Some(dep_file) = dep_file {
        let df = std::fs::File::create(dep_file)
            .map_err(|e| anyhow::anyhow!("Failed to create dep file {}: {}", dep_file, e))?;
        let mut df = std::io::BufWriter::new(df);
        use std::io::Write;
        write!(df, "{}:", escape_dep_string(&output))
            .map_err(|e| anyhow::anyhow!("Failed to write to dep file {}: {}", dep_file, e))?;
        for f in &files {
            write!(df, " {}", escape_dep_string(f))
                .map_err(|e| anyhow::anyhow!("Failed to write to dep file {}: {}", dep_file, e))?;
        }
        writeln!(df)
            .map_err(|e| anyhow::anyhow!("Failed to write to dep file {}: {}", dep_file, e))?;
    }
    for (file, name) in files.iter().zip(reff) {
        let mut f = match std::fs::File::open(file) {
            Ok(f) => f,
            Err(e) => {
                eprintln!("Error opening file {}: {}", file, e);
                COUNTER.inc_error();
                continue;
            }
        };
        let size = match std::fs::metadata(file) {
            Ok(meta) => meta.len(),
            Err(e) => {
                eprintln!("Error getting metadata for file {}: {}", file, e);
                COUNTER.inc_error();
                continue;
            }
        };
        let mut wf = match archive.new_file_non_seek(name, Some(size)) {
            Ok(f) => f,
            Err(e) => {
                eprintln!("Error creating file {} in archive: {}", name, e);
                COUNTER.inc_error();
                continue;
            }
        };
        match std::io::copy(&mut f, &mut wf) {
            Ok(_) => {
                COUNTER.inc(types::ScriptResult::Ok);
            }
            Err(e) => {
                eprintln!("Error writing to file {} in archive: {}", name, e);
                COUNTER.inc_error();
                continue;
            }
        }
    }
    archive.write_header()?;
    Ok(())
}

pub fn unpack_archive(
    filename: &str,
    arg: &args::Arg,
    config: std::sync::Arc<types::ExtraConfig>,
    output: &Option<String>,
    root_dir: Option<&std::path::Path>,
) -> anyhow::Result<types::ScriptResult> {
    eprintln!("Unpacking {}", filename);
    let script = parse_script(filename, arg, config)?.0;
    if !script.is_archive() {
        return Ok(types::ScriptResult::Ignored);
    }
    let odir = match output.as_ref() {
        Some(output) => {
            let mut pb = std::path::PathBuf::from(output);
            let filename = std::path::PathBuf::from(filename);
            if let Some(root_dir) = root_dir {
                let rpath = utils::files::relative_path(root_dir, &filename);
                if let Some(parent) = rpath.parent() {
                    pb.push(parent);
                }
                if let Some(fname) = filename.file_name() {
                    pb.push(fname);
                }
            }
            pb.set_extension("");
            if let Some(ext) = script.archive_output_ext() {
                pb.set_extension(ext);
            }
            pb.to_string_lossy().into_owned()
        }
        None => {
            let mut pb = std::path::PathBuf::from(filename);
            pb.set_extension("");
            if let Some(ext) = script.archive_output_ext() {
                pb.set_extension(ext);
            }
            pb.to_string_lossy().into_owned()
        }
    };
    if !std::fs::exists(&odir)? {
        std::fs::create_dir_all(&odir)?;
    }
    for (index, filename) in script.iter_archive_filename()?.enumerate() {
        let filename = match filename {
            Ok(f) => f,
            Err(e) => {
                eprintln!("Error reading archive filename: {}", e);
                COUNTER.inc_error();
                if arg.backtrace {
                    eprintln!("Backtrace: {}", e.backtrace());
                }
                continue;
            }
        };
        let mut f = match script.open_file(index) {
            Ok(f) => f,
            Err(e) => {
                eprintln!("Error opening file {}: {}", filename, e);
                COUNTER.inc_error();
                if arg.backtrace {
                    eprintln!("Backtrace: {}", e.backtrace());
                }
                continue;
            }
        };
        let out_path = std::path::PathBuf::from(&odir).join(f.name());
        match utils::files::make_sure_dir_exists(&out_path) {
            Ok(_) => {}
            Err(e) => {
                eprintln!(
                    "Error creating parent directory for {}: {}",
                    out_path.display(),
                    e
                );
                COUNTER.inc_error();
                continue;
            }
        }
        match utils::files::write_file(&out_path) {
            Ok(mut fi) => match std::io::copy(&mut f, &mut fi) {
                Ok(_) => {}
                Err(e) => {
                    eprintln!("Error writing to file {}: {}", out_path.display(), e);
                    COUNTER.inc_error();
                    continue;
                }
            },
            Err(e) => {
                eprintln!("Error writing file {}: {}", out_path.display(), e);
                COUNTER.inc_error();
                continue;
            }
        }
        COUNTER.inc(types::ScriptResult::Ok);
    }
    Ok(types::ScriptResult::Ok)
}

pub fn create_file(
    input: &str,
    output: Option<&str>,
    arg: &args::Arg,
    config: std::sync::Arc<types::ExtraConfig>,
) -> anyhow::Result<()> {
    let typ = match &arg.script_type {
        Some(t) => t,
        None => {
            return Err(anyhow::anyhow!("No script type specified"));
        }
    };
    let builder = scripts::BUILDER
        .iter()
        .find(|b| b.script_type() == typ)
        .ok_or_else(|| anyhow::anyhow!("Unsupported script type"))?;

    #[cfg(feature = "image")]
    if builder.is_image() {
        if !builder.can_create_image_file() {
            return Err(anyhow::anyhow!(
                "Script type {:?} does not support image file creation",
                typ
            ));
        }
        let data = utils::img::decode_img(
            arg.image_type.unwrap_or_else(|| {
                types::ImageOutputType::try_from(std::path::Path::new(input))
                    .unwrap_or(types::ImageOutputType::Png)
            }),
            input,
        )?;
        let output = match output {
            Some(output) => output.to_string(),
            None => {
                let mut pb = std::path::PathBuf::from(input);
                let ext = builder.extensions().first().unwrap_or(&"");
                pb.set_extension(ext);
                if pb.to_string_lossy() == input {
                    if ext.is_empty() {
                        pb.set_extension("unk");
                    } else {
                        pb.set_extension(format!("{}.{}", ext, ext));
                    }
                }
                pb.to_string_lossy().into_owned()
            }
        };
        builder.create_image_file_filename(data, &output, input, &config)?;
        return Ok(());
    }

    if !builder.can_create_file() {
        return Err(anyhow::anyhow!(
            "Script type {:?} does not support file creation",
            typ
        ));
    }

    let output = match output {
        Some(output) => output.to_string(),
        None => {
            let mut pb = std::path::PathBuf::from(input);
            let ext = builder.extensions().first().unwrap_or(&"");
            pb.set_extension(ext);
            if pb.to_string_lossy() == input {
                if ext.is_empty() {
                    pb.set_extension("unk");
                } else {
                    pb.set_extension(format!("{}.{}", ext, ext));
                }
            }
            pb.to_string_lossy().into_owned()
        }
    };

    crate::utils::files::make_sure_dir_exists(&output)?;

    builder.create_file_filename(
        input,
        &output,
        get_encoding(arg, builder),
        get_output_encoding(arg),
        &config,
    )?;
    Ok(())
}

pub fn parse_output_script_as_extend(
    input: &str,
    typ: types::OutputScriptType,
    arg: &args::Arg,
) -> anyhow::Result<Vec<types::ExtendedMessage>> {
    match typ {
        types::OutputScriptType::M3t
        | types::OutputScriptType::M3ta
        | types::OutputScriptType::M3tTxt => {
            let enc = get_input_output_script_encoding(arg);
            let b = utils::files::read_file(input)?;
            let s = utils::encoding::decode_to_string(enc, &b, true)?;
            let mut parser = output_scripts::m3t::M3tParser::new(
                &s,
                arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
                arg.m3t_use_original_text,
            );
            let mes = parser.parse_as_extend()?;
            Ok(mes)
        }
        types::OutputScriptType::Po | types::OutputScriptType::Pot => {
            let enc = get_input_output_script_encoding(arg);
            let b = utils::files::read_file(input)?;
            let s = utils::encoding::decode_to_string(enc, &b, true)?;
            let mut parser = output_scripts::po::PoParser::new(
                &s,
                arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
            );
            let mes = parser.parse_as_extend()?;
            Ok(mes)
        }
        _ => Err(anyhow::anyhow!(
            "Output script type {:?} does not support extended messages",
            typ
        )),
    }
}

pub fn parse_output_script(
    input: &str,
    typ: types::OutputScriptType,
    arg: &args::Arg,
) -> anyhow::Result<Vec<types::Message>> {
    match typ {
        types::OutputScriptType::M3t
        | types::OutputScriptType::M3ta
        | types::OutputScriptType::M3tTxt => {
            let enc = get_input_output_script_encoding(arg);
            let b = utils::files::read_file(input)?;
            let s = utils::encoding::decode_to_string(enc, &b, true)?;
            let mut parser = output_scripts::m3t::M3tParser::new(
                &s,
                arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
                arg.m3t_use_original_text,
            );
            let mes = parser.parse()?;
            Ok(mes)
        }
        types::OutputScriptType::Po | types::OutputScriptType::Pot => {
            let enc = get_input_output_script_encoding(arg);
            let b = utils::files::read_file(input)?;
            let s = utils::encoding::decode_to_string(enc, &b, true)?;
            let mut parser = output_scripts::po::PoParser::new(
                &s,
                arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
            );
            let mes = parser.parse()?;
            Ok(mes)
        }
        types::OutputScriptType::Json => {
            let enc = get_input_output_script_encoding(arg);
            let b = utils::files::read_file(input)?;
            let s = utils::encoding::decode_to_string(enc, &b, true)?;
            let mes = serde_json::from_str::<Vec<types::Message>>(&s)?;
            Ok(mes)
        }
        types::OutputScriptType::Yaml => {
            let enc = get_input_output_script_encoding(arg);
            let b = utils::files::read_file(input)?;
            let s = utils::encoding::decode_to_string(enc, &b, true)?;
            let mes = serde_yaml_ng::from_str::<Vec<types::Message>>(&s)?;
            Ok(mes)
        }
        _ => Err(anyhow::anyhow!(
            "Output script type {:?} does not support message parsing",
            typ
        )),
    }
}

pub fn dump_output_script_as_extend(
    output: &str,
    typ: types::OutputScriptType,
    mes: &[types::ExtendedMessage],
    arg: &args::Arg,
) -> anyhow::Result<()> {
    match typ {
        types::OutputScriptType::M3t
        | types::OutputScriptType::M3ta
        | types::OutputScriptType::M3tTxt => {
            let enc = get_output_encoding(arg);
            let s = output_scripts::m3t::M3tDumper::dump_extended(mes);
            let b = utils::encoding::encode_string(enc, &s, false)?;
            utils::files::write_file(output)?.write_all(&b)?;
            Ok(())
        }
        types::OutputScriptType::Po | types::OutputScriptType::Pot => {
            let enc = get_output_encoding(arg);
            let s = output_scripts::po::PoDumper::new();
            let s = s.dump_extended(mes, enc)?;
            let b = utils::encoding::encode_string(enc, &s, false)?;
            utils::files::write_file(output)?.write_all(&b)?;
            Ok(())
        }
        _ => Err(anyhow::anyhow!(
            "Output script type {:?} does not support extended messages",
            typ
        )),
    }
}

pub fn dump_output_script(
    output: &str,
    typ: types::OutputScriptType,
    mes: &[types::Message],
    arg: &args::Arg,
) -> anyhow::Result<()> {
    match typ {
        types::OutputScriptType::M3t
        | types::OutputScriptType::M3ta
        | types::OutputScriptType::M3tTxt => {
            let enc = get_output_encoding(arg);
            let s = output_scripts::m3t::M3tDumper::dump(mes, arg.m3t_no_quote);
            let b = utils::encoding::encode_string(enc, &s, false)?;
            utils::files::write_file(output)?.write_all(&b)?;
            Ok(())
        }
        types::OutputScriptType::Po | types::OutputScriptType::Pot => {
            let enc = get_output_encoding(arg);
            let s = output_scripts::po::PoDumper::new();
            let s = s.dump(mes, enc)?;
            let b = utils::encoding::encode_string(enc, &s, false)?;
            utils::files::write_file(output)?.write_all(&b)?;
            Ok(())
        }
        types::OutputScriptType::Json => {
            let enc = get_output_encoding(arg);
            let s = serde_json::to_string_pretty(mes)?;
            let b = utils::encoding::encode_string(enc, &s, false)?;
            utils::files::write_file(output)?.write_all(&b)?;
            Ok(())
        }
        types::OutputScriptType::Yaml => {
            let enc = get_output_encoding(arg);
            let s = serde_yaml_ng::to_string(mes)?;
            let b = utils::encoding::encode_string(enc, &s, false)?;
            utils::files::write_file(output)?.write_all(&b)?;
            Ok(())
        }
        _ => Err(anyhow::anyhow!(
            "Output script type {:?} does not support message dumping",
            typ
        )),
    }
}

pub fn convert_file(
    input: &str,
    input_type: types::OutputScriptType,
    output: Option<&str>,
    output_type: types::OutputScriptType,
    arg: &args::Arg,
    root_dir: Option<&std::path::Path>,
) -> anyhow::Result<types::ScriptResult> {
    let input_support_src = input_type.is_src_supported();
    let output_support_src = output_type.is_src_supported();
    let output = match output {
        Some(output) => match root_dir {
            Some(root_dir) => {
                let f = std::path::PathBuf::from(input);
                let mut pb = std::path::PathBuf::from(output);
                let rpath = utils::files::relative_path(root_dir, &f);
                if let Some(parent) = rpath.parent() {
                    pb.push(parent);
                }
                if let Some(fname) = f.file_name() {
                    pb.push(fname);
                }
                if arg.output_no_extra_ext {
                    pb.remove_all_extensions();
                }
                pb.set_extension(output_type.as_ref());
                pb.to_string_lossy().into_owned()
            }
            None => output.to_string(),
        },
        None => {
            let mut pb = std::path::PathBuf::from(input);
            if arg.output_no_extra_ext {
                pb.remove_all_extensions();
            }
            pb.set_extension(output_type.as_ref());
            pb.to_string_lossy().into_owned()
        }
    };
    if input_support_src && output_support_src {
        let input_mes = parse_output_script_as_extend(input, input_type, arg)?;
        dump_output_script_as_extend(&output, output_type, &input_mes, arg)?;
        return Ok(types::ScriptResult::Ok);
    }
    let input_mes = parse_output_script(input, input_type, arg)?;
    dump_output_script(&output, output_type, &input_mes, arg)?;
    Ok(types::ScriptResult::Ok)
}

lazy_static::lazy_static! {
    static ref COUNTER: utils::counter::Counter = utils::counter::Counter::new();
    static ref EXIT_LISTENER: std::sync::Mutex<std::collections::BTreeMap<usize, Box<dyn Fn() + Send + Sync>>> = std::sync::Mutex::new(std::collections::BTreeMap::new());
    #[allow(unused)]
    static ref EXIT_LISTENER_ID: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
}

#[allow(dead_code)]
fn add_exit_listener<F: Fn() + Send + Sync + 'static>(f: F) -> usize {
    let id = EXIT_LISTENER_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
    EXIT_LISTENER
        .lock()
        .unwrap_or_else(|err| err.into_inner())
        .insert(id, Box::new(f));
    id
}

#[allow(dead_code)]
fn remove_exit_listener(id: usize) {
    EXIT_LISTENER
        .lock()
        .unwrap_or_else(|err| err.into_inner())
        .remove(&id);
}

fn main() {
    let _ = ctrlc::try_set_handler(|| {
        let listeners = EXIT_LISTENER.lock().unwrap_or_else(|err| err.into_inner());
        for (_, f) in listeners.iter() {
            f();
        }
        eprintln!("Aborted.");
        eprintln!("{}", std::ops::Deref::deref(&COUNTER));
        std::process::exit(1);
    });
    let arg = args::parse_args();
    let argn = std::sync::Arc::new(arg.clone());
    if arg.backtrace {
        unsafe { std::env::set_var("RUST_LIB_BACKTRACE", "1") };
    }
    let cfg = std::sync::Arc::new(types::ExtraConfig {
        #[cfg(feature = "circus")]
        circus_mes_type: arg.circus_mes_type.clone(),
        #[cfg(feature = "escude-arc")]
        escude_fake_compress: arg.escude_fake_compress,
        #[cfg(feature = "escude")]
        escude_enum_scr: arg.escude_enum_scr.clone(),
        #[cfg(feature = "bgi")]
        bgi_import_duplicate: arg.bgi_import_duplicate,
        #[cfg(feature = "bgi")]
        bgi_disable_append: arg.bgi_disable_append,
        #[cfg(feature = "image")]
        image_type: arg.image_type.clone(),
        #[cfg(all(feature = "bgi-arc", feature = "bgi-img"))]
        bgi_is_sysgrp_arc: arg.bgi_is_sysgrp_arc.clone(),
        #[cfg(feature = "bgi-img")]
        bgi_img_scramble: arg.bgi_img_scramble.clone(),
        #[cfg(feature = "cat-system-arc")]
        cat_system_int_encrypt_password: args::get_cat_system_int_encrypt_password(&arg)
            .expect("Failed to get CatSystem2 int encrypt password"),
        #[cfg(feature = "cat-system-img")]
        cat_system_image_canvas: arg.cat_system_image_canvas,
        #[cfg(feature = "kirikiri")]
        kirikiri_language_index: arg.kirikiri_language_index.clone(),
        #[cfg(feature = "kirikiri")]
        kirikiri_export_chat: arg.kirikiri_export_chat,
        #[cfg(feature = "kirikiri")]
        kirikiri_chat_key: arg.kirikiri_chat_key.clone(),
        #[cfg(feature = "kirikiri")]
        kirikiri_chat_json: args::load_kirikiri_chat_json(&arg)
            .expect("Failed to load Kirikiri chat JSON"),
        #[cfg(feature = "kirikiri")]
        kirikiri_languages: arg
            .kirikiri_languages
            .clone()
            .map(|s| std::sync::Arc::new(s)),
        #[cfg(feature = "kirikiri")]
        kirikiri_remove_empty_lines: arg.kirikiri_remove_empty_lines,
        #[cfg(feature = "kirikiri")]
        kirikiri_name_commands: std::sync::Arc::new(std::collections::HashSet::from_iter(
            arg.kirikiri_name_commands.iter().cloned(),
        )),
        #[cfg(feature = "kirikiri")]
        kirikiri_message_commands: std::sync::Arc::new(std::collections::HashSet::from_iter(
            arg.kirikiri_message_commands.iter().cloned(),
        )),
        #[cfg(feature = "bgi-arc")]
        bgi_compress_file: arg.bgi_compress_file,
        #[cfg(feature = "bgi-arc")]
        bgi_compress_min_len: arg.bgi_compress_min_len,
        #[cfg(feature = "emote-img")]
        emote_pimg_overlay: arg.emote_pimg_overlay,
        #[cfg(feature = "artemis-arc")]
        artemis_arc_disable_xor: arg.artemis_arc_disable_xor,
        #[cfg(feature = "artemis")]
        artemis_indent: arg.artemis_indent,
        #[cfg(feature = "artemis")]
        artemis_no_indent: arg.artemis_no_indent,
        #[cfg(feature = "artemis")]
        artemis_max_line_width: arg.artemis_max_line_width,
        #[cfg(feature = "artemis")]
        artemis_ast_lang: arg.artemis_ast_lang.clone(),
        #[cfg(feature = "cat-system")]
        cat_system_cstl_lang: arg.cat_system_cstl_lang.clone(),
        #[cfg(feature = "flate2")]
        zlib_compression_level: arg.zlib_compression_level,
        #[cfg(feature = "image")]
        png_compression_level: arg.png_compression_level,
        #[cfg(feature = "circus-img")]
        circus_crx_keep_original_bpp: arg.circus_crx_keep_original_bpp,
        #[cfg(feature = "circus-img")]
        circus_crx_zstd: arg.circus_crx_zstd,
        #[cfg(feature = "zstd")]
        zstd_compression_level: arg.zstd_compression_level,
        #[cfg(feature = "circus-img")]
        circus_crx_mode: arg.circus_crx_mode,
        #[cfg(feature = "ex-hibit")]
        ex_hibit_rld_xor_key: args::load_ex_hibit_rld_xor_key(&arg)
            .expect("Failed to load RLD XOR key"),
        #[cfg(feature = "ex-hibit")]
        ex_hibit_rld_def_xor_key: args::load_ex_hibit_rld_def_xor_key(&arg)
            .expect("Failed to load RLD DEF XOR key"),
        #[cfg(feature = "ex-hibit")]
        ex_hibit_rld_keys: scripts::ex_hibit::rld::load_keys(arg.ex_hibit_rld_keys.as_ref())
            .expect("Failed to load RLD keys"),
        #[cfg(feature = "ex-hibit")]
        ex_hibit_rld_def_keys: scripts::ex_hibit::rld::load_keys(
            arg.ex_hibit_rld_def_keys.as_ref(),
        )
        .expect("Failed to load RLD DEF keys"),
        #[cfg(feature = "mozjpeg")]
        jpeg_quality: arg.jpeg_quality,
        #[cfg(feature = "webp")]
        webp_lossless: arg.webp_lossless,
        #[cfg(feature = "webp")]
        webp_quality: arg.webp_quality,
        #[cfg(feature = "circus-img")]
        circus_crx_canvas: arg.circus_crx_canvas,
        custom_yaml: arg.custom_yaml.unwrap_or_else(|| {
            arg.output_type
                .map(|s| s == types::OutputScriptType::Yaml)
                .unwrap_or(false)
        }),
        #[cfg(feature = "entis-gls")]
        entis_gls_srcxml_lang: arg.entis_gls_srcxml_lang.clone(),
        #[cfg(feature = "will-plus")]
        will_plus_ws2_no_disasm: arg.will_plus_ws2_no_disasm,
        #[cfg(feature = "artemis-panmimisoft")]
        artemis_panmimisoft_txt_blacklist_names: std::sync::Arc::new(
            args::get_artemis_panmimisoft_txt_blacklist_names(&arg).unwrap(),
        ),
        #[cfg(feature = "artemis-panmimisoft")]
        artemis_panmimisoft_txt_lang: arg.artemis_panmimisoft_txt_lang.clone(),
        #[cfg(feature = "lossless-audio")]
        lossless_audio_fmt: arg.lossless_audio_fmt,
        #[cfg(feature = "audio-flac")]
        flac_compression_level: arg.flac_compression_level,
        #[cfg(feature = "artemis")]
        artemis_asb_format_lua: !arg.artemis_asb_no_format_lua,
        #[cfg(feature = "kirikiri")]
        kirikiri_title: arg.kirikiri_title,
        #[cfg(feature = "favorite")]
        favorite_hcb_filter_ascii: !arg.favorite_hcb_no_filter_ascii,
        #[cfg(feature = "bgi-img")]
        bgi_img_workers: arg.bgi_img_workers,
        #[cfg(feature = "image-jxl")]
        jxl_lossless: !arg.jxl_lossy,
        #[cfg(feature = "image-jxl")]
        jxl_distance: arg.jxl_distance,
        #[cfg(feature = "image-jxl")]
        jxl_workers: arg.jxl_workers,
        #[cfg(feature = "emote-img")]
        psb_process_tlg: !arg.psb_no_process_tlg,
        #[cfg(feature = "softpal-img")]
        pgd_fake_compress: !arg.pgd_compress,
        #[cfg(feature = "softpal")]
        softpal_add_message_index: arg.softpal_add_message_index,
        #[cfg(feature = "kirikiri")]
        kirikiri_chat_multilang: !arg.kirikiri_chat_no_multilang,
        #[cfg(feature = "kirikiri-arc")]
        xp3_simple_crypt: !arg.xp3_no_simple_crypt,
        #[cfg(feature = "kirikiri-arc")]
        xp3_mdf_decompress: !arg.xp3_no_mdf_decompress,
        #[cfg(feature = "kirikiri-arc")]
        xp3_segmenter: arg.xp3_segmenter,
        #[cfg(feature = "kirikiri-arc")]
        xp3_compress_files: !arg.xp3_no_compress_files,
        #[cfg(feature = "kirikiri-arc")]
        xp3_compress_index: !arg.xp3_no_compress_index,
        #[cfg(feature = "kirikiri-arc")]
        xp3_compress_workers: arg.xp3_compress_workers,
        #[cfg(feature = "kirikiri-arc")]
        xp3_zstd: arg.xp3_zstd,
        #[cfg(feature = "kirikiri-arc")]
        xp3_zopfli: arg.xp3_zopfli,
        #[cfg(feature = "kirikiri-arc")]
        xp3_pack_workers: arg.xp3_pack_workers,
        #[cfg(feature = "kirikiri")]
        kirikiri_language_insert: arg.kirikiri_language_insert,
        #[cfg(feature = "musica-arc")]
        musica_game_title: arg.musica_game_title.clone(),
        #[cfg(feature = "musica-arc")]
        musica_xor_key: arg.musica_xor_key,
        #[cfg(feature = "musica-arc")]
        musica_compress: arg.musica_compress,
        #[cfg(feature = "kirikiri-arc")]
        xp3_no_adler: arg.xp3_no_adler,
        #[cfg(feature = "bgi")]
        bgi_add_space: arg.bgi_add_space,
        #[cfg(feature = "escude")]
        escude_op: arg.escude_op,
        #[cfg(feature = "zopfli")]
        zopfli_iteration_count: arg.zopfli_iteration_count,
        #[cfg(feature = "zopfli")]
        zopfli_iterations_without_improvement: arg.zopfli_iterations_without_improvement,
        #[cfg(feature = "zopfli")]
        zopfli_maximum_block_splits: arg.zopfli_maximum_block_splits,
        #[cfg(feature = "artemis-panmimisoft")]
        artemis_panmimisoft_txt_multi_lang: arg.artemis_panmimisoft_txt_multi_lang,
        #[cfg(feature = "entis-gls")]
        entis_gls_csx_disasm: arg.entis_gls_csx_disasm,
        #[cfg(feature = "entis-gls")]
        entis_gls_csx_lf: arg.entis_gls_csx_lf.clone(),
        #[cfg(feature = "entis-gls")]
        entis_gls_csx_ver: arg.entis_gls_csx_ver,
        #[cfg(feature = "entis-gls")]
        entis_gls_csx_v2_ver: arg.entis_gls_csx_v2_ver,
        #[cfg(feature = "entis-gls")]
        entis_gls_csx_no_part_label: arg.entis_gls_csx_no_part_label,
        #[cfg(feature = "qlie-img")]
        qlie_abmp10_process_abmp10: !arg.qlie_abmp10_no_process_abmp10,
        #[cfg(feature = "qlie-arc")]
        qlie_pack_keyfile: arg.qlie_pack_keyfile.clone(),
        #[cfg(feature = "qlie-arc")]
        qlie_pack_compress_files: arg.qlie_pack_compress_files,
        #[cfg(feature = "qlie-img")]
        qlie_dpng_use_raw_png: arg.qlie_dpng_use_raw_png,
        #[cfg(feature = "qlie-img")]
        qlie_dpng_psd: arg.qlie_dpng_psd,
        #[cfg(feature = "utils-psd")]
        psd_compress: !arg.psd_no_compress,
        #[cfg(feature = "emote-img")]
        emote_pimg_psd: arg.emote_pimg_psd,
        #[cfg(feature = "kirikiri")]
        kirikiri_ks_hitret: arg.kirikiri_ks_hitret,
        #[cfg(feature = "kirikiri")]
        kirikiri_ks_lf: arg.kirikiri_ks_lf.clone(),
        #[cfg(feature = "kirikiri")]
        kirikiri_message_tags: std::sync::Arc::new(std::collections::HashSet::from_iter(
            arg.kirikiri_message_tags.iter().cloned(),
        )),
        #[cfg(feature = "kirikiri")]
        kirikiri_ks_bom: arg.kirikiri_ks_bom,
        #[cfg(feature = "emote-img")]
        bc7: arg.bc7,
        #[cfg(feature = "artemis")]
        artemis_asb_end_tags: std::sync::Arc::new(std::collections::HashSet::from_iter(
            arg.artemis_asb_end_tags.iter().cloned(),
        )),
        #[cfg(feature = "kirikiri-arc")]
        xp3_game_title: arg.xp3_game_title.clone(),
        #[cfg(feature = "kirikiri-arc")]
        xp3_debug_archive: arg.xp3_debug_archive,
        #[cfg(feature = "kirikiri-arc")]
        xp3_force_extract: arg.xp3_force_extract,
        #[cfg(feature = "kirikiri-arc")]
        xp3_force_decrypt: arg.xp3_force_decrypt,
        #[cfg(feature = "emote-img")]
        emote_pimg_psd_no_diff: arg.emote_pimg_psd_no_diff,
        #[cfg(feature = "kirikiri-arc")]
        xp3_file_list_path: arg.xp3_file_list_path.clone(),
    });
    match &arg.command {
        args::Command::Export { input, output } => {
            let (scripts, is_dir) =
                utils::files::collect_files(input, arg.recursive, false).unwrap();
            if is_dir {
                match &output {
                    Some(output) => {
                        let op = std::path::Path::new(output);
                        if op.exists() {
                            if !op.is_dir() {
                                eprintln!("Output path is not a directory");
                                std::process::exit(
                                    argn.exit_code_all_failed.unwrap_or(argn.exit_code),
                                );
                            }
                        } else {
                            std::fs::create_dir_all(op).unwrap();
                        }
                    }
                    None => {}
                }
            }
            let root_dir = if is_dir {
                Some(std::path::Path::new(input))
            } else {
                None
            };
            #[cfg(feature = "image")]
            let img_threadpool = if arg.image_workers > 1 {
                let tp = std::sync::Arc::new(
                    utils::threadpool::ThreadPool::<Result<(), anyhow::Error>>::new(
                        arg.image_workers,
                        Some("img-output-worker-"),
                        false,
                    )
                    .expect("Failed to create image thread pool"),
                );
                let tp2 = tp.clone();
                let id = add_exit_listener(move || {
                    for r in tp2.take_results() {
                        if let Err(e) = r {
                            eprintln!("{}", e);
                            COUNTER.inc_error();
                        } else {
                            COUNTER.inc(types::ScriptResult::Ok);
                        }
                    }
                });
                Some((tp, id))
            } else {
                None
            };
            for script in scripts.iter() {
                #[cfg(feature = "image")]
                let re = export_script(
                    &script,
                    &arg,
                    cfg.clone(),
                    output,
                    root_dir,
                    img_threadpool.as_ref().map(|(t, _)| &**t),
                );
                #[cfg(not(feature = "image"))]
                let re = export_script(&script, &arg, cfg.clone(), output, root_dir);
                match re {
                    Ok(s) => {
                        COUNTER.inc(s);
                    }
                    Err(e) => {
                        COUNTER.inc_error();
                        eprintln!("Error exporting {}: {}", script, e);
                        if arg.backtrace {
                            eprintln!("Backtrace: {}", e.backtrace());
                        }
                    }
                }
                #[cfg(feature = "image")]
                img_threadpool.as_ref().map(|(t, _)| {
                    for r in t.take_results() {
                        if let Err(e) = r {
                            COUNTER.inc_error();
                            eprintln!("{}", e);
                        } else {
                            COUNTER.inc(types::ScriptResult::Ok);
                        }
                    }
                });
            }
            #[cfg(feature = "image")]
            img_threadpool.map(|(t, id)| {
                t.join();
                remove_exit_listener(id);
                for r in t.take_results() {
                    if let Err(e) = r {
                        COUNTER.inc_error();
                        eprintln!("{}", e);
                    } else {
                        COUNTER.inc(types::ScriptResult::Ok);
                    }
                }
            });
        }
        args::Command::Import(args) => {
            let name_csv = match &args.name_csv {
                Some(name_csv) => {
                    let name_table = utils::name_replacement::read_csv(name_csv).unwrap();
                    Some(name_table)
                }
                None => None,
            };
            let repl = std::sync::Arc::new(match &args.replacement_json {
                Some(replacement_json) => {
                    let b = utils::files::read_file(replacement_json).unwrap();
                    let s = String::from_utf8(b).unwrap();
                    let table = serde_json::from_str::<types::ReplacementTable>(&s).unwrap();
                    Some(table)
                }
                None => None,
            });
            let (scripts, is_dir) =
                utils::files::collect_files(&args.input, arg.recursive, false).unwrap();
            if is_dir {
                let pb = std::path::Path::new(&args.patched);
                if pb.exists() {
                    if !pb.is_dir() {
                        eprintln!("Patched path is not a directory");
                        std::process::exit(argn.exit_code_all_failed.unwrap_or(argn.exit_code));
                    }
                } else {
                    std::fs::create_dir_all(pb).unwrap();
                }
            }
            let root_dir = if is_dir {
                Some(std::path::Path::new(&args.input))
            } else {
                None
            };
            let workers = if args.jobs > 1 {
                Some(
                    utils::threadpool::ThreadPool::<()>::new(
                        args.jobs,
                        Some("import-worker-"),
                        true,
                    )
                    .unwrap(),
                )
            } else {
                None
            };
            let dep_files = if args.dep_file.is_some() {
                Some(std::sync::Arc::new(std::sync::Mutex::new(
                    std::collections::HashMap::new(),
                )))
            } else {
                None
            };
            for script in scripts.iter() {
                if let Some(workers) = workers.as_ref() {
                    let arg = argn.clone();
                    let cfg = cfg.clone();
                    let script = script.clone();
                    let name_csv = name_csv.as_ref().map(|s| s.clone());
                    let repl = repl.clone();
                    let root_dir = root_dir.map(|s| s.to_path_buf());
                    let args = args.clone();
                    let dep_files = dep_files.clone();
                    if let Err(e) = workers.execute(
                        move |_| {
                            let mut dep_graph = if dep_files.is_some() {
                                Some((String::new(), Vec::new()))
                            } else {
                                None
                            };
                            let re = import_script(
                                &script,
                                &arg,
                                cfg,
                                &args,
                                root_dir.as_ref().map(|s| s.as_path()),
                                name_csv.as_ref(),
                                (*repl).as_ref(),
                                dep_graph.as_mut(),
                            );
                            match re {
                                Ok(s) => {
                                    COUNTER.inc(s);
                                    if let Some((fname, deps)) = dep_graph {
                                        if let Some(dep_files) = dep_files {
                                            let mut lock =
                                                crate::ext::mutex::MutexExt::lock_blocking(
                                                    dep_files.as_ref(),
                                                );
                                            lock.insert(fname, deps);
                                        }
                                    }
                                }
                                Err(e) => {
                                    COUNTER.inc_error();
                                    eprintln!("Error exporting {}: {}", script, e);
                                    if arg.backtrace {
                                        eprintln!("Backtrace: {}", e.backtrace());
                                    }
                                }
                            }
                        },
                        true,
                    ) {
                        COUNTER.inc_error();
                        eprintln!("Error executing import worker: {}", e);
                    }
                } else {
                    let mut dep_graph = if dep_files.is_some() {
                        Some((String::new(), Vec::new()))
                    } else {
                        None
                    };
                    let re = import_script(
                        &script,
                        &arg,
                        cfg.clone(),
                        args,
                        root_dir,
                        name_csv.as_ref(),
                        (*repl).as_ref(),
                        dep_graph.as_mut(),
                    );
                    match re {
                        Ok(s) => {
                            COUNTER.inc(s);
                            if let Some((fname, deps)) = dep_graph {
                                if let Some(dep_files) = dep_files.as_ref() {
                                    let mut lock = crate::ext::mutex::MutexExt::lock_blocking(
                                        dep_files.as_ref(),
                                    );
                                    lock.insert(fname, deps);
                                }
                            }
                        }
                        Err(e) => {
                            COUNTER.inc_error();
                            eprintln!("Error exporting {}: {}", script, e);
                            if arg.backtrace {
                                eprintln!("Backtrace: {}", e.backtrace());
                            }
                        }
                    }
                }
            }
            if let Some(map) = dep_files {
                let lock = crate::ext::mutex::MutexExt::lock_blocking(map.as_ref());
                if let Some(dep_file) = &args.dep_file {
                    let df = std::fs::File::create(dep_file).unwrap();
                    let mut df = std::io::BufWriter::new(df);
                    use std::io::Write;
                    for (fname, deps) in lock.iter() {
                        write!(df, "{}:", escape_dep_string(fname)).unwrap();
                        for d in deps {
                            write!(df, " {}", escape_dep_string(d)).unwrap();
                        }
                        writeln!(df).unwrap();
                    }
                }
            }
        }
        args::Command::Pack {
            input,
            output,
            backslash,
        } => {
            let re = pack_archive(
                input,
                output.as_ref().map(|s| s.as_str()),
                &arg,
                cfg.clone(),
                *backslash,
            );
            if let Err(e) = re {
                COUNTER.inc_error();
                eprintln!("Error packing archive: {}", e);
            }
        }
        args::Command::Unpack { input, output } => {
            let (scripts, is_dir) = utils::files::collect_arc_files(input, arg.recursive).unwrap();
            if is_dir {
                match &output {
                    Some(output) => {
                        let op = std::path::Path::new(output);
                        if op.exists() {
                            if !op.is_dir() {
                                eprintln!("Output path is not a directory");
                                std::process::exit(
                                    argn.exit_code_all_failed.unwrap_or(argn.exit_code),
                                );
                            }
                        } else {
                            std::fs::create_dir_all(op).unwrap();
                        }
                    }
                    None => {}
                }
            }
            let root_dir = if is_dir {
                Some(std::path::Path::new(input))
            } else {
                None
            };
            for script in scripts.iter() {
                let re = unpack_archive(&script, &arg, cfg.clone(), output, root_dir);
                match re {
                    Ok(s) => {
                        COUNTER.inc(s);
                    }
                    Err(e) => {
                        COUNTER.inc_error();
                        eprintln!("Error unpacking {}: {}", script, e);
                        if arg.backtrace {
                            eprintln!("Backtrace: {}", e.backtrace());
                        }
                    }
                }
            }
        }
        args::Command::Create { input, output } => {
            let re = create_file(
                input,
                output.as_ref().map(|s| s.as_str()),
                &arg,
                cfg.clone(),
            );
            if let Err(e) = re {
                COUNTER.inc_error();
                eprintln!("Error creating file: {}", e);
                if arg.backtrace {
                    eprintln!("Backtrace: {}", e.backtrace());
                }
            }
        }
        args::Command::PackV2 {
            output,
            input,
            backslash,
            no_dir,
            dep_file,
        } => {
            if !input.is_empty() {
                let input = input.iter().map(|s| s.as_str()).collect::<Vec<_>>();
                let re = pack_archive_v2(
                    &input,
                    output.as_ref().map(|s| s.as_str()),
                    &arg,
                    cfg.clone(),
                    *backslash,
                    *no_dir,
                    dep_file.as_ref().map(|s| s.as_str()),
                );
                if let Err(e) = re {
                    COUNTER.inc_error();
                    eprintln!("Error packing archive: {}", e);
                }
            } else {
                eprintln!("No input files specified for packing.");
            }
        }
        args::Command::Convert {
            input_type,
            output_type,
            input,
            output,
        } => {
            if input_type.is_custom() {
                eprintln!("Custom input type is not supported for conversion.");
                std::process::exit(argn.exit_code_all_failed.unwrap_or(argn.exit_code));
            }
            if output_type.is_custom() {
                eprintln!("Custom output type is not supported for conversion.");
                std::process::exit(argn.exit_code_all_failed.unwrap_or(argn.exit_code));
            }
            let (scripts, is_dir) =
                utils::files::collect_ext_files(input, arg.recursive, &[input_type.as_ref()])
                    .unwrap();
            if is_dir {
                match &output {
                    Some(output) => {
                        let op = std::path::Path::new(output);
                        if op.exists() {
                            if !op.is_dir() {
                                eprintln!("Output path is not a directory");
                                std::process::exit(
                                    argn.exit_code_all_failed.unwrap_or(argn.exit_code),
                                );
                            }
                        } else {
                            std::fs::create_dir_all(op).unwrap();
                        }
                    }
                    None => {}
                }
            }
            let root_dir = if is_dir {
                Some(std::path::Path::new(input))
            } else {
                None
            };
            for script in scripts.iter() {
                let re = convert_file(
                    &script,
                    *input_type,
                    output.as_ref().map(|s| s.as_str()),
                    *output_type,
                    &arg,
                    root_dir,
                );
                match re {
                    Ok(s) => {
                        COUNTER.inc(s);
                    }
                    Err(e) => {
                        COUNTER.inc_error();
                        eprintln!("Error converting {}: {}", script, e);
                        if arg.backtrace {
                            eprintln!("Backtrace: {}", e.backtrace());
                        }
                    }
                }
            }
        }
    }
    let counter = std::ops::Deref::deref(&COUNTER);
    eprintln!("{}", counter);
    if counter.all_failed() {
        std::process::exit(argn.exit_code_all_failed.unwrap_or(argn.exit_code));
    } else if counter.has_error() {
        std::process::exit(argn.exit_code);
    }
}
