using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Platform.Storage;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Minint.Core.Services;
using Minint.Core.Services.Impl;
using Minint.Infrastructure.Export;
using Minint.Infrastructure.Serialization;
using Minint.Views;

namespace Minint.ViewModels;

public partial class MainWindowViewModel : ViewModelBase
{
    private readonly MinintSerializer _serializer = new();
    private readonly IImageEffectsService _effects = new ImageEffectsService();
    private readonly IPatternGenerator _patternGen = new PatternGenerator();
    private readonly IBmpExporter _bmpExporter = new BmpExporter();
    private readonly IGifExporter _gifExporter = new GifExporter();
    private readonly ICompositor _compositor = new Compositor();

    private static readonly FilePickerFileType MinintFileType = new("Minint Files")
    {
        Patterns = ["*.minint"],
    };

    private static readonly FilePickerFileType BmpFileType = new("BMP Image")
    {
        Patterns = ["*.bmp"],
    };

    private static readonly FilePickerFileType GifFileType = new("GIF Animation")
    {
        Patterns = ["*.gif"],
    };

    [ObservableProperty]
    private EditorViewModel _editor = new();

    [ObservableProperty]
    private string _statusText = "Ready";

    public TopLevel? Owner { get; set; }

    #region File commands

    [RelayCommand]
    private async Task NewFileAsync()
    {
        if (Owner is not Window window) return;

        var dialog = new NewContainerDialog();
        var result = await dialog.ShowDialog<bool?>(window);
        if (result != true) return;

        int w = dialog.CanvasWidth;
        int h = dialog.CanvasHeight;
        Editor.NewContainer(w, h);
        StatusText = $"New {w}×{h} container created.";
    }

    [RelayCommand]
    private async Task OpenFileAsync()
    {
        if (Owner?.StorageProvider is not { } sp) return;

        var files = await sp.OpenFilePickerAsync(new FilePickerOpenOptions
        {
            Title = "Open .minint file",
            FileTypeFilter = [MinintFileType],
            AllowMultiple = false,
        });

        if (files.Count == 0) return;

        var file = files[0];
        try
        {
            await using var stream = await file.OpenReadAsync();
            var container = _serializer.Read(stream);
            var path = file.TryGetLocalPath();
            Editor.LoadContainer(container, path);
            StatusText = $"Opened {file.Name}";
        }
        catch (Exception ex)
        {
            StatusText = $"Error opening file: {ex.Message}";
        }
    }

    [RelayCommand]
    private async Task SaveFileAsync()
    {
        if (Editor.Container is null) return;

        if (Editor.FilePath is not null)
            await SaveToPathAsync(Editor.FilePath);
        else
            await SaveFileAsAsync();
    }

    [RelayCommand]
    private async Task SaveFileAsAsync()
    {
        if (Owner?.StorageProvider is not { } sp || Editor.Container is null) return;

        var file = await sp.SaveFilePickerAsync(new FilePickerSaveOptions
        {
            Title = "Save .minint file",
            DefaultExtension = "minint",
            FileTypeChoices = [MinintFileType],
            SuggestedFileName = Editor.FilePath is not null
                ? Path.GetFileName(Editor.FilePath) : "untitled.minint",
        });

        if (file is null) return;

        var path = file.TryGetLocalPath();
        if (path is null)
        {
            StatusText = "Error: could not resolve file path.";
            return;
        }

        await SaveToPathAsync(path);
    }

    private async Task SaveToPathAsync(string path)
    {
        try
        {
            await using var fs = File.Create(path);
            _serializer.Write(fs, Editor.Container!);
            Editor.FilePath = path;
            StatusText = $"Saved {Path.GetFileName(path)}";
        }
        catch (Exception ex)
        {
            StatusText = $"Error saving file: {ex.Message}";
        }
    }

    #endregion

    #region Effects (A1, A2)

