001/*
002 * Copyright 2022-2026 Revetware LLC.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package com.soklet;
017
018import org.jspecify.annotations.NonNull;
019
020import javax.annotation.concurrent.ThreadSafe;
021import java.util.List;
022import java.util.Locale;
023
024import static java.util.Objects.requireNonNull;
025
026/**
027 * Immutable, framework-supplied input to a Skills variant selector.
028 *
029 * <p>Only accessible, discoverable registrations are exposed, in their configured
030 * group order. The application-local group key is available without exposing the
031 * unfiltered group. Candidate identity is retained so a selector can return an
032 * exact supplied registration. This context is selection input, not an access
033 * grant or a second localization negotiation.
034 *
035 * @author <a href="https://www.revetkn.com">Mark Allen</a>
036 */
037@ThreadSafe
038public final class McpSkillVariantSelectionContext {
039        @NonNull
040        private final String skillGroupKey;
041        @NonNull
042        private final List<@NonNull McpSkillRegistration> skillRegistrations;
043        @NonNull
044        private final List<Locale.@NonNull LanguageRange> languageRanges;
045
046        private McpSkillVariantSelectionContext(@NonNull String skillGroupKey,
047                        @NonNull List<@NonNull McpSkillRegistration> skillRegistrations,
048                        @NonNull List<Locale.@NonNull LanguageRange> languageRanges) {
049                this.skillGroupKey = requireNonNull(skillGroupKey);
050                this.skillRegistrations = List.copyOf(requireNonNull(skillRegistrations));
051                this.languageRanges = List.copyOf(requireNonNull(languageRanges));
052        }
053
054        @NonNull
055        static McpSkillVariantSelectionContext from(@NonNull String skillGroupKey,
056                        @NonNull List<@NonNull McpSkillRegistration> skillRegistrations,
057                        @NonNull List<Locale.@NonNull LanguageRange> languageRanges) {
058                return new McpSkillVariantSelectionContext(skillGroupKey, skillRegistrations, languageRanges);
059        }
060
061        /** @return exact application-defined, endpoint-local group key */
062        @NonNull
063        public String getSkillGroupKey() { return this.skillGroupKey; }
064
065        /** @return immutable accessible, discoverable candidates in configured group order */
066        @NonNull
067        public List<@NonNull McpSkillRegistration> getSkillRegistrations() { return this.skillRegistrations; }
068
069        /**
070         * Returns the immutable bounded client language preferences and exclusions.
071         * The original parsed order and weights, including zero weights, are retained.
072         * An empty list indicates no express language constraint under the bounded
073         * request parsing rules, not a newly selected or inferred locale.
074         *
075         * @return immutable bounded language ranges
076         */
077        @NonNull
078        public List<Locale.@NonNull LanguageRange> getLanguageRanges() { return this.languageRanges; }
079
080        /** @return diagnostic rendering without group, candidate, or language data */
081        @Override
082        @NonNull
083        public String toString() { return "McpSkillVariantSelectionContext[redacted]"; }
084}