///|
/// The cassette schema supported by this release.
pub const CURRENT_FORMAT_VERSION : Int = 1

///|
/// A HTTP header stored in a cassette.
pub(all) struct Header {
  name : String
  value : String
} derive(ToJson, FromJson, Eq, @debug.Debug)

///|
/// A request or response body.
pub(all) enum Body {
  Empty
  Text(String)
  Base64(String)
} derive(ToJson, FromJson, Eq, @debug.Debug)

///|
/// The request captured in one interaction.
pub(all) struct Request {
  method : String
  url : String
  headers : Array[Header]
  body : Body
} derive(ToJson, FromJson, Eq, @debug.Debug)

///|
/// The response captured in one interaction.
pub(all) struct Response {
  status : Int
  headers : Array[Header]
  body : Body
} derive(ToJson, FromJson, Eq, @debug.Debug)

///|
/// One request/response pair in a cassette.
pub(all) struct Interaction {
  request : Request
  response : Response
} derive(ToJson, FromJson, Eq, @debug.Debug)

///|
/// A versioned collection of recorded HTTP interactions.
pub(all) struct Cassette {
  format_version : Int
  interactions : Array[Interaction]
} derive(ToJson, FromJson, Eq, @debug.Debug)

///|
/// Errors raised while reading a cassette.
pub(all) suberror CassetteError {
  InvalidJson
  InvalidSchema
  UnsupportedVersion(Int)
} derive(Eq, @debug.Debug)

///|
/// Create an empty cassette using the current schema version.
pub fn Cassette::empty() -> Cassette {
  { format_version: CURRENT_FORMAT_VERSION, interactions: [], }
}

///|
/// Encode a cassette as deterministic JSON text.
pub fn Cassette::encode(self : Cassette) -> String {
  @json.to_json(self).stringify() + "\n"
}

///|
/// Decode a cassette and reject schema versions this release cannot handle.
pub fn Cassette::decode(text : String) -> Cassette raise CassetteError {
  let json = @json.parse(text) catch { _ => raise InvalidJson }
  let cassette : Cassette = @json.from_json(json) catch {
    _ => raise InvalidSchema
  }
  if cassette.format_version != CURRENT_FORMAT_VERSION {
    raise UnsupportedVersion(cassette.format_version)
  }
  cassette
}

///|
/// Return a small, useful example for documentation and tests.
pub fn example_cassette() -> Cassette {
  {
    format_version: CURRENT_FORMAT_VERSION,
    interactions: [
      {
        request: {
          method: "GET",
          url: "https://api.example.test/v1/items?page=1",
          headers: [{ name: "accept", value: "application/json", }],
          body: Empty,
        },
        response: {
          status: 200,
          headers: [{ name: "content-type", value: "application/json", }],
          body: Text("{\"items\":[]}"),
        },
      },
    ],
  }
}