using System;
using System.IO;
using StardewModdingAPI.Framework.ModHelpers;
using StardewModdingAPI.Toolkit.Serialization;
using StardewModdingAPI.Toolkit.Utilities;
using StardewModdingAPI.Toolkit.Utilities.PathLookups;

namespace StardewModdingAPI.Framework
{
    /// <summary>Manages access to a content pack's metadata and files.</summary>
    internal class ContentPack : IContentPack
    {
        /*********
        ** Fields
        *********/
        /// <summary>Encapsulates SMAPI's JSON file parsing.</summary>
        private readonly JsonHelper JsonHelper;

        /// <summary>A lookup for files within the <see cref="DirectoryPath"/>.</summary>
        private readonly IFileLookup FileLookup;


        /*********
        ** Accessors
        *********/
        /// <inheritdoc />
        public string DirectoryPath { get; }

        /// <inheritdoc />
        public IManifest Manifest { get; }

        /// <inheritdoc />
        public ITranslationHelper Translation => this.TranslationImpl;

        /// <inheritdoc />
        public IModContentHelper ModContent { get; }

        /// <summary>The underlying translation helper.</summary>
        internal TranslationHelper TranslationImpl { get; set; }


        /*********
        ** Public methods
        *********/
        /// <summary>Construct an instance.</summary>
        /// <param name="directoryPath">The full path to the content pack's folder.</param>
        /// <param name="manifest">The content pack's manifest.</param>
        /// <param name="content">Provides an API for loading content assets from the content pack's folder.</param>
        /// <param name="translation">Provides translations stored in the content pack's <c>i18n</c> folder.</param>
        /// <param name="jsonHelper">Encapsulates SMAPI's JSON file parsing.</param>
        /// <param name="fileLookup">A lookup for files within the <paramref name="directoryPath"/>.</param>
        public ContentPack(string directoryPath, IManifest manifest, IModContentHelper content, TranslationHelper translation, JsonHelper jsonHelper, IFileLookup fileLookup)
        {
            this.DirectoryPath = directoryPath;
            this.Manifest = manifest;
            this.ModContent = content;
            this.TranslationImpl = translation;
            this.JsonHelper = jsonHelper;
            this.FileLookup = fileLookup;
        }

        /// <inheritdoc />
        public bool HasFile(string path)
        {
            path = PathUtilities.NormalizePath(path);

            return this.GetFile(path).Exists;
        }

        /// <inheritdoc />
        public TModel? ReadJsonFile<TModel>(string path) where TModel : class
        {
            path = PathUtilities.NormalizePath(path);

            FileInfo file = this.GetFile(path);
            return file.Exists && this.JsonHelper.ReadJsonFileIfExists(file.FullName, out TModel? model)
                ? model
                : null;
        }

        /// <inheritdoc />
        public void WriteJsonFile<TModel>(string path, TModel data) where TModel : class
        {
            path = PathUtilities.NormalizePath(path);

            FileInfo file = this.GetFile(path);
            bool didExist = file.Exists;

            this.JsonHelper.WriteJsonFile(file.FullName, data);

            if (!didExist)
            {
                this.FileLookup.Add(
                    Path.GetRelativePath(this.DirectoryPath, file.FullName)
                );
            }
        }

#if SMAPI_DEPRECATED
        /// <inheritdoc />
        [Obsolete($"Use {nameof(IContentPack.ModContent)}.{nameof(IModContentHelper.Load)} instead. This method will be removed in SMAPI 4.0.0.")]
        public T LoadAsset<T>(string key)
            where T : notnull
        {
            return this.ModContent.Load<T>(key);
        }

        /// <inheritdoc />
        [Obsolete($"Use {nameof(IContentPack.ModContent)}.{nameof(IModContentHelper.GetInternalAssetName)} instead. This method will be removed in SMAPI 4.0.0.")]
        public string GetActualAssetKey(string key)
        {
            return this.ModContent.GetInternalAssetName(key).Name;
        }
#endif


        /*********
        ** Private methods
        *********/
        /// <summary>Get the underlying file info.</summary>
        /// <param name="relativePath">The normalized file path relative to the content pack directory.</param>
        private FileInfo GetFile(string relativePath)
        {
            if (!PathUtilities.IsSafeRelativePath(relativePath))
                throw new InvalidOperationException($"You must call {nameof(IContentPack)} methods with a relative path.");

            return this.FileLookup.GetFile(relativePath);
        }
    }
}