use std::{cmp::Reverse, collections::BTreeMap};
use chrono::NaiveDate;
use serde::Deserialize;
use serde::Serialize;
use crate::db::models;
use self::messages::Historical;
use super::format_date;
use super::CURRENT_PUBLICATION_NAME;
pub mod messages;
#[derive(Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Versions {
pub active_publication: String,
pub active_version: String,
pub active_compare_to: Option<String>,
pub features: Features,
pub path: String,
pub publications: BTreeMap<Reverse<String>, Publication>,
pub messages: Historical,
}
#[derive(Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Features {
pub compare: bool,
pub historical_versions: bool,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Publication {
pub active: bool,
pub revoked: bool,
pub date: String,
pub display: String,
pub name: String,
pub versions: Vec<Version>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Version {
pub date: String,
pub display: String,
#[serde(rename = "version")]
pub index: usize,
}
impl From<models::version::Version> for Version {
fn from(value: models::version::Version) -> Self {
Self {
date: value.codified_date.clone(),
display: value.codified_date,
index: 0,
}
}
}
impl Versions {
#[expect(
clippy::too_many_arguments,
reason = "Basically a model mapper for returning a `Versions` instance, can get simplified in the future. Leave it with too many args for now."
)]
#[must_use]
pub fn build(
active_publication_name: &str,
active_version: String,
active_compare_to: Option<String>,
url: &str,
publications: &[models::publication::Publication],
current_publication_name: &str,
versions: &[Version],
messages: Historical,
) -> Self {
Self {
active_publication: active_publication_name.to_owned(),
active_version,
active_compare_to,
features: Features {
compare: true,
historical_versions: true,
},
path: url.strip_prefix('/').unwrap_or_default().to_owned(),
publications: {
let mut sorted_publications = BTreeMap::new();
for pb in publications {
sorted_publications.insert(
Reverse(pb.name.clone()),
Publication {
active: pb.name.to_lowercase()
== active_publication_name.to_lowercase(),
revoked: pb.revoked != 0,
date: pb.date.clone(),
display: Self::format_display_date(
&pb.name,
&pb.date,
current_publication_name,
),
name: pb.name.clone(),
versions: {
if pb.name.to_lowercase() == active_publication_name.to_lowercase()
{
versions.to_vec()
} else {
vec![]
}
},
},
);
}
sorted_publications
},
messages,
}
}
fn format_display_date(name: &str, date: &str, current_date: &str) -> String {
if name == CURRENT_PUBLICATION_NAME {
CURRENT_PUBLICATION_NAME.to_owned()
} else {
let mut formatted_date = format_date(date);
if name.matches('-').count() == 3 {
if let Some(last_dash) = name.rfind('-') {
if let Some(publication_number_suffix) = name.get(last_dash..) {
let pub_number = publication_number_suffix
.trim_start_matches('-')
.parse::<usize>()
.unwrap_or_default();
let with_space = format!(" ({pub_number})");
formatted_date.push_str(&with_space);
}
}
}
if date == current_date {
formatted_date.push_str(" (current)");
}
formatted_date
}
}
}
impl Version {
#[must_use]
pub const fn new(date: String, display: String, index: usize) -> Self {
Self {
date,
display,
index,
}
}
pub fn insert_if_not_present(versions: &mut Vec<Self>, date: Option<String>) {
let Some(version_date) = date else {
return;
};
if NaiveDate::parse_from_str(&version_date, "%Y-%m-%d").is_err() {
return;
}
if versions.iter().all(|ver| ver.date != version_date) {
let version = Self::new(version_date.clone(), version_date, 0);
Self::insert_version_sorted(versions, version);
}
}
pub fn insert_version_sorted(collection: &mut Vec<Self>, item: Self) {
let mut idx = 0;
for i in collection.iter() {
if i.date < item.date {
break;
}
idx += 1;
}
collection.insert(idx, item);
}
#[must_use]
pub fn find_index_or_closest(versions: &[Self], date: &str) -> usize {
versions
.iter()
.position(|ver| ver.date.as_str() == date)
.unwrap_or_else(|| {
let closest_date = versions
.iter()
.filter(|ver| ver.date.as_str() < date)
.max_by(|current, next| current.date.cmp(&next.date))
.map_or_else(|| None, |ver| Some(ver.date.as_str()))
.unwrap_or("-1");
versions
.iter()
.position(|ver| ver.date.as_str() == closest_date)
.unwrap_or(versions.len())
})
}
}