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

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace Microsoft.AspNetCore.Razor.Language
{
    internal class DefaultRazorProjectFileSystem : RazorProjectFileSystem
    {
        public DefaultRazorProjectFileSystem(string root)
        {
            if (string.IsNullOrEmpty(root))
            {
                throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(root));
            }

            Root = root.Replace('\\', '/').TrimEnd('/');
        }

        public string Root { get; }

        public override IEnumerable<RazorProjectItem> EnumerateItems(string basePath)
        {
            var absoluteBasePath = NormalizeAndEnsureValidPath(basePath);

            var directory = new DirectoryInfo(absoluteBasePath);
            if (!directory.Exists)
            {
                return Enumerable.Empty<RazorProjectItem>();
            }

            return directory
                .EnumerateFiles("*.cshtml", SearchOption.AllDirectories)
                .Concat(directory.EnumerateFiles("*.razor", SearchOption.AllDirectories))
                .Select(file =>
                {
                    var relativePhysicalPath = file.FullName.Substring(absoluteBasePath.Length + 1); // Include leading separator
                    var filePath = "/" + relativePhysicalPath.Replace(Path.DirectorySeparatorChar, '/');

                    return new DefaultRazorProjectItem(basePath, filePath, relativePhysicalPath, fileKind: null, file, cssScope: null);
                });
        }

        public override RazorProjectItem GetItem(string path, string fileKind)
        {
            var absoluteBasePath = NormalizeAndEnsureValidPath("/");
            var absolutePath = NormalizeAndEnsureValidPath(path);

            var file = new FileInfo(absolutePath);
            if (!absolutePath.StartsWith(absoluteBasePath, StringComparison.OrdinalIgnoreCase))
            {
                throw new InvalidOperationException($"The file '{absolutePath}' is not a descendent of the base path '{absoluteBasePath}'.");
            }

            var relativePhysicalPath = file.FullName.Substring(absoluteBasePath.Length + 1); // Include leading separator
            var filePath = "/" + relativePhysicalPath.Replace(Path.DirectorySeparatorChar, '/');

            return new DefaultRazorProjectItem("/", filePath, relativePhysicalPath, fileKind, new FileInfo(absolutePath), cssScope: null);
        }


        public override RazorProjectItem GetItem(string path)
        {
            return GetItem(path, fileKind: null);
        }

        protected override string NormalizeAndEnsureValidPath(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(path));
            }

            var absolutePath = path.Replace('\\', '/');

            // Check if the given path is an absolute path. It is absolute if,
            // 1. It starts with Root or
            // 2. It is a network share path and starts with a '//'. Eg. //servername/some/network/folder
            if (!absolutePath.StartsWith(Root, StringComparison.OrdinalIgnoreCase) &&
                !absolutePath.StartsWith("//", StringComparison.OrdinalIgnoreCase))
            {
                // This is not an absolute path. Strip the leading slash if any and combine it with Root.
                if (path[0] == '/' || path[0] == '\\')
                {
                    path = path.Substring(1);
                }

                absolutePath = Path.Combine(Root, path);
            }

            absolutePath = absolutePath.Replace('\\', '/');

            return absolutePath;
        }
    }
}
