// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

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

namespace SharpEmu.Libs.Kernel;

public static class KernelModuleRegistry
{
    private static readonly object _gate = new();
    private static readonly Dictionary<int, ModuleEntry> _modulesByHandle = new();
    private static readonly Dictionary<string, int> _handleByPath = new(StringComparer.OrdinalIgnoreCase);
    private static readonly Dictionary<string, int> _handleByName = new(StringComparer.OrdinalIgnoreCase);
    private static readonly Dictionary<int, int> _sysmoduleHandleById = new();
    private static int _nextHandle = 1;

    private static readonly Dictionary<int, string[]> KnownSysmoduleNames = new()
    {
        [0x0006] = new[] { "libSceFiber.sprx", "libSceFiber.prx" },
        [0x0021] = new[] { "libSceRudp.sprx", "libSceRudp.prx" },
        [0x0095] = new[] { "libSceIme.sprx", "libSceIme.prx" },
        [0x0096] = new[] { "libSceImeDialog.sprx", "libSceImeDialog.prx" },
        [0x00A9] = new[] { "libSceMouse.sprx", "libSceMouse.prx" },
    };

    public readonly record struct ModuleEntry(
        int Handle,
        string Name,
        string Path,
        ulong BaseAddress,
        ulong EndAddress,
        ulong EntryPoint,
        bool IsMain,
        bool IsSystemModule);

    public static void Reset()
    {
        lock (_gate)
        {
            _modulesByHandle.Clear();
            _handleByPath.Clear();
            _handleByName.Clear();
            _sysmoduleHandleById.Clear();
            _nextHandle = 1;
        }
    }

    public static int RegisterModule(
        string? modulePath,
        ulong baseAddress,
        ulong size,
        ulong entryPoint,
        bool isMain,
        bool isSystemModule = false)
    {
        var normalizedPath = NormalizePath(modulePath);
        lock (_gate)
        {
            if (!string.IsNullOrWhiteSpace(normalizedPath) &&
                _handleByPath.TryGetValue(normalizedPath, out var existingHandle) &&
                _modulesByHandle.TryGetValue(existingHandle, out var existing))
            {
                var updated = existing with
                {
                    BaseAddress = baseAddress,
                    EndAddress = ComputeEnd(baseAddress, size),
                    EntryPoint = entryPoint,
                    IsMain = existing.IsMain || isMain,
                    IsSystemModule = existing.IsSystemModule || isSystemModule,
                };
                _modulesByHandle[existingHandle] = updated;
                _handleByName[updated.Name] = existingHandle;
                return existingHandle;
            }

            var handle = _nextHandle++;
            var name = ResolveName(normalizedPath, handle);
            var entry = new ModuleEntry(
                Handle: handle,
                Name: name,
                Path: normalizedPath,
                BaseAddress: baseAddress,
                EndAddress: ComputeEnd(baseAddress, size),
                EntryPoint: entryPoint,
                IsMain: isMain,
                IsSystemModule: isSystemModule);
            _modulesByHandle[handle] = entry;
            if (!string.IsNullOrWhiteSpace(normalizedPath))
            {
                _handleByPath[normalizedPath] = handle;
            }

            _handleByName[name] = handle;
            return handle;
        }
    }

    public static int RegisterSyntheticModule(string moduleName, bool isSystemModule)
    {
        lock (_gate)
        {
            if (TryResolveHandleByNameLocked(moduleName, out var existingHandle))
            {
                var existing = _modulesByHandle[existingHandle];
                _modulesByHandle[existingHandle] = existing with { IsSystemModule = existing.IsSystemModule || isSystemModule };
                return existingHandle;
            }

            var handle = _nextHandle++;
            var fallbackName = string.IsNullOrWhiteSpace(moduleName)
                ? $"module_{handle:X4}.sprx"
                : moduleName;
            var entry = new ModuleEntry(
                Handle: handle,
                Name: fallbackName,
                Path: string.Empty,
                BaseAddress: 0,
                EndAddress: 0,
                EntryPoint: 0,
                IsMain: false,
                IsSystemModule: isSystemModule);
            _modulesByHandle[handle] = entry;
            _handleByName[fallbackName] = handle;
            return handle;
        }
    }

    public static int MarkSysmoduleLoaded(int sysmoduleId)
    {
        lock (_gate)
        {
            if (_sysmoduleHandleById.TryGetValue(sysmoduleId, out var existingHandle) &&
                _modulesByHandle.ContainsKey(existingHandle))
            {
                return existingHandle;
            }

            var handle = TryResolveKnownSysmoduleHandleLocked(sysmoduleId, out var knownHandle)
                ? knownHandle
                : RegisterSyntheticModule($"sysmodule_0x{sysmoduleId:X4}.sprx", isSystemModule: true);
            _sysmoduleHandleById[sysmoduleId] = handle;
            return handle;
        }
    }

