///|
/// Lux IR explicit JSON serialization/deserialization
///
/// All `to_json()` outputs match `schemas/lux-ir-v1.json`.
/// NOTE: Json enum variants (Object, String, True, False, Array, Number, Null)
/// are read-only — use lowercase factory functions: Json::object, Json::string,
/// Json::boolean, Json::array, Json::number, Json::null.

///|
/// === LucentRole ===
pub fn LucentRole::to_json(self : LucentRole) -> Json {
  Json::string(self.to_string())
}

///|
pub fn LucentRole::from_json(jv : Json) -> Result[LucentRole, String] {
  match jv {
    String(s) =>
      match LucentRole::from_string(s) {
        Some(r) => Ok(r)
        None => Ok(Native(s))
      }
    _ => Err("expected string for LucentRole")
  }
}

///|
/// === LucentModality ===
pub fn LucentModality::to_json(self : LucentModality) -> Json {
  match self {
    Text => Json::string("text")
    Image => Json::string("image")
    Audio => Json::string("audio")
    Video => Json::string("video")
    Pdf => Json::string("pdf")
    Native(s) => Json::string(s)
  }
}

///|
/// === LucentMediaSource ===
pub fn LucentMediaSource::to_json(self : LucentMediaSource) -> Json {
  match self {
    Inline(data) =>
      Json::object({
        "type": Json::string("inline"),
        "data": Json::string(data),
      })
    Url(data) =>
      Json::object({ "type": Json::string("url"), "data": Json::string(data) })
    FileUri(data) =>
      Json::object({
        "type": Json::string("file_uri"),
        "data": Json::string(data),
      })
  }
}

///|
/// === LucentMultimedia ===
pub fn LucentMultimedia::to_json(self : LucentMultimedia) -> Json {
  Json::object({
    "media_type": Json::string(self.media_type),
    "source": self.source.to_json(),
  })
}

///|
/// === LucentAnnotationKind ===
pub fn LucentAnnotationKind::to_json(self : LucentAnnotationKind) -> String {
  match self {
    Url => "url"
    FileCitation => "file_citation"
    WebSearchCitation => "web_search_citation"
    Native(s) => s
  }
}

///|
/// === LucentAnnotation ===
pub fn LucentAnnotation::to_json(self : LucentAnnotation) -> Json {
  let fields : Map[String, Json] = { "kind": Json::string(self.kind.to_json()) }
  match self.text {
    Some(t) => fields["text"] = Json::string(t)
    None => ()
  }
  match self.reference {
    Some(r) => fields["ref"] = Json::string(r)
    None => ()
  }
  match self.start {
    Some(s) => fields["start"] = s.to_json()
    None => ()
  }
  match self.end {
    Some(e) => fields["end"] = e.to_json()
    None => ()
  }
  Json::object(fields)
}

///|
/// === LucentThinking ===
pub fn LucentThinking::to_json(self : LucentThinking) -> Json {
  let fields : Map[String, Json] = {
    "text": Json::string(self.text),
    "redacted": self.redacted.to_json(),
  }
  match self.signature {
    Some(s) => fields["signature"] = Json::string(s)
    None => ()
  }
  match self.summary {
    Some(arr) => fields["summary"] = Json::array(arr.map(fn(c) { c.to_json() }))
    None => ()
  }
  Json::object(fields)
}

///|
/// === LucentToolUse ===
pub fn LucentToolUse::to_json(self : LucentToolUse) -> Json {
  Json::object({
    "id": Json::string(self.id),
    "name": Json::string(self.name),
    "arguments": Json::string(self.arguments_json),
  })
}

///|
/// === LucentToolResult ===
pub fn LucentToolResult::to_json(self : LucentToolResult) -> Json {
  Json::object({
    "tool_use_id": Json::string(self.tool_use_id),
    "content": Json::array(self.content.map(fn(c) { c.to_json() })),
    "is_error": self.is_error.to_json(),
  })
}

///|
/// === LucentAgentAction ===
pub fn LucentAgentAction::to_json(self : LucentAgentAction) -> Json {
  let fields : Map[String, Json] = {
    "kind": Json::string(self.kind),
    "id": Json::string(self.id),
  }
  match self.call_id {
    Some(s) => fields["call_id"] = Json::string(s)
    None => ()
  }
  match self.name {
    Some(s) => fields["name"] = Json::string(s)
    None => ()
  }
  match self.arguments_json {
    Some(s) => fields["arguments"] = Json::string(s)
    None => ()
  }
  match self.result {
    Some(s) => fields["result"] = Json::string(s)
    None => ()
  }
  match self.provider_payload {
    Some(js) => fields["provider_payload"] = js
    None => ()
  }
  Json::object(fields)
}

