Documentation
// Copyright (c) 2026, Salesforce, Inc.,
// All rights reserved.
// For full license text, see the LICENSE.txt file

use crate::proxy_wasm::hostcalls;
use crate::proxy_wasm::hostcalls::call_foreign_function;
use crate::proxy_wasm::types::{Bytes, Status};
use std::mem;

const U32_SIZE: usize = mem::size_of::<u32>();

/// Hostcalls
fn get_shared_data(key: &str) -> Result<(Option<Bytes>, Option<u32>), Status> {
    hostcalls::get_shared_data(key)
}

fn set_shared_data(key: &str, value: &[u8], version: Option<u32>) -> Result<(), Status> {
    hostcalls::set_shared_data(
        key,
        if value.is_empty() { None } else { Some(value) },
        version,
    )
}

fn remove_shared_data(key: &str, version: Option<u32>) -> Result<Option<Bytes>, Status> {
    let version: u32 = version.unwrap_or(0);

    // Pushing version bytes as Little Endian to the byte array arguments vector
    let mut arguments = Vec::from(key.as_bytes());
    arguments.extend(version.to_le_bytes());

    let result = call_foreign_function("remove_shared_data_key", Some(arguments.as_slice()));

    match result {
        Ok(bytes) => {
            if let Some(bytes) = bytes {
                let (value, _cas) = bytes.split_at(bytes.len() - U32_SIZE);
                return Ok(Some(value.to_vec()));
            }

            Ok(None)
        }
        Err(Status::NotFound) => Ok(None),
        Err(status) => Err(status),
    }
}

fn keys_shared_data() -> Result<Vec<String>, Status> {
    let result = call_foreign_function("get_shared_data_keys", None)?;
    let keys = match result {
        None => Vec::default(),
        Some(bytes) => bytes
            .split(|byte| (*byte) == 0)
            .filter(|bytes| !bytes.is_empty())
            .map(std::str::from_utf8)
            .filter(|result| result.is_ok())
            .map(|result| result.unwrap().to_string())
            .collect(),
    };
    Ok(keys)
}

/// An interface of the `Envoy` `Shared Data API`.
pub trait SharedData {
    /// Returns shared data by key.
    ///
    /// # Arguments
    ///
    /// * `key` - key.
    ///
    /// # Return value
    ///
    /// * `value`   - an opaque blob of bytes.
    /// * `version` - optimistic lock version.
    fn shared_data_get(&self, key: &str) -> (Option<Bytes>, Option<u32>);

    /// Shares data under a given key.
    ///
    /// # Arguments
    ///
    /// * `key`     - key.
    /// * `value`   - an opaque blob of bytes.
    /// * `version` - optimistic lock version. Setting it to None will override what it's stored,
    ///   without doing any version validation.
    fn shared_data_set(&self, key: &str, value: &[u8], version: Option<u32>) -> Result<(), Status>;

    /// Removes shared data by key
    ///
    /// # Arguments
    ///
    /// * `key` - key.
    /// * `version` - optimistic lock version. Setting it to None will remove what it's stored,
    ///   without doing any version validation.
    ///
    /// # Return value
    ///
    /// * `value`   - an opaque blob of bytes.
    /// * `version` - optimistic lock version.
    fn shared_data_remove(&self, key: &str, version: Option<u32>) -> Result<Option<Bytes>, Status>;

    /// Returns the key of each entry stored in shared data
    ///
    /// # Return value
    ///
    /// * `keys`    - a String vector containing the keys
    fn shared_data_keys(&self) -> Vec<String>;
}

pub struct DefaultSharedData;

impl SharedData for DefaultSharedData {
    fn shared_data_get(&self, key: &str) -> (Option<Bytes>, Option<u32>) {
        get_shared_data(key).unwrap_or_else(|e| {
            log::warn!("Unhandled proxy-wasm error at DefaultSharedData::shared_data_get: {e:?}.");
            (None, None)
        })
    }

    fn shared_data_set(&self, key: &str, value: &[u8], cas: Option<u32>) -> Result<(), Status> {
        set_shared_data(key, value, cas)
    }

    fn shared_data_remove(&self, key: &str, version: Option<u32>) -> Result<Option<Bytes>, Status> {
        remove_shared_data(key, version)
    }

    fn shared_data_keys(&self) -> Vec<String> {
        keys_shared_data().unwrap_or_default()
    }
}