using MetaBrainz.MusicBrainz;
using UniversalTagEditor.Helpers;
using UniversalTagEditor.Types;

namespace UniversalTagEditor.Core;

public sealed class UteOptions
{
    public required string WorkingDirectory { get; init; }
    public string? AlbumInfoCsv { get; init; }   // имя файла или путь
    public string? AlbumCover { get; init; }     // имя файла или путь
    public string? Format { get; init; }         // flac/mp3/...
    public bool FixTags { get; init; }
    public bool EnhanceStructure { get; init; }
    public int MaxDegreeOfParallelism { get; init; } = 4;
}

public sealed record ProgressInfo(int Done, int Total, string? CurrentFile);

public static class Runner
{
    public static async Task RunAsync(
        UteOptions opt,
        IProgress<string>? log = null,
        IProgress<ProgressInfo>? progress = null,
        CancellationToken ct = default)
    {
        if (string.IsNullOrWhiteSpace(opt.WorkingDirectory))
            throw new ArgumentException("WorkingDirectory is empty.");

        if (!Directory.Exists(opt.WorkingDirectory))
            throw new DirectoryNotFoundException(opt.WorkingDirectory);

        var format = (opt.Format ?? "").Trim();
        if (string.IsNullOrWhiteSpace(format))
            throw new ArgumentException("Format (-f) is required.");

        // Album data (csv + cover)
        AlbumInfo album = new();

        if (!string.IsNullOrWhiteSpace(opt.AlbumInfoCsv))
        {
            var csvPath = MakePath(opt.WorkingDirectory, opt.AlbumInfoCsv!);
            var records = CsvParser.ParseCsvTable(csvPath);
            album = new AlbumInfo { Tracks = records };
        }

        if (!string.IsNullOrWhiteSpace(opt.AlbumCover))
            album.Cover = MakePath(opt.WorkingDirectory, opt.AlbumCover!);

        // Collect files
        var files = new List<string>();
        files.AddRange(Directory.GetFiles(opt.WorkingDirectory, $"*.{format}"));

        if (!opt.EnhanceStructure)
        {
            foreach (var dir in Directory.GetDirectories(opt.WorkingDirectory))
                files.AddRange(Directory.GetFiles(dir, $"*.{format}"));
        }

        if (files.Count == 0)
        {
            log?.Report("Файлы не найдены.");
            return;
        }

        // ВАЖНО: Query НЕ потокобезопасен — делаем отдельный на каждый поток
        var queries = new ThreadLocal<Query>(() =>
            new Query("NavidromeMetadataRecovery", "1.0"));

        int done = 0;
        int total = files.Count;

        // Нормальная параллельность вместо Thread + async void
        await Parallel.ForEachAsync(
            files.Select((path, index) => (path, index)),
            new ParallelOptions
            {
                MaxDegreeOfParallelism = Math.Max(1, opt.MaxDegreeOfParallelism),
                CancellationToken = ct
            },
            async (item, token) =>
            {
                var (file, index) = item;

                if (opt.FixTags)
                {
                    await TagEditor.Fix(file, queries.Value!, opt.EnhanceStructure, format);
                }
                else
                {
                    TagEditor.Edit(file, album, index);
                }

                var nowDone = Interlocked.Increment(ref done);
                progress?.Report(new ProgressInfo(nowDone, total, file));
                log?.Report($"{nowDone}/{total}: {file} успешно обработан");
            });

        log?.Report("Готово!");
    }

    private static string MakePath(string workDir, string maybeFileOrPath)
        => Path.IsPathRooted(maybeFileOrPath)
            ? maybeFileOrPath
            : Path.Combine(workDir, maybeFileOrPath);
}