// 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.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);
            }

            // Instead of `C:filename.ext`, we want `C:/filename.ext`.
            if (Root.EndsWith(":", StringComparison.Ordinal) && !string.IsNullOrEmpty(path))
            {
                absolutePath = Root + "/" + path;
            }
            else
            {
                absolutePath = Path.Combine(Root, path);
            }
        }

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

        return absolutePath;
    }
}
