#include "tools/jotpluggler/internal.h"
#include "common/hardware/hw.h"

#include <unistd.h>

namespace fs = std::filesystem;

namespace {

enum class ModalAction {
  None,
  Primary,
  Secondary,
};

struct FindSignalMatch {
  const std::string *path = nullptr;
  int score = 0;
};

struct DbcEditorSource {
  fs::path path;
  DbcEditorState::SourceKind kind = DbcEditorState::SourceKind::None;
};

StreamSourceConfig stream_source_config_from_ui(const UiState &state) {
  StreamSourceConfig source;
  source.kind = state.stream_source_kind;
  source.address = util::strip(state.stream_address_buffer);
  if (source.kind == StreamSourceKind::CerealLocal) {
    source.address = "127.0.0.1";
  } else {
    source.address = normalize_stream_address(std::move(source.address));
  }
  return source;
}

void open_queued_popup(bool &flag, const char *name) {
  if (flag) {
    ImGui::OpenPopup(name);
    flag = false;
  }
}

ModalAction draw_modal_action_row(const char *primary_label,
                                  const char *secondary_label = "Cancel",
                                  float width = 120.0f) {
  if (ImGui::Button(primary_label, ImVec2(width, 0.0f))) {
    return ModalAction::Primary;
  }
  ImGui::SameLine();
  if (ImGui::Button(secondary_label, ImVec2(width, 0.0f))) {
    return ModalAction::Secondary;
  }
  return ModalAction::None;
}

std::vector<FindSignalMatch> find_signal_matches(const AppSession &session, std::string_view query) {
  std::vector<FindSignalMatch> matches;
  if (query.empty()) {
    return matches;
  }
  const std::string needle = lowercase_copy(query);
  for (const std::string &path : session.route_data.paths) {
    const std::string hay = lowercase_copy(path);
    const size_t pos = hay.find(needle);
    if (pos == std::string::npos) {
      continue;
    }
    const size_t slash = path.find_last_of('/');
    const std::string_view label = slash == std::string::npos ? std::string_view(path) : std::string_view(path).substr(slash + 1);
    int score = static_cast<int>(pos * 8 + path.size());
    if (lowercase_copy(label) == needle) score -= 60;
    if (util::starts_with(hay, needle)) score -= 30;
    matches.push_back({.path = &path, .score = score});
  }
  std::sort(matches.begin(), matches.end(), [](const FindSignalMatch &a, const FindSignalMatch &b) {
    return std::tie(a.score, *a.path) < std::tie(b.score, *b.path);
  });
  if (matches.size() > 200) {
    matches.resize(200);
  }
  return matches;
}

bool open_find_signal_result(UiState *state, const std::string &path) {
  state->selected_browser_paths = {path};
  state->selected_browser_path = path;
  state->browser_selection_anchor = path;
  state->status_text = "Selected signal " + path;
  return true;
}

void draw_open_route_popup(AppSession *session, UiState *state) {
  if (!app_begin_popup_modal("Open Route")) {
    return;
  }
  ImGui::TextUnformatted("Load a route into the current layout.");
  ImGui::Separator();
  input_text_string("Route", &state->route_buffer);
  input_text_string("Data Dir", &state->data_dir_buffer);
  ImGui::Spacing();
  switch (draw_modal_action_row("Load")) {
    case ModalAction::Primary:
      reload_session(session, state, state->route_buffer, state->data_dir_buffer);
      ImGui::CloseCurrentPopup();
      break;
    case ModalAction::Secondary:
      sync_route_buffers(state, *session);
      ImGui::CloseCurrentPopup();
      break;
    case ModalAction::None:
      break;
  }
  ImGui::EndPopup();
}

void draw_stream_popup(AppSession *session, UiState *state) {
  if (!app_begin_popup_modal("Live Stream")) {
    return;
  }

  ImGui::TextUnformatted("Connect to a live source.");
  ImGui::Separator();
  if (ImGui::RadioButton("Local (MSGQ)", state->stream_source_kind == StreamSourceKind::CerealLocal)) {
    state->stream_source_kind = StreamSourceKind::CerealLocal;
  }
  if (ImGui::RadioButton("Remote (ZMQ)", state->stream_source_kind == StreamSourceKind::CerealRemote)) {
    state->stream_source_kind = StreamSourceKind::CerealRemote;
  }

  if (state->stream_source_kind == StreamSourceKind::CerealRemote) {
    input_text_string("Address", &state->stream_address_buffer);
  }
  ImGui::InputDouble("Buffer (seconds)", &state->stream_buffer_seconds, 0.0, 0.0, "%.0f");
  ImGui::Spacing();
  switch (draw_modal_action_row("Connect")) {
    case ModalAction::Primary: {
      const StreamSourceConfig source = stream_source_config_from_ui(*state);
      if (start_stream_session(session, state, source, state->stream_buffer_seconds, false)) {
        ImGui::CloseCurrentPopup();
      }
      break;
    }
    case ModalAction::Secondary:
      sync_stream_buffers(state, *session);
      ImGui::CloseCurrentPopup();
      break;
    case ModalAction::None:
      break;
  }
  ImGui::EndPopup();
}

void draw_load_layout_popup(AppSession *session, UiState *state) {
  if (!app_begin_popup_modal("Load Layout")) {
    return;
  }
  ImGui::TextUnformatted("Load a JotPlugger JSON layout.");
  ImGui::Separator();
  input_text_string("Layout", &state->load_layout_buffer);
  ImGui::Spacing();
  switch (draw_modal_action_row("Load")) {
    case ModalAction::Primary:
      if (reload_layout(session, state, state->load_layout_buffer)) {
        ImGui::CloseCurrentPopup();
      }
      break;
    case ModalAction::Secondary:
      sync_layout_buffers(state, *session);
      ImGui::CloseCurrentPopup();
      break;
    case ModalAction::None:
      break;
  }
  ImGui::EndPopup();
}

void draw_save_layout_popup(AppSession *session, UiState *state) {
  if (!app_begin_popup_modal("Save Layout")) {
    return;
  }
  ImGui::TextUnformatted("Save the current workspace as a JotPlugger JSON layout.");
  ImGui::Separator();
  input_text_string("Layout", &state->save_layout_buffer);
  ImGui::Spacing();
  switch (draw_modal_action_row("Save")) {
    case ModalAction::Primary:
      if (save_layout(session, state, state->save_layout_buffer)) {
        ImGui::CloseCurrentPopup();
      }
      break;
    case ModalAction::Secondary:
      sync_layout_buffers(state, *session);
      ImGui::CloseCurrentPopup();
      break;
    case ModalAction::None:
      break;
  }
  ImGui::EndPopup();
}

void draw_preferences_popup(AppSession *session, UiState *state) {
  if (!app_begin_popup_modal("Preferences")) {
    return;
  }
  if (session->map_data) {
    const MapCacheStats map_cache = session->map_data->cacheStats();
    const MapCacheStats download_cache = directory_cache_stats(Path::download_cache_root());
    ImGui::TextUnformatted("Map");
    ImGui::Separator();
    ImGui::Text("Map cache: %s in %zu file%s",
                format_cache_bytes(map_cache.bytes).c_str(),
                map_cache.files,
                map_cache.files == 1 ? "" : "s");
    if (ImGui::Button("Clear Map Cache", ImVec2(120.0f, 0.0f))) {
      session->map_data->clearCache();
      state->status_text = "Cleared map cache";
    }
    ImGui::Spacing();
    ImGui::TextUnformatted("comma Download Cache");
    ImGui::Separator();
    ImGui::Text("Download cache: %s in %zu file%s",
                format_cache_bytes(download_cache.bytes).c_str(),
                download_cache.files,
                download_cache.files == 1 ? "" : "s");
    ImGui::TextDisabled("%s", Path::download_cache_root().c_str());
    ImGui::Spacing();
  }
  if (ImGui::Button("Close", ImVec2(120.0f, 0.0f))) {
    ImGui::CloseCurrentPopup();
  }
  ImGui::EndPopup();
}

void draw_find_signal_popup(AppSession *session, UiState *state) {
  if (!app_begin_popup_modal("Find Signal")) {
    return;
  }
  ImGui::TextUnformatted("Search decoded signals across the loaded route.");
  ImGui::Separator();
  ImGui::SetNextItemWidth(560.0f);
  input_text_with_hint_string("##find_signal_query", "Search signal path or name...", &state->find_signal_buffer);
  if (ImGui::IsWindowAppearing()) {
    ImGui::SetKeyboardFocusHere(-1);
  }
  const std::vector<FindSignalMatch> matches = find_signal_matches(*session, state->find_signal_buffer);
  ImGui::Spacing();
  ImGui::TextDisabled("%zu match%s", matches.size(), matches.size() == 1 ? "" : "es");
  if (ImGui::BeginChild("##find_signal_results", ImVec2(760.0f, 360.0f), true)) {
    for (const FindSignalMatch &match : matches) {
      const std::string &path = *match.path;
      const size_t slash = path.find_last_of('/');
      const std::string_view label = slash == std::string::npos ? std::string_view(path) : std::string_view(path).substr(slash + 1);
      if (ImGui::Selectable((std::string(label) + "##" + path).c_str(), false, ImGuiSelectableFlags_SpanAllColumns)) {
        if (open_find_signal_result(state, path)) {
          ImGui::CloseCurrentPopup();
        }
      }
      ImGui::SameLine(280.0f);
      ImGui::TextDisabled("%s", path.c_str());
    }
  }
  ImGui::EndChild();
  ImGui::Spacing();
  if (ImGui::Button("Close", ImVec2(120.0f, 0.0f))) {
    ImGui::CloseCurrentPopup();
  }
  ImGui::EndPopup();
}

std::string default_dbc_template() {
  return "VERSION \"\"\n\nNS_ :\nBS_:\nBU_: XXX\n";
}

DbcEditorSource resolve_dbc_editor_source(const std::string &dbc_name) {
  const fs::path generated_dbc_dir = repo_root() / "openpilot" / "tools" / "jotpluggler" / "generated_dbcs";
  const std::array<DbcEditorSource, 2> candidates = {{
    {.path = repo_root() / "opendbc_repo" / "opendbc" / "dbc" / (dbc_name + ".dbc"), .kind = DbcEditorState::SourceKind::Opendbc},
    {.path = generated_dbc_dir / (dbc_name + ".dbc"), .kind = DbcEditorState::SourceKind::Generated},
  }};
  for (const DbcEditorSource &candidate : candidates) {
    if (fs::exists(candidate.path)) {
      return candidate;
    }
  }
  return {};
}

void load_dbc_editor_state(const AppSession &session, UiState *state) {
  DbcEditorState &editor = state->dbc_editor;
  const std::string dbc_name = !session.dbc_override.empty() ? session.dbc_override : session.route_data.dbc_name;
  editor.source_name = dbc_name.empty() ? "untitled" : dbc_name;
  editor.source_path.clear();
  editor.source_kind = DbcEditorState::SourceKind::None;
  if (dbc_name.empty()) {
    editor.save_name = "custom_can";
    editor.text = default_dbc_template();
  } else {
    const DbcEditorSource source = resolve_dbc_editor_source(dbc_name);
    editor.source_kind = source.kind;
    editor.source_path = source.path;
    editor.text = source.path.empty() ? default_dbc_template() : read_file_or_throw(source.path);
    editor.save_name = source.kind == DbcEditorState::SourceKind::Generated ? dbc_name : dbc_name + "_edited";
  }
  editor.loaded = true;
}

bool ensure_dbc_editor_loaded(const AppSession &session, UiState *state) {
  if (!state->dbc_editor.loaded) {
    try {
      load_dbc_editor_state(session, state);
    } catch (const std::exception &err) {
      state->error_text = err.what();
      state->open_error_popup = true;
      return false;
    }
  }
  return true;
}

bool save_dbc_editor_contents(AppSession *session, UiState *state) {
  DbcEditorState &editor = state->dbc_editor;
  editor.save_name = util::strip(editor.save_name);
  if (editor.save_name.empty()) {
    state->error_text = "DBC name cannot be empty";
    state->open_error_popup = true;
    return false;
  }
  if (editor.source_kind == DbcEditorState::SourceKind::Opendbc && editor.save_name == editor.source_name) {
    state->error_text = "Save edited opendbc files under a new name";
    state->open_error_popup = true;
    return false;
  }
  try {
    dbc::Database::fromContent(editor.text, editor.save_name + ".dbc");
    const fs::path generated_dbc_dir = repo_root() / "openpilot" / "tools" / "jotpluggler" / "generated_dbcs";
    fs::create_directories(generated_dbc_dir);
    const fs::path output = generated_dbc_dir / (editor.save_name + ".dbc");
    write_file_or_throw(output, editor.text);
    apply_dbc_override_change(session, state, editor.save_name);
    editor.source_name = editor.save_name;
    editor.source_path = output;
    editor.source_kind = DbcEditorState::SourceKind::Generated;
    editor.loaded = false;
    state->status_text = "Saved DBC " + editor.save_name;
    return true;
  } catch (const std::exception &err) {
    state->error_text = err.what();
    state->open_error_popup = true;
    return false;
  }
}

void draw_dbc_editor_popup(AppSession *session, UiState *state) {
  if (!app_begin_popup_modal("DBC Editor")) {
    return;
  }
  DbcEditorState &editor = state->dbc_editor;
  if (!ensure_dbc_editor_loaded(*session, state)) {
    ImGui::CloseCurrentPopup();
    ImGui::EndPopup();
    return;
  }
  ImGui::TextUnformatted("Edit DBC text and save it into generated_dbcs.");
  ImGui::Separator();
  ImGui::SetNextItemWidth(260.0f);
  input_text_string("DBC Name", &editor.save_name, ImGuiInputTextFlags_AutoSelectAll);
  if (!editor.source_path.empty()) {
    ImGui::TextDisabled("%s", editor.source_path.string().c_str());
  } else {
    ImGui::TextDisabled("New in-memory DBC");
  }
  ImGui::Spacing();
  input_text_multiline_string("##dbc_editor_text", &editor.text, ImVec2(920.0f, 520.0f), ImGuiInputTextFlags_AllowTabInput);
  ImGui::Spacing();
  if (ImGui::Button("Apply + Save", ImVec2(140.0f, 0.0f))) {
    if (save_dbc_editor_contents(session, state)) {
      ImGui::CloseCurrentPopup();
    }
  }
  ImGui::SameLine();
  if (ImGui::Button("Reload Source", ImVec2(140.0f, 0.0f))) {
    editor.loaded = false;
  }
  ImGui::SameLine();
  if (ImGui::Button("Close", ImVec2(120.0f, 0.0f))) {
    editor.loaded = false;
    ImGui::CloseCurrentPopup();
  }
  ImGui::EndPopup();
}

void draw_axis_limits_popup(AppSession *session, UiState *state) {
  if (!app_begin_popup_modal("Edit Axis Limits")) {
    return;
  }
  const WorkspaceTab *tab = app_active_tab(session->layout, *state);
  const bool valid_pane = tab != nullptr
    && state->axis_limits.pane_index >= 0
    && state->axis_limits.pane_index < static_cast<int>(tab->panes.size());
  if (!valid_pane) {
    ImGui::TextWrapped("The selected pane is no longer available.");
    ImGui::Spacing();
    if (ImGui::Button("Close", ImVec2(120.0f, 0.0f))) {
      state->axis_limits.pane_index = -1;
      ImGui::CloseCurrentPopup();
    }
    ImGui::EndPopup();
    return;
  }

  ImGui::TextUnformatted("X range applies to the active tab. Y limits apply to the selected pane.");
  ImGui::Separator();
  ImGui::TextUnformatted("Horizontal");
  ImGui::SetNextItemWidth(180.0f);
  ImGui::InputDouble("X Min", &state->axis_limits.x_min, 0.0, 0.0, "%.3f");
  ImGui::SetNextItemWidth(180.0f);
  ImGui::InputDouble("X Max", &state->axis_limits.x_max, 0.0, 0.0, "%.3f");
  ImGui::Spacing();
  ImGui::TextUnformatted("Vertical");
  ImGui::Checkbox("Use Y Min", &state->axis_limits.y_min_enabled);
  ImGui::BeginDisabled(!state->axis_limits.y_min_enabled);
  ImGui::SetNextItemWidth(180.0f);
  ImGui::InputDouble("Y Min", &state->axis_limits.y_min, 0.0, 0.0, "%.6g");
  ImGui::EndDisabled();
  ImGui::Checkbox("Use Y Max", &state->axis_limits.y_max_enabled);
  ImGui::BeginDisabled(!state->axis_limits.y_max_enabled);
  ImGui::SetNextItemWidth(180.0f);
  ImGui::InputDouble("Y Max", &state->axis_limits.y_max, 0.0, 0.0, "%.6g");
  ImGui::EndDisabled();
  ImGui::Spacing();
  switch (draw_modal_action_row("Apply")) {
    case ModalAction::Primary:
      if (apply_axis_limits_editor(session, state)) {
        state->axis_limits.pane_index = -1;
        ImGui::CloseCurrentPopup();
      }
      break;
    case ModalAction::Secondary:
      state->axis_limits.pane_index = -1;
      ImGui::CloseCurrentPopup();
      break;
    case ModalAction::None:
      break;
  }
  ImGui::EndPopup();
}

void draw_error_popup(UiState *state) {
  if (state->open_error_popup) {
    ImGui::OpenPopup("Error");
    state->open_error_popup = false;
  }
  if (!app_begin_popup_modal("Error")) {
    return;
  }
  ImGui::TextWrapped("%s", state->error_text.c_str());
  ImGui::Spacing();
  if (ImGui::Button("Close", ImVec2(120.0f, 0.0f))) {
    ImGui::CloseCurrentPopup();
  }
  ImGui::EndPopup();
}

}  // namespace

