///|
pub struct CodingAgentNodeSpec[S, P] {
  agent : CodingAgent
  resource_key : @core.ResourceKey
  resource_scope : @core.ResourceScope
  open_context : (@core.NodeContext, S) -> CodingAgentOpenContext raise
  build_request : (@core.NodeContext, S) -> CodingAgentRequest raise
  decode_response : (S, CodingAgentResponse) -> @core.NodeOutput[P] raise
}

///|
/// Creates the callbacks and resource policy used by a coding-agent node.
pub fn[S, P] CodingAgentNodeSpec::CodingAgentNodeSpec(
  agent~ : CodingAgent,
  resource_key~ : @core.ResourceKey,
  resource_scope~ : @core.ResourceScope,
  open_context~ : (@core.NodeContext, S) -> CodingAgentOpenContext raise,
  build_request~ : (@core.NodeContext, S) -> CodingAgentRequest raise,
  decode_response~ : (S, CodingAgentResponse) -> @core.NodeOutput[P] raise,
) -> CodingAgentNodeSpec[S, P] {
  CodingAgentNodeSpec::{
    agent,
    resource_key,
    resource_scope,
    open_context,
    build_request,
    decode_response,
  }
}

///|
priv struct CodingAgentResource {
  session : &CodingAgentSession
}

///|
impl Debug for CodingAgentResource with fn to_repr(_self) {
  @debug.Repr::literal("CodingAgentResource")
}

///|
extenum @any.Payload += {
  CodingAgentResource(CodingAgentResource)
}

///|
impl @any.Anyable for CodingAgentResource with fn iso() {
  {
    id: @any.TypeId::TypeId(
      "totto2727/workgraph-agent-cli",
      "CodingAgentResource",
      [],
    ),
    box: resource => CodingAgentResource(resource),
    unbox: payload => {
      guard payload is CodingAgentResource(resource)
      resource
    },
  }
}

///|
fn coding_agent_resource_ref(
  key : @core.ResourceKey,
) -> @any_collection.AnyRef[@core.ResourceKey, CodingAgentResource] {
  @any_collection.AnyRef::AnyRef(key)
}

///|
/// Create a graph node that acquires a coding-agent session, executes a request, and decodes its response.
pub fn[S, P] coding_agent_node(
  id : @core.NodeId,
  metadata : @core.NodeMetadata,
  spec : CodingAgentNodeSpec[S, P],
) -> @core.Node[S, P] {
  @core.Node::Node(id, metadata, async fn(context, state) {
    let open_context = (spec.open_context)(context, state)
    let reference = coding_agent_resource_ref(spec.resource_key)
    let resource = match spec.resource_scope {
      @core.Node =>
        context.resources.acquire_resource(
          reference,
          @core.Node,
          async fn() {
            CodingAgentResource::{ session: (spec.agent.open)(open_context) }
          },
          async fn(resource) { resource.session.close() },
          owner=context.node_id,
        )
      @core.Run =>
        context.resources.acquire_resource(
          reference,
          @core.Run,
          async fn() {
            CodingAgentResource::{ session: (spec.agent.open)(open_context) }
          },
          async fn(resource) { resource.session.close() },
        )
    }
    let request = (spec.build_request)(context, state)
    let response = resource.session.execute(request)
    (spec.decode_response)(state, response)
  })
}