// gguf-viewer.cpp: HTTP server for GGUF model inspection
//
// Single binary, embedded Svelte webui, REST API for metadata,
// tensor visualization, heatmaps, histograms, and tokenizer browsing.
//
// Usage: gguf-viewer --root /path/to/models [--host 127.0.0.1] [--port 8080]
//
// Scans --root recursively for .gguf files. Each model is loaded lazily
// on first API access (metadata only, tensor data is mmap'd on demand).

#include "version.h"
#include "viewer-http.h"
#include "viewer-model.h"

// embedded webui (generated by xxd.cmake from tools/public/index.html.gz)
#ifdef VIEWER_HAS_WEBUI
#include "index.html.gz.hpp"
#endif

#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wshadow"
#endif
#include "httplib.h"
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif

#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>

static httplib::Server *g_svr = nullptr;

static void on_signal(int) {
  if (g_svr) {
    g_svr->stop();
  }
}

struct cli_params {
  std::string root;
  std::string host = "127.0.0.1";
  int port = 8080;
};

static void print_usage(const char *argv0) {
  fprintf(stderr, "Usage: %s --root <path> [--host 127.0.0.1] [--port 8080]\n",
          argv0);
}

static bool parse_cli(int argc, char **argv, cli_params &params) {
  for (int i = 1; i < argc; ++i) {
    std::string arg(argv[i]);
    if ((arg == "--root" || arg == "-r") && i + 1 < argc) {
      params.root = argv[++i];
    } else if (arg == "--host" && i + 1 < argc) {
      params.host = argv[++i];
    } else if (arg == "--port" && i + 1 < argc) {
      params.port = std::atoi(argv[++i]);
    } else if (arg == "--help" || arg == "-h") {
      print_usage(argv[0]);
      return false;
    } else {
      fprintf(stderr, "Unknown argument: %s\n", arg.c_str());
      print_usage(argv[0]);
      return false;
    }
  }

  if (params.root.empty()) {
    fprintf(stderr, "Missing required --root argument\n");
    print_usage(argv[0]);
    return false;
  }

  if (params.port <= 0 || params.port > 65535) {
    fprintf(stderr, "Port must be between 1 and 65535\n");
    return false;
  }

  return true;
}

int main(int argc, char **argv) {
  cli_params params;
  if (!parse_cli(argc, argv, params)) {
    return 1;
  }

  // resolve root directory
  std::error_code ec;
  fs::path root = fs::weakly_canonical(fs::path(params.root), ec);
  if (ec || !fs::exists(root, ec) || !fs::is_directory(root, ec)) {
    fprintf(stderr, "Invalid root directory: %s\n", params.root.c_str());
    return 1;
  }

  // init server state
  auto state = std::make_shared<server_state>();
  state->root = std::move(root);

  // setup HTTP server
  httplib::Server svr;
  g_svr = &svr;

  svr.set_socket_options([](socket_t sock) {
    int one = 1;
#ifdef _WIN32
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&one, sizeof(one));
#else
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
#endif
  });

  // register API routes
  setup_routes(svr, state);

  // embedded webui: gzipped single-page app (built by tools/webui/).
  // the browser decompresses transparently via Content-Encoding: gzip.
#ifdef VIEWER_HAS_WEBUI
  if (index_html_gz_len > 0) {
    svr.Get("/", [](const httplib::Request &req, httplib::Response &res) {
      if (req.get_header_value("Accept-Encoding").find("gzip") ==
          std::string::npos) {
        res.set_content("Error: gzip is not supported by this browser",
                        "text/plain");
      } else {
        res.set_header("Content-Encoding", "gzip");
        res.set_content(reinterpret_cast<const char *>(index_html_gz),
                        index_html_gz_len, "text/html; charset=utf-8");
      }
    });
  }
#endif

  // graceful shutdown
  signal(SIGINT, on_signal);
  signal(SIGTERM, on_signal);

  fprintf(stderr, "[Viewer] ggufviewer.cpp %s\n", VIEWER_VERSION);
  fprintf(stderr, "[Viewer] Root: %s\n", state->root.string().c_str());
  fprintf(stderr, "[Viewer] Listening on http://%s:%d\n", params.host.c_str(),
          params.port);

  if (!svr.listen(params.host.c_str(), params.port)) {
    fprintf(stderr, "[Viewer] FATAL: cannot bind %s:%d\n", params.host.c_str(),
            params.port);
    return 1;
  }

  fprintf(stderr, "[Viewer] Shutting down...\n");
  return 0;
}
