using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;

namespace Hawkeye.VisionBuilder.Features.ImagesStrip;

public class ImagesViewModel
{
    private const int DefaultBufferSize = 10;
    
    private string? _directoryPath;
    private List<string> _allImagePaths = new();
    private int _currentIndex = -1;
    private int _bufferSize;
    private HashSet<int> _currentLoadedIndices = new();
    
    public ObservableCollection<ImageItem> LoadedImages { get; } = new();
    
    public int BufferSize
    {
        get => _bufferSize;
        set
        {
            if (value < 0)
                throw new ArgumentOutOfRangeException(nameof(value), "Buffer size must be non-negative.");
            
            _bufferSize = value;
            if (_currentIndex >= 0)
                LoadWindow();
        }
    }
    
    public int CurrentIndex => _currentIndex;
    public int CurrentRelativeIndex => _currentIndex - (_currentIndex - _bufferSize < 0 ? 0 : _currentIndex - _bufferSize);

    public int CurrentIndexOnLoaded => GetIndexOfLoaded();

    public int TotalCount => _allImagePaths.Count;
    
    public string? CurrentImagePath => _currentIndex >= 0 && _currentIndex < _allImagePaths.Count 
        ? _allImagePaths[_currentIndex] 
        : null;
    
    public bool CanGoNext => _currentIndex < _allImagePaths.Count - 1;
    
    public bool CanGoPrevious => _currentIndex > 0;
    
    public bool HasImages => _allImagePaths.Count > 0;
    
    // Events
    public event EventHandler<ImageAddedEventArgs[]>? ImagesAdded;
    public event EventHandler<ImageRemovedEventArgs[]>? ImagesRemoved;
    public event EventHandler? ImagesCleared;
    public event EventHandler<CurrentImageChangedEventArgs>? CurrentImageChanged;
    public event EventHandler<ImagesLoadedEventArgs>? ImagesLoaded;
    public event EventHandler? WindowRefreshed;
    
    public ImagesViewModel() : this(DefaultBufferSize)
    {
    }
    
    public ImagesViewModel(int bufferSize)
    {
        if (bufferSize < 0)
            throw new ArgumentOutOfRangeException(nameof(bufferSize), "Buffer size must be non-negative.");
        
        _bufferSize = bufferSize;
    }

    private int GetIndexOfLoaded()
    {
        return LoadedImages.FirstOrDefault(img => img.IsCurrent)?.Index ?? -1;
    }

    public int GetIndexFromPath(string path)
    {
        return _allImagePaths.IndexOf(path);
    }

    /// <summary>
    /// Loads all image files from the specified directory path.
    /// </summary>
    /// <param name="directoryPath">The directory path containing images</param>
    /// <param name="searchPattern">Optional search pattern (default: *.* for all files)</param>
    /// <param name="supportedExtensions">Optional list of supported extensions (e.g., .jpg, .png, .bmp)</param>
    public void LoadFromDirectory(string directoryPath, string searchPattern = "*.*", string[]? supportedExtensions = null)
    {
        if (string.IsNullOrWhiteSpace(directoryPath))
            throw new ArgumentException("Directory path cannot be null or empty.", nameof(directoryPath));
        
        if (!Directory.Exists(directoryPath))
            throw new DirectoryNotFoundException($"Directory not found: {directoryPath}");
        
        _directoryPath = directoryPath;
        
        var defaultExtensions = new[] { ".jpg", ".jpeg", ".png", ".bmp", ".gif", ".tif", ".tiff" };
        var extensions = supportedExtensions ?? defaultExtensions;
        
        var files = Directory.GetFiles(directoryPath, searchPattern, SearchOption.AllDirectories)
            .Where(f => !Path.GetFileNameWithoutExtension(f).Contains("_analysis") && extensions.Contains(Path.GetExtension(f), StringComparer.OrdinalIgnoreCase))
            .OrderBy(f => f)
            .ToList();
        
        _allImagePaths = files;
        _currentIndex = files.Count > 0 ? 0 : -1;
        
        LoadWindow();
        
        OnImagesLoaded(new ImagesLoadedEventArgs(files.Count, _directoryPath));
        OnCurrentImageChanged(new CurrentImageChangedEventArgs(CurrentIndex,CurrentIndex,CurrentImagePath));
    }
    
