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

use crate::bindings::attributes::AttributesBindingAdapter;
use crate::constants::{
    ATTRIBUTES, ATTRIBUTES_REFERENCE, HEADERS, HEADERS_REFERENCE, LOCAL_ADDRESS, METHOD,
    QUERY_PARAMS, QUERY_PARAMS_REFERENCE, QUERY_STRING, REMOTE_ADDRESS, REQUEST_PATH, REQUEST_URI,
    SCHEME, STATUS_CODE, VERSION,
};
use crate::context::input::InputContext;
use crate::context::map::MapContext;
use pel::expression::Symbol;
use pel::runtime::value::Value as PelValue;
use pel::runtime::{Binding as PelBinding, Context, ValueHandler};
use pel::Reference;

pub(crate) struct AttributesContext<'a> {
    input_context: &'a InputContext,
    binding: &'a AttributesBindingAdapter<'a>,
    headers: HeaderValueHandler<'a>,
    query: MapContext<String>,
}

impl<'a> AttributesContext<'a> {
    pub fn new(input_context: &'a InputContext, binding: &'a AttributesBindingAdapter<'a>) -> Self {
        let headers = HeaderValueHandler::new(binding);
        let query = MapContext::new(binding.extract_query_params());
        Self {
            input_context,
            binding,
            headers,
            query,
        }
    }
}

impl Context for AttributesContext<'_> {
    fn resolve(&self, symbol: &Symbol) -> PelBinding {
        match self.input_context.attributes() && symbol.as_str() == ATTRIBUTES {
            true => PelBinding::Available(PelValue::reference(ATTRIBUTES_REFERENCE)),
            false => PelBinding::Unknown,
        }
    }

    fn value_handler(&self, reference: Reference) -> Option<&dyn ValueHandler> {
        match reference {
            ATTRIBUTES_REFERENCE => Some(self),
            HEADERS_REFERENCE => Some(&self.headers),
            QUERY_PARAMS_REFERENCE => Some(&self.query),
            _ => None,
        }
    }
}

impl ValueHandler for AttributesContext<'_> {
    fn detach(&self) -> Option<PelValue> {
        let values = [
            (HEADERS, self.headers.detach()),
            (QUERY_PARAMS, self.query.detach()),
            (METHOD, self.binding.method()),
            (REQUEST_PATH, self.binding.path()),
            (REQUEST_URI, self.binding.uri()),
            (REMOTE_ADDRESS, self.binding.remote_address()),
            (LOCAL_ADDRESS, self.binding.local_address()),
            (QUERY_STRING, self.binding.query_string()),
            (SCHEME, self.binding.scheme()),
            (VERSION, self.binding.version()),
            (STATUS_CODE, self.binding.status_code()),
        ]
        .map(|(k, v)| (k.to_string(), v.unwrap_or_else(PelValue::null)));

        Some(PelValue::object(values.into()))
    }

    fn select_by_key(&self, key: &str) -> Option<PelValue> {
        let selection = match key {
            HEADERS => Some(PelValue::reference(HEADERS_REFERENCE)),
            QUERY_PARAMS => Some(PelValue::reference(QUERY_PARAMS_REFERENCE)),
            METHOD => self.binding.method(),
            REQUEST_PATH => self.binding.path(),
            REQUEST_URI => self.binding.uri(),
            REMOTE_ADDRESS => self.binding.remote_address(),
            LOCAL_ADDRESS => self.binding.local_address(),
            QUERY_STRING => self.binding.query_string(),
            SCHEME => self.binding.scheme(),
            VERSION => self.binding.version(),
            STATUS_CODE => self.binding.status_code(),
            _ => None,
        };
        Some(selection.unwrap_or_else(PelValue::null))
    }
}

struct HeaderValueHandler<'a> {
    binding: &'a AttributesBindingAdapter<'a>,
}

impl<'a> HeaderValueHandler<'a> {
    pub fn new(binding: &'a AttributesBindingAdapter<'a>) -> Self {
        Self { binding }
    }
}

impl ValueHandler for HeaderValueHandler<'_> {
    fn detach(&self) -> Option<PelValue> {
        Some(PelValue::object(self.binding.extract_headers()))
    }

    fn select_by_key(&self, key: &str) -> Option<PelValue> {
        self.binding.extract_header(key)
    }
}