package org.jabref.logic.ai.preferences;

import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

import org.jabref.logic.ai.chatting.PredefinedChatModelUtil;
import org.jabref.logic.util.strings.StringUtil;
import org.jabref.model.ai.embeddings.PredefinedEmbeddingModel;
import org.jabref.model.ai.llm.AiProvider;
import org.jabref.model.ai.llm.PredefinedChatModel;
import org.jabref.model.ai.pipeline.DocumentSplitterKind;
import org.jabref.model.ai.pipeline.ResponseEngineKind;
import org.jabref.model.ai.summarization.SummarizatorKind;
import org.jabref.model.ai.tokenization.TokenEstimatorKind;

import com.github.javakeyring.Keyring;
import com.github.javakeyring.PasswordAccessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/// AI preferences.
///
/// Quick note about [#aiFeaturesEnabledInitially] and [#aiFeaturesEnabledCurrently]:
/// As per `req~ai.general.enabling.restart~1`, when enabled property is changed, a restart required. This implies that
/// AI processes should start and work only if they were initially enabled. So whenever you want to guard some code from
/// executing only if AI is enabled, please use [#aiFeaturesEnabledInitially] via [#getAiFeaturesEnabled()]`.
public class AiPreferences {
    private static final Logger LOGGER = LoggerFactory.getLogger(AiPreferences.class);

    private static final String KEYRING_AI_SERVICE = "org.jabref.ai";
    private static final String KEYRING_AI_SERVICE_ACCOUNT = "apiKey";

    private static final AiProvider DEFAULT_AI_PROVIDER = AiProvider.OPEN_AI;

    private static final Map<AiProvider, PredefinedChatModel> DEFAULT_CHAT_MODEL = Map.of(
            AiProvider.OPEN_AI, PredefinedChatModel.GPT_4O_MINI,
            AiProvider.MISTRAL_AI, PredefinedChatModel.OPEN_MIXTRAL_8X22B,
            AiProvider.GEMINI, PredefinedChatModel.GEMINI_1_5_FLASH,
            AiProvider.HUGGING_FACE, PredefinedChatModel.BLANK_HUGGING_FACE
    );

    /// Controls whether the AI features should start/work in a JabRef session.
    /// This property is set when JabRef has started, and is not persisted.
    private final BooleanProperty aiFeaturesEnabledInitially;
    /// Controls whether to show the privacy policy banner and whether to request a restart.
    /// This property is persisted and controlled by preferences.
    private final BooleanProperty aiFeaturesEnabledCurrently;
    private final BooleanProperty autoGenerateEmbeddings;
    private final BooleanProperty autoGenerateSummaries;

    private final ObjectProperty<AiProvider> aiProvider;

    private final StringProperty openAiChatModel;
    private final StringProperty mistralAiChatModel;
    private final StringProperty geminiChatModel;
    private final StringProperty huggingFaceChatModel;

    private final BooleanProperty customizeExpertSettings;

    private final StringProperty openAiApiBaseUrl;
    private final StringProperty mistralAiApiBaseUrl;
    private final StringProperty geminiApiBaseUrl;
    private final StringProperty huggingFaceApiBaseUrl;

    private final ObjectProperty<SummarizatorKind> summarizatorKind;
    private final ObjectProperty<TokenEstimatorKind> tokenEstimatorKind;
    private final ObjectProperty<PredefinedEmbeddingModel> embeddingModel;
    private final DoubleProperty temperature;
    private final IntegerProperty contextWindowSize;

    private final ObjectProperty<DocumentSplitterKind> documentSplitterKind;
    private final IntegerProperty documentSplitterChunkSize;
    private final IntegerProperty documentSplitterOverlapSize;

    private final ObjectProperty<ResponseEngineKind> responseEngineKind;
    private final IntegerProperty ragMaxResultsCount;
    private final DoubleProperty ragMinScore;

    private final StringProperty chattingSystemMessageTemplate;
    private final StringProperty chattingUserMessageTemplate;
    private final StringProperty summarizationChunkSystemMessageTemplate;
    private final StringProperty summarizationCombineSystemMessageTemplate;
    private final StringProperty summarizationFullDocumentSystemMessageTemplate;
    private final StringProperty citationParsingSystemMessageTemplate;
    private final StringProperty markdownChatExportTemplate;

    private final BooleanProperty generateFollowUpQuestions;
    private final IntegerProperty followUpQuestionsCount;
    private final StringProperty followUpQuestionsTemplate;