    public static void MarkSysmoduleUnloaded(int sysmoduleId)
    {
        lock (_gate)
        {
            _sysmoduleHandleById.Remove(sysmoduleId);
        }
    }

    public static bool IsSysmoduleLoaded(int sysmoduleId)
    {
        lock (_gate)
        {
            return _sysmoduleHandleById.ContainsKey(sysmoduleId);
        }
    }

    public static bool TryGetModuleByAddress(ulong address, out ModuleEntry module)
    {
        lock (_gate)
        {
            ModuleEntry? best = null;
            foreach (var entry in _modulesByHandle.Values)
            {
                if (entry.BaseAddress == 0 || entry.EndAddress <= entry.BaseAddress)
                {
                    continue;
                }

                if (address < entry.BaseAddress || address >= entry.EndAddress)
                {
                    continue;
                }

                if (best is null || (entry.EndAddress - entry.BaseAddress) < (best.Value.EndAddress - best.Value.BaseAddress))
                {
                    best = entry;
                }
            }

            if (best is null)
            {
                module = default;
                return false;
            }

            module = best.Value;
            return true;
        }
    }

    public static bool TryGetModuleByHandle(int handle, out ModuleEntry module)
    {
        lock (_gate)
        {
            return _modulesByHandle.TryGetValue(handle, out module);
        }
    }

    public static bool TryFindByPathOrName(string? modulePathOrName, out ModuleEntry module)
    {
        module = default;
        if (string.IsNullOrWhiteSpace(modulePathOrName))
        {
            return false;
        }

        var normalizedPath = NormalizePath(modulePathOrName);
        var fileName = ResolveName(normalizedPath, 0);
        lock (_gate)
        {
            if (!string.IsNullOrWhiteSpace(normalizedPath) &&
                _handleByPath.TryGetValue(normalizedPath, out var handleByPath) &&
                _modulesByHandle.TryGetValue(handleByPath, out module))
            {
                return true;
            }

            if (TryResolveHandleByNameLocked(fileName, out var handleByName) &&
                _modulesByHandle.TryGetValue(handleByName, out module))
            {
                return true;
            }
        }

        return false;
    }

    public static int[] GetModuleHandles(bool includeSystemModules)
    {
        lock (_gate)
        {
            return _modulesByHandle.Values
                .Where(entry => includeSystemModules || !entry.IsSystemModule)
                .OrderBy(entry => entry.Handle)
                .Select(entry => entry.Handle)
                .ToArray();
        }
    }

    public static bool TryGetFirstModule(out ModuleEntry module)
    {
        lock (_gate)
        {
            if (_modulesByHandle.Count == 0)
            {
                module = default;
                return false;
            }

            var first = _modulesByHandle.Values.OrderBy(entry => entry.Handle).First();
            module = first;
            return true;
        }
    }

    private static bool TryResolveKnownSysmoduleHandleLocked(int sysmoduleId, out int handle)
    {
        handle = 0;
        if (!KnownSysmoduleNames.TryGetValue(sysmoduleId, out var candidates))
        {
            return false;
        }

        foreach (var candidate in candidates)
        {
            if (TryResolveHandleByNameLocked(candidate, out handle))
            {
                var existing = _modulesByHandle[handle];
                _modulesByHandle[handle] = existing with { IsSystemModule = true };
                return true;
            }
        }

        return false;
    }

    private static bool TryResolveHandleByNameLocked(string moduleName, out int handle)
    {
        handle = 0;
        if (string.IsNullOrWhiteSpace(moduleName))
        {
            return false;
        }

        if (_handleByName.TryGetValue(moduleName, out handle))
        {
            return true;
        }

        var altName = moduleName.EndsWith(".sprx", StringComparison.OrdinalIgnoreCase)
            ? moduleName[..^5] + ".prx"
            : (moduleName.EndsWith(".prx", StringComparison.OrdinalIgnoreCase)
                ? moduleName[..^4] + ".sprx"
                : string.Empty);
        return !string.IsNullOrWhiteSpace(altName) && _handleByName.TryGetValue(altName, out handle);
    }

    private static ulong ComputeEnd(ulong baseAddress, ulong size)
    {
        if (size == 0)
        {
            return baseAddress;
        }

        return unchecked(baseAddress + size);
    }

    private static string NormalizePath(string? modulePath)
    {
        if (string.IsNullOrWhiteSpace(modulePath))
        {
            return string.Empty;
        }

        try
        {
            return Path.GetFullPath(modulePath).Replace('\\', '/');
        }
        catch
        {
            return modulePath.Replace('\\', '/');
        }
    }

    private static string ResolveName(string normalizedPath, int handleHint)
    {
        if (!string.IsNullOrWhiteSpace(normalizedPath))
        {
            var fileName = Path.GetFileName(normalizedPath);
            if (!string.IsNullOrWhiteSpace(fileName))
            {
                return fileName;
            }
        }

        return handleHint <= 0
            ? "module.sprx"
            : $"module_{handleHint:X4}.sprx";
    }
}
