using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Sirenix.OdinInspector;
using UnityEngine;
using Object = UnityEngine.Object;

namespace VOID.Generic.DynamicValue
{
    [Serializable]
    public class DynamicText
    {
        // STATIC - Cached markers
        private static List<IMarker> _markers =
            AppDomain.CurrentDomain.GetAssemblies()
                .SelectMany(assembly => assembly.GetTypes())
                .Where(type => typeof(IMarker).IsAssignableFrom(type) && type.IsClass)
                .Select(Activator.CreateInstance)
                .Cast<IMarker>()
                .ToList();

        private static List<IMarkerGroup> _groupMarkers =
            AppDomain.CurrentDomain.GetAssemblies()
                .SelectMany(assembly => assembly.GetTypes())
                .Where(type => typeof(IMarkerGroup).IsAssignableFrom(type) && type.IsClass)
                .Select(Activator.CreateInstance)
                .Cast<IMarkerGroup>()
                .ToList();

#if UNITY_EDITOR
        [ShowInInspector, Searchable, ListDrawerSettings(IsReadOnly = true)]
        private static List<MarkerDummyString> _allMarkers = new List<string>()
            .Union(_markers.Select(m => m.Marker()))
            .Union(_groupMarkers.SelectMany(mg => mg.Markers().Select(m => $"{mg.Group()}.{m}")))
            .Select(str => new MarkerDummyString(str))
            .ToList();
#endif
        
        // User defined formula or text with markers
        [SerializeField, TextArea(1,20)] private string _dynamicValue;
        public string dynamicValue => _dynamicValue;
        
        // TODO: Sources are needed to work, but so far will be given only via script
        private List<Object> _sources = new();
        
        // Parsed
        private bool _isParsed;
        private string _stringFormat;
        private List<Func<string>> _valueGetters;

        public void AddSource(Object obj) => _sources.Add(obj);

        private void Parse()
        {
            _isParsed = true;
            _valueGetters = Regex.Matches(_dynamicValue, "{(.*?)}")
                .Select(match => match.Value)
                .Select(stringMarker =>
                {
                    stringMarker = stringMarker.Trim('{', '}');
                    var dotIndex = stringMarker.LastIndexOf('.');
                    var groupName = string.Join(string.Empty, stringMarker.Take(dotIndex));
                    var markerName = string.Join(string.Empty, stringMarker.Skip(dotIndex+1));

                    return TryParseMarker(markerName) ?? TryParseMarkerGroup(groupName, markerName) ?? (() => $"{{{stringMarker}}}");
                })
                .ToList();

            var matchIndex = 0;
            _stringFormat = Regex.Replace(_dynamicValue, "{(.*?)}", _ => $"{{{matchIndex++}}}");
        }

        private Func<string> TryParseMarker(string markerName)
        {
            var marker = _markers.FirstOrDefault(m => m.Marker().Equals(markerName));
            if (marker == null) return null;
            var source = _sources.FirstOrDefault(source => marker.SourceType().IsInstanceOfType(source));
            if (source == null) return null;
            return marker.Getter(source);
        }

        private Func<string> TryParseMarkerGroup(string groupName, string markerName)
        {
            var markerGroup = _groupMarkers.FirstOrDefault(mg => mg.Group().Equals(groupName) && mg.Markers().Contains(markerName));
            if (markerGroup == null) return null;
            var source = _sources.FirstOrDefault(source => markerGroup.SourceType().IsInstanceOfType(source));
            if (source == null) return null;
            return markerGroup.Getter(source, markerName);
        }

        public string Get()
        {
#if UNITY_EDITOR
            _isParsed = false;
#endif
            if (!_isParsed) Parse();
            return string.Format(_stringFormat, _valueGetters.Select(getter => getter.Invoke()).ToArray<object>());
        }
        
        public static implicit operator string(DynamicText dynamicText) => dynamicText.Get();
    }
}