    private AiPreferences() {
        this(
                false,                                                                       // AI disabled
                false,                                                                       // Auto-generate embeddings
                false,                                                                       // Auto-generate summaries
                DEFAULT_AI_PROVIDER,                                                         // AI provider

                DEFAULT_CHAT_MODEL.get(AiProvider.OPEN_AI).getName(),                        // OpenAI chat model
                DEFAULT_CHAT_MODEL.get(AiProvider.MISTRAL_AI).getName(),                     // Mistral AI chat model
                DEFAULT_CHAT_MODEL.get(AiProvider.GEMINI).getName(),                         // Gemini chat model
                DEFAULT_CHAT_MODEL.get(AiProvider.HUGGING_FACE).getName(),                   // HuggingFace chat model

                false,                                                                       // Customize expert settings

                AiProvider.OPEN_AI.getApiUrl(),                                              // OpenAI API base URL
                AiProvider.MISTRAL_AI.getApiUrl(),                                           // Mistral AI API base URL
                AiProvider.GEMINI.getApiUrl(),                                               // Gemini API base URL
                AiProvider.HUGGING_FACE.getApiUrl(),                                         // HuggingFace API base URL

                AiDefaultExpertSettings.SUMMARIZATOR_KIND,                                   // Summarizator kind
                AiDefaultExpertSettings.TOKEN_ESTIMATOR_KIND,                                // Token estimator kind
                AiDefaultExpertSettings.EMBEDDING_MODEL,                                     // Embedding model
                AiDefaultExpertSettings.TEMPERATURE,                                         // Temperature
                PredefinedChatModelUtil.getContextWindowSize(
                        DEFAULT_AI_PROVIDER,
                        DEFAULT_CHAT_MODEL.get(DEFAULT_AI_PROVIDER).getName()),              // Context window size
                AiDefaultExpertSettings.DOCUMENT_SPLITTER_KIND,                              // Document splitter kind
                AiDefaultExpertSettings.DOCUMENT_SPLITTER_CHUNK_SIZE,                        // Document splitter chunk size
                AiDefaultExpertSettings.DOCUMENT_SPLITTER_OVERLAP_SIZE,                      // Document splitter overlap size
                AiDefaultExpertSettings.RESPONSE_ENGINE_KIND,                                // Response engine kind
                AiDefaultExpertSettings.RAG_MAX_RESULTS_COUNT,                               // RAG max results count
                AiDefaultExpertSettings.RAG_MIN_SCORE,                                       // RAG min score

                AiDefaultTemplates.CHATTING_SYSTEM_MESSAGE_TEMPLATE,                         // Chatting system message template
                AiDefaultTemplates.CHATTING_USER_MESSAGE_TEMPLATE,                           // Chatting user message template
                AiDefaultTemplates.SUMMARIZATION_CHUNK_SYSTEM_MESSAGE_TEMPLATE,              // Summarization chunk system message template
                AiDefaultTemplates.SUMMARIZATION_COMBINE_SYSTEM_MESSAGE_TEMPLATE,            // Summarization combine system message template
                AiDefaultTemplates.SUMMARIZATION_FULL_DOCUMENT_SYSTEM_MESSAGE_TEMPLATE,      // Summarization full document system message template
                AiDefaultTemplates.CITATION_PARSING_SYSTEM_MESSAGE_TEMPLATE,                 // Citation parsing system message template
                AiDefaultTemplates.MARKDOWN_CHAT_EXPORT_TEMPLATE,                            // Markdown chat export template

                true,                                                                        // Generate follow-up questions
                3,                                                                           // Follow-up questions count
                AiDefaultTemplates.FOLLOW_UP_QUESTIONS_TEMPLATE                              // Follow-up questions template
        );
    }

