public sealed class ImportDirectoryParser
{
    public IReadOnlyList<ImportInfo> Parse(byte[] rawBytes, NtHeaders nt, IReadOnlyList<SectionInfo> sections, SafetyLimits limits)
    {
        if (nt.OptionalHeader.DataDirectories.Count <= 1)
        {
            return Array.Empty<ImportInfo>();
        }

        var dir = nt.OptionalHeader.DataDirectories[1];
        if (dir.VirtualAddress == 0 || dir.Size == 0)
        {
            return Array.Empty<ImportInfo>();
        }

        var offset = RvaMapper.RvaToFileOffset(dir.VirtualAddress, sections, nt.OptionalHeader.SizeOfHeaders);
        if (offset is null)
        {
            return Array.Empty<ImportInfo>();
        }

        BoundsChecker.EnsureRange(offset.Value, 20, rawBytes.Length, "import directory");

        var imports = new List<ImportInfo>();
        using var ms = new MemoryStream(rawBytes, writable: false);
        using var reader = new BinaryReader(ms);

        ms.Position = offset.Value;
        while (ms.Position + 20 <= ms.Length)
        {
            BoundsChecker.EnsureCount(imports.Count + 1, limits.MaxImportsCount, "Import descriptors");

            var originalFirstThunk = reader.ReadUInt32();
            var timeDateStamp = reader.ReadUInt32();
            var forwarderChain = reader.ReadUInt32();
            var nameRva = reader.ReadUInt32();
            var firstThunk = reader.ReadUInt32();

            if (originalFirstThunk == 0 && timeDateStamp == 0 && forwarderChain == 0 && nameRva == 0 && firstThunk == 0)
            {
                break;
            }

            var dllName = DirectoryParserHelpers.ReadAsciiAtRva(rawBytes, nameRva, nt, sections, 512);
            var thunkRva = originalFirstThunk != 0 ? originalFirstThunk : firstThunk;
            var thunkOffset = RvaMapper.RvaToFileOffset(thunkRva, sections, nt.OptionalHeader.SizeOfHeaders);
            var functions = new List<ImportFunctionInfo>();

            if (thunkOffset is not null)
            {
                var thunkEntrySize = nt.OptionalHeader.IsPe32Plus ? 8 : 4;
                BoundsChecker.EnsureRange(thunkOffset.Value, thunkEntrySize, rawBytes.Length, "import thunk table");

                using var thunkStream = new MemoryStream(rawBytes, writable: false);
                using var thunkReader = new BinaryReader(thunkStream);
                thunkStream.Position = thunkOffset.Value;

                while (thunkStream.Position + thunkEntrySize <= thunkStream.Length)
                {
                    BoundsChecker.EnsureCount(functions.Count + 1, limits.MaxImportsCount, "Import functions");

                    ulong thunkValue = nt.OptionalHeader.IsPe32Plus
                        ? thunkReader.ReadUInt64()
                        : thunkReader.ReadUInt32();

                    if (thunkValue == 0)
                    {
                        break;
                    }

                    var isOrdinal = nt.OptionalHeader.IsPe32Plus
                        ? (thunkValue & 0x8000000000000000UL) != 0
                        : (thunkValue & 0x80000000U) != 0;

                    if (isOrdinal)
                    {
                        functions.Add(new ImportFunctionInfo(null, null, thunkValue & 0xFFFF, true));
                    }
                    else
                    {
                        var ibnOffset = RvaMapper.RvaToFileOffset((uint)thunkValue, sections, nt.OptionalHeader.SizeOfHeaders);
                        if (ibnOffset is null)
                        {
                            break;
                        }

                        BoundsChecker.EnsureRange(ibnOffset.Value, 2, rawBytes.Length, "import by name hint");
                        var hint = BitConverter.ToUInt16(rawBytes, ibnOffset.Value);
                        var name = BinaryReaderExtensions.ReadAsciiStringAt(rawBytes, ibnOffset.Value + 2, 1024);
                        functions.Add(new ImportFunctionInfo(name, hint, thunkValue, false));
                    }
                }
            }

            imports.Add(new ImportInfo(dllName, functions));
        }

        return imports;
    }
}