///|
/// === LucentContent ===
pub fn LucentContent::to_json(self : LucentContent) -> Json {
  match self {
    Text(s, anns) => {
      let fields : Map[String, Json] = {
        "type": Json::string("text"),
        "text": Json::string(s),
      }
      match anns {
        Some(a) =>
          fields["annotations"] = Json::array(a.map(fn(x) { x.to_json() }))
        None => ()
      }
      Json::object(fields)
    }
    ToolUse(tu) =>
      Json::object({
        "type": Json::string("tool_use"),
        "tool_use": tu.to_json(),
      })
    ToolResult(tr) =>
      Json::object({
        "type": Json::string("tool_result"),
        "tool_result": tr.to_json(),
      })
    Thinking(th) =>
      Json::object({
        "type": Json::string("thinking"),
        "thinking": th.to_json(),
      })
    Refusal(r) =>
      Json::object({
        "type": Json::string("refusal"),
        "refusal": Json::string(r),
      })
    Image(m) =>
      Json::object({ "type": Json::string("image"), "image": m.to_json() })
    Audio(m) =>
      Json::object({ "type": Json::string("audio"), "audio": m.to_json() })
    Video(m) =>
      Json::object({ "type": Json::string("video"), "video": m.to_json() })
    File(m) =>
      Json::object({ "type": Json::string("file"), "file": m.to_json() })
    Native(tag, raw) =>
      Json::object({
        "type": Json::string("native"),
        "native_tag": Json::string(tag),
        "native_payload": raw,
      })
  }
}

///|
/// === LucentMessage ===
pub fn LucentMessage::to_json(self : LucentMessage) -> Json {
  let fields : Map[String, Json] = {
    "role": self.role.to_json(),
    "content": Json::array(self.content.map(fn(c) { c.to_json() })),
  }
  match self.phase {
    Some(p) => fields["phase"] = Json::string(p)
    None => ()
  }
  match self.reasoning {
    Some(r) => fields["reasoning"] = r.to_json()
    None => ()
  }
  Json::object(fields)
}

///|
/// === LucentConversationItem ===
pub fn LucentConversationItem::to_json(self : LucentConversationItem) -> Json {
  match self {
    Message(m) =>
      Json::object({ "type": Json::string("message"), "message": m.to_json() })
    ToolCall(tu) =>
      Json::object({
        "type": Json::string("tool_call"),
        "tool_call": tu.to_json(),
      })
    ToolResult(tr) =>
      Json::object({
        "type": Json::string("tool_result"),
        "tool_result": tr.to_json(),
      })
    Reasoning(th) =>
      Json::object({
        "type": Json::string("reasoning"),
        "reasoning": th.to_json(),
      })
    AgentAction(aa) =>
      Json::object({
        "type": Json::string("agent_action"),
        "agent_action": aa.to_json(),
      })
  }
}

///|
/// === LucentTool ===
pub fn LucentTool::to_json(self : LucentTool) -> Json {
  let params = @json.parse(self.parameters_json) catch { _ => Json::null() }
  let kind_str = match self.kind {
    Function => "function"
    FileSearch => "file_search"
    WebSearch => "web_search"
    CodeInterpreter => "code_interpreter"
    ComputerUse => "computer_use"
    CodeExecution => "code_execution"
    Shell => "shell"
    ApplyPatch => "apply_patch"
    MCP => "mcp"
    Native(s) => s
  }
  let fields : Map[String, Json] = {
    "name": Json::string(self.name),
    "kind": Json::string(kind_str),
    "parameters": params,
  }
  match self.description {
    Some(d) => fields["description"] = Json::string(d)
    None => ()
  }
  match self.strict {
    Some(s) => fields["strict"] = s.to_json()
    None => ()
  }
  Json::object(fields)
}

///|
/// === LucentToolChoice ===
pub fn LucentToolChoice::to_json(self : LucentToolChoice) -> Json {
  match self {
    Auto => Json::string("auto")
    None => Json::string("none")
    Required => Json::string("required")
    SpecificTool(name) =>
      Json::object({
        "type": Json::string("specific_tool"),
        "name": Json::string(name),
      })
  }
}