    public AiPreferences(
            boolean aiFeaturesEnabledCurrently,
            boolean autoGenerateEmbeddings,
            boolean autoGenerateSummaries,
            AiProvider aiProvider,
            String openAiChatModel,
            String mistralAiChatModel,
            String geminiChatModel,
            String huggingFaceChatModel,
            boolean customizeExpertSettings,
            String openAiApiBaseUrl,
            String mistralAiApiBaseUrl,
            String geminiApiBaseUrl,
            String huggingFaceApiBaseUrl,
            SummarizatorKind summarizatorKind,
            TokenEstimatorKind tokenEstimatorKind,
            PredefinedEmbeddingModel embeddingModel,
            double temperature,
            int contextWindowSize,
            DocumentSplitterKind documentSplitterKind,
            int documentSplitterChunkSize,
            int documentSplitterOverlapSize,
            ResponseEngineKind responseEngineKind,
            int ragMaxResultsCount,
            double ragMinScore,
            String chattingSystemMessageTemplate,
            String chattingUserMessageTemplate,
            String summarizationChunkSystemMessageTemplate,
            String summarizationCombineSystemMessageTemplate,
            String summarizationFullDocumentSystemMessageTemplate,
            String citationParsingSystemMessageTemplate,
            String markdownChatExportTemplate,
            boolean generateFollowUpQuestions,
            int followUpQuestionsCount,
            String followUpQuestionsTemplate
    ) {
        this.aiFeaturesEnabledInitially = new SimpleBooleanProperty(aiFeaturesEnabledCurrently);
        this.aiFeaturesEnabledCurrently = new SimpleBooleanProperty(aiFeaturesEnabledCurrently);
        this.autoGenerateEmbeddings = new SimpleBooleanProperty(autoGenerateEmbeddings);
        this.autoGenerateSummaries = new SimpleBooleanProperty(autoGenerateSummaries);

        this.aiProvider = new SimpleObjectProperty<>(aiProvider);

        this.openAiChatModel = new SimpleStringProperty(openAiChatModel);
        this.mistralAiChatModel = new SimpleStringProperty(mistralAiChatModel);
        this.geminiChatModel = new SimpleStringProperty(geminiChatModel);
        this.huggingFaceChatModel = new SimpleStringProperty(huggingFaceChatModel);

        this.customizeExpertSettings = new SimpleBooleanProperty(customizeExpertSettings);

        this.openAiApiBaseUrl = new SimpleStringProperty(openAiApiBaseUrl);
        this.mistralAiApiBaseUrl = new SimpleStringProperty(mistralAiApiBaseUrl);
        this.geminiApiBaseUrl = new SimpleStringProperty(geminiApiBaseUrl);
        this.huggingFaceApiBaseUrl = new SimpleStringProperty(huggingFaceApiBaseUrl);

        this.summarizatorKind = new SimpleObjectProperty<>(summarizatorKind);
        this.tokenEstimatorKind = new SimpleObjectProperty<>(tokenEstimatorKind);
        this.embeddingModel = new SimpleObjectProperty<>(embeddingModel);
        this.temperature = new SimpleDoubleProperty(temperature);
        this.contextWindowSize = new SimpleIntegerProperty(contextWindowSize);

        this.documentSplitterKind = new SimpleObjectProperty<>(documentSplitterKind);
        this.documentSplitterChunkSize = new SimpleIntegerProperty(documentSplitterChunkSize);
        this.documentSplitterOverlapSize = new SimpleIntegerProperty(documentSplitterOverlapSize);

        this.responseEngineKind = new SimpleObjectProperty<>(responseEngineKind);
        this.ragMaxResultsCount = new SimpleIntegerProperty(ragMaxResultsCount);
        this.ragMinScore = new SimpleDoubleProperty(ragMinScore);

        this.chattingSystemMessageTemplate = new SimpleStringProperty(chattingSystemMessageTemplate);
        this.chattingUserMessageTemplate = new SimpleStringProperty(chattingUserMessageTemplate);
        this.summarizationChunkSystemMessageTemplate = new SimpleStringProperty(summarizationChunkSystemMessageTemplate);
        this.summarizationCombineSystemMessageTemplate = new SimpleStringProperty(summarizationCombineSystemMessageTemplate);
        this.summarizationFullDocumentSystemMessageTemplate = new SimpleStringProperty(summarizationFullDocumentSystemMessageTemplate);
        this.citationParsingSystemMessageTemplate = new SimpleStringProperty(citationParsingSystemMessageTemplate);
        this.markdownChatExportTemplate = new SimpleStringProperty(markdownChatExportTemplate);

        this.generateFollowUpQuestions = new SimpleBooleanProperty(generateFollowUpQuestions);
        this.followUpQuestionsCount = new SimpleIntegerProperty(followUpQuestionsCount);
        this.followUpQuestionsTemplate = new SimpleStringProperty(followUpQuestionsTemplate);

        // This listener is needed for the following scenario:
        //
        // 1. AI features initially are enabled.
        // 2. User turns off AI features.
        // 3. User sees the privacy notice pane, and again enables AI features.
        //
        // If there was no such listener as written below, AI features would continue to work,
        // however, between steps 2 and 3, the user could change a lot in the running JabRef
        // instance, such as opening many libraries, which then wouldn't get tracked by the AI.
        // As a result, it is better to set [#aiFeaturesEnabledInitially] to just `false`.
        this.aiFeaturesEnabledCurrently.addListener((_, _, newValue) -> {
            if (!newValue) {
                this.aiFeaturesEnabledInitially.set(false);
            }
        });
    }

