use crate::deals::{flat_status, holder, load_deals, FlatStatus};
use crate::terms::{bare_name, land_use, size_line, terms};
use crate::land::{area_m2, load_map, LandFlat, FLAG_SVG};
use crate::nav::CyberiaNav;
use crate::plot_board::LandBoard;
use crate::plot_side::SideColumn;
use crate::plot_land::{load_state, Frame};
use leptos::prelude::*;
use leptos_router::hooks::{use_location, use_params_map};
use std::sync::Arc;
fn district_color(zone: &str) -> &'static str {
let z = zone.to_lowercase();
if z.contains("avalon") {
"#ff5b00"
} else if z.contains("sinwood") {
"#00fe00"
} else if z.contains("bridge") {
"#00acff"
} else if z.contains("core") {
"#fcf000"
} else if z.contains("ether") {
"#d500f9"
} else if z.contains("asgard") {
"#ff0000"
} else if z.contains("edem") || z.contains("canyon") {
"#304ffe"
} else {
"#ffffff"
}
}
fn title_case(s: &str) -> String {
let mut c = s.chars();
match c.next() {
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
None => String::new(),
}
}
fn passport_json(flat: &LandFlat, frame: &Frame, status: FlatStatus) -> String {
let deals = load_deals();
let deal = deals.get(&flat.id);
let st = load_state(&flat.id);
let doc = serde_json::json!({
"id": flat.id,
"name": bare_name(&flat.name),
"holder": holder(&flat.id),
"lease_book": terms(&flat.id),
"zone": flat.zone,
"phase": flat.phase,
"status": status.label(),
"area_m2": (area_m2(&flat.coords) * 10.0).round() / 10.0,
"perimeter_m": (frame.perimeter_m() * 10.0).round() / 10.0,
"vertices": flat.coords.len(),
"coords_lonlat": flat.coords,
"grid_cell_m": crate::plot_land::CELL_M,
"pruned_cells": st.pruned,
"placed": st.placed,
"deal": deal,
"generated_ms": crate::deals::now_ms(),
"source": "cyberia.my · phase 0 · soft3 local",
});
serde_json::to_string_pretty(&doc).unwrap_or_default()
}
fn boundary_kml(flat: &LandFlat) -> String {
let ring: Vec<String> = flat
.coords
.iter()
.map(|c| format!("{},{},0", c[0], c[1]))
.collect();
format!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\"><Document><name>{name}</name><Placemark><name>{name}</name><description>{zone} · phase {phase} · cyberia.my</description><Polygon><outerBoundaryIs><LinearRing><coordinates>{coords}</coordinates></LinearRing></outerBoundaryIs></Polygon></Placemark></Document></kml>\n",
name = bare_name(&flat.name),
zone = flat.zone,
phase = flat.phase,
coords = ring.join(" "),
)
}
#[component]
pub fn PlotPage() -> impl IntoView {
let params = use_params_map();
let location = use_location();
let map = load_map();
let id = params.get_untracked().get("id").unwrap_or_default();
let lease_mode = location.pathname.get_untracked().trim_end_matches('/').ends_with("/lease");
let flat = map.phase0.iter().find(|f| f.id == id).cloned();
let Some(flat) = flat else {
document().set_title("Cyberia — no such flat");
return view! {
<div class="page-shell" style="padding:40px;">
<h1 style="color: var(--cyber-red);">"404"</h1>
<p style="color:#777777; margin-top:12px;">{format!("no flat called {id} on the Gesing map")}</p>
<a href="/map" style="color: var(--cyber-green);">"← back to the map"</a>
</div>
}
.into_any();
};
let frame = Arc::new(Frame::new(&flat.coords));
let deals = RwSignal::new(load_deals());
leptos::task::spawn_local(async move {
let index = crate::orders::fetch_index().await;
if !index.is_empty() {
deals.update(|d| crate::deals::merge_published(d, &index));
}
});
let pdata = RwSignal::new(None::<crate::orders::PlotData>);
{
let fid_data = flat.id.clone();
leptos::task::spawn_local(async move {
pdata.set(crate::orders::fetch_plot_data(&fid_data).await);
});
}
let state = RwSignal::new(load_state(&flat.id));
let name = bare_name(&flat.name);
let owner = holder(&flat.id).map(|s| s.to_string());
let book = terms(&flat.id);
let zone = if flat.zone.is_empty() { "gesing".to_string() } else { flat.zone.clone() };
let area = area_m2(&flat.coords);
let dcol = district_color(&zone);
let fid = flat.id.clone();
let status = Memo::new({
let fid = fid.clone();
move |_| flat_status(&fid, &deals.get())
});
{
let title = format!(
"{} · {} {} in {}, Cyber Valley, Bali",
name,
size_line(book, area),
land_use(&flat.id).label().to_lowercase(),
title_case(&zone),
);
Effect::new(move |_| {
document().set_title(&title);
});
}
let passport = passport_json(&flat, &frame, status.get_untracked());
let kml = boundary_kml(&flat);
let map_href = format!("/map?plot={}", flat.id);
let owner_line = owner.clone().map(|h| format!(" · held by {h}")).unwrap_or_default();
let fid_pill = flat.id.clone();
let lu_page = land_use(&flat.id);
view! {
<div class="page-shell cities-shell plot-shell">
<div class="site-chrome cyberia-chrome">
<div class="chrome-inner">
<div class="header-row1">
<div class="logo-zone">
<h1 class="logo">
<a href="/cities" class="brand-flag" title="home" inner_html=FLAG_SVG></a>
<span style="color: var(--cyber-green);">"cyber"</span>
<span style="color: var(--cyber-green); margin: 0 1px;">"•"</span>
<span style="color: #fff;">"ia"</span>
</h1>
</div>
<div class="cyberia-phase-pill is-real">
<span class="phase-dot" style:background=land_use(&fid_pill).color()></span>
{move || format!("{} · {}", name.clone(), status.get().label())}
</div>
<CyberiaNav active="map" />
</div>
</div>
</div>
<div class="cities-stage plot-stage-wrap">
<div class="cities-hero plot-hero">
<div>
<div class="cities-kicker" style:color=dcol>{format!("FLAT · {}", zone.to_uppercase())}</div>
<h2 class="cities-title">{bare_name(&flat.name)}</h2>
<p class="cities-lead">
{format!("{} · {}{owner_line}", lu_page.label(), size_line(book, area))}
</p>
</div>
<a class="cta-btn cta-lease dock-found" href=map_href style="text-decoration:none; max-width: 220px;">
<span class="cta-copy">
<span class="cta-title">"← MAP"</span>
<span class="cta-sub">"back to the valley"</span>
</span>
</a>
</div>
<div class=if lease_mode { "plot-stage lease-first" } else { "plot-stage" }>
<div class="plot-col plot-col-land">
<LandBoard
plot_id=flat.id.clone()
frame=frame.clone()
state=state
district_color=dcol
status_color=land_use(&flat.id).color()
/>
</div>
<div class="plot-col plot-col-side">
<SideColumn
plot_id=flat.id.clone()
plot_name=bare_name(&flat.name)
drawn_m2=area
passport=passport
kml=kml
lease_mode=lease_mode
deals=deals
pdata=pdata
/>
</div>
</div>
</div>
</div>
}
.into_any()
}