//! Host-owned event and effect protocol for active resident Components.
//!
//! A [`ResidentEvent`] is always produced by a Host driver and delivered
//! through the same bounded Actor mailbox as ordinary service invocations.
//! The Component never receives an operating-system descriptor. It can only
//! reference opaque [`ResourceId`] values and return [`ResidentEffect`] values
//! for the owning driver to validate and apply.
//!
//! Driver authors normally use [`ResidentSession`](crate::ResidentSession)
//! instead of constructing events directly. The session proves that every ID
//! belongs to the current service revision and converts raw effects into
//! validated [`ResidentOperation`](crate::ResidentOperation) values.

use serde::{Deserialize, Serialize};

use crate::{RuntimeError, bindings::resident::exports::wasmeld::resident::actor as wit};

pub(crate) const RESIDENT_INTERFACE: &str = "wasmeld:resident/actor@0.1.0";

/// Component execution model discovered from its exported WIT interfaces.
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ComponentExecution {
    /// Retains memory but runs only for `invoke` mailbox commands.
    Service,
    /// Also accepts Host events and returns effects for Host-owned resources.
    Resident,
}

/// Opaque Host resource identity scoped to one resident Actor revision.
///
/// IDs are never reused while the Actor is alive. `0` is reserved so an
/// uninitialized ID cannot accidentally address a real resource.
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(transparent)]
pub struct ResourceId(u64);

impl ResourceId {
    /// Creates a valid opaque resource identity.
    pub fn new(value: u64) -> Option<Self> {
        (value != 0).then_some(Self(value))
    }

    /// Returns the WIT-compatible integer representation.
    pub const fn get(self) -> u64 {
        self.0
    }
}

/// Why a Host-owned stream stopped accepting events and effects.
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum StreamCloseReason {
    /// The remote peer completed or reset the connection.
    PeerClosed,
    /// The platform or Component requested an orderly close.
    HostClosed,
    /// The Host driver enforced its configured inactivity timeout.
    IdleTimeout,
    /// A protocol driver rejected malformed or out-of-order bytes.
    ProtocolError,
    /// The operating system or transport driver reported an I/O failure.
    TransportError,
}

/// One bounded event delivered to a resident Component.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ResidentEvent {
    /// A registered Host timer reached its deadline.
    Timer {
        /// Timer resource previously registered with the session.
        timer_id: ResourceId,
        /// Intended firing instant on the Host monotonic clock, in nanoseconds.
        ///
        /// This is not wall-clock time and may be earlier than delivery when
        /// the Actor mailbox is busy.
        scheduled_at_ns: u64,
    },
    /// A TCP or Unix listener accepted a new byte stream.
    StreamOpened {
        /// Listener resource that accepted the connection.
        endpoint_id: ResourceId,
        /// Newly allocated stream resource scoped to this Actor revision.
        stream_id: ResourceId,
        /// Driver-formatted peer address, when the transport provides one.
        peer: Option<String>,
    },
    /// A bounded chunk was read from an open stream.
    StreamData {
        /// Stream that produced the bytes.
        stream_id: ResourceId,
        /// Raw transport bytes; framing remains the Component or protocol driver's job.
        bytes: Vec<u8>,
    },
    /// Backpressure cleared and the stream can accept another write.
    StreamWritable {
        /// Stream whose driver write buffer has capacity.
        stream_id: ResourceId,
    },
    /// The peer sent EOF but the local write half remains available.
    StreamHalfClosed {
        /// Stream entering the peer-half-closed state.
        stream_id: ResourceId,
    },
    /// A stream reached terminal state and will not produce more events.
    StreamClosed {
        /// Stream being released after this callback.
        stream_id: ResourceId,
        /// Terminal reason reported by the Host driver.
        reason: StreamCloseReason,
    },
    /// One UDP packet arrived on a registered datagram endpoint.
    Datagram {
        /// UDP endpoint that received the packet.
        endpoint_id: ResourceId,
        /// Driver-formatted source address used for a possible reply.
        peer: String,
        /// Complete datagram payload; datagram boundaries are preserved.
        bytes: Vec<u8>,
    },
    /// A broker or internal queue delivered one message.
    Message {
        /// Host-owned subscription that delivered the message.
        subscription_id: ResourceId,
        /// Driver-scoped delivery identity used when acknowledging.
        message_id: u64,
        /// Opaque message body.
        bytes: Vec<u8>,
    },
    /// An event arrived from a separately versioned extension driver.
    Source {
        /// Registered extension source, such as a file watcher or serial port.
        source_id: ResourceId,
        /// Capability-defined event name.
        event: String,
        /// Capability-defined binary payload.
        payload: Vec<u8>,
    },
    /// The deployment is draining and no later events should be accepted.
    Shutdown,
}

