//! Marker file reader/writer.
//!
//! The markers file is JSONL: one event per line. The post-processor reads it
//! to figure out where the speedup segments live in the source video.
//!
//! Event types:
//!   recording_start  - written by `mark init`. Must be the first event.
//!   speedup_start    - written by `mark start` or `wrap`. Has speed + label.
//!   speedup_end      - written by `mark end` or `wrap`. Closes the previous start.

use serde::{Deserialize, Serialize};
use std::fs::{File, OpenOptions};
use std::io::{BufRead, BufReader, Write};
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(tag = "action", rename_all = "snake_case")]
pub enum Event {
    RecordingStart {
        epoch: f64,
    },
    SpeedupStart {
        epoch: f64,
        speed: u32,
        label: String,
    },
    SpeedupEnd {
        epoch: f64,
    },
}

impl Event {
    pub fn epoch(&self) -> f64 {
        match self {
            Event::RecordingStart { epoch } => *epoch,
            Event::SpeedupStart { epoch, .. } => *epoch,
            Event::SpeedupEnd { epoch } => *epoch,
        }
    }
}

/// Current epoch with millisecond precision.
pub fn now_epoch() -> f64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|d| d.as_secs_f64())
        .unwrap_or(0.0)
}

/// Default markers file path. Honors $VIDEO_SPEEDUP_MARKERS env var.
pub fn default_path() -> String {
    std::env::var("VIDEO_SPEEDUP_MARKERS")
        .unwrap_or_else(|_| "/tmp/adom-video-post-markers.jsonl".to_string())
}

/// Initialize the markers file: clear it and write a recording_start event.
pub fn init(path: &str) -> Result<f64, String> {
    let epoch = now_epoch();
    let mut f = File::create(path).map_err(|e| format!("create {}: {}", path, e))?;
    let event = Event::RecordingStart { epoch };
    writeln!(f, "{}", serde_json::to_string(&event).unwrap())
        .map_err(|e| format!("write {}: {}", path, e))?;
    Ok(epoch)
}

/// Append an event to an existing markers file.
pub fn append(path: &str, event: &Event) -> Result<(), String> {
    let mut f = OpenOptions::new()
        .create(true)
        .append(true)
        .open(path)
        .map_err(|e| format!("open {}: {}", path, e))?;
    writeln!(f, "{}", serde_json::to_string(event).unwrap())
        .map_err(|e| format!("write {}: {}", path, e))?;
    Ok(())
}

/// Read all events from a markers file.
pub fn read_all<P: AsRef<Path>>(path: P) -> Result<Vec<Event>, String> {
    let path = path.as_ref();
    let f = File::open(path).map_err(|e| format!("open {}: {}", path.display(), e))?;
    let reader = BufReader::new(f);
    let mut events = Vec::new();
    for (i, line) in reader.lines().enumerate() {
        let line = line.map_err(|e| format!("read line {}: {}", i + 1, e))?;
        let line = line.trim();
        if line.is_empty() {
            continue;
        }
        let event: Event = serde_json::from_str(line)
            .map_err(|e| format!("parse line {}: {} ({})", i + 1, e, line))?;
        events.push(event);
    }
    Ok(events)
}

/// A speedup segment paired up from start/end events. Offset is relative to
/// the recording_start epoch (so 0.0 = first frame of the video).
#[derive(Debug, Clone)]
pub struct Segment {
    pub start_offset: f64,
    pub end_offset: f64,
    pub speed: u32,
    pub label: String,
}

impl Segment {
    pub fn duration(&self) -> f64 {
        self.end_offset - self.start_offset
    }
    pub fn sped_duration(&self) -> f64 {
        self.duration() / self.speed as f64
    }
}

/// Parse the events into a list of speedup segments. Returns the recording
/// start epoch and the segments. Unclosed speedup_start markers are warned
/// about and treated as ending at the end of the video (caller decides where
/// the end is).
pub struct ParseResult {
    pub recording_start: f64,
    pub segments: Vec<Segment>,
    pub warnings: Vec<String>,
    pub unclosed: Vec<(f64, u32, String)>, // for unclosed starts that need an end injected
}

pub fn parse(events: &[Event]) -> Result<ParseResult, String> {
    let mut iter = events.iter();
    let recording_start = match iter.next() {
        Some(Event::RecordingStart { epoch }) => *epoch,
        Some(other) => {
            return Err(format!(
                "first event must be recording_start, got {:?}. Hint: call `adom-video-post mark init` before any other markers.",
                other
            ));
        }
        None => return Err("markers file is empty. Hint: call `adom-video-post mark init` first.".to_string()),
    };

    let mut segments = Vec::new();
    let mut warnings = Vec::new();
    let mut unclosed = Vec::new();
    let mut pending_start: Option<(f64, u32, String)> = None;

    for ev in iter {
        match ev {
            Event::RecordingStart { .. } => {
                warnings.push("duplicate recording_start event ignored".to_string());
            }
            Event::SpeedupStart { epoch, speed, label } => {
                if let Some((prev_epoch, prev_speed, prev_label)) = pending_start.take() {
                    warnings.push(format!(
                        "speedup_start at {:.3} found while previous speedup_start ({}, '{}') at {:.3} was unclosed; previous one will end at this start time",
                        epoch - recording_start, prev_speed, prev_label, prev_epoch - recording_start
                    ));
                    segments.push(Segment {
                        start_offset: prev_epoch - recording_start,
                        end_offset: epoch - recording_start,
                        speed: prev_speed,
                        label: prev_label,
                    });
                }
                pending_start = Some((*epoch, *speed, label.clone()));
            }
            Event::SpeedupEnd { epoch } => {
                if let Some((start_epoch, speed, label)) = pending_start.take() {
                    segments.push(Segment {
                        start_offset: start_epoch - recording_start,
                        end_offset: epoch - recording_start,
                        speed,
                        label,
                    });
                } else {
                    warnings.push(format!(
                        "speedup_end at {:.3} with no matching speedup_start (ignored)",
                        epoch - recording_start
                    ));
                }
            }
        }
    }

    if let Some((epoch, speed, label)) = pending_start {
        unclosed.push((epoch - recording_start, speed, label));
    }

    // Sort segments by start time (defensive — they should already be in order)
    segments.sort_by(|a, b| a.start_offset.partial_cmp(&b.start_offset).unwrap());

    Ok(ParseResult {
        recording_start,
        segments,
        warnings,
        unclosed,
    })
}