📄 src/App/ViewModels/Panels/WeatherPanelViewModel.cs
using System;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.Extensions.DependencyInjection;
using MMirror.Integrations.HomeAssistant;
using MMirror.Integrations.Smhi;

namespace MMirror.App.ViewModels.Panels;

public partial class WeatherPanelViewModel(
    TimeProvider timeProvider,
    SmhiClient smhiClient,
    HomeAssistantClient homeAssistantClient
) : ViewModelBase
{
    private readonly TimeProvider timeProvider = timeProvider;
    private readonly SmhiClient smhiClient = smhiClient;
    private readonly HomeAssistantClient homeAssistantClient = homeAssistantClient;

    private CancellationTokenSource cts = new();

    [ObservableProperty]
    public partial PointForecast? Forecast { get; private set; }

    [ObservableProperty]
    public required partial string LocationName { get; set; }

    public double Latitude { get; set; }
    public double Longitude { get; set; }

    public void SubscribeToUpdates()
    {
        Task.Run(() => FetchLoop(cts.Token));
    }

    private async Task FetchLoop(CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            var newForecastTask = smhiClient.GetPointForecast(Longitude, Latitude, cancellationToken);
            try
            {
                var localTemperature = await homeAssistantClient.GetTemperatures(cancellationToken);
                var newForecast = await newForecastTask;
                Forecast = newForecast with
                {
                    Now = newForecast.Now with
                    {
                        Forecast = newForecast.Now.Forecast with
                        {
                            TemperatureCelsius = localTemperature.OutsideCelsius,
                            InsideTemperatureCelsius = localTemperature.InsideCelsius,
                        },
                    },
                };
            }
            catch
            {
                Forecast = await newForecastTask;
            }

            await Task.Delay(TimeSpan.FromMinutes(15), timeProvider, cancellationToken);
        }
    }

    public void UnsubscribeFromUpdates()
    {
        cts.Cancel();
        cts = new();
    }
}

internal static class WeatherPanelViewModelServiceCollectionExtensions
{
    extension(IServiceCollection services)
    {
        public IServiceCollection AddWeatherPanelViewModel()
        {
            services.AddTransient<WeatherPanelViewModel>();
            return services;
        }
    }
}