// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Text;

namespace Microsoft.Extensions.Tools.Internal
{
    public class TemporaryCSharpProject
    {
        private readonly string _filename;
        private readonly TemporaryDirectory _directory;
        private readonly List<string> _items = new List<string>();
        private readonly List<string> _properties = new List<string>();

        public TemporaryCSharpProject(string name, TemporaryDirectory directory, string sdk)
        {
            Name = name;
            _filename = name + ".csproj";
            _directory = directory;
            Sdk = sdk;
        }

        public string Name { get; }
        public string Path => System.IO.Path.Combine(_directory.Root, _filename);

        public string Sdk { get; }

        protected virtual string Template =>
@"<Project Sdk=""{2}"">
  <PropertyGroup>
    {0}
    <OutputType>Exe</OutputType>
  </PropertyGroup>
  <ItemGroup>
    {1}
  </ItemGroup>
</Project>";

        public TemporaryCSharpProject WithTargetFrameworks(params string[] tfms)
        {
            Debug.Assert(tfms.Length > 0);
            var propertySpec = new PropertySpec
            {
                Value = string.Join(";", tfms)
            };
            propertySpec.Name = tfms.Length == 1
                ? "TargetFramework"
                : "TargetFrameworks";

            return WithProperty(propertySpec);
        }

        public TemporaryCSharpProject WithProperty(string name, string value)
            => WithProperty(new PropertySpec { Name = name, Value = value });

        public TemporaryCSharpProject WithProperty(PropertySpec property)
        {
            var sb = new StringBuilder();
            sb.Append('<').Append(property.Name).Append('>')
                .Append(property.Value)
                .Append("</").Append(property.Name).Append('>');
            _properties.Add(sb.ToString());
            return this;
        }

        public TemporaryCSharpProject WithItem(string itemName, string include, string condition = null)
            => WithItem(new ItemSpec { Name = itemName, Include = include, Condition = condition });

        public TemporaryCSharpProject WithItem(ItemSpec item)
        {
            var sb = new StringBuilder("<");
            sb.Append(item.Name).Append(' ');
            if (item.Include != null) sb.Append(" Include=\"").Append(item.Include).Append('"');
            if (item.Remove != null) sb.Append(" Remove=\"").Append(item.Remove).Append('"');
            if (item.Update != null) sb.Append(" Update=\"").Append(item.Update).Append('"');
            if (item.Exclude != null) sb.Append(" Exclude=\"").Append(item.Exclude).Append('"');
            if (item.Condition != null) sb.Append(" Exclude=\"").Append(item.Condition).Append('"');
            if (!item.Watch) sb.Append(" Watch=\"false\" ");
            AddAdditionalAttributes(sb, item);
            sb.Append(" />");
            _items.Add(sb.ToString());
            return this;
        }

        protected virtual void AddAdditionalAttributes(StringBuilder sb, ItemSpec item)
        {
        }

        public TemporaryCSharpProject WithProjectReference(TemporaryCSharpProject reference, bool watch = true)
        {
            if (ReferenceEquals(this, reference))
            {
                throw new InvalidOperationException("Can add project reference to self");
            }

            return WithItem(new ItemSpec { Name = "ProjectReference", Include = reference.Path, Watch = watch });
        }

        public TemporaryDirectory Dir() => _directory;

        public void Create()
        {
            _directory.CreateFile(_filename, string.Format(CultureInfo.InvariantCulture, Template, string.Join("\r\n", _properties), string.Join("\r\n", _items), Sdk));
        }

        public class ItemSpec
        {
            public string Name { get; set; }
            public string Include { get; set; }
            public string Exclude { get; set; }
            public string Update { get; set; }
            public string Remove { get; set; }
            public bool Watch { get; set; } = true;
            public string Condition { get; set; }
        }

        public class PropertySpec
        {
            public string Name { get; set; }
            public string Value { get; set; }
        }
    }
}
