terapi 0.5.0

A terminal UI for REST API and GraphQL automation
use std::collections::{HashMap, HashSet};

use super::*;
use super::http::http_status_label;
use crate::storage::HistoryEntry;

impl App {
    pub fn tick(&mut self) {
        if let Ok(outcome) = self.schema_rx.try_recv() {
            match outcome {
                Ok(SchemaMsg::TypeList(types)) => {
                    self.schema_type_cursor = 0;
                    self.schema_field_scroll = 0;
                    self.schema_state = SchemaState::Ready {
                        types,
                        detail: SchemaDetail::None,
                    };
                }
                Ok(SchemaMsg::TypeDetail(detail)) => {
                    if let SchemaState::Ready { detail: ref mut d, .. } = self.schema_state {
                        *d = SchemaDetail::Loaded(detail);
                    }
                }
                Err(msg) => {
                    match self.schema_state {
                        SchemaState::LoadingList => {
                            self.schema_state = SchemaState::Error(msg);
                        }
                        SchemaState::Ready { detail: ref mut d, .. } => {
                            *d = SchemaDetail::Error(msg);
                        }
                        _ => {
                            self.schema_state = SchemaState::Error(msg);
                        }
                    }
                }
            }
        }

        if let Ok(outcome) = self.response_rx.try_recv() {
            self.request_loading = false;
            match outcome {
                Ok(http) => {
                    self.response_status = Some(http.status);
                    self.response_elapsed_ms = Some(http.elapsed_ms);
                    self.response_headers = http.headers.clone();
                    self.response_body = Some(http.body.clone());
                    self.response_cursor = 0;
                    self.response_scroll = 0;
                    self.response_folds = HashSet::new();
                    self.status_message = format!(
                        "{}  {}ms  —  Tab: panels  e: edit URL  s: send  m: method  ←/→: section  r: raw  q: quit",
                        http_status_label(self.response_status.unwrap_or(0)),
                        self.response_elapsed_ms.unwrap_or(0),
                    );
                    self.record_history(Some(http.status), Some(http.elapsed_ms), Some(http.body));
                }
                Err(msg) => {
                    self.response_status = None;
                    self.response_body = Some(format!("Error: {}", msg));
                    self.response_view = ResponseView::Raw;
                    self.response_cursor = 0;
                    self.response_scroll = 0;
                    self.response_folds = HashSet::new();
                    let short = msg.lines().next().unwrap_or(&msg).chars().take(80).collect::<String>();
                    self.status_message = format!("Error: {}  —  r: JSON view  e: edit URL  s: retry  q: quit", short);
                    self.record_history(None, None, Some(format!("Error: {}", msg)));
                }
            }
        }
    }

    pub(super) fn response_line_count(&self) -> usize {
        crate::json_highlight::rows(
            self.response_body.as_deref().unwrap_or(""),
            &self.response_folds,
        )
        .len()
    }

    pub(super) fn sync_scroll(&mut self) {
        self.response_scroll = (self.response_cursor as u16).saturating_sub(3);
    }

    fn record_history(&mut self, status: Option<u16>, elapsed_ms: Option<u64>, response_body: Option<String>) {
        if let Some(raw) = &self.last_request_raw {
            let entry = HistoryEntry {
                timestamp_secs: std::time::SystemTime::now()
                    .duration_since(std::time::UNIX_EPOCH)
                    .unwrap_or_default()
                    .as_secs(),
                method: raw.method.clone(),
                url: raw.url.clone(),
                headers: raw.headers.iter().cloned().collect::<HashMap<_, _>>(),
                body: raw.body.clone(),
                status,
                elapsed_ms,
                response_body,
            };
            self.history.insert(0, entry);
            if self.history.len() > 100 {
                self.history.truncate(100);
            }
            let _ = crate::storage::save_history(&self.history);
        }
    }

    pub(super) fn toggle_response_fold(&mut self) {
        let json = self.response_body.as_deref().unwrap_or("");
        let json_rows = crate::json_highlight::rows(json, &self.response_folds);

        if let Some(path) = json_rows
            .get(self.response_cursor)
            .and_then(|r| r.fold_path.clone())
        {
            if !self.response_folds.remove(&path) {
                self.response_folds.insert(path);
            }
            let new_len = self.response_line_count();
            if self.response_cursor >= new_len && new_len > 0 {
                self.response_cursor = new_len - 1;
            }
            self.sync_scroll();
        }
    }
}