struct Sec_Hover_Info {
  Byte_Range rng;
  // A string tree where children are more indented than parents
  String8_Node *desc;
  String8_Node *highlighted_desc;
};

template <typename T> T bswap_if_needed(T x) { return x; }
template <> u16 bswap_if_needed(u16 x) { return bswap(x); }
template <> u32 bswap_if_needed(u32 x) { return bswap(x); }
template <> u64 bswap_if_needed(u64 x) { return bswap(x); }

template <typename T> 
String8_Node *hover_display_val_be(Arena *arena, String8_Node *prev, const char *fmt, T val)
{
  static_assert(!std::is_same_v<T, String8>);
  val = bswap_if_needed(val);
  return push_str8_node_child(arena, prev, fmt, val);
}

String8_Node *hover_display_val_str8(Arena *arena, String8_Node *prev, const char *fmt, String8 val)
{
  return push_str8_node_child(arena, prev, fmt, val.str ? val.c() : "");
}

template <typename T> 
String8_Node *hover_display_val_le(Arena *arena, String8_Node *prev, const char *fmt, T val)
{
  return push_str8_node_child(arena, prev, fmt, val);
}

template <typename T> 
String8_Node *hover_display_val_le_abs(Arena *arena, String8_Node *prev, const char *fmt, T val)
{
  return push_str8_node_child(arena, prev, fmt, std::abs(val));
}

String8_Node *hover_display_generic_range(Arena *arena, String8_Node *prev, const char *desc, const u8 *) 
{
  return push_str8_node_child(arena, prev, desc);
}

