use crate::orders::PublishedDeal;
use crate::terms::{bare_name, terms, LandUse};
use crate::land::load_map;
use crate::wallet::{load_intents, save_intents};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub const DEALS_KEY: &str = "cyberia.deals.v2";
const LEGACY_DEALS_KEY: &str = "cyberia.deals.v1";
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FlatStatus {
Available,
OnRequest,
Transfer,
Owned,
Closed,
}
impl FlatStatus {
pub fn label(self) -> &'static str {
match self {
FlatStatus::Available => "AVAILABLE",
FlatStatus::OnRequest => "ON REQUEST",
FlatStatus::Transfer => "IN TRANSFER",
FlatStatus::Owned => "OWNED",
FlatStatus::Closed => "NOT ON OFFER",
}
}
}
pub const STEPS: &[(&str, &str)] = &[
("request", "lease requested โ the flat is spoken for"),
("review", "council review โ plot, price, neighbours"),
("docs", "documents โ passport, agreement, survey"),
("transfer", "rights in transfer โ signatures on both sides"),
("signed", "signed โ the flat is yours"),
];
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct DealEvent {
pub step: String,
pub ts_ms: f64,
pub note: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Deal {
pub flat_id: String,
pub flat_name: String,
pub step: String,
#[serde(default)]
pub cancelled: bool,
#[serde(default)]
pub history: Vec<DealEvent>,
#[serde(default)]
pub published: Option<PublishedDeal>,
}
impl Deal {
pub fn is_signed(&self) -> bool {
!self.cancelled && self.step == "signed"
}
pub fn is_live(&self) -> bool {
!self.cancelled && !self.is_signed()
}
pub fn step_index(&self) -> usize {
STEPS.iter().position(|(s, _)| *s == self.step).unwrap_or(0)
}
pub fn step_line(&self) -> &'static str {
STEPS
.iter()
.find(|(s, _)| *s == self.step)
.map(|(_, line)| *line)
.unwrap_or("")
}
}
pub fn now_ms() -> f64 {
js_sys::Date::now()
}
pub fn fmt_ts(ms: f64) -> String {
let d = js_sys::Date::new(&ms.into());
format!(
"{:04}-{:02}-{:02} {:02}:{:02}",
d.get_full_year(),
d.get_month() + 1,
d.get_date(),
d.get_hours(),
d.get_minutes()
)
}
pub fn load_deals() -> HashMap<String, Deal> {
let Some(ls) = web_sys::window().and_then(|w| w.local_storage().ok().flatten()) else {
return HashMap::new();
};
if let Some(raw) = ls.get_item(LEGACY_DEALS_KEY).ok().flatten() {
let legacy: HashMap<String, Deal> = serde_json::from_str(&raw).unwrap_or_default();
let mut q = load_intents();
q.retain(|i| !(i.fleet == "YOU" && i.action == "lease" && legacy.contains_key(&i.flat)));
save_intents(&q);
let _ = ls.remove_item(LEGACY_DEALS_KEY);
}
ls.get_item(DEALS_KEY)
.ok()
.flatten()
.and_then(|s| serde_json::from_str(&s).ok())
.unwrap_or_default()
}
pub fn save_deals(m: &HashMap<String, Deal>) {
let own: HashMap<&String, &Deal> = m.iter().filter(|(_, d)| d.published.is_none()).collect();
if let Some(ls) = web_sys::window().and_then(|w| w.local_storage().ok().flatten()) {
if let Ok(s) = serde_json::to_string(&own) {
let _ = ls.set_item(DEALS_KEY, &s);
}
}
}
pub fn merge_published(deals: &mut HashMap<String, Deal>, index: &HashMap<String, PublishedDeal>) {
let map = load_map();
for (id, pd) in index {
let step = match pd.status.as_str() {
"reserved" | "transfer" => "transfer",
"owned" => "signed",
_ => {
if deals.get(id).map(|d| d.published.is_some()).unwrap_or(false) {
deals.remove(id);
}
continue;
}
};
let name = map
.phase0
.iter()
.find(|f| &f.id == id)
.map(|f| bare_name(&f.name))
.unwrap_or_else(|| id.to_uppercase());
let own_history = deals.get(id).map(|d| d.history.clone()).unwrap_or_default();
let mut history = own_history;
history.push(DealEvent {
step: step.into(),
ts_ms: now_ms(),
note: format!("published by the valley ยท {}", pd.line()),
});
deals.insert(
id.clone(),
Deal {
flat_id: id.clone(),
flat_name: name,
step: step.into(),
cancelled: false,
history,
published: Some(pd.clone()),
},
);
}
}
pub fn holder(id: &str) -> Option<&'static str> {
terms(id).and_then(|t| t.holder())
}
pub fn flat_status(id: &str, deals: &HashMap<String, Deal>) -> FlatStatus {
match deals.get(id) {
Some(d) if d.is_signed() => return FlatStatus::Owned,
Some(d) if d.is_live() => return FlatStatus::Transfer,
_ => {}
}
let Some(t) = terms(id) else {
return FlatStatus::Closed;
};
if t.holder().is_some() {
return FlatStatus::Owned;
}
if t.in_reregistration() {
return FlatStatus::Closed;
}
let priced_and_free = !t.is_city_land() && t.price().is_some();
match t.land_use() {
LandUse::Venture if id.starts_with("avalon") => FlatStatus::OnRequest,
LandUse::Lease | LandUse::Dual | LandUse::Venture if priced_and_free => FlatStatus::Available,
LandUse::Lease | LandUse::Dual if !t.is_city_land() => FlatStatus::Available,
LandUse::Venture => FlatStatus::OnRequest,
LandUse::Hgb if !t.is_city_land() => FlatStatus::OnRequest,
_ => FlatStatus::Closed,
}
}
pub fn open_deal(deals: &mut HashMap<String, Deal>, id: &str, name: &str) {
let fresh = Deal {
flat_id: id.to_string(),
flat_name: name.to_string(),
step: "request".into(),
cancelled: false,
history: vec![DealEvent {
step: "request".into(),
ts_ms: now_ms(),
note: "lease requested on the flat page".into(),
}],
published: None,
};
match deals.get_mut(id) {
Some(d) if !d.cancelled => {}
_ => {
deals.insert(id.to_string(), fresh);
}
}
save_deals(deals);
}
pub fn advance_deal(deals: &mut HashMap<String, Deal>, id: &str, note: &str) {
if let Some(d) = deals.get_mut(id) {
let i = d.step_index();
if d.cancelled || d.published.is_some() || i + 1 >= STEPS.len() {
return;
}
d.step = STEPS[i + 1].0.into();
d.history.push(DealEvent {
step: d.step.clone(),
ts_ms: now_ms(),
note: note.into(),
});
}
save_deals(deals);
}
pub fn cancel_deal(deals: &mut HashMap<String, Deal>, id: &str, note: &str) {
if let Some(d) = deals.get_mut(id) {
if d.is_signed() || d.published.is_some() {
return;
}
d.cancelled = true;
d.history.push(DealEvent {
step: "cancelled".into(),
ts_ms: now_ms(),
note: note.into(),
});
}
save_deals(deals);
}
pub const DD_KEY: &str = "cyberia.dd.v1";
pub fn load_dd() -> HashMap<String, f64> {
web_sys::window()
.and_then(|w| w.local_storage().ok().flatten())
.and_then(|ls| ls.get_item(DD_KEY).ok().flatten())
.and_then(|s| serde_json::from_str(&s).ok())
.unwrap_or_default()
}
pub fn request_dd(m: &mut HashMap<String, f64>, id: &str) {
m.entry(id.to_string()).or_insert_with(now_ms);
if let Some(ls) = web_sys::window().and_then(|w| w.local_storage().ok().flatten()) {
if let Ok(s) = serde_json::to_string(m) {
let _ = ls.set_item(DD_KEY, &s);
}
}
}
pub const JV_KEY: &str = "cyberia.jv.v1";
pub fn load_jv() -> HashMap<String, f64> {
web_sys::window()
.and_then(|w| w.local_storage().ok().flatten())
.and_then(|ls| ls.get_item(JV_KEY).ok().flatten())
.and_then(|s| serde_json::from_str(&s).ok())
.unwrap_or_default()
}
pub fn request_jv(m: &mut HashMap<String, f64>, id: &str) {
m.entry(id.to_string()).or_insert_with(now_ms);
if let Some(ls) = web_sys::window().and_then(|w| w.local_storage().ok().flatten()) {
if let Ok(s) = serde_json::to_string(m) {
let _ = ls.set_item(JV_KEY, &s);
}
}
}