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

use pel::expression::Symbol;
use pel::runtime::value::Value as PelValue;
use pel::runtime::{Binding as PelBinding, Context, ValueHandler};
use pel::Reference;

use crate::constants::{
    AUTHENTICATION, AUTHENTICATION_REFERENCE, CLIENT_ID, CLIENT_NAME, PRINCIPAL, PROPERTIES,
};
use crate::context::input::InputContext;
use crate::{AuthenticationBinding, Value};

pub(crate) struct AuthenticationContext<'a> {
    input_context: &'a InputContext,
    authentication: &'a dyn AuthenticationBinding,
}

impl<'a> AuthenticationContext<'a> {
    pub fn new(
        input_context: &'a InputContext,
        authentication: &'a dyn AuthenticationBinding,
    ) -> Self {
        Self {
            input_context,
            authentication,
        }
    }

    fn client_id(&self) -> Option<PelValue> {
        self.authentication.client_id().map(PelValue::string)
    }
    fn client_name(&self) -> Option<PelValue> {
        self.authentication.client_name().map(PelValue::string)
    }
    fn principal(&self) -> Option<PelValue> {
        self.authentication.principal().map(PelValue::string)
    }
    fn properties(&self) -> Option<PelValue> {
        self.authentication.properties().map(Value::into_pel)
    }
}

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

    fn value_handler(&self, reference: Reference) -> Option<&dyn ValueHandler> {
        match reference {
            AUTHENTICATION_REFERENCE => Some(self),
            _ => None,
        }
    }
}

impl ValueHandler for AuthenticationContext<'_> {
    fn select_by_key(&self, key: &str) -> Option<PelValue> {
        let result = match key {
            CLIENT_ID => self.client_id(),
            CLIENT_NAME => self.client_name(),
            PRINCIPAL => self.principal(),
            PROPERTIES => self.properties(),
            _ => None,
        };
        Some(result.unwrap_or_else(PelValue::null))
    }

    fn detach(&self) -> Option<PelValue> {
        let values = [
            (CLIENT_ID, self.client_id()),
            (CLIENT_NAME, self.client_name()),
            (PRINCIPAL, self.principal()),
            (PROPERTIES, self.properties()),
        ]
        .map(|(k, v)| (k.to_string(), v.unwrap_or_else(PelValue::null)));

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