internal
String8_Node *hover_display_datetime_str(Arena *arena, String8_Node *prev, const char *fmt_pre, u32 datetime)
{
  datetime = bswap(datetime);
  
  // datetime:
  // year (6b) | month (4b) | day (5b) | hour (5b) | min (6b) | sec (6b)
  u32 year = (datetime >> 26) + 1995;
  u32 month = ((datetime & 0x3ff'ffff) >> 22);
  u32 day = (datetime & 0x3f'ffff) >> 17;
  u32 hour = (datetime & 0x1'ffff) >> 12;
  u32 min = (datetime & 0xfff) >> 6;
  u32 sec = datetime & 0x3f;
  return push_str8_node_child(arena, prev, "%s%u/%02u/%02u %02u:%02u:%02u", fmt_pre, year, month, day, hour, min, sec);
}

// Returns null is `src` doesn't point to a zipped block
internal
String8_Node *display_val_rootzip(Arena *arena, String8_Node *prev, const char *fmt, const u8 *src)
{ 
  const u8 Z_DEFLATED = 8;

  String8 zip_method;
  if (src[0] == 'Z' && src[1] == 'L' && src[2] == Z_DEFLATED) {
    zip_method = str8("ZLIB");
  } else if (src[0] == 'C' && src[1] == 'S' && src[2] == Z_DEFLATED) {
    zip_method = str8("Old");
  } else if (src[0] == 'X' && src[1] == 'Z' && src[2] == 0) {
    zip_method = str8("LZMA");
  } else if (src[0] == 'L' && src[1] == '4') {
    zip_method = str8("LZ4");
  } else if (src[0] == 'Z' && src[1] == 'S' && src[2] == 1) {
    zip_method = str8("ZSTD");
  } else {
    return nullptr;
  }
  u32 comp_size = src[3] | (src[4] << 8) | (src[5] << 16);
  u32 uncomp_size = src[6] | (src[7] << 8) | (src[8] << 16);

  String8_Node *sn = push_str8_node_child(arena, prev, "%s", fmt);
  sn = push_str8_node_child(arena, sn, "Zip method: %s", zip_method.c());
  sn = push_str8_node(arena, sn, "Compressed size: %s", to_pretty_size(arena, comp_size).c());
  sn = push_str8_node(arena, sn, "Uncompressed size: %s", to_pretty_size(arena, uncomp_size).c());
  sn = push_str8_node(arena, sn, "Comp. ratio: %.2f", (f32)comp_size / uncomp_size);

  return sn;
}

template <typename T>
using Display_Fn = String8_Node *(*)(Arena *, String8_Node *, const char *, T);

enum Hover_Section_Flags {
  HoverSec_None = 0,
  HoverSec_HideIfNotHovered = 1,
};

// Functor used by get_section_hover_info to describe the structure of a section and print data about it.
struct Sec_Hover_Fn {
  u64 start; // the start of the section (including the pre_size, e.g. the TKey)
  u64 roff; // the offset relative to the section start
  const u8 *data; // the entire file data
  const Section &section;
  Arena *arena;
  Sec_Hover_Info &info;
  u64 &cur_field_off;
  b8 display_grouped;
  b8 ended = false;
  u8 cur_section_nesting = 0;
  u8 innermost_section_highlighted = 0;

  template <typename F>
  void titled_section(const char *title, F &&sec_body_fn, u64 flags = 0)
  {
    // if (ended)
    //   return;
    
    ++cur_section_nesting;
    String8_Node *prev_desc = info.desc;
    info.desc = push_str8_node_child(arena, prev_desc, title);

    u64 sec_start = cur_field_off;

    sec_body_fn();

    assert(cur_field_off >= sec_start);
    b8 hovered = roff >= sec_start && roff <= cur_field_off;

    if (!hovered & (flags & HoverSec_HideIfNotHovered)) {
      pop_str8_node_child(prev_desc, info.desc);
    } else if (display_grouped) {
      // if we're in display_grouped mode, we want to highlight the entire range of the section;
      u64 sec_len = cur_field_off - sec_start;
      info.rng = { start + sec_start, sec_len };
      // In case of nested sections, only highlight the innermost
      // FIXME: sometimes no section ends up highlighted!
      if (innermost_section_highlighted <= cur_section_nesting) {
        info.highlighted_desc = info.desc;
      }
      innermost_section_highlighted = max(cur_section_nesting, innermost_section_highlighted);
      // ended = true;
    }

    --cur_section_nesting;

    info.desc = prev_desc;
    return;
  }

  // returns true if `val_read` was read
  template <typename T>
  b8 field(const char *desc_fmt, Display_Fn<T> display_val, T *val_read = nullptr)
  {
    static_assert(!std::is_same_v<T, String8>, "use field_str8 instead.");
    // if (ended)
    //   return false;

    u64 field_len = sizeof(T);
    b8 hovered = cur_field_off <= roff && roff < cur_field_off + field_len;

    T val;
    memcpy(&val, (u8 *)data + start + cur_field_off, field_len);
    String8_Node *desc = display_val(arena, info.desc, desc_fmt, val);
    if (hovered && !display_grouped)
      info.highlighted_desc = desc;
    if (val_read)
      *val_read = val;

    if (display_grouped || hovered) {
      info.rng = { start + cur_field_off, field_len };
      // truncate the hovered section here if we're not in display_grouped mode.
      // ended = !display_grouped;
    }
    cur_field_off += field_len;
    return true;
  }

  template <typename TStrSize>
  void field_str8(const char *desc_fmt, Display_Fn<String8> display_val = hover_display_val_str8)
  {
    // if (ended)
    //   return;
    
    // String size can be stored as different types, like u8 (by ROOT I/O) or u32 (by RNTuple).
    TStrSize str_size;
    memcpy(&str_size, data + start + cur_field_off, sizeof(TStrSize));
    // DEBUG
    if (str_size > 1000) {
      printf("read str_size = %u at offset 0x%lX!\n", str_size, start + cur_field_off);
      // ended = true;
      return;
    }
    u64 field_len = sizeof(TStrSize) + (u64)str_size;
    b8 hovered = cur_field_off <= roff && roff < cur_field_off + field_len;

    u8 *buf = arena_push_array_nozero<u8>(arena, str_size + 1);
    memcpy(buf, data + start + cur_field_off + sizeof(TStrSize), str_size);
    buf[str_size] = 0;
    String8 s = { buf, str_size };
    String8_Node *desc = display_val(arena, info.desc, desc_fmt, s);
    if (hovered && !display_grouped)
      info.highlighted_desc = desc;

    if (display_grouped || hovered) {
      info.rng = { start + cur_field_off, field_len };
      // ended = !display_grouped;
    }
    cur_field_off += field_len;
  }

  template <typename T>
  b8 field_be(const char *desc_fmt, T *val_read = nullptr)
  {
    return field<T>(desc_fmt, hover_display_val_be<T>, val_read);
  }

  template <typename T>
  b8 field_le(const char *desc_fmt, T *val_read = nullptr)
  {
    return field<T>(desc_fmt, hover_display_val_le<T>, val_read);
  }

  // An unspecified range of bytes
  void range(const char *desc, u64 range_len, Display_Fn<const u8 *> display_val = hover_display_generic_range)
  {
    // if (ended) 
    //   return;

    b8 hovered = cur_field_off <= roff && roff < cur_field_off + range_len;
    String8_Node *dsc = display_val(arena, info.desc, desc, data + start + cur_field_off);
    if (hovered && !display_grouped)
      info.highlighted_desc = dsc;

    if (display_grouped || hovered) {
      info.rng = { start + cur_field_off, range_len };
      // ended = !display_grouped;
    }
    cur_field_off += range_len;
  }

  // Returns true if `was_zipped` was read.
  b8 maybe_rootzip(b8 *was_zipped = nullptr)
  {
    // if (ended)
    //   return false;

    // TODO boundary checks
    const u64 range_len = 9;
    b8 hovered = cur_field_off <= roff && roff < cur_field_off + range_len;
    if (display_val_rootzip(arena, info.desc, "Zipped Block", data + start + cur_field_off)) {
      if (display_grouped || hovered) {
        if (was_zipped)
          *was_zipped = true;
        info.rng = { start + cur_field_off, range_len };
        if (hovered)
          info.highlighted_desc = info.desc;
        // ended = !display_grouped;
      }
      cur_field_off += range_len;
    } else if (was_zipped) {
      *was_zipped = false;
    }

    return true;
  }

  void tkey()
  {
    titled_section("TKey", [this] {
      u16 version_be;
      memcpy(&version_be, data + start + 4, sizeof(u16));
      u32 version = bswap(version_be);
      b8 is_big = version > 1000;

      field_be<u32>("NBytes: %u");
      field<u16>("Version: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u16 x) { 
         x = bswap(x);
         x -= (x > 1000) * 1000; 
         return push_str8_node_child(arena, prev, fmt, x);
      });
      field_be<u32>("Obj Len: %u");
      field<u32>("Datetime: ", hover_display_datetime_str);
      field_be<u16>("Key Len: %u");
      field_be<u16>("Cycle: %u");
      if (is_big) {
        field_be<u64>("Seek Key: 0x%" PRIX64);
        field_be<u64>("Seek Pdir: 0x%" PRIX64);
      } else {
        field_be<u32>("Seek Key: 0x%" PRIX64);
        field_be<u32>("Seek Pdir: 0x%" PRIX64);
      }
      field_str8<u8>("Class Name: %s");
      field_str8<u8>("Obj Name: %s");
      field_str8<u8>("Obj Title: %s");
    }, HoverSec_HideIfNotHovered);
  }

  void envelope_preamble()
  {
    static const char *const envelope_names[] = { "INVALID", "Header", "Footer", "Page List" };
    titled_section("Envelope Preamble", [this] {
      field<u16>("Envelope type: %s", [] (Arena *arena, String8_Node *prev, const char *fmt, u16 val) {
        const char *name = (val >= countof(envelope_names)) ? "Unknown" : envelope_names[val];
        return push_str8_node_child(arena, prev, fmt, name);
      });
      range("Envelope size: %s", 6, [] (Arena *arena, String8_Node *prev, const char *fmt, const u8 *payload) {
        u64 size;
        memcpy(&size, payload, 6);
        return push_str8_node_child(arena, prev, fmt, to_pretty_size(arena, size));
      });
    });
  }

  enum Frame_Type {
    Frame_INVALID,
    Frame_Record,
    Frame_List
  };

  Frame_Type frame_header(u64 &size, u32 *n_items = nullptr, const char *title = nullptr)
  {
    String8 titlestr = title ? push_str8f(arena, "Frame Header: %s", title) : str8("Frame Header");
    Frame_Type frame_type = Frame_INVALID;
    titled_section(titlestr.c(), [this, &frame_type, &frame_size = size, n_items] {
      i64 size;
      memcpy(&size, data + start + cur_field_off, sizeof(size));
      if (size >= 0) {
        frame_type = Frame_Record;
        field<i64>("Record frame size: %" PRIi64 " B", hover_display_val_le_abs<i64>);
      } else {
        if (!n_items) {
          // Since the caller didn't pass us a pointer to n_items, they think we're supposed 
          // to be parsing a record frame. But it turns out this is actually a frame list!
          // Something fishy is going on, so just bail out.
          frame_type = Frame_INVALID;
        } else {
          frame_type = Frame_List;
          memcpy(n_items, data + start + cur_field_off + sizeof(i64), sizeof(u32));
          titled_section("List Frame", [this] {
            field<i64>("Size: %" PRIi64 " B", hover_display_val_le_abs<i64>);
            field_le<u32>("N Items: %u");
          });
        }
      }

      frame_size = std::abs(size);
    });
    return frame_type;
  }

  void field_desc(const char *title)
  {
    static const char *const field_struct_names[] = {
      "Leaf", "Collection", "Record", "Variant", "Unsplit"
    };

    frame<Frame_Record>(title, [this] {
      field_le<u32>("Field version: %u");
      field_le<u32>("Type version: %u");
      field_le<u32>("On-disk parent id: %u");
      field<u16>("Field structure: %s", [] (Arena *arena, String8_Node *prev, const char *fmt, u16 type) {
        const char *name = (type >= countof(field_struct_names)) ? "Unknown" : field_struct_names[type];
        return push_str8_node_child(arena, prev, fmt, name);
      });

      u16 flags;
      if (!field<u16>("Flags: 0b%b", hover_display_val_le, &flags))
        return;

      if (flags & RNTupleSerializer::kFlagRepetitiveField)
        field_le<u64>("N Repetitions: %" PRIu64);
      if (flags & RNTupleSerializer::kFlagProjectedField)
        field_le<u32>("On disk proj.src id: %u");
      if (flags & RNTupleSerializer::kFlagHasTypeChecksum)
        field_le<u32>("Checksum: %u");

      field_str8<u32>("Name: %s");
      field_str8<u32>("Type Name: %s");
      field_str8<u32>("Type Alias: %s");
      field_str8<u32>("Description: %s");
    }, HoverSec_HideIfNotHovered);
  }

  void column_desc(const char *title)
  {
    frame<Frame_Record>(title, [this] {    
      field<u16>("Column type: %s", [](Arena *arena, String8_Node *prev, const char *fmt, u16 val) {
        const char *readable_col_type = get_column_type_name(val);
        return push_str8_node_child(arena, prev, fmt, readable_col_type);
      });
      field_le<u16>("Bits on storage: %u");
      field_le<u32>("Field ID: %u");

      u16 flags;
      if (!field<u16>("Flags: 0b%b", hover_display_val_le, &flags))
        return;
      
      field_le<u16>("Representation idx: %u");
      if (flags & RNTupleSerializer::kFlagDeferredColumn) {
        field_le<u64>("First element: %" PRIu64);
      }
      // if (flags & RNTupleSerializer::kFlagHasValueRange) {
      //   field_le<double>("Value Min: %f");
      //   field_le<double>("Value Max: %f");
      // }
    }, HoverSec_HideIfNotHovered);
  }

  void schema_description(const char *title)
  {
    titled_section(title, [this] {
      // TODO: Columns and alias columns are not the same
      frame<Frame_List>("Fields",        [this] (u32 idx) { field_desc(push_str8f(arena, "Field %u", idx).c()); });
      frame<Frame_List>("Columns",       [this] (u32 idx) { column_desc(push_str8f(arena, "Column %u", idx).c()); });
      frame<Frame_List>("Alias Columns", [this] (u32 idx) { column_desc(push_str8f(arena, "Alias Column %u", idx).c()); });
      frame<Frame_List>("Extra Type Infos", [this] (u32) {
        field_le<u32>("Content identifier: %lu");
        field_le<u32>("Type version from: %lu");
        field_le<u32>("Type version to: %lu");
      });     
    });
  }

  void locator(const char *title)
  {
    // TODO
    // return titled_section(title, [this] {
    //   return true;
    // });
  }

  void cluster_group()
  {
    frame<Frame_List>("Cluster Group", [this] (u32) {
      field_le<u64>("Min Entry: %" PRIu64);
      field_le<u64>("Entry Span: %" PRIu64);
      field_le<u32>("N Clusters: %u");
      field_le<u64>("Env.Link Len: %" PRIu64);
      locator("Env.Link Locator");
    });
  }

  void rntuple_anchor()
  {
    titled_section("RNTuple Anchor", [this] {
      tkey();
      titled_section("Data", [this] {
        field<u32>("Object len: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u32 x) { 
          x = bswap(x);
          x -= 0x4000'0000;
          return push_str8_node_child(arena, prev, fmt, x);
        });
        field_be<u16>("Class version: %u");
        field_be<u16>("Version Epoch: %u");
        field_be<u16>("Version Major: %u");
        field_be<u16>("Version Minor: %u");
        field_be<u16>("Version Patch: %u");
        field_be<u64>("Seek Header: 0x%" PRIX64);
        field_be<u64>("NBytes Header: %u");
        field_be<u64>("Len Header: %u");
        field_be<u64>("Seek Footer: 0x%" PRIX64);
        field_be<u64>("NBytes Footer: %u");
        field_be<u64>("Len Footer: %u");
        field_be<u64>("Max Key Size: %u");
        field_le<u64>("Checksum: 0x%" PRIX64);
      }, HoverSec_HideIfNotHovered);
    });
  }

  void rntuple_header()
  {
    titled_section("RNTuple Header", [this] {
      tkey();
      titled_section("Data", [this] {
        b8 zipped;
        if (!maybe_rootzip(&zipped))
          return;

        if (zipped) {
          // XXX: why -1?
          range("Compressed payload", section.range.len - section.post_size - sizeof(u64) - 1);
          field_le<u64>("Checksum: 0x%" PRIX64);
        } else {
          envelope_preamble();
          // NOTE: flags in principle require a more complex handling, but for now they are unused,
          // so they're always occupying only 8 bytes.
          field_le<u64>("Flags: 0x%" PRIX64);
          field_str8<u32>("Name: %s");
          field_str8<u32>("Description: %s");
          field_str8<u32>("ROOT version: %s");
          schema_description("Schema Description");
          field_le<u64>("Checksum: 0x%" PRIX64);
        }
      }, HoverSec_HideIfNotHovered);
    });
  }
  
  void rntuple_footer()
  {
    titled_section("RNTuple Footer", [this] {
      tkey();
      titled_section("Data", [this] {
        b8 zipped;
        if (!maybe_rootzip(&zipped)) 
          return;

        if (zipped) {
          // XXX: why -1?
          range("Payload", section.range.len - section.post_size - sizeof(u64) - 1);
          field_le<u64>("Checksum: 0x%" PRIX64);
        } else {
          envelope_preamble();
          // NOTE: flags in principle require a more complex handling, but for now they are unused,
          // so they're always occupying only 8 bytes.
          field_le<u64>("Flags: 0x%" PRIX64);
          field_le<u64>("Header checksum: 0x%" PRIX64);
          schema_description("Schema Extension");
          //  - list of column group record frames (TODO)
          //frame_header("Column Groups");
          //  - list of cluster group record frames (TODO)
          //frame_header("Cluster Groups");
          range("Payload", section.range.len - cur_field_off);
          field_le<u64>("Checksum: 0x%" PRIX64);
        }
      }, HoverSec_HideIfNotHovered);
    });
  }

  void cluster_summary()
  {
    frame<Frame_Record>("Cluster Summary", [this] {
      field_le<u64>("First Entry: %" PRIu64);
      field<u64>("", [] (Arena *arena, String8_Node *prev, const char *, u64 x) {
        u64 entries = (x << 8) >> 8;
        u8 flags = x >> 56;
        String8_Node *sn = push_str8_node_child(arena, prev, "Entries: %" PRIu64, entries);
        return push_str8_node(arena, sn, "Flags: 0b%b", flags);          
      });
    });
  }

  void cluster()
  {
    frame<Frame_List>("Cluster", [this] (u32 col_idx) {
      titled_section(push_str8f(arena, "Column %u", col_idx).c(), [this] {
        frame<Frame_List>("Pages", [this] (u32 page_idx) {
          titled_section(push_str8f(arena, "Page %u", page_idx).c(), [this] {
            field<i32>("", [] (Arena *arena, String8_Node *prev, const char *, i32 n_elems) {
              b8 has_checksum = n_elems < 0;
              String8_Node *sn = push_str8_node_child(arena, prev, "N Elements: %u", std::abs(n_elems));
              return push_str8_node(arena, sn, "Has Checksum: %s", has_checksum ? "yes" : "no");
            });
            locator("Element Locator");
          }, HoverSec_HideIfNotHovered);
        });

        i64 n_cols;
        if (!field<i64>("", [] (Arena *arena, String8_Node *prev, const char *, i64 n_cols) {
          if (n_cols < 0) {
            return push_str8_node_child(arena, prev, "Element Offset: <suppressed>");
          }
          return push_str8_node_child(arena, prev, "Element Offset: %" PRIi64, n_cols);
        }, &n_cols))
        {
          return;
        }
        if (n_cols >= 0)
          field_le<i32>("Compression Settings: %d");
      }, HoverSec_HideIfNotHovered);
    });
  }

  template <Frame_Type FType, typename F>
  void frame(const char *title, F &&frame_body_fn, u64 sec_flags = 0)
  {
    u64 start_off = cur_field_off;
    u64 size;
    
    titled_section(title, [this, title, start_off, &size, &frame_body_fn] {
      u32 n_items = 0;
      Frame_Type ftype = frame_header(size, &n_items);
      if (ftype != FType)
        return;

      if constexpr (FType == Frame_List) {
        for (u32 i = 0; i < n_items; ++i)
          frame_body_fn(i);
      } else {
        frame_body_fn();
      }
      
      assert(cur_field_off >= start_off);
      u64 allocated_size = cur_field_off - start_off;
      if (size < allocated_size) {
        fprintf(stderr, "Frame %s told us its size was %" PRIu64 " but we accounted for %" PRIu64 " bytes!\n",
                title, size, allocated_size);
      }

      u64 extra_size = size - allocated_size;
      if (extra_size > 0)
        range("Unknown", extra_size);
    }, sec_flags);

    cur_field_off = start_off + size;
  }

  void page_list()
  {
    titled_section("Page List", [this] {
      tkey();
      titled_section("Data", [this] {
        b8 zipped;
        if (!maybe_rootzip(&zipped))
          return;
        if (zipped) {
          // XXX: why -1?
          range("Payload", section.range.len - section.post_size - sizeof(u64) - 1);
          field_le<u64>("Checksum: 0x%" PRIX64);
        } else {
          envelope_preamble();
          field_le<u64>("Header checksum: 0x%" PRIX64);
          frame<Frame_List>("Cluster Summaries", [this] (u32) { cluster_summary(); });
          frame<Frame_List>("Clusters", [this] (u32) { cluster(); });
        }
      }, HoverSec_HideIfNotHovered);
    });
  }
  
  void tfile_header()
  {
    titled_section("TFile Header", [this] {
      u32 root_version_be;
      memcpy(&root_version_be, data + start + 4, sizeof(u32));
      u32 root_version = bswap(root_version_be);
      b8 is_big = root_version > 1000'000;

      field_be<u32>("ROOT magic number");
      field<u32>("ROOT version: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u32 x) {
        x = bswap(x);
        x -= (x > 1000'000) * 1000'000;
        return push_str8_node_child(arena, prev, fmt, x);
      });
      field_be<u32>("fBEGIN: 0x%" PRIX64);
      if (is_big) {
        field_be<u64>("fEND: 0x%" PRIX64);
        field_be<u64>("Seek Free: 0x%" PRIX64);
      } else {
        field_be<u32>("fEND: 0x%" PRIX64);
        field_be<u32>("Seek Free: 0x%" PRIX64);
      }
      field_be<u32>("NBytes Free: %u");
      field_be<u32>("N Free: %u");
      field_be<u32>("NBytes Name: %u");
      field_be<u8>("Units: %u");
      field_be<u32>("Compression: %u");
      if (is_big)
        field_be<u64>("Seek Info: 0x%" PRIX64);
      else
        field_be<u32>("Seek Info: 0x%" PRIX64);
      field_be<u32>("NBytes Info: %u");
      range("Padding", section.post_size);
    });
  }

  void tfile_object()
  {
    titled_section("TFile Object", [this] {
      tkey();
      field_str8<u8>("File Name: %s");
      field_str8<u8>("File Title: %s");

      u16 version_be;
      memcpy(&version_be, data + cur_field_off, sizeof(u16));
      u16 version = bswap(version_be);
      b8 is_big = version > 1000;

      field<u16>("Version: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u16 x) { 
         x = bswap(x);
         x -= (x > 1000) * 1000; 
         return push_str8_node_child(arena, prev, fmt, x);
      });
      field<u32>("Created: ", hover_display_datetime_str);
      field<u32>("Modified: ", hover_display_datetime_str);
      field_be<u32>("NBytes Key: %u");
      field_be<u32>("NBytes Name: %u");
      if (is_big) {
        field_be<u64>("Seek Dir: 0x%" PRIX64) ;
        field_be<u64>("Seek Parent: 0x%" PRIX64) ;
        field_be<u64>("Seek Keys: 0x%" PRIX64) ;
      } else {
        field_be<u32>("Seek Dir: 0x%" PRIX64) ;
        field_be<u32>("Seek Parent: 0x%" PRIX64) ;
        field_be<u32>("Seek Keys: 0x%" PRIX64) ;
      }
      field_be<u16>("UUID Vers.Class: %u");
      field_le<u16>("UUID: %u");
      if (!is_big)
        range("Padding", 3 * sizeof(u32));
    });
  }

  void tfile_info()
  {
    titled_section("TFile Info", [this] {
      tkey();
      b8 zipped;
      if (!maybe_rootzip(&zipped))
        return;

      if (zipped) {
        range("Compressed Payload", section.range.len);
      } else {
        field<u32>("Byte Count: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u32 x) { 
          x = bswap(x);
          x -= 0x400000000; 
          return push_str8_node_child(arena, prev, fmt, x);
        });
        field_be<u16>("Version: %u");
        // hover_try_object(hover)
        field_be<u8>("Name: %u");
        field_be<u32>("N Objects: %u");;
      }
    });
  }
    
  void tfile_freelist()
  {
    titled_section("TFile FreeList", [this] {
      tkey();
      u16 version_be;
      memcpy(&version_be, data + start + cur_field_off, sizeof(u16));
      u32 version = bswap(version_be);
      b8 is_big = version > 1000;

      field<u16>("Version: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u16 x) { 
         x = bswap(x);
         x -= (x > 1000) * 1000; 
         return push_str8_node_child(arena, prev, fmt, x);
      });
      if (is_big) {
        field_be<u64>("First: 0x%" PRIX64);
        field_be<u64>("Last: 0x%" PRIX64);
      } else {
        field_be<u32>("First: 0x%X");
        field_be<u32>("Last: 0x%X");
      }
    });
  }
};

