#pragma once

#include <unordered_set>

#include "bpftrace.h"
#include <clang-c/Index.h>

namespace bpftrace {

namespace ast {
class Program;
}

class ClangParser {
public:
  bool parse(ast::Program *program,
             BPFtrace &bpftrace,
             std::vector<std::string> extra_flags = {});

private:
  bool visit_children(CXCursor &cursor, BPFtrace &bpftrace);
  // The user might have written some struct definitions that rely on types
  // supplied by BTF data.
  //
  // This method will pull out any forward-declared / incomplete struct
  // definitions and return the types (in string form) of the unresolved types.
  //
  // Note that this method does not report "errors". This is because the user
  // could have typo'd and actually referenced a non-existent type. Put
  // differently, this method is best effort.
  std::unordered_set<std::string> get_incomplete_types();
  // Iteratively check for incomplete types, pull their definitions from BTF,
  // and update the input files with the definitions.
  void resolve_incomplete_types_from_btf(BPFtrace &bpftrace,
                                         const ast::ProbeList &probes);

  // Collect names of types defined by typedefs that are in non-included
  // headers as they may pose problems for clang parser.
  std::unordered_set<std::string> get_unknown_typedefs();
  // Iteratively check for unknown typedefs, pull their definitions from BTF,
  // and update the input files with the definitions.
  void resolve_unknown_typedefs_from_btf(BPFtrace &bpftrace);

  static std::optional<std::string> get_unknown_type(
      const std::string &diagnostic_msg);

  CXUnsavedFile get_btf_generated_header(BPFtrace &bpftrace);
  CXUnsavedFile get_empty_btf_generated_header();

  std::string get_arch_include_path();
  std::vector<std::string> system_include_paths();

  std::string input;
  std::vector<const char *> args;
  std::vector<CXUnsavedFile> input_files;
  std::string btf_cdef;

  class ClangParserHandler {
  public:
    ClangParserHandler();

    ~ClangParserHandler();

    bool parse_file(const std::string &filename,
                    const std::vector<const char *> &args,
                    std::vector<CXUnsavedFile> &unsaved_files,
                    bool bail_on_errors = true);

    CXTranslationUnit get_translation_unit();

    CXErrorCode parse_translation_unit(const char *source_filename,
                                       const char *const *command_line_args,
                                       int num_command_line_args,
                                       struct CXUnsavedFile *unsaved_files,
                                       unsigned num_unsaved_files,
                                       unsigned options);

    // Check diagnostics and collect all error messages.
    // Return true if an error occurred. If bail_on_error is false, only fail
    // on fatal errors.
    bool check_diagnostics(bool bail_on_error);

    CXCursor get_translation_unit_cursor();

    const std::vector<std::string> &get_error_messages();

    bool has_redefinition_error();
    bool has_unknown_type_error();

  private:
    CXIndex index;
    CXTranslationUnit translation_unit = nullptr;
    std::vector<std::string> error_msgs;
  };
};

} // namespace bpftrace