    /// <summary>
    /// Navigate to the first image.
    /// </summary>
    public bool First()
    {
        if (_allImagePaths.Count == 0)
            return false;
        
        if (_currentIndex == 0)
            return false;
        
        var oldIndex = _currentIndex;
        _currentIndex = 0;
        LoadWindow();
        OnCurrentImageChanged(new CurrentImageChangedEventArgs(oldIndex, _currentIndex, CurrentImagePath));
        
        return true;
    }
    
    /// <summary>
    /// Navigate to the previous image.
    /// </summary>
    public bool Previous()
    {
        if (!CanGoPrevious)
            return false;
        
        var oldIndex = _currentIndex;
        _currentIndex--;
        LoadWindow();
        OnCurrentImageChanged(new CurrentImageChangedEventArgs(oldIndex, _currentIndex, CurrentImagePath));
        
        return true;
    }
    
    /// <summary>
    /// Navigate to the next image.
    /// </summary>
    public bool Next()
    {
        if (!CanGoNext)
            return false;
        
        var oldIndex = _currentIndex;
        _currentIndex++;
        LoadWindow();
        OnCurrentImageChanged(new CurrentImageChangedEventArgs(oldIndex, _currentIndex, CurrentImagePath));
        
        return true;
    }
    
    /// <summary>
    /// Navigate to the last image.
    /// </summary>
    public bool Last()
    {
        if (_allImagePaths.Count == 0)
            return false;
        
        var lastIndex = _allImagePaths.Count - 1;
        if (_currentIndex == lastIndex)
            return false;
        
        var oldIndex = _currentIndex;
        _currentIndex = lastIndex;
        LoadWindow();
        OnCurrentImageChanged(new CurrentImageChangedEventArgs(oldIndex, _currentIndex, CurrentImagePath));
        
        return true;
    }
    
    /// <summary>
    /// Navigate to the Nth image (0-based index).
    /// </summary>
    public bool SelectNth(int index)
    {
        if (index < 0 || index >= _allImagePaths.Count)
            throw new ArgumentOutOfRangeException(nameof(index), $"Index must be between 0 and {_allImagePaths.Count - 1}.");
        
       
        var oldIndex = _currentIndex;
        _currentIndex = index;
        LoadWindow();
        OnCurrentImageChanged(new CurrentImageChangedEventArgs(oldIndex, _currentIndex, CurrentImagePath));
        
        return true;
    }

    public bool SelectByPath(string path)
    {
        var index = _allImagePaths.IndexOf(path);
        if (index < 0)
            return false;
        
        return SelectNth(index);
    }
    
    /// <summary>
    /// Clears all loaded images and resets the state.
    /// </summary>
    public void Clear()
    {
        // Fire remove events for all currently loaded images
        
        OnImagesRemoved(_currentLoadedIndices.OrderByDescending(i => i).Select(x => new ImageRemovedEventArgs(x, _allImagePaths[x])).ToArray());
        _allImagePaths.Clear();
        LoadedImages.Clear();
        _currentLoadedIndices.Clear();
        _currentIndex = -1;
        _directoryPath = null;
        
        OnImagesCleared();
    }
    
    /// <summary>
    /// Refreshes the current window by reloading images from disk.
    /// </summary>
    public void RefreshWindow()
    {
        LoadWindow();
        OnWindowRefreshed();
    }
    
    /// <summary>
    /// Reloads the directory, useful when files have been added or removed.
    /// </summary>
    public void ReloadDirectory(bool preserveCurrentImage = true)
    {
        if (string.IsNullOrWhiteSpace(_directoryPath))
            return;
        
        string? currentPath = preserveCurrentImage ? CurrentImagePath : null;
        LoadFromDirectory(_directoryPath);
        
        if (preserveCurrentImage && currentPath != null)
        {
            var newIndex = _allImagePaths.IndexOf(currentPath);
            if (newIndex >= 0)
            {
                _currentIndex = newIndex;
                LoadWindow();
            }
        }
    }
    
