#include "core/diagnostic_bundle.h"

#include <cstdint>
#include <fstream>
#include <limits>
#include <sstream>
#include <string_view>
#include <utility>

namespace aptt {
namespace {

struct FileToWrite {
  std::string name;
  std::string content;
};

std::string EscapeJson(std::string_view value) {
  std::string escaped;
  escaped.reserve(value.size() + 8);
  for (const char ch : value) {
    switch (ch) {
      case '\\':
        escaped += "\\\\";
        break;
      case '"':
        escaped += "\\\"";
        break;
      case '\n':
        escaped += "\\n";
        break;
      case '\r':
        escaped += "\\r";
        break;
      case '\t':
        escaped += "\\t";
        break;
      default:
        escaped += ch;
        break;
    }
  }
  return escaped;
}

std::string CsvCell(std::string_view value) {
  std::string escaped;
  escaped.reserve(value.size() + 2);
  bool quote = false;
  for (const char ch : value) {
    if (ch == '"' || ch == ',' || ch == '\n' || ch == '\r') {
      quote = true;
    }
    if (ch == '"') {
      escaped += "\"\"";
    } else {
      escaped += ch;
    }
  }
  if (!quote) {
    return escaped;
  }
  return "\"" + escaped + "\"";
}

std::string BoolJson(bool value) {
  return value ? "true" : "false";
}

std::string BundleTimestamp(const DiagnosticBundleInput& input) {
  return input.created_utc.empty() ? "unspecified" : input.created_utc;
}

std::string BundleSessionId(const DiagnosticBundleInput& input) {
  return input.session_id.empty() ? "unspecified" : input.session_id;
}

DiagnosticBuildMetadata EffectiveBuildMetadata(
    const DiagnosticBuildMetadata& metadata) {
  if (!metadata.app_name.empty()) {
    return metadata;
  }
  return MakeCurrentDiagnosticBuildMetadata();
}

void AppendStringArray(std::ostringstream& out,
                       const std::vector<std::string>& values,
                       std::string_view indent) {
  out << "[";
  for (std::size_t i = 0; i < values.size(); ++i) {
    if (i != 0) {
      out << ", ";
    }
    out << '"' << EscapeJson(values[i]) << '"';
  }
  out << "]";
  (void)indent;
}

void AppendFormatJson(std::ostringstream& out,
                      const FormatDescriptor& format,
                      std::string_view indent) {
  out << "{\n";
  out << indent << "  \"signal\": \"" << ToString(format.signal_mode) << "\",\n";
  out << indent << "  \"sample_rate_hz\": " << format.sample_rate_hz << ",\n";
  out << indent << "  \"sample_type\": \"" << ToString(format.sample_type) << "\",\n";
  out << indent << "  \"valid_bits_per_sample\": "
      << format.valid_bits_per_sample << ",\n";
  out << indent << "  \"container_bits_per_sample\": "
      << format.container_bits_per_sample << ",\n";
  out << indent << "  \"channel_count\": "
      << format.channels.channel_count << ",\n";
  out << indent << "  \"channel_mask\": "
      << format.channels.channel_mask << ",\n";
  out << indent << "  \"channel_labels\": ";
  AppendStringArray(out, format.channels.labels, indent);
  out << ",\n";
  out << indent << "  \"dsd_rate_multiplier\": ";
  if (format.dsd_rate_multiplier) {
    out << *format.dsd_rate_multiplier;
  } else {
    out << "null";
  }
  out << ",\n";
  out << indent << "  \"dop_marker_starts_with_0x05\": "
      << BoolJson(format.dop_marker_starts_with_0x05) << "\n";
  out << indent << "}";
}

std::string BuildConfigJson(const PlaybackConfig& config) {
  std::ostringstream out;
  out << "{\n";
  out << "  \"schema_version\": " << kDiagnosticBundleSchemaVersion << ",\n";
  out << "  \"backend\": \"" << ToString(config.backend) << "\",\n";
  out << "  \"device\": {\n";
  out << "    \"backend\": \"" << ToString(config.device.backend) << "\",\n";
  out << "    \"display_name\": \"" << EscapeJson(config.device.display_name) << "\",\n";
  out << "    \"stable_id\": \"" << EscapeJson(config.device.stable_id) << "\",\n";
  out << "    \"driver_name\": \"" << EscapeJson(config.device.driver_name) << "\",\n";
  out << "    \"is_default\": " << BoolJson(config.device.is_default) << ",\n";
  out << "    \"is_known_problem_device\": "
      << BoolJson(config.device.is_known_problem_device) << "\n";
  out << "  },\n";
  out << "  \"format\": ";
  AppendFormatJson(out, config.format, "  ");
  out << ",\n";
  out << "  \"signal\": {\n";
  out << "    \"frequency_hz\": " << config.frequency_hz << ",\n";
  out << "    \"amplitude_dbfs\": " << config.amplitude_dbfs << ",\n";
  out << "    \"duration_ms\": " << config.duration_ms << ",\n";
  out << "    \"continuous\": " << BoolJson(config.continuous) << ",\n";
  out << "    \"fade_in_ms\": " << config.fade_in_ms << ",\n";
  out << "    \"fade_out_ms\": " << config.fade_out_ms << ",\n";
  out << "    \"silence_preroll_ms\": " << config.silence_preroll_ms << ",\n";
  out << "    \"silence_postroll_ms\": " << config.silence_postroll_ms << ",\n";
  out << "    \"left_channel_enabled\": "
      << BoolJson(config.left_channel_enabled) << ",\n";
  out << "    \"right_channel_enabled\": "
      << BoolJson(config.right_channel_enabled) << ",\n";
  out << "    \"invert_right_phase\": "
      << BoolJson(config.invert_right_phase) << ",\n";
  out << "    \"right_channel_phase_offset_degrees\": "
      << config.right_channel_phase_offset_degrees << "\n";
  out << "  },\n";
  out << "  \"lifecycle\": {\n";
  out << "    \"hold_mode\": \"" << ToString(config.hold_mode) << "\",\n";
  out << "    \"release_cooldown_ms\": " << config.release_cooldown_ms << ",\n";
  out << "    \"reacquire_cooldown_ms\": " << config.reacquire_cooldown_ms << ",\n";
  out << "    \"max_reacquire_attempts\": "
      << config.max_reacquire_attempts << "\n";
  out << "  },\n";
  out << "  \"api_scheduling\": {\n";
  out << "    \"mme_buffer_count\": " << config.mme_buffer_count << ",\n";
  out << "    \"mme_buffer_duration_ms\": "
      << config.mme_buffer_duration_ms << ",\n";
  out << "    \"directsound_secondary_buffer_duration_ms\": "
      << config.directsound_secondary_buffer_duration_ms << ",\n";
  out << "    \"directsound_write_chunk_ms\": "
      << config.directsound_write_chunk_ms << ",\n";
  out << "    \"directsound_cursor_poll_interval_ms\": "
      << config.directsound_cursor_poll_interval_ms << ",\n";
  out << "    \"wasapi_shared_buffer_duration_ms\": "
      << config.wasapi_shared_buffer_duration_ms << ",\n";
  out << "    \"wasapi_shared_render_poll_interval_ms\": "
      << config.wasapi_shared_render_poll_interval_ms << ",\n";
  out << "    \"wasapi_exclusive_buffer_duration_ms\": "
      << config.wasapi_exclusive_buffer_duration_ms << ",\n";
  out << "    \"wasapi_exclusive_periodicity_ms\": "
      << config.wasapi_exclusive_periodicity_ms << ",\n";
  out << "    \"wasapi_exclusive_render_poll_interval_ms\": "
      << config.wasapi_exclusive_render_poll_interval_ms << ",\n";
  out << "    \"wasapi_exclusive_event_callback_enabled\": "
      << BoolJson(config.wasapi_exclusive_event_callback_enabled) << ",\n";
  out << "    \"wasapi_exclusive_alignment_retry_enabled\": "
      << BoolJson(config.wasapi_exclusive_alignment_retry_enabled) << ",\n";
  out << "    \"wasapi_exclusive_event_timeout_ms\": "
      << config.wasapi_exclusive_event_timeout_ms << ",\n";
  out << "    \"audio_thread_mmcss_enabled\": "
      << BoolJson(config.audio_thread_mmcss_enabled) << ",\n";
  out << "    \"audio_thread_mmcss_task_name\": \""
      << EscapeJson(config.audio_thread_mmcss_task_name) << "\",\n";
  out << "    \"audio_thread_mmcss_priority\": "
      << config.audio_thread_mmcss_priority << ",\n";
  out << "    \"asio_use_output_ready_if_supported\": "
      << BoolJson(config.asio_use_output_ready_if_supported) << ",\n";
  out << "    \"high_load_diagnostics_enabled\": "
      << BoolJson(config.high_load_diagnostics_enabled) << ",\n";
  out << "    \"high_load_risk_acknowledged\": "
      << BoolJson(config.high_load_risk_acknowledged) << ",\n";
  out << "    \"high_load_mechanism\": \""
      << (config.high_load_diagnostics_enabled
              ? "bounded_render_callback_delay"
              : "off")
      << "\",\n";
  out << "    \"high_load_render_delay_ms\": "
      << config.high_load_render_delay_ms << ",\n";
  out << "    \"high_load_delay_every_callbacks\": "
      << config.high_load_delay_every_callbacks << "\n";
  out << "  }\n";
  out << "}\n";
  return out.str();
}

std::string BuildDeviceDumpJson(const DeviceInfo& device) {
  std::ostringstream out;
  out << "{\n";
  out << "  \"schema_version\": " << kDiagnosticBundleSchemaVersion << ",\n";
  out << "  \"backend\": \"" << ToString(device.backend) << "\",\n";
  out << "  \"display_name\": \"" << EscapeJson(device.display_name) << "\",\n";
  out << "  \"stable_id\": \"" << EscapeJson(device.stable_id) << "\",\n";
  out << "  \"driver_name\": \"" << EscapeJson(device.driver_name) << "\",\n";
  out << "  \"is_default\": " << BoolJson(device.is_default) << ",\n";
  out << "  \"is_known_problem_device\": "
      << BoolJson(device.is_known_problem_device) << "\n";
  out << "}\n";
  return out.str();
}

DiscoverySnapshot BuildFallbackDeviceSnapshot(
    const DiagnosticBundleInput& input) {
  DiscoverySnapshot snapshot;
  snapshot.schema_version = kDeviceDiscoverySchemaVersion;
  snapshot.capture_utc = BundleTimestamp(input);
  snapshot.backend = input.device.backend;
  snapshot.opens_audio_device = false;
  snapshot.initializes_stream = false;
  snapshot.starts_stream = false;

  auto device_snapshot = MakeDiscoveredDeviceSnapshot(
      input.device,
      DeviceAvailability::kActive);
  device_snapshot.backend_fields["bundle_source"] =
      "constructed-from-diagnostic-bundle-input";
  for (const auto& format : input.format_probe.supported_formats) {
    CapabilityProbeRecord probe;
    probe.kind = CapabilityProbeKind::kFormatSupport;
    probe.support = CapabilitySupport::kSupported;
    probe.requested_format = input.format_probe.requested;
    probe.accepted_format = format;
    probe.detail = "accepted format captured by diagnostic bundle";
    probe.backend_fields["bundle_source"] = "format-probe-result";
    device_snapshot.probes.push_back(std::move(probe));
  }
  for (const auto& reason : input.format_probe.rejection_reasons) {
    CapabilityProbeRecord probe;
    probe.kind = CapabilityProbeKind::kFormatSupport;
    probe.support = CapabilitySupport::kUnsupported;
    probe.requested_format = input.format_probe.requested;
    probe.detail = reason;
    probe.backend_fields["bundle_source"] = "format-probe-result";
    device_snapshot.probes.push_back(std::move(probe));
  }
  snapshot.devices.push_back(std::move(device_snapshot));
  return snapshot;
}

std::string BuildFormatProbeJson(const FormatProbeResult& probe) {
  std::ostringstream out;
  out << "{\n";
  out << "  \"schema_version\": " << kDiagnosticBundleSchemaVersion << ",\n";
  out << "  \"requested\": ";
  AppendFormatJson(out, probe.requested, "  ");
  out << ",\n";
  out << "  \"supported_count\": " << probe.supported_formats.size() << ",\n";
  out << "  \"supported_formats\": [";
  for (std::size_t i = 0; i < probe.supported_formats.size(); ++i) {
    if (i != 0) {
      out << ",";
    }
    out << "\n    ";
    AppendFormatJson(out, probe.supported_formats[i], "    ");
  }
  if (!probe.supported_formats.empty()) {
    out << "\n  ";
  }
  out << "],\n";
  out << "  \"rejection_reasons\": [";
  for (std::size_t i = 0; i < probe.rejection_reasons.size(); ++i) {
    if (i != 0) {
      out << ", ";
    }
    out << '"' << EscapeJson(probe.rejection_reasons[i]) << '"';
  }
  out << "]\n";
  out << "}\n";
  return out.str();
}

std::string BuildRealtimeCsv(const RealtimeStats& stats) {
  std::ostringstream out;
  out << "frames_generated,frames_submitted,frames_accepted,callback_count,"
         "expected_callback_interval_ms,last_callback_interval_ms,"
         "max_callback_delay_ms,underrun_count,discontinuity_count,"
         "device_invalidated_count\n";
  out << stats.frames_generated << ','
      << stats.frames_submitted << ','
      << stats.frames_accepted << ','
      << stats.callback_count << ','
      << stats.expected_callback_interval_ms << ','
      << stats.last_callback_interval_ms << ','
      << stats.max_callback_delay_ms << ','
      << stats.underrun_count << ','
      << stats.discontinuity_count << ','
      << stats.device_invalidated_count << '\n';
  return out.str();
}

std::string BuildReleaseJson(const DiagnosticBundleInput& input) {
  std::ostringstream out;
  const RealtimeStats& stats =
      input.release_attempted ? input.release_stats : input.final_stats;
  out << "{\n";
  out << "  \"schema_version\": " << kDiagnosticBundleSchemaVersion << ",\n";
  out << "  \"release_attempted\": "
      << BoolJson(input.release_attempted) << ",\n";
  out << "  \"release_ok\": " << BoolJson(input.release_ok) << ",\n";
  out << "  \"release_backend\": \""
      << EscapeJson(input.release_backend) << "\",\n";
  out << "  \"release_status\": \""
      << EscapeJson(input.release_status) << "\",\n";
  out << "  \"release_error\": \""
      << EscapeJson(input.release_error) << "\",\n";
  out << "  \"final_counters_after_release_or_export\": {\n";
  out << "    \"frames_generated\": " << stats.frames_generated << ",\n";
  out << "    \"frames_submitted\": " << stats.frames_submitted << ",\n";
  out << "    \"frames_accepted\": " << stats.frames_accepted << ",\n";
  out << "    \"callback_count\": " << stats.callback_count << ",\n";
  out << "    \"underrun_count\": " << stats.underrun_count << ",\n";
  out << "    \"discontinuity_count\": " << stats.discontinuity_count << ",\n";
  out << "    \"device_invalidated_count\": "
      << stats.device_invalidated_count << "\n";
  out << "  }\n";
  out << "}\n";
  return out.str();
}

std::string BuildSummaryCsv(const DiagnosticBundleInput& input) {
  std::ostringstream out;
  out << "key,value\n";
  out << "schema_version," << kDiagnosticBundleSchemaVersion << "\n";
  out << "session_id," << CsvCell(BundleSessionId(input)) << "\n";
  out << "created_utc," << CsvCell(BundleTimestamp(input)) << "\n";
  out << "backend," << CsvCell(ToString(input.config.backend)) << "\n";
  out << "device_id," << CsvCell(input.device.stable_id) << "\n";
  out << "device_name," << CsvCell(input.device.display_name) << "\n";
  out << "signal," << CsvCell(ToString(input.config.format.signal_mode)) << "\n";
  out << "sample_rate_hz," << input.config.format.sample_rate_hz << "\n";
  out << "sample_type," << CsvCell(ToString(input.config.format.sample_type)) << "\n";
  out << "channel_count," << input.config.format.channels.channel_count << "\n";
  out << "hold_mode," << CsvCell(ToString(input.config.hold_mode)) << "\n";
  out << "frames_generated," << input.final_stats.frames_generated << "\n";
  out << "frames_submitted," << input.final_stats.frames_submitted << "\n";
  out << "frames_accepted," << input.final_stats.frames_accepted << "\n";
  out << "callback_count," << input.final_stats.callback_count << "\n";
  out << "underrun_count," << input.final_stats.underrun_count << "\n";
  out << "discontinuity_count," << input.final_stats.discontinuity_count << "\n";
  out << "device_invalidated_count,"
      << input.final_stats.device_invalidated_count << "\n";
  out << "release_attempted," << BoolJson(input.release_attempted) << "\n";
  out << "release_ok," << BoolJson(input.release_ok) << "\n";
  out << "release_backend," << CsvCell(input.release_backend) << "\n";
  out << "release_status," << CsvCell(input.release_status) << "\n";
  out << "release_error," << CsvCell(input.release_error) << "\n";
  const RealtimeStats& release_stats =
      input.release_attempted ? input.release_stats : input.final_stats;
  out << "release_callback_count," << release_stats.callback_count << "\n";
  out << "release_underrun_count," << release_stats.underrun_count << "\n";
  return out.str();
}

std::string BuildPlaybackJsonl(const std::vector<DiagnosticEvent>& events) {
  std::string jsonl;
  for (const auto& event : events) {
    jsonl += SerializeDiagnosticEventJsonl(event);
  }
  return jsonl;
}

std::string BuildUiLogText(const std::vector<std::string>& lines) {
  std::ostringstream out;
  for (const auto& line : lines) {
    out << line << "\n";
  }
  return out.str();
}

std::string BuildInitializationLogText(const DiagnosticBundleInput& input) {
  std::ostringstream out;
  out << "session_id=" << BundleSessionId(input) << "\n";
  out << "created_utc=" << BundleTimestamp(input) << "\n";
  out << "backend=" << ToString(input.config.backend) << "\n";
  out << "device_id=" << input.device.stable_id << "\n";
  out << "device_name=" << input.device.display_name << "\n";
  out << "signal=" << ToString(input.config.format.signal_mode) << "\n";
  out << "sample_rate_hz=" << input.config.format.sample_rate_hz << "\n";
  out << "sample_type=" << ToString(input.config.format.sample_type) << "\n";
  out << "valid_bits_per_sample="
      << input.config.format.valid_bits_per_sample << "\n";
  out << "container_bits_per_sample="
      << input.config.format.container_bits_per_sample << "\n";
  out << "channel_count=" << input.config.format.channels.channel_count << "\n";
  out << "supported_format_count="
      << input.format_probe.supported_formats.size() << "\n";
  out << "event_count=" << input.events.size() << "\n";
  out << "ui_log_line_count=" << input.ui_log_lines.size() << "\n";
  return out.str();
}

std::string BuildBuildMetadataJson(const DiagnosticBuildMetadata& metadata) {
  std::ostringstream out;
  out << "{\n";
  out << "  \"schema_version\": " << metadata.schema_version << ",\n";
  out << "  \"app_name\": \"" << EscapeJson(metadata.app_name) << "\",\n";
  out << "  \"app_version\": \"" << EscapeJson(metadata.app_version) << "\",\n";
  out << "  \"build_type\": \"" << EscapeJson(metadata.build_type) << "\",\n";
  out << "  \"compiler\": \"" << EscapeJson(metadata.compiler) << "\",\n";
  out << "  \"compiler_version\": \""
      << EscapeJson(metadata.compiler_version) << "\",\n";
  out << "  \"cxx_standard\": \"" << EscapeJson(metadata.cxx_standard) << "\",\n";
  out << "  \"target_arch\": \"" << EscapeJson(metadata.target_arch) << "\",\n";
  out << "  \"enable_asio\": " << BoolJson(metadata.enable_asio) << "\n";
  out << "}\n";
  return out.str();
}

std::string BuildManifestJson(const DiagnosticBundleInput& input,
                              const std::vector<FileToWrite>& files,
                              const DiagnosticBuildMetadata& metadata) {
  std::ostringstream out;
  out << "{\n";
  out << "  \"schema_version\": " << kDiagnosticBundleSchemaVersion << ",\n";
  out << "  \"bundle_kind\": \"audio-path-tone-tester-diagnostic\",\n";
  out << "  \"session_id\": \"" << EscapeJson(BundleSessionId(input)) << "\",\n";
  out << "  \"created_utc\": \"" << EscapeJson(BundleTimestamp(input)) << "\",\n";
  out << "  \"app_name\": \"" << EscapeJson(metadata.app_name) << "\",\n";
  out << "  \"app_version\": \"" << EscapeJson(metadata.app_version) << "\",\n";
  out << "  \"backend\": \"" << ToString(input.config.backend) << "\",\n";
  out << "  \"device_id\": \"" << EscapeJson(input.device.stable_id) << "\",\n";
  out << "  \"files\": [\"bundle_manifest.json\"";
  for (std::size_t i = 0; i < files.size(); ++i) {
    out << ", ";
    out << '"' << EscapeJson(files[i].name) << '"';
  }
  out << "]\n";
  out << "}\n";
  return out.str();
}

std::vector<FileToWrite> BuildDiagnosticBundleFiles(
    const DiagnosticBundleInput& input) {
  const auto build_metadata = EffectiveBuildMetadata(input.build_metadata);
  const auto snapshot = input.discovery_snapshot
      ? *input.discovery_snapshot
      : BuildFallbackDeviceSnapshot(input);

  std::vector<FileToWrite> files = {
      {"config.json", BuildConfigJson(input.config)},
      {"playback.jsonl", BuildPlaybackJsonl(input.events)},
      {"realtime.csv", BuildRealtimeCsv(input.final_stats)},
      {"release.json", BuildReleaseJson(input)},
      {"summary.csv", BuildSummaryCsv(input)},
      {"pcm_summary.json", SerializePcmRunSummaryJson(input.pcm_summary)},
      {"device_dump.json", BuildDeviceDumpJson(input.device)},
      {"device_snapshot.json", SerializeDiscoverySnapshotJson(snapshot)},
      {"format_probe.json", BuildFormatProbeJson(input.format_probe)},
      {"init.log", BuildInitializationLogText(input)},
      {"build_metadata.json", BuildBuildMetadataJson(build_metadata)},
      {"ui_log.txt", BuildUiLogText(input.ui_log_lines)},
      {"summary.txt", input.summary},
  };
  if (input.selected_condition_json) {
    files.push_back({"selected_condition.json", *input.selected_condition_json});
  }
  if (input.diagnostic_plan_json) {
    files.push_back({"diagnostic_plan.json", *input.diagnostic_plan_json});
  }
  if (input.diagnostic_score_json) {
    files.push_back({"diagnostic_score.json", *input.diagnostic_score_json});
  }
  files.insert(
      files.begin(),
      {"bundle_manifest.json", BuildManifestJson(input, files, build_metadata)});
  return files;
}

bool WriteTextFile(const std::filesystem::path& path,
                   const std::string& content) {
  std::ofstream file(path, std::ios::binary);
  if (!file) {
    return false;
  }
  file << content;
  return file.good();
}

void AppendU16(std::ostream& out, std::uint16_t value) {
  out.put(static_cast<char>(value & 0xFFu));
  out.put(static_cast<char>((value >> 8u) & 0xFFu));
}

void AppendU32(std::ostream& out, std::uint32_t value) {
  out.put(static_cast<char>(value & 0xFFu));
  out.put(static_cast<char>((value >> 8u) & 0xFFu));
  out.put(static_cast<char>((value >> 16u) & 0xFFu));
  out.put(static_cast<char>((value >> 24u) & 0xFFu));
}

std::uint32_t Crc32(std::string_view content) {
  std::uint32_t crc = 0xFFFFFFFFu;
  for (const unsigned char byte : content) {
    crc ^= byte;
    for (int bit = 0; bit < 8; ++bit) {
      const std::uint32_t mask = 0u - (crc & 1u);
      crc = (crc >> 1u) ^ (0xEDB88320u & mask);
    }
  }
  return crc ^ 0xFFFFFFFFu;
}

std::string NormalizeZipName(std::string name) {
  for (char& ch : name) {
    if (ch == '\\') {
      ch = '/';
    }
  }
  return name;
}

OperationResult WriteZipStoreArchive(const std::filesystem::path& zip_path,
                                     const std::vector<FileToWrite>& files,
                                     bool allow_overwrite) {
  if (!allow_overwrite && std::filesystem::exists(zip_path)) {
    return OperationResult::Failure(
        3,
        "refusing to overwrite existing diagnostic bundle zip");
  }
  if (!zip_path.parent_path().empty()) {
    std::error_code create_error;
    std::filesystem::create_directories(zip_path.parent_path(), create_error);
    if (create_error) {
      return OperationResult::Failure(
          1,
          "failed to create diagnostic bundle zip directory");
    }
  }

  std::ofstream out(zip_path, std::ios::binary);
  if (!out) {
    return OperationResult::Failure(
        2,
        "failed to create diagnostic bundle zip");
  }

  struct CentralRecord {
    std::string name;
    std::uint32_t crc = 0;
    std::uint32_t size = 0;
    std::uint32_t local_header_offset = 0;
  };
  std::vector<CentralRecord> central_records;

  constexpr std::uint16_t kDosTime = 0;
  constexpr std::uint16_t kDosDate =
      static_cast<std::uint16_t>(((2026 - 1980) << 9) | (1 << 5) | 1);

  for (const auto& file : files) {
    if (file.content.size() > std::numeric_limits<std::uint32_t>::max() ||
        file.name.size() > std::numeric_limits<std::uint16_t>::max()) {
      return OperationResult::Failure(
          4,
          "diagnostic bundle zip entry is too large");
    }
    const auto offset = static_cast<std::uint64_t>(out.tellp());
    if (offset > std::numeric_limits<std::uint32_t>::max()) {
      return OperationResult::Failure(4, "diagnostic bundle zip is too large");
    }

    const std::string zip_name = NormalizeZipName(file.name);
    const auto size = static_cast<std::uint32_t>(file.content.size());
    const auto crc = Crc32(file.content);

    AppendU32(out, 0x04034B50u);
    AppendU16(out, 20);
    AppendU16(out, 0);
    AppendU16(out, 0);
    AppendU16(out, kDosTime);
    AppendU16(out, kDosDate);
    AppendU32(out, crc);
    AppendU32(out, size);
    AppendU32(out, size);
    AppendU16(out, static_cast<std::uint16_t>(zip_name.size()));
    AppendU16(out, 0);
    out.write(zip_name.data(), static_cast<std::streamsize>(zip_name.size()));
    out.write(file.content.data(), static_cast<std::streamsize>(file.content.size()));

    central_records.push_back(CentralRecord{
        .name = zip_name,
        .crc = crc,
        .size = size,
        .local_header_offset = static_cast<std::uint32_t>(offset),
    });
  }

  const auto central_directory_offset =
      static_cast<std::uint64_t>(out.tellp());
  if (central_directory_offset > std::numeric_limits<std::uint32_t>::max()) {
    return OperationResult::Failure(4, "diagnostic bundle zip is too large");
  }

  for (const auto& record : central_records) {
    AppendU32(out, 0x02014B50u);
    AppendU16(out, 20);
    AppendU16(out, 20);
    AppendU16(out, 0);
    AppendU16(out, 0);
    AppendU16(out, kDosTime);
    AppendU16(out, kDosDate);
    AppendU32(out, record.crc);
    AppendU32(out, record.size);
    AppendU32(out, record.size);
    AppendU16(out, static_cast<std::uint16_t>(record.name.size()));
    AppendU16(out, 0);
    AppendU16(out, 0);
    AppendU16(out, 0);
    AppendU16(out, 0);
    AppendU32(out, 0);
    AppendU32(out, record.local_header_offset);
    out.write(record.name.data(), static_cast<std::streamsize>(record.name.size()));
  }

  const auto central_directory_end = static_cast<std::uint64_t>(out.tellp());
  const auto central_directory_size =
      central_directory_end - central_directory_offset;
  if (central_records.size() > std::numeric_limits<std::uint16_t>::max() ||
      central_directory_size > std::numeric_limits<std::uint32_t>::max()) {
    return OperationResult::Failure(4, "diagnostic bundle zip is too large");
  }

  AppendU32(out, 0x06054B50u);
  AppendU16(out, 0);
  AppendU16(out, 0);
  AppendU16(out, static_cast<std::uint16_t>(central_records.size()));
  AppendU16(out, static_cast<std::uint16_t>(central_records.size()));
  AppendU32(out, static_cast<std::uint32_t>(central_directory_size));
  AppendU32(out, static_cast<std::uint32_t>(central_directory_offset));
  AppendU16(out, 0);

  if (!out.good()) {
    return OperationResult::Failure(
        2,
        "failed to write diagnostic bundle zip");
  }
  return OperationResult::Success();
}

}  // namespace

DiagnosticBuildMetadata MakeCurrentDiagnosticBuildMetadata() {
  DiagnosticBuildMetadata metadata;
  metadata.schema_version = kDiagnosticBundleSchemaVersion;
  metadata.app_name = "Audio Path Tone Tester";
#ifdef APTT_PROJECT_VERSION
  metadata.app_version = APTT_PROJECT_VERSION;
#else
  metadata.app_version = "0.1.0";
#endif
#ifdef NDEBUG
  metadata.build_type = "release";
#else
  metadata.build_type = "debug";
#endif
#if defined(_MSC_VER)
  metadata.compiler = "MSVC";
  metadata.compiler_version = std::to_string(_MSC_VER);
#elif defined(__clang__)
  metadata.compiler = "Clang";
  metadata.compiler_version = __clang_version__;
#elif defined(__GNUC__)
  metadata.compiler = "GCC";
  metadata.compiler_version = std::to_string(__GNUC__) + "." +
                              std::to_string(__GNUC_MINOR__) + "." +
                              std::to_string(__GNUC_PATCHLEVEL__);
#else
  metadata.compiler = "unknown";
  metadata.compiler_version = "unknown";
#endif
  metadata.cxx_standard = std::to_string(__cplusplus);
#if defined(_M_X64) || defined(__x86_64__) || defined(_WIN64)
  metadata.target_arch = "x64";
#elif defined(_M_IX86) || defined(__i386__)
  metadata.target_arch = "x86";
#elif defined(_M_ARM64) || defined(__aarch64__)
  metadata.target_arch = "arm64";
#else
  metadata.target_arch = "unknown";
#endif
#ifdef APTT_ENABLE_ASIO
  metadata.enable_asio = APTT_ENABLE_ASIO != 0;
#else
  metadata.enable_asio = false;
#endif
  return metadata;
}

OperationResult WriteDiagnosticBundleDirectory(
    const std::filesystem::path& output_directory,
    const DiagnosticBundleInput& input) {
  std::error_code error;
  std::filesystem::create_directories(output_directory, error);
  if (error) {
    return OperationResult::Failure(
        1,
        "failed to create diagnostic bundle directory");
  }

  const auto files = BuildDiagnosticBundleFiles(input);
  for (const auto& file : files) {
    const auto target = output_directory / file.name;
    if (!input.allow_overwrite && std::filesystem::exists(target)) {
      return OperationResult::Failure(
          3,
          "refusing to overwrite existing diagnostic bundle file: " + file.name);
    }
  }

  for (const auto& file : files) {
    if (!WriteTextFile(output_directory / file.name, file.content)) {
      return OperationResult::Failure(
          2,
          std::string("failed to write ") + file.name);
    }
  }

  return OperationResult::Success();
}

OperationResult WriteDiagnosticBundleZip(
    const std::filesystem::path& zip_path,
    const DiagnosticBundleInput& input) {
  return WriteZipStoreArchive(
      zip_path,
      BuildDiagnosticBundleFiles(input),
      input.allow_overwrite);
}

}  // namespace aptt