///|
/// === LucentStructuredOutput ===
pub fn LucentStructuredOutput::to_json(self : LucentStructuredOutput) -> Json {
  match self {
    JsonObject => Json::string("json_object")
    Text => Json::string("text")
    JsonSchema(schema_str) => {
      let schema = @json.parse(schema_str) catch { _ => Json::object(Map([])) }
      Json::object({ "type": Json::string("json_schema"), "schema": schema })
    }
  }
}

///|
/// === LucentOptions ===
pub fn LucentOptions::to_json(self : LucentOptions) -> Json {
  let fields : Map[String, Json] = { "stream": self.stream.to_json() }
  match self.temperature {
    Some(t) => fields["temperature"] = t.to_json()
    None => ()
  }
  match self.top_p {
    Some(p) => fields["top_p"] = p.to_json()
    None => ()
  }
  match self.top_k {
    Some(k) => fields["top_k"] = k.to_json()
    None => ()
  }
  match self.max_output_tokens {
    Some(n) => fields["max_output_tokens"] = n.to_json()
    None => ()
  }
  match self.stop {
    Some(arr) =>
      fields["stop"] = Json::array(arr.map(fn(s) { Json::string(s) }))
    None => ()
  }
  match self.candidate_count {
    Some(n) => fields["candidate_count"] = n.to_json()
    None => ()
  }
  match self.structured_output {
    Some(so) => fields["structured_output"] = so.to_json()
    None => ()
  }
  match self.store {
    Some(s) => fields["store"] = s.to_json()
    None => ()
  }
  match self.extras {
    Some(ext) => fields["extras"] = Json::object(ext)
    None => ()
  }
  Json::object(fields)
}

///|
/// === LucentReasoningConfig ===
pub fn LucentReasoningConfig::to_json(self : LucentReasoningConfig) -> Json {
  let fields : Map[String, Json] = { "enabled": self.enabled.to_json() }
  match self.budget_tokens {
    Some(n) => fields["budget_tokens"] = n.to_json()
    None => ()
  }
  match self.effort {
    Some(e) =>
      fields["effort"] = match e {
        Low => Json::string("low")
        Medium => Json::string("medium")
        High => Json::string("high")
        XHigh => Json::string("xhigh")
      }
    None => ()
  }
  match self.summary {
    Some(s) =>
      fields["summary"] = match s {
        Auto => Json::string("auto")
        Concise => Json::string("concise")
        Detailed => Json::string("detailed")
      }
    None => ()
  }
  Json::object(fields)
}

///|
/// === SupportLevel ===
pub fn SupportLevel::to_json(self : SupportLevel) -> Json {
  Json::string(self.to_string())
}

///|
/// === LucentCapabilities ===
pub fn LucentCapabilities::to_json(self : LucentCapabilities) -> Json {
  Json::object({
    "tool_calling": self.tool_calling.to_json(),
    "parallel_tool_calls": self.parallel_tool_calls.to_json(),
    "reasoning": self.reasoning.to_json(),
    "multimodal_input": self.multimodal_input.to_json(),
    "structured_output": self.structured_output.to_json(),
    "input_modalities": Json::array(
      self.input_modalities.map(fn(m) { m.to_json() }),
    ),
    "output_modalities": Json::array(
      self.output_modalities.map(fn(m) { m.to_json() }),
    ),
  })
}

///|
/// === LucentRequest ===
pub fn LucentRequest::to_json(self : LucentRequest) -> Json {
  let fields : Map[String, Json] = {
    "schema_version": Json::string(self.schema_version),
    "model": Json::string(self.model),
    "conversation": Json::array(self.conversation.map(fn(ci) { ci.to_json() })),
    "options": self.options.to_json(),
  }
  match self.instructions {
    Some(instrs) =>
      fields["instructions"] = Json::array(instrs.map(fn(c) { c.to_json() }))
    None => ()
  }
  match self.tools {
    Some(tools) =>
      fields["tools"] = Json::array(tools.map(fn(t) { t.to_json() }))
    None => ()
  }
  match self.tool_choice {
    Some(tc) => fields["tool_choice"] = tc.to_json()
    None => ()
  }
  match self.capabilities {
    Some(cap) => fields["capabilities"] = cap.to_json()
    None => ()
  }
  match self.reasoning {
    Some(rc) => fields["reasoning"] = rc.to_json()
    None => ()
  }
  match self.metadata {
    Some(m) => {
      let meta_fields : Map[String, Json] = Map([])
      for entry in m {
        match entry {
          (k, v) => meta_fields[k] = Json::string(v)
        }
      }
      if meta_fields.length() > 0 {
        fields["metadata"] = Json::object(meta_fields)
      }
    }
    None => ()
  }
  match self.extra {
    Some(e) => {
      let extra_fields : Map[String, Json] = Map([])
      for entry in e {
        match entry {
          (k, v) => extra_fields[k] = v
        }
      }
      if extra_fields.length() > 0 {
        fields["extra"] = Json::object(extra_fields)
      }
    }
    None => ()
  }
  Json::object(fields)
}