    [RelayCommand]
    private async Task ApplyContrastAsync()
    {
        if (Editor.ActiveDocument is null || Owner is not Window window) return;

        var dialog = new ContrastDialog();
        var result = await dialog.ShowDialog<bool?>(window);
        if (result != true) return;

        _effects.ApplyContrast(Editor.ActiveDocument, dialog.Factor);
        Editor.RefreshCanvas();
        StatusText = $"Contrast ×{dialog.Factor:F1} applied.";
    }

    [RelayCommand]
    private void ApplyGrayscale()
    {
        if (Editor.ActiveDocument is null) return;

        _effects.ApplyGrayscale(Editor.ActiveDocument);
        Editor.RefreshCanvas();
        StatusText = "Grayscale applied.";
    }

    #endregion

    #region Pattern generation (Б4)

    [RelayCommand]
    private async Task GeneratePatternAsync()
    {
        if (Editor.Container is null || Owner is not Window window) return;

        var dialog = new PatternDialog();
        var result = await dialog.ShowDialog<bool?>(window);
        if (result != true) return;

        try
        {
            var doc = _patternGen.Generate(
                dialog.SelectedPattern,
                Editor.Container.Width,
                Editor.Container.Height,
                [dialog.PatternColor1, dialog.PatternColor2],
                dialog.PatternParam1,
                dialog.PatternParam2);

            Editor.Container.Documents.Add(doc);
            Editor.SyncAfterExternalChange();
            Editor.SelectDocument(doc);
            StatusText = $"Pattern '{dialog.SelectedPattern}' generated.";
        }
        catch (Exception ex)
        {
            StatusText = $"Pattern generation failed: {ex.Message}";
        }
    }

    #endregion

    #region Export (BMP / GIF)

    [RelayCommand]
    private async Task ExportBmpAsync()
    {
        if (Editor.Container is null || Editor.ActiveDocument is null) return;
        if (Owner?.StorageProvider is not { } sp) return;

        var file = await sp.SaveFilePickerAsync(new FilePickerSaveOptions
        {
            Title = "Export document as BMP",
            DefaultExtension = "bmp",
            FileTypeChoices = [BmpFileType],
            SuggestedFileName = $"{Editor.ActiveDocument.Name}.bmp",
        });
        if (file is null) return;

        var path = file.TryGetLocalPath();
        if (path is null) { StatusText = "Error: could not resolve file path."; return; }

        try
        {
            int w = Editor.Container.Width, h = Editor.Container.Height;
            uint[] argb = _compositor.Composite(Editor.ActiveDocument, w, h);
            await using var fs = File.Create(path);
            _bmpExporter.Export(fs, argb, w, h);
            StatusText = $"Exported BMP: {Path.GetFileName(path)}";
        }
        catch (Exception ex)
        {
            StatusText = $"BMP export failed: {ex.Message}";
        }
    }

    [RelayCommand]
    private async Task ExportGifAsync()
    {
        if (Editor.Container is null || Editor.Container.Documents.Count == 0) return;
        if (Owner?.StorageProvider is not { } sp) return;

        var file = await sp.SaveFilePickerAsync(new FilePickerSaveOptions
        {
            Title = "Export animation as GIF",
            DefaultExtension = "gif",
            FileTypeChoices = [GifFileType],
            SuggestedFileName = Editor.FilePath is not null
                ? Path.GetFileNameWithoutExtension(Editor.FilePath) + ".gif"
                : "animation.gif",
        });
        if (file is null) return;

        var path = file.TryGetLocalPath();
        if (path is null) { StatusText = "Error: could not resolve file path."; return; }

        try
        {
            int w = Editor.Container.Width, h = Editor.Container.Height;
            var frames = new List<(uint[] Pixels, uint DelayMs)>();
            foreach (var doc in Editor.Container.Documents)
            {
                uint[] argb = _compositor.Composite(doc, w, h);
                frames.Add((argb, doc.FrameDelayMs));
            }

            await using var fs = File.Create(path);
            _gifExporter.Export(fs, frames, w, h);
            StatusText = $"Exported GIF ({frames.Count} frames): {Path.GetFileName(path)}";
        }
        catch (Exception ex)
        {
            StatusText = $"GIF export failed: {ex.Message}";
        }
    }

    #endregion
}