bool reset_layout(AppSession *session, UiState *state) {
  try {
    if (session->layout_path.empty()) {
      start_new_layout(session, state, "Reset layout");
      return true;
    }
    clear_layout_autosave(*session);
    session->layout = load_sketch_layout(session->layout_path);
    state->layout_dirty = false;
    session->autosave_path = autosave_path_for_layout(session->layout_path);
    state->undo.reset(session->layout);
    refresh_replaced_layout_ui(session, state, false);
    reset_shared_range(state, *session);
    state->status_text = "Reset layout";
    return true;
  } catch (const std::exception &err) {
    state->error_text = err.what();
    state->open_error_popup = true;
    state->status_text = "Failed to reset layout";
    return false;
  }
}

bool reload_layout(AppSession *session, UiState *state, const std::string &layout_arg) {
  try {
    const bool preserve_shared_range = session->route_data.has_time_range && state->has_shared_range;
    const double preserved_x_min = state->x_view_min;
    const double preserved_x_max = state->x_view_max;
    const fs::path layout_path = resolve_layout_path(layout_arg);
    session->autosave_path = autosave_path_for_layout(layout_path);
    const bool load_draft = fs::exists(session->autosave_path);
    session->layout = load_sketch_layout(load_draft ? session->autosave_path : layout_path);
    session->layout_path = layout_path;
    state->layout_dirty = load_draft;
    state->undo.reset(session->layout);
    refresh_replaced_layout_ui(session, state, true);
    if (preserve_shared_range) {
      state->has_shared_range = true;
      state->x_view_min = preserved_x_min;
      state->x_view_max = preserved_x_max;
      clamp_shared_range(state, *session);
    } else {
      reset_shared_range(state, *session);
    }
    state->status_text = std::string(load_draft ? "Loaded layout draft " : "Loaded layout ")
      + layout_path.filename().string();
    return true;
  } catch (const std::exception &err) {
    state->error_text = err.what();
    state->open_error_popup = true;
    state->status_text = "Failed to load layout";
    return false;
  }
}

