use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::mpsc::Receiver;
use log::debug;
use serde_json::Value;
use zahirscan;
use crate::config::UblxOpts;
use super::nefax_ops;
pub type ZahirResult = zahirscan::ZahirScanResult;
pub type ZahirOutput = zahirscan::Output;
pub type ZahirOutputSink = zahirscan::OutputSink;
pub type ZahirFT = zahirscan::FileType;
pub type ZahirOutputMode = zahirscan::OutputMode;
pub type ZahirRC = zahirscan::RuntimeConfig;
pub use zahirscan::utils::ffprobe_handler::run_ffprobe_safe;
#[must_use]
pub fn file_type_from_metadata_name(s: &str) -> Option<zahirscan::FileType> {
zahirscan::FileType::from_metadata_name(s)
}
#[must_use]
pub fn detect_delimiter_byte(content: &str) -> u8 {
zahirscan::parsers::structured::detect_delimiter_byte(content)
}
#[must_use]
pub fn delimiter_from_path_for_viewer(path: &str) -> Option<u8> {
let ext = Path::new(path)
.extension()
.and_then(|e| e.to_str())
.map(str::to_ascii_lowercase)?;
match ext.as_str() {
"csv" => Some(b','),
"tsv" | "tab" => Some(b'\t'),
"psv" => Some(b'|'),
_ => None,
}
}
#[must_use]
fn metadata_name_from_detect_key(key: &str) -> Option<String> {
let ft = zahirscan::utils::filetypes::detect_file_type(key);
(ft != zahirscan::FileType::Unknown).then(|| ft.as_metadata_name().to_string())
}
#[must_use]
pub fn zahir_metadata_name_from_path_hint(path_str: &str) -> Option<String> {
metadata_name_from_detect_key(path_str)
}
#[must_use]
pub fn zahir_metadata_name_from_indexed_file(full_path: &Path, path_str: &str) -> Option<String> {
let key = if full_path.exists() {
full_path.to_string_lossy().into_owned()
} else {
path_str.to_string()
};
metadata_name_from_detect_key(&key)
}
#[must_use]
pub fn needs_zahir(
prior_nefax: Option<&nefax_ops::NefaxResult>,
path: &PathBuf,
current_mtime_ns: i64,
) -> bool {
match prior_nefax.and_then(|p| p.get(path)) {
Some(prior_meta) => prior_meta.mtime_ns != current_mtime_ns,
None => true,
}
}
fn zahir_empty_when_no_paths(
paths: &[String],
mode_label: &'static str,
) -> Option<zahirscan::ZahirScanResult> {
if paths.is_empty() {
debug!("zahir {mode_label}: no paths received, returning empty result");
Some(zahirscan::ZahirScanResult::default())
} else {
None
}
}
pub fn run_zahir_batch(
paths: &[impl AsRef<Path>],
ublx_opts: &UblxOpts,
) -> Result<zahirscan::ZahirScanResult, anyhow::Error> {
let config = ublx_opts.zahir_runtime_config();
let path_strings: Vec<String> = paths
.iter()
.map(|p| p.as_ref().to_string_lossy().into_owned())
.collect();
if let Some(empty) = zahir_empty_when_no_paths(&path_strings, "batch") {
return Ok(empty);
}
zahirscan::extract_zahir(
path_strings,
config.output_mode,
Some(&config),
None,
&ZahirOutputSink::Collect,
)
}
pub fn run_zahir_from_stream(
paths_rx: &Receiver<String>,
ublx_opts: &UblxOpts,
output_sink: &ZahirOutputSink,
) -> Result<zahirscan::ZahirScanResult, anyhow::Error> {
let config = ublx_opts.zahir_runtime_config();
let path_strings: Vec<String> = paths_rx.iter().collect();
if let Some(empty) = zahir_empty_when_no_paths(&path_strings, "stream") {
return Ok(empty);
}
zahirscan::extract_zahir(
path_strings,
config.output_mode,
Some(&config),
None,
output_sink,
)
}
#[must_use]
pub fn get_zahir_output_by_path<'a>(
zahir_result: &'a ZahirResult,
dir_to_ublx_abs: Option<&Path>,
) -> HashMap<String, &'a ZahirOutput> {
zahir_result
.outputs
.iter()
.filter_map(|o| {
let s = o.source.as_ref()?;
let key = match dir_to_ublx_abs {
Some(r) => Path::new(s)
.strip_prefix(r)
.ok()?
.to_string_lossy()
.into_owned(),
None => s.clone(),
};
Some((key, o))
})
.collect()
}
#[must_use]
pub fn zahir_output_to_json(output: Option<&ZahirOutput>) -> String {
output
.and_then(|o| serde_json::to_string(o).ok())
.unwrap_or_default()
}
fn zahir_json_needs_path_file_type(v: &Value) -> bool {
match v.get("file_type") {
None | Some(Value::Null) => true,
Some(Value::String(s)) => s.trim().is_empty(),
_ => false,
}
}
fn inject_path_detected_file_type(v: &mut Value, full_path: &Path, path_str: &str) {
if !zahir_json_needs_path_file_type(v) {
return;
}
let Some(name) = zahir_metadata_name_from_indexed_file(full_path, path_str) else {
return;
};
if let Some(obj) = v.as_object_mut() {
obj.insert("file_type".to_string(), Value::String(name));
}
}
#[must_use]
pub fn zahir_output_to_json_for_path(
output: Option<&ZahirOutput>,
full_path: &Path,
path_str: &str,
) -> String {
let Some(o) = output else {
return String::new();
};
let Ok(mut v) = serde_json::to_value(o) else {
return zahir_output_to_json(Some(o));
};
inject_path_detected_file_type(&mut v, full_path, path_str);
serde_json::to_string(&v).unwrap_or_default()
}