///|
/// === LucentUsage ===
pub fn LucentUsage::to_json(self : LucentUsage) -> Json {
  let fields : Map[String, Json] = {
    "prompt_tokens": self.prompt_tokens.to_json(),
    "completion_tokens": self.completion_tokens.to_json(),
    "total_tokens": self.total_tokens.to_json(),
  }
  match self.reasoning_tokens {
    Some(n) => fields["reasoning_tokens"] = n.to_json()
    None => ()
  }
  match self.cached_tokens {
    Some(n) => fields["cached_tokens"] = n.to_json()
    None => ()
  }
  match self.cache_creation_tokens {
    Some(n) => fields["cache_creation_tokens"] = n.to_json()
    None => ()
  }
  Json::object(fields)
}

///|
/// === LucentFinishReason ===
pub fn LucentFinishReason::to_json(self : LucentFinishReason) -> Json {
  Json::string(self.to_string())
}

///|
/// === LucentSafetyRating ===
pub fn LucentSafetyRating::to_json(self : LucentSafetyRating) -> Json {
  Json::object({
    "category": Json::string(self.category),
    "probability": Json::string(self.probability),
  })
}

///|
/// === LucentChoice ===
pub fn LucentChoice::to_json(self : LucentChoice) -> Json {
  let fields : Map[String, Json] = {
    "index": self.index.to_json(),
    "message": self.message.to_json(),
    "finish_reason": self.finish_reason.to_json(),
  }
  match self.safety_ratings {
    Some(arr) =>
      fields["safety_ratings"] = Json::array(arr.map(fn(sr) { sr.to_json() }))
    None => ()
  }
  Json::object(fields)
}

///|
/// === LucentResponse ===
pub fn LucentResponse::to_json(self : LucentResponse) -> Json {
  let fields : Map[String, Json] = {
    "schema_version": Json::string(self.schema_version),
    "id": Json::string(self.id),
    "model": Json::string(self.model),
    "choices": Json::array(self.choices.map(fn(c) { c.to_json() })),
  }
  match self.created_at {
    Some(n) => fields["created_at"] = n.to_json()
    None => ()
  }
  match self.usage {
    Some(u) => fields["usage"] = u.to_json()
    None => ()
  }
  match self.provider_payload {
    Some(p) => fields["provider_payload"] = p
    None => ()
  }
  Json::object(fields)
}

///|
/// === LucentError ===
pub fn LucentError::to_json(self : LucentError) -> Json {
  let fields : Map[String, Json] = {
    "kind": self.kind.to_json(),
    "message": Json::string(self.message),
  }
  match self.provider_code {
    Some(c) => fields["provider_code"] = Json::string(c)
    None => ()
  }
  Json::object(fields)
}

///|
/// === LucentErrorKind ===
pub fn LucentErrorKind::to_json(self : LucentErrorKind) -> Json {
  match self {
    RateLimit => Json::string("rate_limit")
    InvalidRequest => Json::string("invalid_request")
    Authentication => Json::string("authentication")
    ServerError => Json::string("server_error")
    ContentFilter => Json::string("content_filter")
    Native(s) => Json::string(s)
  }
}

///|
/// === LucentConversationMeta ===
pub fn LucentConversationMeta::to_json(self : LucentConversationMeta) -> Json {
  let fields : Map[String, Json] = Map([])
  match self.id {
    Some(id) => fields["id"] = Json::string(id)
    None => ()
  }
  match self.model {
    Some(m) => fields["model"] = Json::string(m)
    None => ()
  }
  match self.usage_input {
    Some(n) => fields["usage_input"] = n.to_json()
    None => ()
  }
  Json::object(fields)
}