    public static AiPreferences getDefault() {
        return new AiPreferences();
    }

    /// Creates an independent copy of `other`, e.g. as a dialog-scoped working copy that is
    /// edited freely and flushed back via [#copyFrom(AiPreferences)] on save.
    public static AiPreferences copyOf(AiPreferences other) {
        AiPreferences copy = new AiPreferences();
        copy.copyFrom(other);
        return copy;
    }

    /// Copies all persisted, user-editable values from `other` into this instance.
    ///
    /// [#aiFeaturesEnabledInitially] is deliberately not copied: it captures the state at JabRef
    /// startup and belongs to the instance's own session, not to the copied values.
    public void copyFrom(AiPreferences other) {
        setAiFeaturesEnabledCurrently(other.getAiFeaturesEnabledCurrently());
        setAutoGenerateEmbeddings(other.getAutoGenerateEmbeddings());
        setAutoGenerateSummaries(other.getAutoGenerateSummaries());

        setAiProvider(other.getAiProvider());

        setOpenAiChatModel(other.getOpenAiChatModel());
        setMistralAiChatModel(other.getMistralAiChatModel());
        setGeminiChatModel(other.getGeminiChatModel());
        setHuggingFaceChatModel(other.getHuggingFaceChatModel());

        setCustomizeExpertSettings(other.getCustomizeExpertSettings());

        setOpenAiApiBaseUrl(other.getOpenAiApiBaseUrl());
        setMistralAiApiBaseUrl(other.getMistralAiApiBaseUrl());
        setGeminiApiBaseUrl(other.getGeminiApiBaseUrl());
        setHuggingFaceApiBaseUrl(other.getHuggingFaceApiBaseUrl());

        setSummarizatorKind(other.getSummarizatorKind());
        setTokenEstimatorKind(other.getTokenEstimatorKind());
        setEmbeddingModel(other.getEmbeddingModel());
        setTemperature(other.getTemperature());
        setContextWindowSize(other.getContextWindowSize());

        setDocumentSplitterKind(other.getDocumentSplitterKind());
        setDocumentSplitterChunkSize(other.getDocumentSplitterChunkSize());
        setDocumentSplitterOverlapSize(other.getDocumentSplitterOverlapSize());

        setResponseEngineKind(other.getResponseEngineKind());
        setRagMaxResultsCount(other.getRagMaxResultsCount());
        setRagMinScore(other.getRagMinScore());

        setChattingSystemMessageTemplate(other.getChattingSystemMessageTemplate());
        setChattingUserMessageTemplate(other.getChattingUserMessageTemplate());
        setSummarizationChunkSystemMessageTemplate(other.getSummarizationChunkSystemMessageTemplate());
        setSummarizationCombineSystemMessageTemplate(other.getSummarizationCombineSystemMessageTemplate());
        setSummarizationFullDocumentSystemMessageTemplate(other.getSummarizationFullDocumentSystemMessageTemplate());
        setCitationParsingSystemMessageTemplate(other.getCitationParsingSystemMessageTemplate());
        setMarkdownChatExportTemplate(other.getMarkdownChatExportTemplate());

        setGenerateFollowUpQuestions(other.getGenerateFollowUpQuestions());
        setFollowUpQuestionsCount(other.getFollowUpQuestionsCount());
        setFollowUpQuestionsTemplate(other.getFollowUpQuestionsTemplate());
    }

    public String getApiKeyForAiProvider(AiProvider aiProvider) {
        try (final Keyring keyring = Keyring.create()) {
            return keyring.getPassword(KEYRING_AI_SERVICE, KEYRING_AI_SERVICE_ACCOUNT + "-" + aiProvider.name());
        } catch (PasswordAccessException e) {
            LOGGER.debug("No API key stored for provider {}. Returning an empty string", aiProvider.name());
            return "";
        } catch (Exception e) {
            LOGGER.warn("JabRef could not open keyring for retrieving {} API token", aiProvider.name(), e);
            return "";
        }
    }