bool save_layout(AppSession *session, UiState *state, const std::string &layout_path) {
  try {
    if (layout_path.empty()) throw std::runtime_error("Layout path is empty");
    session->layout.current_tab_index = state->active_tab_index;
    const fs::path previous_autosave = session->autosave_path;
    const fs::path output = fs::absolute(fs::path(layout_path));
    save_layout_json(session->layout, output);
    session->layout_path = output;
    session->autosave_path = autosave_path_for_layout(output);
    if (!previous_autosave.empty() && previous_autosave != session->autosave_path && fs::exists(previous_autosave)) {
      fs::remove(previous_autosave);
    }
    clear_layout_autosave(*session);
    state->layout_dirty = false;
    sync_layout_buffers(state, *session);
    state->status_text = "Saved layout " + output.filename().string();
    return true;
  } catch (const std::exception &err) {
    state->error_text = err.what();
    state->open_error_popup = true;
    state->status_text = "Failed to save layout";
    return false;
  }
}

void rebuild_session_route_data(AppSession *session, UiState *state,
                                const RouteLoadProgressCallback &progress) {
  apply_route_data(session, state, load_route_data(session->route_name, session->data_dir, session->dbc_override, progress));
}

void stop_stream_session(AppSession *session, UiState *state, bool preserve_data) {
  if (preserve_data && session->stream_poller && session->data_mode == SessionDataMode::Stream) {
    session->stream_poller->setPaused(true);
  } else if (session->stream_poller) {
    session->stream_poller->stop();
  }
  session->stream_paused = preserve_data && session->data_mode == SessionDataMode::Stream;
  if (!preserve_data) {
    session->stream_time_offset.reset();
    apply_route_data(session, state, RouteData{});
  }
  sync_stream_buffers(state, *session);
}