// `off` is the absolute offset into `data`.
internal
Sec_Hover_Info get_section_hover_info(Arena *arena, Section section, u64 off, const u8 *data, b8 display_grouped)
{
  Sec_Hover_Info info {};

  // printf("off: 0x%" PRIX64 ", sec start - pre_size: (0x%" PRIX64 " - %" PRIu64 ") = 0x%" PRIX64 "\n", off, section.range.start, section.pre_size, section.range.start - section.pre_size);
  assert(off >= section.range.start - section.pre_size);
  
  // Hover info header
  String8 sec_name = section_names[section.id];
  if (section.id == Sec_Page && section.info) {
    Page_Info_Node *pinfo = (Page_Info_Node *)section.info;
    info.desc = push_str8_node(arena, nullptr, "%s [%s]", 
                               sec_name.c(), pinfo->elem_type_name.c());
    push_str8_node_child(arena, info.desc, "Field: %s", pinfo->owner_field_name.c());
    push_str8_node_child(arena, info.desc, "N. Elems: %d", abs(pinfo->n_elems));
    push_str8_node_child(arena, info.desc, "Bits per elem: %u", pinfo->bits_per_elem);
    push_str8_node_child(arena, info.desc, "-----------");
  } else {
    info.desc = push_str8_node(arena, nullptr, "%s", sec_name.c());
  }

  u64 start = section.range.start - section.pre_size;
  u64 roff = off - start; // offset relative to `section`
  u64 cur_field_off = 0;
  Sec_Hover_Fn hover { start, roff, data, section, arena, info, cur_field_off, display_grouped };
  
  switch (section.id) {
  case Sec_RNTuple_Anchor: {
    hover.rntuple_anchor();
  } break;
  
  case Sec_TFile_Header: {
    hover.tfile_header();
  } break;
  
  case Sec_TFile_Object: {
    hover.tfile_object();
  } break;

  case Sec_RNTuple_Header: {
    hover.rntuple_header();
  } break;

  case Sec_RNTuple_Footer: {
    hover.rntuple_footer();
  } break;

  case Sec_Page_List: {
    hover.page_list();
  } break;

  #if 0
  case Sec_Page: {
    // only try hovering a key if this is the first page of the cluster (<=> pre_size != 0)
    b8 ok = section.pre_size && hover.tkey();
    ok = ok || hover.maybe_rootzip()
    || hover.range("Payload", section.range.len - section.post_size) // TODO: improve
    || hover.field_le<u64>("Checksum: 0x%" PRIX64)
    ;
  } break;

  case Sec_TFile_Info: {
    hover.tfile_info();
  } break;
  #endif

  case Sec_TFile_FreeList: {
    hover.tfile_freelist();
  } break;

  default:;
  }

  return info;
}