    public void storeAiApiKeyInKeyring(AiProvider aiProvider, String newKey) {
        try (final Keyring keyring = Keyring.create()) {
            if (StringUtil.isNullOrEmpty(newKey)) {
                try {
                    keyring.deletePassword(KEYRING_AI_SERVICE, KEYRING_AI_SERVICE_ACCOUNT + "-" + aiProvider.name());
                } catch (PasswordAccessException ex) {
                    LOGGER.debug("API key for provider {} not stored in keyring. JabRef does not store an empty key.", aiProvider.name());
                }
            } else {
                keyring.setPassword(KEYRING_AI_SERVICE, KEYRING_AI_SERVICE_ACCOUNT + "-" + aiProvider.name(), newKey);
            }
        } catch (Exception e) {
            LOGGER.warn("JabRef could not open keyring for storing {} API token", aiProvider.name(), e);
        }
    }

    public BooleanBinding restartNeededBinding() {
        return (aiFeaturesEnabledProperty().not().and(aiFeaturesEnabledCurrentlyProperty()))
                .or(aiFeaturesEnabledProperty().and(aiFeaturesEnabledCurrentlyProperty().not()));
    }

    public ReadOnlyBooleanProperty aiFeaturesEnabledProperty() {
        return aiFeaturesEnabledInitially;
    }

    public boolean getAiFeaturesEnabled() {
        return aiFeaturesEnabledInitially.get();
    }

    public BooleanProperty aiFeaturesEnabledCurrentlyProperty() {
        return aiFeaturesEnabledCurrently;
    }

    public boolean getAiFeaturesEnabledCurrently() {
        return aiFeaturesEnabledCurrently.get();
    }

    public void setAiFeaturesEnabledCurrently(boolean aiFeaturesEnabledCurrently) {
        this.aiFeaturesEnabledCurrently.set(aiFeaturesEnabledCurrently);
    }

    public BooleanProperty autoGenerateEmbeddingsProperty() {
        return autoGenerateEmbeddings;
    }

    public boolean getAutoGenerateEmbeddings() {
        return autoGenerateEmbeddings.get();
    }

    public void setAutoGenerateEmbeddings(boolean autoGenerateEmbeddings) {
        this.autoGenerateEmbeddings.set(autoGenerateEmbeddings);
    }

    public BooleanProperty autoGenerateSummariesProperty() {
        return autoGenerateSummaries;
    }

    public boolean getAutoGenerateSummaries() {
        return autoGenerateSummaries.get();
    }

    public void setAutoGenerateSummaries(boolean autoGenerateSummaries) {
        this.autoGenerateSummaries.set(autoGenerateSummaries);
    }

    public ObjectProperty<AiProvider> aiProviderProperty() {
        return aiProvider;
    }

    public AiProvider getAiProvider() {
        return aiProvider.get();
    }

    public void setAiProvider(AiProvider aiProvider) {
        this.aiProvider.set(aiProvider);
    }

    public StringProperty openAiChatModelProperty() {
        return openAiChatModel;
    }

    public String getOpenAiChatModel() {
        return openAiChatModel.get();
    }

    public void setOpenAiChatModel(String openAiChatModel) {
        this.openAiChatModel.set(openAiChatModel);
    }

    public StringProperty mistralAiChatModelProperty() {
        return mistralAiChatModel;
    }

    public String getMistralAiChatModel() {
        return mistralAiChatModel.get();
    }

    public void setMistralAiChatModel(String mistralAiChatModel) {
        this.mistralAiChatModel.set(mistralAiChatModel);
    }

    public StringProperty geminiChatModelProperty() {
        return geminiChatModel;
    }

    public String getGeminiChatModel() {
        return geminiChatModel.get();
    }

    public void setGeminiChatModel(String geminiChatModel) {
        this.geminiChatModel.set(geminiChatModel);
    }

    public StringProperty huggingFaceChatModelProperty() {
        return huggingFaceChatModel;
    }

    public String getHuggingFaceChatModel() {
        return huggingFaceChatModel.get();
    }

    public void setHuggingFaceChatModel(String huggingFaceChatModel) {
        this.huggingFaceChatModel.set(huggingFaceChatModel);
    }

    public BooleanProperty customizeExpertSettingsProperty() {
        return customizeExpertSettings;
    }

    public boolean getCustomizeExpertSettings() {
        return customizeExpertSettings.get();
    }

    public void setCustomizeExpertSettings(boolean customizeExpertSettings) {
        this.customizeExpertSettings.set(customizeExpertSettings);
    }

    public ObjectProperty<SummarizatorKind> summarizatorKindProperty() {
        return summarizatorKind;
    }

