use std::sync::Arc;
use axum::extract::{Path, Query, State};
use axum::Json;
use chrono::{DateTime, Utc};
use serde::Deserialize;
use oxios_calendar::{EventDraft, EventPatch};
use crate::error::AppError;
use crate::server::AppState;
#[derive(Debug, Deserialize)]
pub struct DateRangeParams {
pub from: String,
pub to: String,
}
#[derive(Debug, Deserialize)]
pub struct CreateEventRequest {
pub title: String,
pub start: String,
pub end: String,
#[serde(default)]
pub all_day: Option<bool>,
pub description: Option<String>,
pub location: Option<String>,
pub repeat: Option<oxios_calendar::Repeat>,
pub reminder_minutes: Option<Vec<u32>>,
}
#[derive(Debug, Deserialize)]
pub struct UpdateEventRequest {
pub title: Option<String>,
pub start: Option<String>,
pub end: Option<String>,
pub all_day: Option<bool>,
pub description: Option<Option<String>>,
pub location: Option<Option<String>>,
pub repeat: Option<Option<oxios_calendar::Repeat>>,
pub reminder_minutes: Option<Vec<u32>>,
}
#[derive(Debug, Deserialize)]
pub struct SearchParams {
pub q: String,
}
fn parse_dt(s: &str, field: &str) -> Result<DateTime<Utc>, AppError> {
s.parse::<DateTime<Utc>>()
.map_err(|e| AppError::BadRequest(format!("Invalid {field}: {e}")))
}
macro_rules! calendar_api {
($state:expr) => {
$state
.kernel
.calendar
.as_ref()
.ok_or_else(|| AppError::ServiceUnavailable("Calendar subsystem not available".into()))
};
}
pub(crate) async fn handle_calendar_events(
state: State<Arc<AppState>>,
Query(params): Query<DateRangeParams>,
) -> Result<Json<serde_json::Value>, AppError> {
let api = calendar_api!(state)?;
let from = parse_dt(¶ms.from, "from")?;
let to = parse_dt(¶ms.to, "to")?;
let events = api
.list(from, to)
.await
.map_err(|e| AppError::Internal(e.to_string()))?;
Ok(Json(serde_json::json!({ "events": events })))
}
pub(crate) async fn handle_calendar_event_get(
state: State<Arc<AppState>>,
Path(uid): Path<String>,
) -> Result<Json<serde_json::Value>, AppError> {
let api = calendar_api!(state)?;
let event = api
.get(&uid)
.await
.map_err(|e| AppError::NotFound(e.to_string()))?;
Ok(Json(serde_json::to_value(event).unwrap()))
}
pub(crate) async fn handle_calendar_event_create(
state: State<Arc<AppState>>,
Json(body): Json<CreateEventRequest>,
) -> Result<Json<serde_json::Value>, AppError> {
let api = calendar_api!(state)?;
let draft = EventDraft {
title: body.title,
start: parse_dt(&body.start, "start")?,
end: parse_dt(&body.end, "end")?,
all_day: body.all_day.unwrap_or(false),
description: body.description,
location: body.location,
repeat: body.repeat,
reminder_minutes: body.reminder_minutes.unwrap_or_default(),
source: oxios_calendar::EventSource::User,
};
let result = api
.create(draft)
.await
.map_err(|e| AppError::Internal(e.to_string()))?;
Ok(Json(serde_json::to_value(result).unwrap()))
}
pub(crate) async fn handle_calendar_event_update(
state: State<Arc<AppState>>,
Path(uid): Path<String>,
Json(body): Json<UpdateEventRequest>,
) -> Result<Json<serde_json::Value>, AppError> {
let api = calendar_api!(state)?;
let mut patch = EventPatch::default();
if let Some(title) = body.title {
patch.title = Some(title);
}
if let Some(start) = body.start {
patch.start = Some(parse_dt(&start, "start")?);
}
if let Some(end) = body.end {
patch.end = Some(parse_dt(&end, "end")?);
}
if let Some(all_day) = body.all_day {
patch.all_day = Some(all_day);
}
if let Some(desc) = body.description {
patch.description = Some(desc);
}
if let Some(loc) = body.location {
patch.location = Some(loc);
}
if let Some(rep) = body.repeat {
patch.repeat = Some(rep);
}
if let Some(reminders) = body.reminder_minutes {
patch.reminder_minutes = Some(reminders);
}
let result = api
.update(&uid, patch)
.await
.map_err(|e| AppError::Internal(e.to_string()))?;
Ok(Json(serde_json::to_value(result).unwrap()))
}
pub(crate) async fn handle_calendar_event_delete(
state: State<Arc<AppState>>,
Path(uid): Path<String>,
) -> Result<Json<serde_json::Value>, AppError> {
let api = calendar_api!(state)?;
api.delete(&uid)
.await
.map_err(|e| AppError::Internal(e.to_string()))?;
Ok(Json(serde_json::json!({ "deleted": uid })))
}
pub(crate) async fn handle_calendar_search(
state: State<Arc<AppState>>,
Query(params): Query<SearchParams>,
) -> Result<Json<serde_json::Value>, AppError> {
let api = calendar_api!(state)?;
let events = api
.search(¶ms.q)
.await
.map_err(|e| AppError::Internal(e.to_string()))?;
Ok(Json(serde_json::json!({ "events": events })))
}
pub(crate) async fn handle_calendar_freebusy(
state: State<Arc<AppState>>,
Query(params): Query<DateRangeParams>,
) -> Result<Json<serde_json::Value>, AppError> {
let api = calendar_api!(state)?;
let from = parse_dt(¶ms.from, "from")?;
let to = parse_dt(¶ms.to, "to")?;
let slots = api
.freebusy(from, to)
.await
.map_err(|e| AppError::Internal(e.to_string()))?;
Ok(Json(serde_json::json!({ "slots": slots })))
}