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

use crate::bindings::payload::PayloadBinding;
use crate::constants::PAYLOAD;
use crate::context::input::InputContext;
use crate::context::xml::xml_to_pel;
use crate::input::Format;
use crate::value::Value;
use pel::expression::Symbol;
use pel::runtime::value::Value as PelValue;
use pel::runtime::{Binding as PelBinding, Context, ValueHandler};
use pel::Reference;
use std::cell::RefCell;

pub(crate) struct PayloadContext<'a> {
    input_context: &'a InputContext,
    binding: &'a dyn PayloadBinding,
    payload: RefCell<Option<PelValue>>,
    content_type: Option<Format>,
}

impl<'a> PayloadContext<'a> {
    pub fn new(
        input_context: &'a InputContext,
        binding: &'a dyn PayloadBinding,
        content_type: Option<Format>,
    ) -> Self {
        Self {
            input_context,
            binding,
            payload: RefCell::new(None),
            content_type,
        }
    }

    fn do_resolve(&self) -> PelValue {
        let present = self.payload.borrow().is_some();

        if !present {
            let bytes = self.binding.as_bytes();

            let value = self
                .input_context
                .format()
                .iter()
                .filter(|f| match &self.content_type {
                    None => true,
                    Some(content_type) => (*f).eq(content_type),
                })
                .map(|f| Self::match_format(f, bytes.as_ref()))
                .find(|v| !v.is_null());

            self.payload.replace(value);
        }

        self.payload
            .borrow()
            .as_ref()
            .cloned()
            .unwrap_or_else(PelValue::null)
    }

    fn match_format(format: &Format, bytes: &[u8]) -> PelValue {
        match format {
            #[cfg(feature = "experimental_coerced_type")]
            Format::Json => serde_json::from_slice(bytes)
                .unwrap_or(Value::Null)
                .coerced_pel(bytes),
            #[cfg(not(feature = "experimental_coerced_type"))]
            Format::Json => serde_json::from_slice(bytes)
                .unwrap_or(Value::Null)
                .into_pel(),
            Format::PlainText => PelValue::string(String::from_utf8_lossy(bytes).to_string()),
            Format::Xml => xml_to_pel(String::from_utf8_lossy(bytes).to_string()),
        }
    }
}

impl Context for PayloadContext<'_> {
    fn resolve(&self, symbol: &Symbol) -> PelBinding {
        match !self.input_context.format().is_empty() && symbol.as_str() == PAYLOAD {
            true => PelBinding::Available(self.do_resolve()),
            false => PelBinding::Unknown,
        }
    }

    fn value_handler(&self, _reference: Reference) -> Option<&dyn ValueHandler> {
        None
    }
}