    public SummarizatorKind getSummarizatorKind() {
        return summarizatorKind.get();
    }

    public void setSummarizatorKind(SummarizatorKind summarizatorKind) {
        this.summarizatorKind.set(summarizatorKind);
    }

    public ObjectProperty<TokenEstimatorKind> tokenEstimatorKindProperty() {
        return tokenEstimatorKind;
    }

    public TokenEstimatorKind getTokenEstimatorKind() {
        return tokenEstimatorKind.get();
    }

    public void setTokenEstimatorKind(TokenEstimatorKind tokenEstimatorKind) {
        this.tokenEstimatorKind.set(tokenEstimatorKind);
    }

    public ObjectProperty<PredefinedEmbeddingModel> embeddingModelProperty() {
        return embeddingModel;
    }

    public PredefinedEmbeddingModel getEmbeddingModel() {
        if (getCustomizeExpertSettings()) {
            return embeddingModel.get();
        } else {
            return AiDefaultExpertSettings.EMBEDDING_MODEL;
        }
    }

    public void setEmbeddingModel(PredefinedEmbeddingModel embeddingModel) {
        this.embeddingModel.set(embeddingModel);
    }

    public StringProperty openAiApiBaseUrlProperty() {
        return openAiApiBaseUrl;
    }

    public String getOpenAiApiBaseUrl() {
        return openAiApiBaseUrl.get();
    }

    public void setOpenAiApiBaseUrl(String openAiApiBaseUrl) {
        this.openAiApiBaseUrl.set(openAiApiBaseUrl);
    }

    public StringProperty mistralAiApiBaseUrlProperty() {
        return mistralAiApiBaseUrl;
    }

    public String getMistralAiApiBaseUrl() {
        return mistralAiApiBaseUrl.get();
    }

    public void setMistralAiApiBaseUrl(String mistralAiApiBaseUrl) {
        this.mistralAiApiBaseUrl.set(mistralAiApiBaseUrl);
    }

    public StringProperty geminiApiBaseUrlProperty() {
        return geminiApiBaseUrl;
    }

    public String getGeminiApiBaseUrl() {
        return geminiApiBaseUrl.get();
    }

    public void setGeminiApiBaseUrl(String geminiApiBaseUrl) {
        this.geminiApiBaseUrl.set(geminiApiBaseUrl);
    }

    public StringProperty huggingFaceApiBaseUrlProperty() {
        return huggingFaceApiBaseUrl;
    }

    public String getHuggingFaceApiBaseUrl() {
        return huggingFaceApiBaseUrl.get();
    }

    public void setHuggingFaceApiBaseUrl(String huggingFaceApiBaseUrl) {
        this.huggingFaceApiBaseUrl.set(huggingFaceApiBaseUrl);
    }

    public DoubleProperty temperatureProperty() {
        return temperature;
    }

    public double getTemperature() {
        if (getCustomizeExpertSettings()) {
            return temperature.get();
        } else {
            return AiDefaultExpertSettings.TEMPERATURE;
        }
    }

    public void setTemperature(double temperature) {
        this.temperature.set(temperature);
    }

    public IntegerProperty contextWindowSizeProperty() {
        return contextWindowSize;
    }

    public int getContextWindowSize() {
        if (getCustomizeExpertSettings()) {
            return contextWindowSize.get();
        } else {
            return switch (aiProvider.get()) {
                case OPEN_AI ->
                        PredefinedChatModelUtil.getContextWindowSize(AiProvider.OPEN_AI, openAiChatModel.get());
                case MISTRAL_AI ->
                        PredefinedChatModelUtil.getContextWindowSize(AiProvider.MISTRAL_AI, mistralAiChatModel.get());
                case HUGGING_FACE ->
                        PredefinedChatModelUtil.getContextWindowSize(AiProvider.HUGGING_FACE, huggingFaceChatModel.get());
                case GEMINI ->
                        PredefinedChatModelUtil.getContextWindowSize(AiProvider.GEMINI, geminiChatModel.get());
            };
        }
    }

    public void setContextWindowSize(int contextWindowSize) {
        this.contextWindowSize.set(contextWindowSize);
    }

    public ObjectProperty<DocumentSplitterKind> documentSplitterKindProperty() {
        return documentSplitterKind;
    }

    public DocumentSplitterKind getDocumentSplitterKind() {
        return documentSplitterKind.get();
    }

    public void setDocumentSplitterKind(DocumentSplitterKind documentSplitterKind) {
        this.documentSplitterKind.set(documentSplitterKind);
    }

