use wasm_bindgen::prelude::*;
use crate::app::{dom, templates};
use crate::encoding::{bytes_to_hex_str, tx_short_hash};
enum CreateBtnState {
Disabled,
Ready,
Failed,
}
fn set_create_button_state(state: CreateBtnState) {
match state {
CreateBtnState::Disabled => set_create_button_classes(false, false, "create"),
CreateBtnState::Ready => set_create_button_classes(true, false, "create"),
CreateBtnState::Failed => set_create_button_classes(false, true, "✗ failed"),
}
}
fn set_create_button_failed_with(label: &str) {
set_create_button_classes(false, true, label);
}
fn set_create_button_classes(enabled: bool, failed: bool, label: &str) {
let Some(btn) = dom::by_id("create-btn") else { return };
let stripped: String = btn
.class_name()
.split_whitespace()
.filter(|c| *c != "ready" && *c != "failed")
.collect::<Vec<_>>()
.join(" ");
if enabled {
let _ = btn.remove_attribute("disabled");
} else {
let _ = btn.set_attribute("disabled", "");
}
let class = if enabled {
format!("{stripped} ready")
} else if failed {
format!("{stripped} failed")
} else {
stripped
};
btn.set_class_name(&class);
btn.set_inner_html(label);
}
pub(super) fn on_apex_input() {
let Some(input) = dom::input_by_id("apex-input") else { return };
let raw = input.value();
let cleaned = crate::app::tenant::sanitize(&raw);
if cleaned != raw {
input.set_value(&cleaned);
}
dom::swap_inner("claim-fund-slot", "");
if cleaned.len() < 3 || cleaned.len() > 32 {
set_create_button_state(CreateBtnState::Disabled);
return;
}
set_create_button_state(CreateBtnState::Disabled);
let pending = cleaned.clone();
wasm_bindgen_futures::spawn_local(async move {
let result = crate::app::registry::check_name(&pending).await;
let still_pending = dom::input_by_id("apex-input")
.map(|i| crate::app::tenant::sanitize(&i.value()) == pending)
.unwrap_or(false);
if !still_pending {
return;
}
match result {
Ok(crate::app::registry::Status::Available) => {
set_create_button_state(CreateBtnState::Ready);
}
_ => {
set_create_button_state(CreateBtnState::Disabled);
}
}
});
}
async fn submit_claim(name: &str, create_if_missing: bool) -> Result<String, String> {
match crate::app::registry::check_name(name).await {
Ok(crate::app::registry::Status::Available) => {}
Ok(other) => return Err(format!("name not available: {other:?}")),
Err(err) => return Err(format!("check_name: {err}")),
}
let cached = crate::app::APP.with(|cell| {
cell.borrow()
.wallet
.as_ref()
.map(|w| (w.signer.clone(), bytes_to_hex_str(&w.address)))
});
let (signer, addr_hex) = match cached {
Some(pair) => pair,
None if !create_if_missing => return Err("__NO_WALLET__".to_string()),
None => match crate::app::wallet_store::create_and_persist().await {
Ok(wallet) => {
let pair = (wallet.signer.clone(), wallet.address_hex());
crate::app::APP.with(|cell| cell.borrow_mut().wallet = Some(wallet));
pair
}
Err(err) => return Err(format!("wallet: {err}")),
},
};
let cost = crate::app::registry::registration_cost().await.unwrap_or(0);
if cost > 0 {
let wallet = crate::app::registry::token_balance_of(&addr_hex).await.unwrap_or(0);
if wallet < cost {
let meter = crate::app::registry::withdrawable_credit_of(&addr_hex)
.await
.unwrap_or(0);
if wallet + meter < cost {
let deficit_lh = (cost - wallet - meter) / 1_000_000_000_000_000_000u128;
return Err(format!("__NEED_LH__{deficit_lh}"));
}
}
}
let fee_payer =
crate::app::sponsor::signer().map_err(|e| format!("sponsor key: {e}"))?;
crate::app::registry::claim_and_maybe_set_main_sponsored(
&signer,
&fee_payer,
name,
crate::app::registry::ALPHA_USD_ADDRESS(),
)
.await
.map_err(|e| format!("claim_name: {e}"))
}
fn redirect_to_agent(name: &str) {
let target = format!("https://{name}.localharness.xyz/?claim=1");
if let Ok(window) = dom::window() {
let _ = window.location().assign(&target);
}
}
pub(super) async fn run_apex_claim(name: String, create_if_missing: bool) {
set_create_button_busy(true);
match submit_claim(&name, create_if_missing).await {
Ok(tx_hash) => {
web_sys::console::log_1(&JsValue::from_str(&format!(
"claimed {name} (tx {})",
tx_short_hash(&tx_hash)
)));
redirect_to_agent(&name);
}
Err(err) if err == "__NO_WALLET__" => {
set_create_button_busy(false);
dom::swap_outer("agents-list", &templates::identity_choice(&name).into_string());
}
Err(err) => {
web_sys::console::warn_1(&JsValue::from_str(&format!("apex claim failed: {err}")));
if let Some(rest) = err.strip_prefix("__NEED_LH__") {
set_create_button_failed_with(&format!("need {rest} more LH"));
dom::swap_inner("claim-fund-slot", &templates::buy_to_claim().into_string());
} else {
set_create_button_state(CreateBtnState::Failed);
}
}
}
}
pub(super) async fn onboard_claim(name: String) {
if let Some(root) = dom::by_id("root") {
root.set_inner_html(&templates::onboard_claiming(&name).into_string());
}
match submit_claim(&name, true).await {
Ok(tx_hash) => {
web_sys::console::log_1(&JsValue::from_str(&format!(
"onboard-claimed {name} (tx {})",
tx_short_hash(&tx_hash)
)));
redirect_to_agent(&name);
}
Err(err) => {
web_sys::console::warn_1(&JsValue::from_str(&format!(
"onboard claim failed: {err}"
)));
crate::app::paint_apex(crate::app::tenant::Host::Apex).await;
}
}
}
fn set_create_button_busy(busy: bool) {
let Some(btn) = dom::by_id("create-btn") else { return };
if busy {
btn.set_inner_html("creating…");
let _ = btn.set_attribute("disabled", "");
} else {
btn.set_inner_html("create");
}
}