@page "/aiart"

@using OpenAI_API
@using OpenAI_API.Chat
@using OpenAI_API.Models
@using DataAccess.Data
@using DataAccess.Models
@using KIKunstKirstenKlöckner.Data
@using KIKunstKirstenKlöckner.Extensions
@using System.Diagnostics

@inject IJSRuntime JSRuntime
@inject IConfiguration Config
@inject TooltipService TooltipService
@inject NotificationService NotificationService
@inject DialogService DialogService

@inject ImageGenerator ImageGenerator;
@inject BildInfoData BildInfoData;
@inject WunschInfoData WunschInfoData;

@implements IAsyncDisposable

<style>
    .invisible-rectangle {
        width: 100%;
        height: 600px;
        opacity: 0;
        background-color: transparent;
        pointer-events: none;
    }

    .invisible-rectangle-small {
        width: 100%;
        height: 100px;
        opacity: 0;
        background-color: transparent;
        pointer-events: none;
    }
</style>

<PageTitle>Wunschbild Generator</PageTitle>

<section class="about_section" style="background-image: url('images/5KeineAngstvorFehlern2014.jpeg'); background-size: cover; background-repeat: no-repeat; background-blend-mode:lighten">

    <div class="hero_area hero-area-visible">

        <!-- slider section -->
        <section class="slider_section" style="background-image: url('images/3730Kilo2015.jpeg'); background-size: cover; background-repeat: no-repeat; background-blend-mode:lighten">
          <div class="container">
            <div class="row">
              <div class="col-md-6 ">
                <div class="detail_box">
                  <h1>
                    Hier werden <br>
                    deine Wünsche <br>
                    Wirklichkeit
                  </h1>
                  <p>
                    Weiter unten findest du meinen Assistenten. Gebe ihm deinen Wunsch und er wird sich etwas tolles dazu überlegen. Es lohnt sich geduldig zu sein. <br>
                    Scrolle einfach nach unten!
                  </p>
                </div>
              </div>
              <div class="col-lg-5 col-md-6 offset-lg-1">
                <div class="img_content">
                  <div class="img_container">
                    <div id="carouselExampleControls" class="carousel slide" data-ride="carousel">
                      <div class="carousel-inner">
                        @if (_allBildInfos != null)
                        {
                            @foreach (var bildInfo in _allBildInfos)
                            {
                                if (_indexForCarusell == 0)
                                {
                                    <div class="carousel-item active">
                                        <div class="img-box">
                                            <img src="@bildInfo.Dateiname"/>
                                        </div>
                                    </div>
                                }
                                else
                                {
                                    <div class="carousel-item">
                                        <div class="img-box">
                                            <img src="@bildInfo.Dateiname"/>
                                        </div>
                                    </div>
                                }      
                                _indexForCarusell++;
                            }
                        }            
                      </div>
                    </div>
                  </div>
                  <a class="carousel-control-prev" href="#carouselExampleControls" role="button" data-slide="prev">
                    <span class="sr-only">Previous</span>
                  </a>
                  <a class="carousel-control-next" href="#carouselExampleControls" role="button" data-slide="next">
                    <span class="sr-only">Next</span>
                  </a>
                </div>

              </div>
            </div>
          </div>
        </section>
        <!-- end slider section -->
      </div>
    <div class="invisible-rectangle-small"></div>

    <!-- start entry section -->
    <section id="entrySection" class="section-animate-left" @ref="animatedEntrySection">
        <div class="container">

        <RadzenStack Orientation="Orientation.Vertical" AlignItems="AlignItems.Center">
        <h1>Wunschbilder von KI nur für dich</h1>
        <RadzenText TextStyle="TextStyle.H2">Nenne uns deinen Wunsch:</RadzenText>
        <RadzenTextBox @bind-Value=@_userIdea Placeholder="Dein Wunsch"/>
        
        @* Zusätzliche Optionen *@
        <RadzenPanel AllowCollapse="true" Style="width: 500px;" Text="Mehr Optionen">
            <ChildContent>
                <RadzenCard class="rz-mt-4">
                    <RadzenStack Orientation="Orientation.Horizontal"
                                 MouseEnter="@(args => ShowTemperatureTooltip(args))"
                                 MouseLeave="TooltipService.Close"
                                 AlignItems="AlignItems.Center" Wrap="FlexWrap.Wrap">
                        <RadzenText>Temperature:</RadzenText>
                        <RadzenSlider @bind-Value=@_temperature TValue="float"
                                      Step="0.1" Min="0.0m" Max="2.0m">
                        </RadzenSlider>
                        <RadzenText>@_temperature</RadzenText>
                    </RadzenStack>
                    <RadzenStack Orientation="Orientation.Horizontal"
                                    AlignItems="AlignItems.Center" Wrap="FlexWrap.Wrap">
                        <RadzenText>Resolution:</RadzenText>
                        <RadzenStack Orientation="Orientation.Horizontal">
                        <RadzenNumeric ShowUpDown = "false" TValue = "int?" @bind-Value=@_imageWidth />
                        x
                        <RadzenNumeric ShowUpDown = "false" TValue = "int?" @bind-Value=@_imageHeight />
                        </RadzenStack>
                    </RadzenStack>
                    <RadzenRadioButtonList @bind-Value=@_usedGptModel TValue="GptModel" class="mb-5">
                        <Items>
                            <RadzenRadioButtonListItem Text="GPT-3.5 Turbo" Value="GptModel.Gpt3_5_turbo"
                                 MouseEnter="@(args => ShowTooltip("Schnell aber nicht sonderlich clever.", args))"
                                 MouseLeave="TooltipService.Close">
                            </RadzenRadioButtonListItem>
                            <RadzenRadioButtonListItem Text="GPT-4 Turbo" Value="GptModel.Gpt4_turbo"
                                 MouseEnter="@(args => ShowTooltip("Etwas langsamer, aber recht klug.", args))"
                                 MouseLeave="TooltipService.Close">
                            </RadzenRadioButtonListItem>
                            <RadzenRadioButtonListItem Text="GPT-4o" Value="GptModel.Gpt4o"
                                 MouseEnter="@(args => ShowTooltip("Schnell, gut und günstig. Warum gibt es überhaupt eine Auswahlmöglichkeit?", args))"
                                 MouseLeave="TooltipService.Close">
                            </RadzenRadioButtonListItem>
                        </Items>
                    </RadzenRadioButtonList>
                    @* <RadzenRadioButtonList Data="@_imageModels" @bind-Value=@_selectedImageModel TValue="ModelInfo" TextProperty="Name" class="mb-5"> *@
                    <RadzenRadioButtonList Data="@_imageModels" @bind-Value=@_selectedImageModel TValue="ModelInfo" TextProperty="Name" ValueProperty="" class="mb-5">
                        <Items>
                            <RadzenRadioButtonListItem Text="Zufällig" Value="@null" TValue="ModelInfo"
                                 MouseEnter="@(args => ShowTooltip("Für jedes Bild wird zufällig ein Generator ausgewählt.", args))"
                                 MouseLeave="TooltipService.Close"/>
                        </Items>
                    </RadzenRadioButtonList>
                </RadzenCard>
            </ChildContent>
            </RadzenPanel>

            <RadzenButton Visible=@_buttonVisible Click="@OnGenerateButtonClickAsync">Generate</RadzenButton>
            </RadzenStack>
        </div>
    </section>
    <!-- end entry section -->

    <!-- start image section -->
    <section id="imageSection">
        <div class="container">
        @if (_imageSectionVisible)
            {
                <section id="imageSection">
                            <RadzenStack Orientation="Orientation.Vertical" AlignItems="AlignItems.Center">
                        <RadzenText TextStyle="TextStyle.H4">Die Idee, die gemalt wird:</RadzenText>

                        <RadzenCard class="rz-mt-4" Style="width: 800px;">
                            <RadzenText>@_imageIdea</RadzenText>
                        </RadzenCard>

                        <RadzenProgressBarCircular Visible=@_progressVisible ProgressBarStyle="ProgressBarStyle.Secondary" Value="100" ShowValue="false" Mode="ProgressBarMode.Indeterminate" />
                        <RadzenText Visible=@_progressVisible TextStyle="TextStyle.H6" Text=@BusyMessage></RadzenText>

                        <RadzenImage Path=@_imageUrl></RadzenImage>

                        <RadzenText Visible=@_addonsVisible TextStyle="TextStyle.H2">Verändere hier dein Bild durch Worte:</RadzenText>
                        <RadzenTextBox Visible=@_addonsVisible @bind-Value=@_updateRequest Placeholder="z.B. Mehr Farben" />
                        <RadzenButton Visible=@_bothVisible Click=@(async ()=> await UpdateImagesAsync())>Generate</RadzenButton>

                        <RadzenCard>
                            <RadzenRow Style="width:24.5em" Gap="0.5rem" RowGap="0.5rem">
                                <RadzenColumn Size="6">
                                    <FlippingImage ImageUrl="@_imageUrls[0]" HideImage="false"
                                                    Show="@(_imageStates[0] == ImageState.FadeIn)" FlipTo="FlippingImage.FlipDirection.Up"
                                                    Click="() => ShowImageDialog(_imageUrls[0])" />
                                    <RadzenPanel AllowCollapse="true" Collapsed="true" Text="Info">
                                        @_imagePromts[0]
                                        <br />
                                        Model: @_bildInfos[0]?.ImageModel
                                    </RadzenPanel>
                                </RadzenColumn>
                                <RadzenColumn Size="6">
                                    <FlippingImage ImageUrl="@_imageUrls[1]" HideImage="false"
                                                    Show="@(_imageStates[1] == ImageState.FadeIn)" FlipTo="FlippingImage.FlipDirection.Right" FlipDelay="200"
                                                    Click="() => ShowImageDialog(_imageUrls[1])" />
                                    <RadzenPanel AllowCollapse="true" Collapsed="true" Text="Info">
                                        @_imagePromts[1]
                                        <br />
                                        Model: @_bildInfos[1]?.ImageModel
                                    </RadzenPanel>
                                </RadzenColumn>
                                <RadzenColumn Size="6">
                                    <FlippingImage ImageUrl="@_imageUrls[2]" HideImage="false"
                                                    Show="@(_imageStates[2] == ImageState.FadeIn)" FlipTo="FlippingImage.FlipDirection.Left" FlipDelay="600"
                                                    Click="() => ShowImageDialog(_imageUrls[2])" />
                                    <RadzenPanel AllowCollapse="true" Collapsed="true" Text="Info">
                                        @_imagePromts[2]
                                        <br />
                                        Model: @_bildInfos[2]?.ImageModel
                                    </RadzenPanel>
                                </RadzenColumn>
                                <RadzenColumn Size="6">
                                    <FlippingImage ImageUrl="@_imageUrls[3]" HideImage="false"
                                                    Show="@(_imageStates[3] == ImageState.FadeIn)" FlipTo="FlippingImage.FlipDirection.Down" FlipDelay="400"
                                                    Click="() => ShowImageDialog(_imageUrls[3])" />
                                    <RadzenPanel AllowCollapse="true" Collapsed="true" Text="Info">
                                        @_imagePromts[3]
                                        <br />
                                        Model: @_bildInfos[3]?.ImageModel
                                    </RadzenPanel>
                                </RadzenColumn>
                            </RadzenRow>
                        </RadzenCard>
                    </RadzenStack>
                </section>
            }
            @if (!_imageSectionVisible)
            {
                <div class="invisible-rectangle"></div>
            }
        </div>
    </section>
    <!-- end image section -->
    
