govctl 0.9.4

Project governance CLI for RFC, ADR, and Work Item management
use super::super::{ArtifactType, path};
use super::mutate::{array_items_mut, ensure_array_path_mut};
use super::support::{
    remove_indices_preserving_order, scalar_list_item_text, set_object_string_field,
    status_list_entry_line, status_list_text, status_list_texts, string_list_item_text,
    string_list_item_texts, type_mismatch, unknown_field_error, value_at_path,
};
use super::{simple_runtime_list_path, simple_status_list_spec};
use crate::diagnostic::{Diagnostic, DiagnosticCode, DiagnosticResult};
use serde_json::Value;

pub fn get_simple_list_item(
    artifact: ArtifactType,
    doc: &Value,
    field: &str,
    index: i32,
    id: &str,
) -> DiagnosticResult<String> {
    if let Some(path) = simple_runtime_list_path(artifact, field) {
        let items = array_items(doc, path, id)?;
        let resolved = path::resolve_index(index, items.len())?;
        return Ok(scalar_list_item_text(&items[resolved]));
    }

    if let Some(spec) = simple_status_list_spec(artifact, field) {
        let items = array_items(doc, spec.path, id)?;
        let resolved = path::resolve_index(index, items.len())?;
        return status_list_entry_line(&items[resolved], spec.status_key, spec.text_key, id);
    }

    Err(unknown_field_error(artifact, field, id))
}

pub fn add_simple_list_value(
    artifact: ArtifactType,
    doc: &mut Value,
    field: &str,
    value: &str,
    id: &str,
) -> DiagnosticResult<bool> {
    let Some(path) = simple_runtime_list_path(artifact, field) else {
        return Ok(false);
    };
    let slot = ensure_array_path_mut(doc, path, id)?;
    let items = slot.as_array_mut().ok_or_else(|| {
        Diagnostic::new(
            DiagnosticCode::E0817PathTypeMismatch,
            "Expected an array value",
            id,
        )
    })?;
    if !items.iter().any(|item| item.as_str() == Some(value)) {
        items.push(Value::String(value.to_string()));
    }
    Ok(true)
}

pub fn set_simple_list_item(
    artifact: ArtifactType,
    doc: &mut Value,
    field: &str,
    index: i32,
    value: &str,
    id: &str,
) -> DiagnosticResult<()> {
    let Some(path) = simple_runtime_list_path(artifact, field) else {
        return Err(unknown_field_error(artifact, field, id));
    };
    let items = array_items_mut(doc, path, id)?;
    let resolved = path::resolve_index(index, items.len())?;
    let slot = &mut items[resolved];
    if !slot.is_string() && !slot.is_null() {
        return Err(type_mismatch("Expected string item in list", id));
    }
    *slot = Value::String(value.to_string());
    Ok(())
}

pub fn remove_simple_list_values_with_matcher<F>(
    artifact: ArtifactType,
    doc: &mut Value,
    field: &str,
    id: &str,
    resolve: F,
) -> DiagnosticResult<Option<Vec<String>>>
where
    F: FnOnce(&[&str]) -> DiagnosticResult<Vec<usize>>,
{
    let Some(path) = simple_runtime_list_path(artifact, field) else {
        return Ok(None);
    };
    let items = array_items_mut(doc, path, id)?;

    let texts = string_list_item_texts(items, "Expected string entries in array", id)?;
    let indices = resolve(&texts)?;
    let removed = remove_indices_preserving_order(items, indices, |item| {
        Ok(string_list_item_text(item, "Expected string entries in array", id)?.to_string())
    })?;
    Ok(Some(removed))
}

pub fn remove_simple_status_list_values_with_matcher<F>(
    artifact: ArtifactType,
    doc: &mut Value,
    field: &str,
    id: &str,
    resolve: F,
) -> DiagnosticResult<Option<Vec<String>>>
where
    F: FnOnce(&[&str]) -> DiagnosticResult<Vec<usize>>,
{
    let Some(spec) = simple_status_list_spec(artifact, field) else {
        return Ok(None);
    };
    let items = array_items_mut(doc, spec.path, id)?;

    let texts = status_list_texts(items, spec.text_key, id)?;
    let indices = resolve(&texts)?;
    let removed = remove_indices_preserving_order(items, indices, |item| {
        Ok(status_list_text(item, spec.text_key, id)?.to_string())
    })?;
    Ok(Some(removed))
}

pub fn tick_simple_status_list_item_with_matcher<F>(
    artifact: ArtifactType,
    doc: &mut Value,
    field: &str,
    id: &str,
    new_status: &str,
    resolve: F,
) -> DiagnosticResult<Option<String>>
where
    F: FnOnce(&[&str]) -> DiagnosticResult<Vec<usize>>,
{
    let Some(spec) = simple_status_list_spec(artifact, field) else {
        return Ok(None);
    };
    let items = array_items_mut(doc, spec.path, id)?;
    let texts = status_list_texts(items, spec.text_key, id)?;
    let idx = resolve(&texts)?[0];
    let text = texts[idx].to_string();
    set_object_string_field(
        &mut items[idx],
        spec.status_key,
        new_status,
        "Expected object entries in array",
        id,
    )?;
    Ok(Some(text))
}

fn array_items<'a>(doc: &'a Value, path: &[&str], id: &str) -> DiagnosticResult<&'a [Value]> {
    value_at_path(doc, path)
        .and_then(Value::as_array)
        .map(Vec::as_slice)
        .ok_or_else(|| {
            Diagnostic::new(
                DiagnosticCode::E0817PathTypeMismatch,
                "Expected an array value",
                id,
            )
        })
}