using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using Newtonsoft.Json.Linq;

namespace StandardScene.MagCarSimulator
{
    public sealed class SimpleLiteSite
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public double X { get; set; }
        public double Y { get; set; }
        public int? TagValue { get; set; }
        public bool NeedStop { get; set; }
        public IReadOnlyDictionary<string, string> Fields { get; set; }
    }

    public sealed class SimpleLiteTrack
    {
        public int Id { get; set; }
        public int SiteA { get; set; }
        public int SiteB { get; set; }
        public int Direction { get; set; }
    }

    /// <summary>解析 SimpleLite <c>maps/*.json</c> 的 Sites / Tracks。</summary>
    public sealed class SimpleLiteMap
    {
        public const string NeedStopField = "Mag_NeedStop";

        public string FilePath { get; private set; }
        public string FileName { get; private set; }
        public IReadOnlyDictionary<int, SimpleLiteSite> Sites { get; private set; }
        public IReadOnlyList<SimpleLiteTrack> Tracks { get; private set; }
        public IReadOnlyDictionary<int, IReadOnlyList<int>> Adjacency { get; private set; }
        private readonly HashSet<int> _singleOccupancySites = new HashSet<int>();

        public static SimpleLiteMap Load(string path, bool useTagValueAsNode)
        {
            if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
            {
                throw new FileNotFoundException("找不到 SimpleLite 地图文件", path);
            }

            var root = JObject.Parse(File.ReadAllText(path));
            var sites = ParseSites(root["Sites"] as JObject);
            var tracks = ParseTracks(root["Tracks"] as JObject);
            var adjacency = BuildAdjacency(sites, tracks);

            var map = new SimpleLiteMap
            {
                FilePath = Path.GetFullPath(path),
                FileName = Path.GetFileName(path),
                Sites = sites,
                Tracks = tracks,
                Adjacency = adjacency,
                UseTagValueAsNode = useTagValueAsNode
            };
            map.ResetSingleOccupancyFromStops();
            return map;
        }

        public bool UseTagValueAsNode { get; private set; }

        public bool TryGetSite(int siteId, out SimpleLiteSite site)
        {
            return Sites.TryGetValue(siteId, out site);
        }

        public ushort ResolveNode(int siteId)
        {
            if (!Sites.TryGetValue(siteId, out var site))
            {
                return (ushort)Math.Clamp(siteId, 0, ushort.MaxValue);
            }

            if (UseTagValueAsNode && site.TagValue.HasValue && site.TagValue.Value >= 0)
            {
                return (ushort)Math.Clamp(site.TagValue.Value, 0, ushort.MaxValue);
            }

            return (ushort)Math.Clamp(site.Id, 0, ushort.MaxValue);
        }

        public bool NeedStop(int siteId)
        {
            return Sites.TryGetValue(siteId, out var site) && site.NeedStop;
        }

        /// <summary>停止点或交管点：同时只允许一辆。普通站可叠车。</summary>
        public bool SingleOccupancy(int siteId)
        {
            return _singleOccupancySites.Contains(siteId);
        }

        public IReadOnlyList<int> ListSingleOccupancySiteIds()
        {
            var list = new List<int>(_singleOccupancySites);
            list.Sort();
            return list;
        }

        public void ResetSingleOccupancyFromStops()
        {
            _singleOccupancySites.Clear();
            foreach (var site in Sites.Values)
            {
                if (site.NeedStop)
                {
                    _singleOccupancySites.Add(site.Id);
                }
            }
        }

        public void AddSingleOccupancySites(IEnumerable<int> siteIds)
        {
            if (siteIds == null)
            {
                return;
            }

            foreach (var id in siteIds)
            {
                if (id > 0 && Sites.ContainsKey(id))
                {
                    _singleOccupancySites.Add(id);
                }
            }
        }

        public IReadOnlyList<int> ListNeedStopSiteIds()
        {
            var list = new List<int>();
            foreach (var site in Sites.Values)
            {
                if (site.NeedStop)
                {
                    list.Add(site.Id);
                }
            }

            list.Sort();
            return list;
        }

        private static Dictionary<int, SimpleLiteSite> ParseSites(JObject sitesNode)
        {
            var result = new Dictionary<int, SimpleLiteSite>();
            if (sitesNode == null)
            {
                return result;
            }

            foreach (var property in sitesNode.Properties())
            {
                if (property.Value is not JObject obj)
                {
                    continue;
                }

                var id = obj.Value<int?>("id") ?? ParseInt(property.Name);
                if (id <= 0)
                {
                    continue;
                }

                var fields = ReadFields(obj["fields"] as JObject);
                var site = new SimpleLiteSite
                {
                    Id = id,
                    Name = obj.Value<string>("name") ?? "",
                    X = obj.Value<double?>("x") ?? 0,
                    Y = obj.Value<double?>("y") ?? 0,
                    TagValue = TryReadInt(fields, "TagValue"),
                    NeedStop = IsNeedStop(fields),
                    Fields = fields
                };
                result[id] = site;
            }

            return result;
        }

        private static List<SimpleLiteTrack> ParseTracks(JObject tracksNode)
        {
            var result = new List<SimpleLiteTrack>();
            if (tracksNode == null)
            {
                return result;
            }

            foreach (var property in tracksNode.Properties())
            {
                if (property.Value is not JObject obj)
                {
                    continue;
                }

                var siteA = obj.Value<int?>("siteA") ?? obj.Value<int?>("_siteA") ?? 0;
                var siteB = obj.Value<int?>("siteB") ?? obj.Value<int?>("_siteB") ?? 0;
                if (siteA <= 0 || siteB <= 0)
                {
                    continue;
                }

                result.Add(new SimpleLiteTrack
                {
                    Id = obj.Value<int?>("id") ?? ParseInt(property.Name),
                    SiteA = siteA,
                    SiteB = siteB,
                    Direction = obj.Value<int?>("direction") ?? 0
                });
            }

            return result;
        }

        private static Dictionary<int, IReadOnlyList<int>> BuildAdjacency(
            Dictionary<int, SimpleLiteSite> sites,
            List<SimpleLiteTrack> tracks)
        {
            var mutable = new Dictionary<int, List<int>>();
            foreach (var id in sites.Keys)
            {
                mutable[id] = new List<int>();
            }

            foreach (var track in tracks)
            {
                AddEdge(mutable, track.SiteA, track.SiteB, track.Direction);
            }

            var result = new Dictionary<int, IReadOnlyList<int>>();
            foreach (var pair in mutable)
            {
                result[pair.Key] = pair.Value;
            }

            return result;
        }

        private static void AddEdge(Dictionary<int, List<int>> graph, int from, int to, int direction)
        {
            // 0 双向；1 仅 A→B；2 仅 B→A。其它值按双向处理。
            var aToB = direction != 2;
            var bToA = direction != 1;
            if (aToB)
            {
                AddUnique(graph, from, to);
            }

            if (bToA)
            {
                AddUnique(graph, to, from);
            }
        }

        private static void AddUnique(Dictionary<int, List<int>> graph, int from, int to)
        {
            if (!graph.TryGetValue(from, out var list))
            {
                list = new List<int>();
                graph[from] = list;
            }

            if (!list.Contains(to))
            {
                list.Add(to);
            }
        }

        private static Dictionary<string, string> ReadFields(JObject fields)
        {
            var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            if (fields == null)
            {
                return result;
            }

            foreach (var property in fields.Properties())
            {
                result[property.Name] = property.Value?.Type == JTokenType.Null
                    ? ""
                    : property.Value.ToString();
            }

            return result;
        }

        private static bool IsNeedStop(Dictionary<string, string> fields)
        {
            if (!fields.TryGetValue(NeedStopField, out var raw))
            {
                return false;
            }

            raw = (raw ?? "").Trim();
            return raw.Equals("true", StringComparison.OrdinalIgnoreCase)
                   || raw.Equals("1", StringComparison.OrdinalIgnoreCase)
                   || raw.Equals("yes", StringComparison.OrdinalIgnoreCase);
        }

        private static int? TryReadInt(Dictionary<string, string> fields, string key)
        {
            if (!fields.TryGetValue(key, out var raw) || string.IsNullOrWhiteSpace(raw))
            {
                return null;
            }

            return int.TryParse(raw, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)
                ? value
                : (int?)null;
        }

        private static int ParseInt(string text)
        {
            return int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)
                ? value
                : 0;
        }
    }
}
