use std::path::PathBuf;

use serde::{Deserialize, Serialize};
use tokio::sync::mpsc;

pub mod auto_trigger;
pub mod backend;
pub mod in_flight;
pub mod llm;
pub mod prompt;
pub mod recovery;
pub mod render;
pub mod worker;

pub use in_flight::{InFlight, InFlightGuard};

/// Single-document-per-case filename. A re-analysis overwrites the
/// document in place; the file is a typed JSON envelope so the
/// `covered_mtime` snapshot stays glued to the markdown body it was
/// produced from.
pub const DOCUMENT_FILE: &str = "document.json";

/// Persisted analysis output for a case. The `covered_mtime` field is
/// the snapshot mtime of the recordings the LLM saw — *not* the
/// wallclock time at which the file was written. Comparing it to the
/// current `latest_mtime` on disk tells `evaluate_state` whether the
/// stored markdown still describes the case faithfully or whether a
/// new recording arrived after the snapshot was frozen and a fresh
/// analysis is required.
#[derive(Debug, Serialize, Deserialize)]
pub struct Document {
    pub covered_mtime: String,
    pub written_at: String,
    pub backend_id: String,
    pub content_md: String,
}

/// A single request to analyze a case. Carries the full [`AnalysisInput`]
/// payload over the channel so the worker does not need to read it back
/// from disk. Lifecycle is process-local — see [`InFlight`] — so a server
/// restart between send and worker pickup just drops the job; the next
/// `/cases` render re-triggers via the normal `Required → Idle` path.
#[derive(Debug, Clone)]
pub struct AnalyzeJob {
    pub case_dir: PathBuf,
    pub input: AnalysisInput,
}

pub type AnalyzeSender = mpsc::UnboundedSender<AnalyzeJob>;
pub type AnalyzeReceiver = mpsc::UnboundedReceiver<AnalyzeJob>;

pub fn channel() -> (AnalyzeSender, AnalyzeReceiver) {
    mpsc::unbounded_channel()
}

/// Input payload for a single analysis run. Built by the trigger
/// (handler or auto-trigger), shipped to the worker via [`AnalyzeJob`].
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnalysisInput {
    /// Latest filesystem mtime (server clock) of any `.m4a` in the case at
    /// the moment the analyze button was clicked. Carried through for
    /// diagnostics / future "Nachtrag" detection.
    pub last_recording_mtime: String,
    pub recordings: Vec<RecordingInput>,
    /// Selected LLM backend id; empty string falls back to the default
    /// backend. Old inputs without the field deserialize to `""`.
    #[serde(default)]
    pub backend_id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecordingInput {
    /// Recording timestamp from the watch (filename-derived, ISO-8601).
    pub recorded_at: String,
    pub text: String,
}