    public IntegerProperty documentSplitterChunkSizeProperty() {
        return documentSplitterChunkSize;
    }

    public int getDocumentSplitterChunkSize() {
        if (getCustomizeExpertSettings()) {
            return documentSplitterChunkSize.get();
        } else {
            return AiDefaultExpertSettings.DOCUMENT_SPLITTER_CHUNK_SIZE;
        }
    }

    public void setDocumentSplitterChunkSize(int documentSplitterChunkSize) {
        this.documentSplitterChunkSize.set(documentSplitterChunkSize);
    }

    public IntegerProperty documentSplitterOverlapSizeProperty() {
        return documentSplitterOverlapSize;
    }

    public int getDocumentSplitterOverlapSize() {
        if (getCustomizeExpertSettings()) {
            return documentSplitterOverlapSize.get();
        } else {
            return AiDefaultExpertSettings.DOCUMENT_SPLITTER_OVERLAP_SIZE;
        }
    }

    public void setDocumentSplitterOverlapSize(int documentSplitterOverlapSize) {
        this.documentSplitterOverlapSize.set(documentSplitterOverlapSize);
    }

    public ObjectProperty<ResponseEngineKind> responseEngineKindProperty() {
        return responseEngineKind;
    }

    public ResponseEngineKind getResponseEngineKind() {
        return responseEngineKind.get();
    }

    public void setResponseEngineKind(ResponseEngineKind responseEngineKind) {
        this.responseEngineKind.set(responseEngineKind);
    }

    public IntegerProperty ragMaxResultsCountProperty() {
        return ragMaxResultsCount;
    }

    public int getRagMaxResultsCount() {
        if (getCustomizeExpertSettings()) {
            return ragMaxResultsCount.get();
        } else {
            return AiDefaultExpertSettings.RAG_MAX_RESULTS_COUNT;
        }
    }

    public void setRagMaxResultsCount(int ragMaxResultsCount) {
        this.ragMaxResultsCount.set(ragMaxResultsCount);
    }

    public DoubleProperty ragMinScoreProperty() {
        return ragMinScore;
    }

    public double getRagMinScore() {
        if (getCustomizeExpertSettings()) {
            return ragMinScore.get();
        } else {
            return AiDefaultExpertSettings.RAG_MIN_SCORE;
        }
    }

    public void setRagMinScore(double ragMinScore) {
        this.ragMinScore.set(ragMinScore);
    }

    public List<Property<?>> getEmbeddingsProperties() {
        return List.of(
                embeddingModel,
                documentSplitterKind,
                documentSplitterChunkSize,
                documentSplitterOverlapSize
        );
    }

    private List<Property<?>> getChatModelNamesProperties() {
        return List.of(openAiChatModel, mistralAiChatModel, huggingFaceChatModel, geminiChatModel);
    }

    private List<Property<?>> getApiBaseUrlsProperties() {
        return List.of(openAiApiBaseUrl, mistralAiApiBaseUrl, huggingFaceApiBaseUrl, geminiApiBaseUrl);
    }

    public List<Property<?>> getChatProperties() {
        return (List<Property<?>>) Stream.of(
                List.of(aiFeaturesEnabledCurrently, aiProvider, customizeExpertSettings, temperature, contextWindowSize, tokenEstimatorKind),
                getChatModelNamesProperties(),
                getApiBaseUrlsProperties()
        ).flatMap(List::stream).toList();
    }

    public List<? extends Property<?>> getSummarizatorProperties() {
        return Stream.of(
                List.of(aiFeaturesEnabledCurrently, customizeExpertSettings, summarizatorKind),
                List.of(summarizationChunkSystemMessageTemplate, summarizationCombineSystemMessageTemplate, summarizationFullDocumentSystemMessageTemplate)
        ).flatMap(List::stream).toList();
    }

    public List<? extends Property<?>> getResponseEngineProperties() {
        return List.of(
                aiFeaturesEnabledCurrently,
                embeddingModel,
                customizeExpertSettings,
                responseEngineKind,
                ragMaxResultsCount,
                ragMinScore
        );
    }

    public List<? extends Property<?>> getDocumentSplitterProperties() {
        return List.of(
                aiFeaturesEnabledCurrently,
                customizeExpertSettings,
                documentSplitterKind,
                documentSplitterChunkSize,
                documentSplitterOverlapSize
        );
    }