bool start_stream_session(AppSession *session,
                          UiState *state,
                          const StreamSourceConfig &source,
                          double buffer_seconds,
                          bool preserve_existing_data) {
  try {
    if (session->route_loader) {
      session->route_loader.reset();
    }
    session->data_mode = SessionDataMode::Stream;
    session->route_id = {};
    session->route_name.clear();
    session->data_dir.clear();
    session->stream_source = source;
    if (session->stream_source.kind == StreamSourceKind::CerealLocal) {
      session->stream_source.address = "127.0.0.1";
    }
    session->stream_buffer_seconds = std::max(1.0, buffer_seconds);
    session->next_stream_custom_refresh_time = 0.0;
    session->stream_paused = false;
    if (preserve_existing_data && session->stream_poller) {
      StreamPollSnapshot snapshot = session->stream_poller->snapshot();
      if (snapshot.active) {
        session->stream_poller->setPaused(false);
        sync_route_buffers(state, *session);
        sync_stream_buffers(state, *session);
        state->follow_latest = true;
        state->playback_playing = false;
        state->status_text = "Resumed stream " + stream_source_target_label(session->stream_source);
        return true;
      }
    }
    if (!preserve_existing_data) {
      session->stream_time_offset.reset();
      apply_route_data(session, state, RouteData{});
    }
    if (!session->stream_poller) {
      session->stream_poller = std::make_unique<StreamPoller>();
    }
    session->stream_poller->start(session->stream_source,
                                  session->stream_buffer_seconds,
                                  session->dbc_override,
                                  session->stream_time_offset);
    sync_route_buffers(state, *session);
    sync_stream_buffers(state, *session);
    state->follow_latest = true;
    state->playback_playing = false;
    state->status_text = preserve_existing_data ? "Resumed stream " + stream_source_target_label(session->stream_source)
                                                : "Streaming from " + stream_source_target_label(session->stream_source);
    return true;
  } catch (const std::exception &err) {
    state->error_text = err.what();
    state->open_error_popup = true;
    state->status_text = "Failed to start stream";
    return false;
  }
}

