//! Read/write helpers for the `merge_jobs` LWW-map collection.
//!
//! Merge-job entries are keyed by `story_id` and track the status, timing,
//! and optional error for each story merge operation.

use bft_json_crdt::json_crdt::{JsonValue, *};
use bft_json_crdt::op::ROOT_ID;
use serde_json::json;

use super::super::state::{apply_and_persist, get_crdt, rebuild_merge_job_index};
use super::super::types::{MergeJobCrdt, MergeJobView};
use super::list_id_at;

/// Write or update a merge-job entry keyed by `story_id`.
pub fn write_merge_job(
    story_id: &str,
    status: &str,
    started_at: f64,
    finished_at: Option<f64>,
    error: Option<&str>,
) {
    let Some(state_mutex) = get_crdt() else {
        return;
    };
    let Ok(mut state) = state_mutex.lock() else {
        return;
    };

    if let Some(&idx) = state.merge_job_index.get(story_id) {
        apply_and_persist(&mut state, |s| {
            s.crdt.doc.merge_jobs[idx].status.set(status.to_string())
        });
        apply_and_persist(&mut state, |s| {
            s.crdt.doc.merge_jobs[idx].started_at.set(started_at)
        });
        if let Some(fa) = finished_at {
            apply_and_persist(&mut state, |s| {
                s.crdt.doc.merge_jobs[idx].finished_at.set(fa)
            });
        }
        if let Some(e) = error {
            apply_and_persist(&mut state, |s| {
                s.crdt.doc.merge_jobs[idx].error.set(e.to_string())
            });
        }
    } else {
        let entry: JsonValue = json!({
            "story_id": story_id,
            "status": status,
            "started_at": started_at,
            "finished_at": finished_at.unwrap_or(0.0),
            "error": error.unwrap_or(""),
        })
        .into();
        apply_and_persist(&mut state, |s| s.crdt.doc.merge_jobs.insert(ROOT_ID, entry));
        state.merge_job_index = rebuild_merge_job_index(&state.crdt);
        // After inserting a new entry via JSON, explicitly set the error field via
        // .set() so that entry.error.view() returns the correct value immediately.
        // The JSON insert path initialises LWW fields, but an explicit .set() is
        // required for the stale-merge reaper to read the field reliably via .view().
        if let Some(e) = error
            && let Some(&idx) = state.merge_job_index.get(story_id)
        {
            apply_and_persist(&mut state, |s| {
                s.crdt.doc.merge_jobs[idx].error.set(e.to_string())
            });
        }
    }
}

/// Read all merge-job entries.
pub fn read_all_merge_jobs() -> Option<Vec<MergeJobView>> {
    let state_mutex = get_crdt()?;
    let state = state_mutex.lock().ok()?;
    let mut out = Vec::new();
    for entry in state.crdt.doc.merge_jobs.iter() {
        if let Some(v) = extract_merge_job_view(entry) {
            out.push(v);
        }
    }
    Some(out)
}

/// Read a single merge-job entry by `story_id`.
pub fn read_merge_job(story_id: &str) -> Option<MergeJobView> {
    let state_mutex = get_crdt()?;
    let state = state_mutex.lock().ok()?;
    let &idx = state.merge_job_index.get(story_id)?;
    extract_merge_job_view(&state.crdt.doc.merge_jobs[idx])
}

/// Tombstone a merge-job entry by `story_id`.
pub fn delete_merge_job(story_id: &str) -> bool {
    let Some(state_mutex) = get_crdt() else {
        return false;
    };
    let Ok(mut state) = state_mutex.lock() else {
        return false;
    };
    let Some(&idx) = state.merge_job_index.get(story_id) else {
        return false;
    };
    let Some(op_id) = list_id_at(&state.crdt.doc.merge_jobs, idx) else {
        return false;
    };
    apply_and_persist(&mut state, |s| s.crdt.doc.merge_jobs.delete(op_id));
    state.merge_job_index = rebuild_merge_job_index(&state.crdt);
    true
}

/// Convert a CRDT merge-job entry into its read-only view representation.
pub(super) fn extract_merge_job_view(entry: &MergeJobCrdt) -> Option<MergeJobView> {
    let story_id = match entry.story_id.view() {
        JsonValue::String(s) if !s.is_empty() => s,
        _ => return None,
    };
    let status = match entry.status.view() {
        JsonValue::String(s) => s,
        _ => String::new(),
    };
    let started_at = match entry.started_at.view() {
        JsonValue::Number(n) => n,
        _ => 0.0,
    };
    let finished_at = match entry.finished_at.view() {
        JsonValue::Number(n) if n > 0.0 => Some(n),
        _ => None,
    };
    let error = match entry.error.view() {
        JsonValue::String(s) if !s.is_empty() => Some(s),
        _ => None,
    };
    Some(MergeJobView {
        story_id,
        status,
        started_at,
        finished_at,
        error,
    })
}
