// Copyright (c) 2025-2026 Zensical and contributors

// SPDX-License-Identifier: MIT
// All contributions are certified under the DCO

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

// ----------------------------------------------------------------------------

//! Select operator.

use std::marker::PhantomData;

use zrx_scheduler::action::context::Binding;
use zrx_scheduler::action::options::Interest;
use zrx_scheduler::action::{Action, Context, Options};
use zrx_scheduler::schedule::Subscriber;
use zrx_scheduler::step::{IntoSteps, Scope};
use zrx_scheduler::{Id, Key, Value};

use crate::stream::barrier::{Barrier, Barriers};
use crate::stream::Stream;

use super::Operator;

// ----------------------------------------------------------------------------
// Structs
// ----------------------------------------------------------------------------

/// Select operator.
#[derive(Debug)]
pub struct Select<I, T> {
    /// Barrier set.
    barriers: Barriers<I>,
    /// Capture types.
    marker: PhantomData<T>,
}

// ----------------------------------------------------------------------------
// Implementations
// ----------------------------------------------------------------------------

impl<I, T> Stream<I, T>
where
    I: Id + Value,
    T: Value,
{
    /// Selects scopes from the stream using the provided barriers.
    #[inline]
    pub fn select<B>(&self, iter: B) -> Stream<I, Vec<(Key<I>, T)>>
    where
        B: IntoIterator<Item = (Key<I>, Barrier<I>)>,
    {
        let options = Options::default().interest(Interest::Enter);
        let barriers = Barriers::from_iter(iter);
        self.subscribe(
            Subscriber::new(Select { barriers, marker: PhantomData })
                .with_options(options),
        )
    }
}

// ----------------------------------------------------------------------------
// Trait implementations
// ----------------------------------------------------------------------------

impl<I, T> Action<I> for Select<I, T>
where
    I: Id + Value,
    T: Value,
{
    type Inputs = (T,);
    type Output<'a> = Vec<(Key<I>, T)>;

    /// Executes the operator.
    fn execute(&mut self, ctx: Context<I, Self>) -> impl IntoSteps<I, Self> {
        let Binding {
            events,
            scopes,
            inputs,
            mut output,
            ..
        } = ctx.bind();

        // Drive all lifecycle events and notifications into the barrier set.
        for event in events {
            self.barriers.handle(&event);
        }
        for scope in scopes {
            if inputs.contains_key(scope.key()) {
                self.barriers.notify(scope.key());
            }
        }

        // Drain all fulfilled barriers in a single pass.
        self.barriers.drain().map(move |advance| {
            let new_key = advance.scope().clone();
            output.insert(
                new_key.clone(),
                advance
                    .into_iter()
                    .cloned()
                    .map(|key| {
                        let value = inputs.get(&key).expect("invariant");
                        (key, value.clone())
                    })
                    .collect(),
            );
            Scope::from(new_key).done()
        })
    }
}