/// A requested operation returned by a resident Component.
///
/// Effects are inert until the Host supervisor checks resource ownership,
/// payload limits, current stream state, and driver policy.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ResidentEffect {
    /// Queue bytes for an open stream.
    WriteStream {
        /// Stream to write; it must belong to the current session.
        stream_id: ResourceId,
        /// One bounded write chunk.
        bytes: Vec<u8>,
    },
    /// Ask the driver to close both halves of a stream.
    CloseStream {
        /// Stream to transition into `closing`.
        stream_id: ResourceId,
    },
    /// Stop delivering read events until a matching resume effect.
    PauseStream {
        /// Stream whose read side should be paused.
        stream_id: ResourceId,
    },
    /// Resume read delivery after Component backpressure clears.
    ResumeStream {
        /// Previously paused stream.
        stream_id: ResourceId,
    },
    /// Send one UDP packet while preserving datagram boundaries.
    SendDatagram {
        /// Registered UDP endpoint used to send the packet.
        endpoint_id: ResourceId,
        /// Driver-formatted destination address.
        peer: String,
        /// Complete bounded datagram payload.
        bytes: Vec<u8>,
    },
    /// Schedule or reschedule a registered Host timer.
    ArmTimer {
        /// Timer resource to schedule.
        timer_id: ResourceId,
        /// Delay from the time the Host applies this effect.
        delay_ms: u64,
        /// Optional repeat period; `None` creates a one-shot timer.
        interval_ms: Option<u64>,
    },
    /// Cancel a registered timer.
    CancelTimer {
        /// Timer whose pending occurrence should be removed.
        timer_id: ResourceId,
    },
    /// Confirm successful processing of a delivered message.
    AcknowledgeMessage {
        /// Subscription that produced the delivery.
        subscription_id: ResourceId,
        /// Exact message identity received in [`ResidentEvent::Message`].
        message_id: u64,
    },
    /// Send a capability-defined command to an extension driver.
    SourceCommand {
        /// Extension source that should execute the command.
        source_id: ResourceId,
        /// Capability-defined command name.
        command: String,
        /// Capability-defined bounded binary payload.
        payload: Vec<u8>,
    },
}

/// Execution limits specific to active resident Components.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ResidentLimits {
    /// Maximum number of Host resources owned by one Actor revision.
    pub max_resources: usize,
    /// Maximum effects accepted from one event handler call.
    pub max_effects_per_event: usize,
    /// Maximum bytes in one stream read or write.
    pub max_stream_chunk_bytes: usize,
    /// Maximum bytes in one received or sent datagram.
    pub max_datagram_bytes: usize,
    /// Minimum delay accepted for Component-created timers.
    pub min_timer_delay_ms: u64,
}

impl Default for ResidentLimits {
    fn default() -> Self {
        Self {
            max_resources: 1024,
            max_effects_per_event: 64,
            max_stream_chunk_bytes: 64 * 1024,
            max_datagram_bytes: 64 * 1024,
            min_timer_delay_ms: 1,
        }
    }
}

impl ResidentLimits {
    pub(crate) fn validate(&self) -> Result<(), RuntimeError> {
        if self.max_resources == 0 {
            return Err(invalid_limit("max_resources"));
        }
        if self.max_effects_per_event == 0 {
            return Err(invalid_limit("max_effects_per_event"));
        }
        if self.max_stream_chunk_bytes == 0 {
            return Err(invalid_limit("max_stream_chunk_bytes"));
        }
        if self.max_datagram_bytes == 0 {
            return Err(invalid_limit("max_datagram_bytes"));
        }
        if self.min_timer_delay_ms == 0 {
            return Err(invalid_limit("min_timer_delay_ms"));
        }
        Ok(())
    }
}

