using System;
using System.Collections.Generic;
using System.Linq;
using Dacryptero.Search.AST;

namespace Dacryptero.Search.Parser
{
    public class AstPostTransformer
    {
        public AstBase Transform(AstBase ast, SearchResultType type, string? role)
        {
            return type switch
            {
                SearchResultType.Person =>
                    ast.Transform(ReducePerson(role)) ?? ast,
                SearchResultType.Interview =>
                    ast.Transform(ReduceInterview()) ?? ast,
                SearchResultType.File =>
                    ast.Transform(ReduceFile()) ?? ast,
                _ => ast,
            };
        }

        private Func<AstBase, AstBase?> ReducePerson(string? roleName)
        {
            return ast =>
            {
                if (ast is Group group && group.Options.Count > 0)
                {
                    var newOpts = new List<OptionBase>(group.Options.Count);
                    foreach (var opt in group.Options)
                        switch (opt)
                        {
                            case RoleOption roleOption:
                                if (roleName is not null)
                                {
                                    if (roleOption.Role != roleName)
                                        return Fail.Default;
                                }
                                else
                                {
                                    newOpts.Add(opt);
                                }
                                break;
                            case TypeOption typeOpt:
                                if (typeOpt.Type != SearchResultType.Person)
                                    return Fail.Default;
                                break;
                            default:
                                newOpts.Add(opt);
                                break;
                        }
                    if (newOpts.Count == 0 && group.Items.Count == 0)
                        return Succeed.Default;
                    group.Options.Clear();
                    group.Options.AddRange(newOpts);
                    return group;
                }
                return null;
            };
        }

        private Func<AstBase, AstBase?> ReduceFile()
        {
            return ast =>
            {
                if (ast is Group group && group.Options.Count > 0)
                {
                    var newOpts = new List<OptionBase>(group.Options.Count);
                    foreach (var opt in group.Options)
                        switch (opt)
                        {
                            case AttributeOption:
                            case RoleOption:
                                return Fail.Default;
                            case TypeOption typeOpt:
                                if (typeOpt.Type != SearchResultType.File)
                                    return Fail.Default;
                                break;
                            default:
                                newOpts.Add(opt);
                                break;
                        }
                    if (newOpts.Count == 0 && group.Items.Count == 0)
                        return Succeed.Default;
                    group.Options.Clear();
                    group.Options.AddRange(newOpts);
                    return group;
                }
                return null;
            };
        }

        private Func<AstBase, AstBase?> ReduceInterview()
        {
            return ast =>
            {
                if (ast is Group group && group.Options.Count > 0)
                {
                    var newOpts = new List<OptionBase>(group.Options.Count);
                    foreach (var opt in group.Options)
                        switch (opt)
                        {
                            case AttributeOption:
                            case RoleOption:
                                return Fail.Default;
                            case TypeOption typeOpt:
                                if (typeOpt.Type != SearchResultType.Interview)
                                    return Fail.Default;
                                break;
                            default:
                                newOpts.Add(opt);
                                break;
                        }
                    if (newOpts.Count == 0 && group.Items.Count == 0)
                        return Succeed.Default;
                    group.Options.Clear();
                    group.Options.AddRange(newOpts);
                    return group;
                }
                return null;
            };
        }
    }
}