///|
/// === LucentBlockType ===
pub fn LucentBlockType::to_json(self : LucentBlockType) -> Json {
  match self {
    Text => Json::string("text")
    ToolCall(id, name) =>
      Json::object({
        "type": Json::string("tool_call"),
        "id": Json::string(id),
        "name": Json::string(name),
      })
    Thinking => Json::string("thinking")
    Refusal => Json::string("refusal")
    Image => Json::string("image")
    Audio => Json::string("audio")
    Video => Json::string("video")
    Native(tag) => Json::string(tag)
  }
}

///|
/// === LucentBlockDelta ===
pub fn LucentBlockDelta::to_json(self : LucentBlockDelta) -> Json {
  match self {
    TextDelta(s) =>
      Json::object({ "type": Json::string("text"), "text": Json::string(s) })
    ThinkingDelta(s) =>
      Json::object({ "type": Json::string("thinking"), "text": Json::string(s) })
    SignatureDelta(s) =>
      Json::object({
        "type": Json::string("signature"),
        "signature": Json::string(s),
      })
    ToolArgumentsDelta(s) =>
      Json::object({
        "type": Json::string("tool_arguments"),
        "arguments": Json::string(s),
      })
    RefusalDelta(s) =>
      Json::object({ "type": Json::string("refusal"), "text": Json::string(s) })
    NativeDelta(tag, raw) =>
      Json::object({
        "type": Json::string("native"),
        "native_tag": Json::string(tag),
        "native_payload": raw,
      })
  }
}

///|
/// === LucentStreamEvent ===
pub fn LucentStreamEvent::to_json(self : LucentStreamEvent) -> Json {
  match self {
    ConversationStart(meta) =>
      Json::object({
        "event": Json::string("conversation_start"),
        "meta": meta.to_json(),
      })
    ItemStart(idx, item) =>
      Json::object({
        "event": Json::string("item_start"),
        "index": idx.to_json(),
        "item": item.to_json(),
      })
    BlockStart(idx, bt) =>
      Json::object({
        "event": Json::string("block_start"),
        "index": idx.to_json(),
        "block_type": bt.to_json(),
      })
    BlockDelta(idx, delta) =>
      Json::object({
        "event": Json::string("block_delta"),
        "index": idx.to_json(),
        "delta": delta.to_json(),
      })
    BlockEnd(idx) =>
      Json::object({
        "event": Json::string("block_end"),
        "index": idx.to_json(),
      })
    ItemEnd(idx) =>
      Json::object({ "event": Json::string("item_end"), "index": idx.to_json() })
    BlockDiscard(idx) =>
      Json::object({
        "event": Json::string("block_discard"),
        "index": idx.to_json(),
      })
    Annotations(idx, anns) =>
      Json::object({
        "event": Json::string("annotations"),
        "index": idx.to_json(),
        "annotations": Json::array(anns.map(fn(a) { a.to_json() })),
      })
    Finish(fr) =>
      Json::object({
        "event": Json::string("finish"),
        "finish_reason": fr.to_json(),
      })
    Usage(u) =>
      Json::object({ "event": Json::string("usage"), "usage": u.to_json() })
    Error(e) =>
      Json::object({ "event": Json::string("error"), "error": e.to_json() })
    Done => Json::object({ "event": Json::string("done") })
  }
}

///|
/// === ProviderCapability ===
pub fn ProviderCapability::to_json(self : ProviderCapability) -> Json {
  let fields : Map[String, Json] = {
    "model_pattern": Json::string(self.model_pattern),
    "provider": Json::string(self.provider),
    "capabilities": self.capabilities.to_json(),
  }
  match self.extra_params {
    Some(ep) => {
      let ep_fields : Map[String, Json] = Map([])
      for entry in ep {
        match entry {
          (k, v) => ep_fields[k] = v
        }
      }
      if ep_fields.length() > 0 {
        fields["extra_params"] = Json::object(ep_fields)
      }
    }
    None => ()
  }
  Json::object(fields)
}

///|
/// === LucentDiscovery ===
pub fn LucentDiscovery::to_json(self : LucentDiscovery) -> Json {
  Json::object({
    "schema_version": Json::string(self.schema_version),
    "protocol_version": Json::string(self.protocol_version),
    "schema_url": Json::string(self.schema_url),
    "providers": Json::array(self.providers.map(fn(p) { p.to_json() })),
  })
}

///|
/// === Helper: stringify a Lux type to JSON ===
pub fn lux_to_json_string(jv : Json) -> String {
  jv.stringify()
}