pub(crate) fn into_wit_event(event: ResidentEvent) -> wit::Event {
    match event {
        ResidentEvent::Timer {
            timer_id,
            scheduled_at_ns,
        } => wit::Event::Timer(wit::TimerFired {
            timer_id: timer_id.get(),
            scheduled_at_ns,
        }),
        ResidentEvent::StreamOpened {
            endpoint_id,
            stream_id,
            peer,
        } => wit::Event::StreamOpened(wit::StreamOpened {
            endpoint_id: endpoint_id.get(),
            stream_id: stream_id.get(),
            peer,
        }),
        ResidentEvent::StreamData { stream_id, bytes } => {
            wit::Event::StreamData(wit::StreamChunk {
                stream_id: stream_id.get(),
                bytes,
            })
        }
        ResidentEvent::StreamWritable { stream_id } => wit::Event::StreamWritable(stream_id.get()),
        ResidentEvent::StreamHalfClosed { stream_id } => {
            wit::Event::StreamHalfClosed(stream_id.get())
        }
        ResidentEvent::StreamClosed { stream_id, reason } => {
            wit::Event::StreamClosed(wit::StreamClosed {
                stream_id: stream_id.get(),
                reason: match reason {
                    StreamCloseReason::PeerClosed => wit::CloseReason::PeerClosed,
                    StreamCloseReason::HostClosed => wit::CloseReason::HostClosed,
                    StreamCloseReason::IdleTimeout => wit::CloseReason::IdleTimeout,
                    StreamCloseReason::ProtocolError => wit::CloseReason::ProtocolError,
                    StreamCloseReason::TransportError => wit::CloseReason::TransportError,
                },
            })
        }
        ResidentEvent::Datagram {
            endpoint_id,
            peer,
            bytes,
        } => wit::Event::Datagram(wit::Datagram {
            endpoint_id: endpoint_id.get(),
            peer,
            bytes,
        }),
        ResidentEvent::Message {
            subscription_id,
            message_id,
            bytes,
        } => wit::Event::Message(wit::Message {
            subscription_id: subscription_id.get(),
            message_id,
            bytes,
        }),
        ResidentEvent::Source {
            source_id,
            event,
            payload,
        } => wit::Event::Source(wit::SourceEvent {
            source_id: source_id.get(),
            kind: event,
            payload,
        }),
        ResidentEvent::Shutdown => wit::Event::Shutdown,
    }
}

pub(crate) fn from_wit_effects(
    effects: Vec<wit::Effect>,
) -> Result<Vec<ResidentEffect>, RuntimeError> {
    effects.into_iter().map(from_wit_effect).collect()
}

fn from_wit_effect(effect: wit::Effect) -> Result<ResidentEffect, RuntimeError> {
    Ok(match effect {
        wit::Effect::WriteStream(effect) => ResidentEffect::WriteStream {
            stream_id: resource_id(effect.stream_id)?,
            bytes: effect.bytes,
        },
        wit::Effect::CloseStream(stream_id) => ResidentEffect::CloseStream {
            stream_id: resource_id(stream_id)?,
        },
        wit::Effect::PauseStream(stream_id) => ResidentEffect::PauseStream {
            stream_id: resource_id(stream_id)?,
        },
        wit::Effect::ResumeStream(stream_id) => ResidentEffect::ResumeStream {
            stream_id: resource_id(stream_id)?,
        },
        wit::Effect::SendDatagram(effect) => ResidentEffect::SendDatagram {
            endpoint_id: resource_id(effect.endpoint_id)?,
            peer: effect.peer,
            bytes: effect.bytes,
        },
        wit::Effect::ArmTimer(effect) => ResidentEffect::ArmTimer {
            timer_id: resource_id(effect.timer_id)?,
            delay_ms: effect.delay_ms,
            interval_ms: effect.interval_ms,
        },
        wit::Effect::CancelTimer(timer_id) => ResidentEffect::CancelTimer {
            timer_id: resource_id(timer_id)?,
        },
        wit::Effect::AcknowledgeMessage(effect) => ResidentEffect::AcknowledgeMessage {
            subscription_id: resource_id(effect.subscription_id)?,
            message_id: effect.message_id,
        },
        wit::Effect::SourceCommand(effect) => ResidentEffect::SourceCommand {
            source_id: resource_id(effect.source_id)?,
            command: effect.command,
            payload: effect.payload,
        },
    })
}

fn resource_id(value: u64) -> Result<ResourceId, RuntimeError> {
    ResourceId::new(value)
        .ok_or_else(|| RuntimeError::InvalidResidentEffect("resource ID 0 is reserved".to_owned()))
}

fn invalid_limit(field: &str) -> RuntimeError {
    RuntimeError::InvalidManifest(format!("limits.resident.{field} must be greater than zero"))
}
