use std::path::Path;

use eidos_kernel::schema::Node;

use super::model::*;
use super::repair::*;

pub(crate) fn finding_kind_label(kind: &QualityFindingKind) -> String {
    serde_json::to_value(kind)
        .ok()
        .and_then(|v| v.as_str().map(String::from))
        .unwrap_or_else(|| "unknown".into())
}

pub(crate) fn stable_hash(input: &str) -> String {
    let mut hash = 0xcbf29ce484222325_u64;
    for byte in input.as_bytes() {
        hash ^= u64::from(*byte);
        hash = hash.wrapping_mul(0x100000001b3);
    }
    format!("{hash:016x}")
}

pub(crate) fn finding_id(
    kind: &QualityFindingKind,
    source: Option<&str>,
    node_id: Option<&str>,
    field: Option<&str>,
    target: Option<&str>,
    message: &str,
    suggested_replacement: Option<&str>,
) -> String {
    let mut input = String::new();
    input.push_str(&finding_kind_label(kind));
    input.push('\0');
    input.push_str(source.unwrap_or(""));
    input.push('\0');
    input.push_str(node_id.unwrap_or(""));
    input.push('\0');
    input.push_str(field.unwrap_or(""));
    input.push('\0');
    input.push_str(target.unwrap_or(""));
    input.push('\0');
    input.push_str(suggested_replacement.unwrap_or(message));
    format!("aun-{}", stable_hash(&input))
}

pub(crate) fn finding(
    kind: QualityFindingKind,
    severity: DiagnosticSeverity,
    node: Option<&Node>,
    source: Option<String>,
    field: Option<&str>,
    target: Option<String>,
    message: String,
) -> QualityFinding {
    let suggested_action = suggested_action(&kind, field, target.as_deref());
    let suggested_replacement = suggested_replacement(&kind, field, target.as_deref());
    let node_id = node.map(|n| n.id.clone());
    let source = source.or_else(|| node.and_then(source_for));
    let field = field.map(str::to_string);
    let (repairability, repair_tool) = repair_metadata(
        &kind,
        source.as_deref(),
        field.as_deref(),
        target.as_deref(),
        suggested_action.as_deref(),
        suggested_replacement.as_deref(),
    );
    let finding_id = finding_id(
        &kind,
        source.as_deref(),
        node_id.as_deref(),
        field.as_deref(),
        target.as_deref(),
        &message,
        suggested_replacement.as_deref(),
    );
    QualityFinding {
        finding_id,
        kind,
        severity,
        repairability,
        node_id,
        source,
        field,
        target,
        suggested_action,
        repair_tool,
        suggested_replacement,
        message,
    }
}

pub(crate) fn has_non_empty_scalar(
    fm: &super::super::frontmatter::Frontmatter,
    fields: &[&str],
) -> bool {
    fields
        .iter()
        .any(|field| fm.get(field).is_some_and(|value| !value.trim().is_empty()))
}

pub(crate) fn is_index_md(path: &Path) -> bool {
    path.file_name().is_some_and(|name| name == "index.md")
}

pub(crate) fn is_log_md(path: &Path) -> bool {
    path.file_name().is_some_and(|name| name == "log.md")
}

pub(crate) fn body_has_content(body: &str) -> bool {
    body.lines().any(|line| {
        let trimmed = line.trim();
        !trimmed.is_empty() && trimmed != "---"
    })
}

pub(crate) fn is_date_heading(line: &str) -> bool {
    let trimmed = line.trim_start_matches('#').trim();
    let bytes = trimmed.as_bytes();
    bytes.len() >= 10
        && bytes[0..4].iter().all(u8::is_ascii_digit)
        && bytes[4] == b'-'
        && bytes[5..7].iter().all(u8::is_ascii_digit)
        && bytes[7] == b'-'
        && bytes[8..10].iter().all(u8::is_ascii_digit)
}