    private void LoadWindow()
    {
        if (_currentIndex < 0 || _allImagePaths.Count == 0)
        {

            OnImagesRemoved(_currentLoadedIndices.OrderByDescending(i => i).Select(x => new ImageRemovedEventArgs(x, _allImagePaths[x])).ToArray());
            LoadedImages.Clear();
            _currentLoadedIndices.Clear();
            return;
        }
        
        int startIndex = Math.Max(0, _currentIndex - _bufferSize);
        int endIndex = Math.Min(_allImagePaths.Count - 1, _currentIndex + _bufferSize);
        
        // Create a set of new indices that should be loaded
        var newIndices = new HashSet<int>();
        for (int i = startIndex; i <= endIndex; i++)
        {
            newIndices.Add(i);
        }
        
        // Find indices to remove (in current but not in new)
        var indicesToRemove = _currentLoadedIndices.Except(newIndices).OrderByDescending(i => i).ToList();
        
        // Find indices to add (in new but not in current)
        var indicesToAdd = newIndices.Except(_currentLoadedIndices).OrderBy(i => i).ToList();
        
        // Remove images that are no longer in the window
        foreach (var index in indicesToRemove)
        {
            var item = LoadedImages.FirstOrDefault(img => img.Index == index);
            if (item != null)
            {
                LoadedImages.Remove(item);
            }
            _currentLoadedIndices.Remove(index);
        }
        OnImagesRemoved(indicesToRemove.Select(x=>new ImageRemovedEventArgs(x, _allImagePaths[x])).ToArray());
        
        // Add new images to the window
        foreach (var index in indicesToAdd)
        {
            var item = new ImageItem(index, _allImagePaths[index], index == _currentIndex);
            
            // Insert at the correct position to maintain order
            int insertIndex = 0;
            for (int i = 0; i < LoadedImages.Count; i++)
            {
                if (LoadedImages[i].Index < index)
                    insertIndex = i + 1;
                else
                    break;
            }
            
            LoadedImages.Insert(insertIndex, item);
            _currentLoadedIndices.Add(index);
        }
        OnImagesAdded(indicesToAdd.Select(x => new ImageAddedEventArgs(x, _allImagePaths[x])).ToArray());

        // Update IsCurrent flag for all items
        for (int i = 0; i < LoadedImages.Count; i++)
        {
            var currentItem = LoadedImages[i];
            if (currentItem.IsCurrent != (currentItem.Index == _currentIndex))
            {
                // Replace with updated item
                LoadedImages[i] = new ImageItem(currentItem.Index, currentItem.FilePath, currentItem.Index == _currentIndex);
            }
        }
    }
    
    protected virtual void OnImagesAdded(ImageAddedEventArgs[] e)
    {
        ImagesAdded?.Invoke(this, e);
    }
    
    protected virtual void OnImagesRemoved(ImageRemovedEventArgs[] e)
    {
        ImagesRemoved?.Invoke(this, e);
    }
    
    protected virtual void OnImagesCleared()
    {
        ImagesCleared?.Invoke(this, EventArgs.Empty);
    }
    
    protected virtual void OnCurrentImageChanged(CurrentImageChangedEventArgs e)
    {
        CurrentImageChanged?.Invoke(this, e);
    }
    
    protected virtual void OnImagesLoaded(ImagesLoadedEventArgs e)
    {
        ImagesLoaded?.Invoke(this, e);
    }
    
    protected virtual void OnWindowRefreshed()
    {
        WindowRefreshed?.Invoke(this, EventArgs.Empty);
    }
}

public class ImageItem
{
    public int Index { get; }
    public string FilePath { get; }
    public string FileName { get; }
    public bool IsCurrent { get; }
    
    public ImageItem(int index, string filePath, bool isCurrent)
    {
        Index = index;
        FilePath = filePath;
        FileName = Path.GetFileName(filePath);
        IsCurrent = isCurrent;
    }
}

public class ImageAddedEventArgs : EventArgs
{
    public int Index { get; }
    public string FilePath { get; }
    
    public ImageAddedEventArgs(int index, string filePath)
    {
        Index = index;
        FilePath = filePath;
    }
}

public class ImageRemovedEventArgs : EventArgs
{
    public int Index { get; }
    public string FilePath { get; }
    
    public ImageRemovedEventArgs(int index, string filePath)
    {
        Index = index;
        FilePath = filePath;
    }
}

public class CurrentImageChangedEventArgs : EventArgs
{
    public int OldIndex { get; }
    public int NewIndex { get; }
    public string? NewImagePath { get; }
    
    public CurrentImageChangedEventArgs(int oldIndex, int newIndex, string? newImagePath)
    {
        OldIndex = oldIndex;
        NewIndex = newIndex;
        NewImagePath = newImagePath;
    }
}

public class ImagesLoadedEventArgs : EventArgs
{
    public int TotalCount { get; }
    public string DirectoryPath { get; }
    
    public ImagesLoadedEventArgs(int totalCount, string directoryPath)
    {
        TotalCount = totalCount;
        DirectoryPath = directoryPath;
    }
}