    public String getSelectedChatModel() {
        return switch (aiProvider.get()) {
            case OPEN_AI ->
                    openAiChatModel.get();
            case MISTRAL_AI ->
                    mistralAiChatModel.get();
            case HUGGING_FACE ->
                    huggingFaceChatModel.get();
            case GEMINI ->
                    geminiChatModel.get();
        };
    }

    public String getSelectedApiBaseUrl() {
        if (customizeExpertSettings.get()) {
            return switch (aiProvider.get()) {
                case OPEN_AI ->
                        openAiApiBaseUrl.get();
                case MISTRAL_AI ->
                        mistralAiApiBaseUrl.get();
                case HUGGING_FACE ->
                        huggingFaceApiBaseUrl.get();
                case GEMINI ->
                        geminiApiBaseUrl.get();
            };
        } else {
            return aiProvider.get().getApiUrl();
        }
    }

    public StringProperty chattingSystemMessageTemplateProperty() {
        return chattingSystemMessageTemplate;
    }

    public String getChattingSystemMessageTemplate() {
        return chattingSystemMessageTemplate.get();
    }

    public void setChattingSystemMessageTemplate(String template) {
        chattingSystemMessageTemplate.set(template);
    }

    public StringProperty chattingUserMessageTemplateProperty() {
        return chattingUserMessageTemplate;
    }

    public String getChattingUserMessageTemplate() {
        return chattingUserMessageTemplate.get();
    }

    public void setChattingUserMessageTemplate(String template) {
        chattingUserMessageTemplate.set(template);
    }

    public StringProperty summarizationChunkSystemMessageTemplateProperty() {
        return summarizationChunkSystemMessageTemplate;
    }

    public String getSummarizationChunkSystemMessageTemplate() {
        return summarizationChunkSystemMessageTemplate.get();
    }

    public void setSummarizationChunkSystemMessageTemplate(String template) {
        summarizationChunkSystemMessageTemplate.set(template);
    }

    public StringProperty summarizationCombineSystemMessageTemplateProperty() {
        return summarizationCombineSystemMessageTemplate;
    }

    public String getSummarizationCombineSystemMessageTemplate() {
        return summarizationCombineSystemMessageTemplate.get();
    }

    public void setSummarizationCombineSystemMessageTemplate(String template) {
        summarizationCombineSystemMessageTemplate.set(template);
    }

    public StringProperty summarizationFullDocumentSystemMessageTemplateProperty() {
        return summarizationFullDocumentSystemMessageTemplate;
    }

    public String getSummarizationFullDocumentSystemMessageTemplate() {
        return summarizationFullDocumentSystemMessageTemplate.get();
    }

    public void setSummarizationFullDocumentSystemMessageTemplate(String template) {
        summarizationFullDocumentSystemMessageTemplate.set(template);
    }

    public StringProperty citationParsingSystemMessageTemplateProperty() {
        return citationParsingSystemMessageTemplate;
    }

    public String getCitationParsingSystemMessageTemplate() {
        return citationParsingSystemMessageTemplate.get();
    }

    public void setCitationParsingSystemMessageTemplate(String template) {
        citationParsingSystemMessageTemplate.set(template);
    }

    public StringProperty markdownChatExportTemplateProperty() {
        return markdownChatExportTemplate;
    }

    public String getMarkdownChatExportTemplate() {
        return markdownChatExportTemplate.get();
    }

    public void setMarkdownChatExportTemplate(String template) {
        markdownChatExportTemplate.set(template);
    }

    public BooleanProperty generateFollowUpQuestionsProperty() {
        return generateFollowUpQuestions;
    }

    public boolean getGenerateFollowUpQuestions() {
        return generateFollowUpQuestions.get();
    }

    public void setGenerateFollowUpQuestions(boolean generateFollowUpQuestions) {
        this.generateFollowUpQuestions.set(generateFollowUpQuestions);
    }

    public IntegerProperty followUpQuestionsCountProperty() {
        return followUpQuestionsCount;
    }

    public int getFollowUpQuestionsCount() {
        return followUpQuestionsCount.get();
    }

    public void setFollowUpQuestionsCount(int followUpQuestionsCount) {
        this.followUpQuestionsCount.set(followUpQuestionsCount);
    }

    public StringProperty followUpQuestionsTemplateProperty() {
        return followUpQuestionsTemplate;
    }

    public String getFollowUpQuestionsTemplate() {
        return followUpQuestionsTemplate.get();
    }

    public void setFollowUpQuestionsTemplate(String followUpQuestionsTemplate) {
        this.followUpQuestionsTemplate.set(followUpQuestionsTemplate);
    }
}