void start_async_route_load(AppSession *session, UiState *state) {
  if (!session->route_loader) {
    return;
  }
  apply_route_data(session, state, RouteData{});
  session->route_loader->start(session->route_name, session->data_dir, session->dbc_override);
  state->status_text = session->route_name.empty() ? "Ready" : "Loading route " + session->route_name;
}

void poll_async_route_load(AppSession *session, UiState *state) {
  if (!session->route_loader) {
    return;
  }
  RouteData loaded_route;
  std::string error_text;
  if (!session->route_loader->consume(&loaded_route, &error_text)) {
    return;
  }
  if (!error_text.empty()) {
    state->error_text = error_text;
    state->open_error_popup = true;
    state->status_text = "Failed to load route";
    return;
  }
  apply_route_data(session, state, std::move(loaded_route));
  state->status_text = session->route_name.empty() ? "Ready" : "Loaded route " + session->route_name;
}

bool reload_session(AppSession *session, UiState *state, const std::string &route_name, const std::string &data_dir) {
  try {
    stop_stream_session(session, state, false);
    session->data_mode = SessionDataMode::Route;
    session->route_name = route_name;
    session->route_id = parse_route_identifier(route_name);
    session->data_dir = data_dir;
    if (session->async_route_loading) {
      if (!session->route_loader) {
        session->route_loader = std::make_unique<AsyncRouteLoader>(::isatty(STDERR_FILENO) != 0);
      }
      start_async_route_load(session, state);
    } else {
      rebuild_session_route_data(session, state);
      state->status_text = "Loaded route " + route_name;
    }
    sync_route_buffers(state, *session);
    return true;
  } catch (const std::exception &err) {
    state->error_text = err.what();
    state->open_error_popup = true;
    state->status_text = "Failed to load route";
    return false;
  }
}

void draw_popups(AppSession *session, UiState *state) {
  open_queued_popup(state->open_open_route, "Open Route");
  if (state->open_stream) {
    sync_stream_buffers(state, *session);
  }
  open_queued_popup(state->open_stream, "Live Stream");
  if (state->open_load_layout || state->open_save_layout) {
    sync_layout_buffers(state, *session);
  }
  open_queued_popup(state->open_load_layout, "Load Layout");
  open_queued_popup(state->open_save_layout, "Save Layout");
  open_queued_popup(state->open_preferences, "Preferences");
  open_queued_popup(state->dbc_editor.open, "DBC Editor");
  open_queued_popup(state->open_find_signal, "Find Signal");
  open_queued_popup(state->axis_limits.open, "Edit Axis Limits");

  draw_open_route_popup(session, state);
  draw_stream_popup(session, state);
  draw_load_layout_popup(session, state);
  draw_save_layout_popup(session, state);
  draw_preferences_popup(session, state);
  draw_dbc_editor_popup(session, state);
  draw_find_signal_popup(session, state);
  draw_axis_limits_popup(session, state);
  draw_error_popup(state);
}
