///|
/// Result of parsing a WebHook JSON payload.
pub(all) struct PayloadDocument {
  body : String
  json : Json?
  error : String
} derive(Debug)

///|
/// Parse JSON while preserving invalid input as a diagnostic value.
pub fn parse_payload(body : String) -> PayloadDocument {
  { body, json: Some(@json.parse(body)), error: "" } catch {
    error => { body, json: None, error: error.to_string() }
  }
}

///|
/// Return a nested JSON value using object-key path segments.
pub fn json_path(json : Json, path : Array[String]) -> Json? {
  let mut current = json
  for segment in path {
    match current {
      Object(fields) =>
        match fields.get(segment) {
          Some(next) => current = next
          None => return None
        }
      _ => return None
    }
  }
  Some(current)
}

///|
/// Convert JSON values to stable text for normalized event attributes.
fn json_text(value : Json) -> String {
  match value {
    String(text) => text
    True => "true"
    False => "false"
    Null => "null"
    Number(_, ..) | Array(_) | Object(_) => value.stringify()
  }
}

///|
/// Read a nested JSON value as text.
pub fn payload_path(body : String, path : Array[String]) -> String? {
  match parse_payload(body).json {
    Some(json) => json_path(json, path).map(json_text)
    None => None
  }
}

///|
/// Copy the first existing path into a normalized event attribute.
fn attach_first_path(
  event : HookEvent,
  key : String,
  paths : Array[Array[String]],
) -> HookEvent {
  for path in paths {
    match payload_path(event.body, path) {
      Some(value) => return event.with_attribute(key, value)
      None => ()
    }
  }
  event
}

///|
fn first_payload_path(body : String, paths : Array[Array[String]]) -> String? {
  for path in paths {
    match payload_path(body, path) {
      Some(value) => return Some(value)
      None => ()
    }
  }
  None
}

///|
fn payload_event_type(request : HookRequest) -> String {
  if request.event_type != "unknown" && request.event_type != "" {
    return request.event_type
  }
  let parsed = match request.provider {
    Feishu =>
      first_payload_path(request.body, [["header", "event_type"], ["type"]])
    WeCom => first_payload_path(request.body, [["Event"], ["event"]])
    Stripe => first_payload_path(request.body, [["type"]])
    GitLab => first_payload_path(request.body, [["object_kind"]])
    GitHub | Gitee | Custom(_) => None
  }
  parsed.unwrap_or("unknown")
}

///|
fn payload_delivery_id(request : HookRequest) -> String {
  if request.delivery_id != "unknown" && request.delivery_id != "" {
    return request.delivery_id
  }
  let parsed = match request.provider {
    Feishu =>
      first_payload_path(request.body, [["header", "event_id"], ["event_id"]])
    WeCom => first_payload_path(request.body, [["MsgId"], ["msgid"]])
    Stripe => first_payload_path(request.body, [["id"]])
    GitHub | GitLab | Gitee | Custom(_) => None
  }
  parsed.unwrap_or("unknown")
}

///|
/// Enrich a normalized event with real provider payload metadata.
pub fn enrich_provider_event(request : HookRequest) -> HookEvent {
  let document = parse_payload(request.body)
  let mut event = request
    .to_event()
    .with_identity(
      event_type=payload_event_type(request),
      delivery_id=payload_delivery_id(request),
    )
    .with_attribute(
      "payload.valid_json",
      if document.json is Some(_) {
        "true"
      } else {
        "false"
      },
    )
  event = match request.provider {
    GitHub => {
      let enriched = attach_first_path(event, "action", [["action"]])
      let enriched = attach_first_path(enriched, "ref", [["ref"]])
      let enriched = attach_first_path(enriched, "repository", [
        ["repository", "full_name"],
        ["repository", "name"],
      ])
      attach_first_path(enriched, "actor", [["sender", "login"]])
    }
    GitLab => {
      let enriched = attach_first_path(event, "object_kind", [["object_kind"]])
      let enriched = attach_first_path(enriched, "ref", [["ref"]])
      let enriched = attach_first_path(enriched, "repository", [
        ["project", "path_with_namespace"],
        ["repository", "name"],
      ])
      attach_first_path(enriched, "actor", [["user_username"], ["user_name"]])
    }
    Gitee => {
      let enriched = attach_first_path(event, "action", [["action"]])
      let enriched = attach_first_path(enriched, "ref", [["ref"]])
      let enriched = attach_first_path(enriched, "repository", [
        ["repository", "full_name"],
        ["project", "path_with_namespace"],
      ])
      attach_first_path(enriched, "actor", [["sender", "login"], ["user_name"]])
    }
    Feishu => {
      let enriched = attach_first_path(event, "event_id", [
        ["header", "event_id"],
        ["event_id"],
      ])
      let enriched = attach_first_path(enriched, "event_type", [
        ["header", "event_type"],
        ["type"],
      ])
      attach_first_path(enriched, "actor", [
        ["event", "sender", "sender_id", "open_id"],
      ])
    }
    WeCom => {
      let enriched = attach_first_path(event, "message_id", [
        ["MsgId"],
        ["msgid"],
      ])
      let enriched = attach_first_path(enriched, "event", [["Event"], ["event"]])
      attach_first_path(enriched, "actor", [
        ["FromUserName"],
        ["from_user_name"],
      ])
    }
    Stripe => {
      let enriched = attach_first_path(event, "event_id", [["id"]])
      let enriched = attach_first_path(enriched, "event_type", [["type"]])
      attach_first_path(enriched, "object_id", [["data", "object", "id"]])
    }
    Custom(_) => event
  }
  event
}