// Asset Naming Manager — the result of one scan.
using System;
using System.Collections.Generic;

namespace AssetNamingManager
{
    /// <summary>What a scan looked at and what it found.</summary>
    public class ScanReport
    {
        /// <summary>Every finding, in the order the scanner produced them.</summary>
        public readonly List<AssetNamingViolation> violations = new List<AssetNamingViolation>();

        /// <summary>How many non-folder assets were examined.</summary>
        public int assetsScanned;

        /// <summary>How many folders were examined.</summary>
        public int foldersScanned;

        /// <summary>How many assets were skipped because they matched an ignore glob.</summary>
        public int assetsIgnored;

        /// <summary>Wall-clock time the scan took.</summary>
        public double durationSeconds;

        /// <summary>Name of the rule set that produced this report.</summary>
        public string ruleSetName = "";

        /// <summary>When the scan ran, in UTC.</summary>
        public DateTime timestampUtc = DateTime.UtcNow;

        /// <summary>True when the user hit Cancel, so the numbers below are partial.</summary>
        public bool cancelled;

        int _errors = -1, _warnings = -1, _infos = -1;

        /// <summary>Number of <see cref="NamingSeverity.Error"/> findings.</summary>
        public int ErrorCount { get { EnsureCounts(); return _errors; } }

        /// <summary>Number of <see cref="NamingSeverity.Warning"/> findings.</summary>
        public int WarningCount { get { EnsureCounts(); return _warnings; } }

        /// <summary>Number of <see cref="NamingSeverity.Info"/> findings.</summary>
        public int InfoCount { get { EnsureCounts(); return _infos; } }

        /// <summary>Distinct assets that have at least one finding.</summary>
        public int AffectedAssetCount
        {
            get
            {
                var set = new HashSet<string>(StringComparer.Ordinal);
                foreach (var v in violations) set.Add(v.assetPath);
                return set.Count;
            }
        }

        /// <summary>Findings the Cop can fix for you without asking anything else.</summary>
        public int AutoFixableCount
        {
            get
            {
                int n = 0;
                foreach (var v in violations) if (v.CanAutoFix) n++;
                return n;
            }
        }

        /// <summary>
        /// A 0-100 headline number for the project. Errors cost more than warnings, warnings more
        /// than info. A project with no findings scores 100.
        /// </summary>
        public float HealthScore
        {
            get
            {
                int total = assetsScanned + foldersScanned;
                if (total <= 0) return 100f;

                float weighted = ErrorCount * 3f + WarningCount * 1f + InfoCount * 0.35f;
                float score = 100f * (1f - weighted / total);
                if (score < 0f) score = 0f;
                if (score > 100f) score = 100f;
                return score;
            }
        }

        /// <summary>One-word verdict derived from <see cref="HealthScore"/>.</summary>
        public string HealthLabel
        {
            get
            {
                float s = HealthScore;
                if (violations.Count == 0) return "Spotless";
                if (s >= 95f) return "Healthy";
                if (s >= 85f) return "Minor drift";
                if (s >= 70f) return "Needs a pass";
                if (s >= 50f) return "Messy";
                return "Critical";
            }
        }

        /// <summary>Counts per rule name, biggest first when enumerated through <see cref="Sorted"/>.</summary>
        public Dictionary<string, int> CountBy(Func<AssetNamingViolation, string> selector)
        {
            var map = new Dictionary<string, int>(StringComparer.Ordinal);
            foreach (var v in violations)
            {
                string key = selector(v);
                if (string.IsNullOrEmpty(key)) key = "(none)";
                int current;
                map.TryGetValue(key, out current);
                map[key] = current + 1;
            }
            return map;
        }

        /// <summary>Turns a count map into a descending list, capped at <paramref name="take"/> entries.</summary>
        public static List<KeyValuePair<string, int>> Sorted(Dictionary<string, int> map, int take)
        {
            var list = new List<KeyValuePair<string, int>>(map);
            list.Sort((a, b) =>
            {
                int byCount = b.Value.CompareTo(a.Value);
                return byCount != 0 ? byCount : string.Compare(a.Key, b.Key, StringComparison.Ordinal);
            });
            if (take > 0 && list.Count > take) list.RemoveRange(take, list.Count - take);
            return list;
        }

        /// <summary>Highest severity present in the report, or null when the report is clean.</summary>
        public NamingSeverity? WorstSeverity
        {
            get
            {
                if (ErrorCount > 0) return NamingSeverity.Error;
                if (WarningCount > 0) return NamingSeverity.Warning;
                if (InfoCount > 0) return NamingSeverity.Info;
                return null;
            }
        }

        /// <summary>True when the report should fail a CI gate set to <paramref name="gate"/>.</summary>
        public bool FailsGate(NamingSeverity gate)
        {
            switch (gate)
            {
                case NamingSeverity.Error:   return ErrorCount > 0;
                case NamingSeverity.Warning: return ErrorCount > 0 || WarningCount > 0;
                default:                     return violations.Count > 0;
            }
        }

        /// <summary>Call after mutating <see cref="violations"/> directly.</summary>
        public void InvalidateCounts()
        {
            _errors = _warnings = _infos = -1;
        }

        void EnsureCounts()
        {
            if (_errors >= 0) return;

            _errors = _warnings = _infos = 0;
            foreach (var v in violations)
            {
                switch (v.severity)
                {
                    case NamingSeverity.Error:   _errors++;   break;
                    case NamingSeverity.Warning: _warnings++; break;
                    default:                     _infos++;    break;
                }
            }
        }

        /// <summary>One-line summary used in the console and the status bar.</summary>
        public string Summary()
        {
            return string.Format(
                "{0} assets + {1} folders scanned in {2:0.00}s — {3} findings ({4} errors, {5} warnings, {6} info). Health {7:0}/100.",
                assetsScanned, foldersScanned, durationSeconds, violations.Count,
                ErrorCount, WarningCount, InfoCount, HealthScore);
        }
    }
}