</section>

@code {
    // Animation stuff
    private int _indexForCarusell = 0;
    private ElementReference animatedEntrySection;
    private List<ElementReference> animatedElements = new List<ElementReference>();

    private Func<Task> removeScrollListener;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            animatedElements.Add(animatedEntrySection);

            removeScrollListener = await JSRuntime.InvokeAsync<Func<Task>>(
            "initHeroAreaScrollAnimation"
        );

            await JSRuntime.InvokeVoidAsync("initScrollAnimations", animatedElements);
        }
    }

    private bool isDisposed = false;

    public async ValueTask DisposeAsync()
    {
        if (!isDisposed)
        {
            if (removeScrollListener != null)
            {
                await removeScrollListener.Invoke();
            }
            isDisposed = true;
        }
    }

    private async Task OnGenerateButtonClickAsync()
    {
        _imageSectionVisible = true;
        StateHasChanged();
        await Task.Delay(100);
        await JSRuntime.InvokeVoidAsync("scrollToElement", "imageSection");
        await GenerateImagesAsync();
    }

    IEnumerable<BildInfoModel>? _allBildInfos;

    enum GptModel
    {
        Gpt3_5_turbo,
        Gpt4_turbo,
        Gpt4o
    }

    /// <summary>
    /// Das ChatGPT Model, das zur Erzeugung der Idee und Bild-Promts verwendet wird.
    /// </summary>
    private GptModel _usedGptModel = GptModel.Gpt3_5_turbo;

    private int maxAddons = 2;
    private int amountOfAddons = 0; // wird später geändert
    private bool _progressVisible = false;
    private bool _buttonVisible = true;
    private bool _addonsVisible = false;
    private bool _bothVisible = false;
    private bool _imageSectionVisible = false;

    public string BusyMessage { get; set; } = "Initial Message";

    private string?[] _imageUrls = new string?[4];
    private string?[] _imagePromts = new string?[4];
    private BildInfoModel?[] _bildInfos = new BildInfoModel?[4];
    private ImageState[] _imageStates = new ImageState[4];

    enum ImageState
    {
        FadeOut,
        FadeIn
    }

    async Task ShowImageDialog(string imageUrl)
    {
        var result = await DialogService.OpenAsync("", ds =>
            @<div>
            <RadzenImage Style="object-fit: contain; width: 100%; height:100%;" Path="@imageUrl"/>
            </div>, new DialogOptions() { CloseDialogOnOverlayClick = true });
    }

    void ShowTooltip(ElementReference elementReference, string text, TooltipOptions? options = null) => TooltipService.Open(elementReference, text, options);

    void ShowTemperatureTooltip(ElementReference elementReference) => TooltipService.Open(elementReference, ds =>
        @<div>
            Gibt an, wie <em>kreativ</em> ChatGPT sein soll.<br/>
        </div>
    ,
        new() { Position = TooltipPosition.Bottom, Duration = null});

    void ShowTooltip(string text, ElementReference elementReference) => TooltipService.Open(elementReference, ds =>
            @<div>@text</div>
        ,
        new() { Position = TooltipPosition.Bottom, Duration = null});

    private string _imageIdea = "";

    private float _temperature = 0.9f; 
    private int? _imageWidth = null; 
    private int? _imageHeight = null; 
    private string _userIdea = "";
    private string _updateRequest = "";
    private OpenAIAPI _openAiApi;
    private Conversation? _conversation;

    private string _basePrompt;
    private string _ideaPrompt;

    private string _imageUrl;

    private WunschInfoModel? _wunschInfo;

    async Task UpdateBusyMessage(string newMessage)
    {
        BusyMessage = newMessage;
        await InvokeAsync(StateHasChanged);
    }

    private string _openAiApiKey = "";

    class ModelInfo
    {
        public string Name { get; set; }
        public string StableDiffusionModel { get; set; }
        public string PromptFormat { get; set; }
    }

    private List<ModelInfo> _imageModels;

    private ModelInfo? _selectedImageModel = null;

    protected override async Task OnInitializedAsync()
    {
        _allBildInfos = (await BildInfoData.GetAllBildInfosAsync()).ToList().PickRandom(10);
        //await InvokeAsync(StateHasChanged);

        _openAiApiKey = Config.GetValue<string>("API:OpenAI");

        _openAiApi = new OpenAIAPI(_openAiApiKey);

        _imageModels = Config.GetSection("ImageModels").Get<List<ModelInfo>>();
        //_selectedImageModel = _imageModels[0];

        await base.OnInitializedAsync();
    }

    ///  <summary>
    /// Setzt alle Felder zurück, die zu einem bestimmten Wunsch gehören.
    /// </summary>
    private void ClearOldGeneration()
    {
        // Bilder verbergen
        for (int i = 0; i < 4; i++)
        {
            _imageStates[i] = ImageState.FadeOut;
            _imagePromts[i] = null;
        }

        _imageIdea = "";
    }

    /// <summary>
    /// Gibt ChatGPT den Wunsch und erlangt die Bild Idee.
    /// </summary>
    /// <returns></returns>
    private async Task RequestImageIdeaAsync()
    {
        string ideaBasePrompt = await File.ReadAllTextAsync($"{Directory.GetCurrentDirectory()}{@"/wwwroot/idea_prompt.txt"}");

        ChatRequest chatRequest = new ChatRequest
        {
            Temperature = _temperature,
                Model = _usedGptModel switch
                {
                    GptModel.Gpt3_5_turbo => Model.ChatGPTTurbo,
                    GptModel.Gpt4_turbo => ChatGPT.GPT4Turbo,
                    GptModel.Gpt4o => ChatGPT.GPT4o,
                    _ => throw new ArgumentOutOfRangeException()
                }
        };

        _conversation = _openAiApi.Chat.CreateConversation(chatRequest);

        // Wunsch an GPT senden und Bild Idee anfordern
        _conversation.AppendUserInput(ideaBasePrompt + " " + _userIdea);
        _imageIdea = await _conversation.GetResponseFromChatbotAsync();
    }
    /// <summary>
    /// Fordert für mehrere Bilder Bild-Prompts an und generiert die dazugehörigen Bilder.
    /// </summary>
    private async Task RequestImagesAsync(WunschInfoModel wunschInfo, string requestImagePrompt)
    {
        // Nachricht mit Bildpromt anfrage senden
        _conversation!.AppendUserInput(requestImagePrompt);

        // Task-Liste, damit wir parallel Prompts anfordern und Bilder generieren können.
        Task[] imagePromts = new Task[4];

        for (int i = 0; i < 4; i++)
        {
            imagePromts[i] = RequestImagePromptAndGenerateImageAsync(i, wunschInfo);
        }

        await Task.WhenAll(imagePromts);
    }

    /// <summary>
    /// Fordert einen Bild Prompt an und generiert ein Bild für diesen.
    /// </summary>
    /// <param name="index">Der Index des Bildes (für UI zeug)</param>
    /// <param name="wunschInfo">Der Wunsch für den ein Bild erzeugt wird.</param>
    private async Task RequestImagePromptAndGenerateImageAsync(int index, WunschInfoModel wunschInfo)
    {
        ModelInfo imageModel = _selectedImageModel ?? _imageModels[Random.Shared.Next(_imageModels.Count)];

        // Bild Prompt von ChatGPT anfordern
        string imagePrompt = await _conversation!.GetResponseFromChatbotAsync();

        // Keywords anhängen um Kirstens Stil zu aktivieren.
        // TODO: Gucken, ob wir dem Watercolor bums brauchen
        imagePrompt = string.Format(imageModel.PromptFormat, imagePrompt);//"kkkk " + imagePrompt;// + " kkkk Watercolor + ink on paper, Pen drawing, wet-on-wet technique, dry-on-dry technique, dabbing technique. ";

        // Debug only: Promt anzeigen
        _imagePromts[index] = imagePrompt;
        await InvokeAsync(StateHasChanged);

        BildInfoModel? bildInfo = await GenerateImageAsync(imagePrompt, imageModel.StableDiffusionModel, wunschInfo);

        // TODO: Fehler im UI anzeigen (zur Zeit bleibt einfach Ladebalken)
        _imageUrls[index] = bildInfo?.Dateiname;
        _bildInfos[index] = bildInfo;
        _imageStates[index] = ImageState.FadeIn;
        await InvokeAsync(StateHasChanged);
    }

    /// <summary>
    /// Erzeugt ein Bild für den gegebenen Prompt und Wunsch.
    /// </summary>
    /// <param name="imagePrompt">Der Bild Prompt</param>
    /// <param name="wunschInfo">Der Wunsch.</param>
    /// <returns>Die URL, falls das Bild generiert wurde; oder null, wenn kein Bild generiert werden konnte.</returns>
    private async Task<BildInfoModel?> GenerateImageAsync(string imagePrompt, string modelName, WunschInfoModel wunschInfo)
    {
        try
        {
            BildInfoModel? bildInfo = await ImageGenerator.GenerateImageAsync(imagePrompt, modelName, wunschInfo, _imageWidth, _imageHeight);

            // Kein Bild -> Fehler
            if (bildInfo == null)
            {
                bool? retry = await DialogService.Confirm(
                    "Leider konnte das Bild nicht gemalt werden. Möchtest du es noch eimal versuchen?",
                    "Ups, ein Fehler ist aufgetreten...",
                    new ConfirmOptions { OkButtonText = "Ja", CancelButtonText = "Nein" });

                if (retry == true)
                {
                    bildInfo = await ImageGenerator.GenerateImageAsync(imagePrompt, modelName, wunschInfo, _imageWidth, _imageHeight);
                }
            }

            return bildInfo;
        }
        catch (Exception e)
        {
            NotificationService.Notify(new NotificationMessage()
                {
                    Summary = "Es ist ein Fehler beim Erzeugen der Bilder aufgetreten, bitte versuche es erneut."
                });
        }

        return null;
    }

    /// <summary>
    /// Generiert Bilder oder aktualisiert sie mit dem neuen Prompt.
    /// </summary>
    private async Task GenerateImagesAsync()
    {
        _progressVisible = true;
        _buttonVisible = false;

        ClearOldGeneration();

        amountOfAddons = maxAddons;
        _addonsVisible = false;
        _bothVisible = _buttonVisible && _addonsVisible;
        await UpdateBusyMessage("Kirstens Assistent zerbricht sich über deine Idee den Kopf...");

        await RequestImageIdeaAsync();

        await UpdateBusyMessage("Kirstens Assistent hat eine Idee! Er wird sie nun malen...");

        _wunschInfo = new()
        {
            BildBeschreibung = _imageIdea,
            BildPrompt = "Individuelle Bild Prompts",
            Datum = DateTime.Now,
            GPTModel = _conversation!.Model,
            Wunsch = _userIdea,
            VorherigerWunsch = null
        };

        try
        {
            await WunschInfoData.AddWunschInfoAsync(_wunschInfo);
        }
        catch (Exception e)
        {
            NotificationService.Notify(new NotificationMessage()
            {
                Summary = "Es ist ein Fehler aufgetreten, bitte versuche es erneut."
            });

            return;
        }

        string requestImagePrompt = await File.ReadAllTextAsync($"{Directory.GetCurrentDirectory()}{@"/wwwroot/image_prompt.txt"}");

        await RequestImagesAsync(_wunschInfo, requestImagePrompt);

        _progressVisible = false;
        _buttonVisible = true;
        if (amountOfAddons > 0)
        {
            _addonsVisible = true;
            _bothVisible = _buttonVisible && _addonsVisible;
            await InvokeAsync(StateHasChanged);
        }
        else
        {
            _addonsVisible = false;
            _bothVisible = false;
        }
    }

    private async Task UpdateImagesAsync()
    {
        Debug.Assert(_conversation != null);

        string updatePrompt = $"Gebe nun einen neuen Prompt unter berücksichtigung vorheriger Anweisungen und passe ihn folgender Maßen an: {_updateRequest}";

        _wunschInfo = new()
        {
            BildBeschreibung = _updateRequest,
            BildPrompt = "Individuelle Bild Prompts",
            Datum = DateTime.Now,
            GPTModel = _conversation.Model,
            Wunsch = _userIdea,
            VorherigerWunsch = _wunschInfo!.Id
        };
        
        try
        {
            await WunschInfoData.AddWunschInfoAsync(_wunschInfo);
        }
        catch (Exception e)
        {
            NotificationService.Notify(new NotificationMessage()
            {
                Summary = "Es ist ein Fehler aufgetreten, bitte versuche es erneut."
            });

            return;
        }

        await RequestImagesAsync(_wunschInfo, updatePrompt);
    }
}
